add rate limit to user

This commit is contained in:
2025-11-30 22:31:46 -03:00
parent 6e726601d2
commit 8d312893fa
5 changed files with 264 additions and 94 deletions

View File

@@ -1,5 +1,6 @@
import type { Route } from './+types/profile'
import { AlertCircleIcon } from 'lucide-react'
import { useForm } from 'react-hook-form'
import { Link, useOutletContext } from 'react-router'
import { PatternFormat } from 'react-number-format'
@@ -24,14 +25,34 @@ import {
import { Input } from '@repo/ui/components/ui/input'
import { Spinner } from '@repo/ui/components/ui/spinner'
import { type User } from '@repo/ui/routes/users/data'
import { request as req, HttpMethod } from '@repo/util/request'
import { formSchema, type Schema } from './emails/data'
import { useFetcher } from 'react-router'
import { userContext } from '@repo/auth/context'
import {
Alert,
AlertDescription,
AlertTitle
} from '@repo/ui/components/ui/alert'
export async function action({ request }: Route.ActionArgs) {
export async function action({ request, context }: Route.ActionArgs) {
const body = await request.json()
console.log(body)
return { ok: true }
const user = context.get(userContext)
const r = await req({
url: `/users/${user.sub}`,
method: HttpMethod.PATCH,
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(body),
request,
context
})
if (r.ok) {
return { ok: true }
}
return { ok: false, error: await r.json() }
}
export default function Route({}: Route.ComponentProps) {
@@ -52,95 +73,124 @@ export default function Route({}: Route.ComponentProps) {
return (
<Form {...form}>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-2xl">Meu perfil</CardTitle>
<CardDescription>
Mantenha os dados do seu perfil atualizados para que apareçam
corretamente em seus cursos e certificados.
</CardDescription>
</CardHeader>
<form onSubmit={handleSubmit(onSubmit)}>
<fieldset disabled={!!user?.rate_limit_exceeded} className="space-y-4">
{user?.rate_limit_exceeded && (
<Alert variant="destructive">
<AlertCircleIcon />
<AlertTitle>Limite de atualizações excedido</AlertTitle>
<AlertDescription>
Nova tentativa disponível a partir de{' '}
{remainingTime(user.rate_limit_exceeded.ttl)}
</AlertDescription>
</Alert>
)}
<CardContent className="space-y-4">
<FormField
control={control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Nome</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Card>
<CardHeader>
<CardTitle className="text-2xl">Meu perfil</CardTitle>
<CardDescription>
Mantenha os dados do seu perfil atualizados para que apareçam
corretamente em seus cursos e certificados.
</CardDescription>
</CardHeader>
<FormField
control={control}
name="email"
disabled={true}
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormLabel className="text-sm font-normal text-muted-foreground">
<span>
Para gerenciar os emails ou trocar o email principal, use
as{' '}
<Link
to="emails"
className="text-blue-400 underline hover:no-underline"
>
configurações de emails
</Link>
</span>
</FormLabel>
<FormMessage />
</FormItem>
)}
/>
<CardContent className="space-y-4">
<FormField
control={control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Nome</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name="cpf"
render={({ field: { onChange, ref, ...props } }) => (
<FormItem>
<FormLabel>CPF</FormLabel>
<FormControl>
<PatternFormat
format="###.###.###-##"
mask="_"
placeholder="___.___.___-__"
customInput={Input}
getInputRef={ref}
onValueChange={({ value }) => {
onChange(value)
}}
{...props}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
<FormField
control={control}
name="email"
disabled={true}
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormLabel className="text-sm font-normal text-muted-foreground">
<span>
Para gerenciar os emails ou trocar o email principal,
use as{' '}
<Link
to="emails"
className="text-blue-400 underline hover:no-underline"
>
configurações de emails
</Link>
</span>
</FormLabel>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button
type="submit"
className="cursor-pointer"
disabled={formState.isSubmitting}
>
{formState.isSubmitting && <Spinner />}
Atualizar perfil
</Button>
</div>
<FormField
control={control}
name="cpf"
render={({ field: { onChange, ref, ...props } }) => (
<FormItem>
<FormLabel>CPF</FormLabel>
<FormControl>
<PatternFormat
format="###.###.###-##"
mask="_"
placeholder="___.___.___-__"
customInput={Input}
getInputRef={ref}
onValueChange={({ value }) => {
onChange(value)
}}
{...props}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
<div className="flex justify-end">
<Button
type="submit"
className="cursor-pointer"
disabled={formState.isSubmitting}
>
{formState.isSubmitting && <Spinner />}
Atualizar perfil
</Button>
</div>
</fieldset>
</form>
</Form>
)
}
function remainingTime(ttl: number) {
const date = new Date(ttl * 1000)
const day = date.toLocaleDateString('pt-BR', {
day: '2-digit',
month: '2-digit'
})
const time = date.toLocaleTimeString('pt-BR', {
hour: '2-digit',
minute: '2-digit'
})
return `${day} às ${time}`
}