add lookup

This commit is contained in:
2025-12-01 22:27:14 -03:00
parent f3e3d9f8c2
commit 8eb5427af4
22 changed files with 548 additions and 133 deletions

View File

@@ -136,7 +136,7 @@ export default function Index({}: Route.ComponentProps) {
<p className="text-white/50 text-sm">
Não tem uma senha?{' '}
<Link
to="/signup"
to="/register"
className="font-medium text-white hover:underline"
>
Criar senha
@@ -183,6 +183,7 @@ export default function Index({}: Route.ComponentProps) {
<FormControl>
<Input
type={show ? 'text' : 'password'}
autoComplete="false"
placeholder="••••••••"
{...field}
/>

View File

@@ -0,0 +1,97 @@
import { PatternFormat } from 'react-number-format'
import { useRequest } from 'ahooks'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { Button } from '@repo/ui/components/ui/button'
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage
} from '@repo/ui/components/ui/form'
import { Input } from '@repo/ui/components/ui/input'
import { Spinner } from '@repo/ui/components/ui/spinner'
import { cpf, type RegisterContextProps, type User } from './data'
import { RegisterContext } from './data'
import { use } from 'react'
const formSchema = z.object({
cpf: cpf
})
type Schema = z.infer<typeof formSchema>
export function Cpf() {
const { setUser } = use(RegisterContext) as RegisterContextProps
const form = useForm({
resolver: zodResolver(formSchema)
})
const { control, handleSubmit, formState } = form
const { runAsync } = useRequest(
async ({ cpf }) => {
return await fetch(`/lookup?cpf=${cpf}`, {
method: 'GET',
headers: new Headers({ 'Content-Type': 'application/json' })
})
},
{ manual: true }
)
const onSubmit = async ({ cpf }: Schema) => {
const r = await runAsync({ cpf })
const user = (await r.json()) as any
setUser({ cpf, ...user })
}
return (
<>
<Form {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
<fieldset disabled={formState.isSubmitting} className="grid gap-6">
<FormField
control={control}
name="cpf"
defaultValue=""
render={({ field: { ref, onChange, ...props } }) => (
<FormItem>
<FormLabel>CPF</FormLabel>
<FormControl>
<PatternFormat
format="###.###.###-##"
mask="_"
placeholder="___.___.___-__"
customInput={Input}
autoFocus={true}
getInputRef={ref}
onValueChange={({ value }) => {
onChange(value)
}}
{...props}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
className="w-full cursor-pointer relative overflow-hidden"
>
{formState.isSubmitting && (
<div className="absolute bg-lime-500 inset-0 flex items-center justify-center">
<Spinner />
</div>
)}
Continuar
</Button>
</fieldset>
</form>
</Form>
</>
)
}

View File

@@ -0,0 +1,39 @@
import { z } from 'zod'
import { isValidCPF } from '@brazilian-utils/brazilian-utils'
import { createContext } from 'react'
const isName = (name: string) => name && name.includes(' ')
export type User = {
id?: string | null
cpf: string
name: string
email: string
}
export const cpf = z
.string()
.nonempty('Digite seu CPF')
.refine(isValidCPF, 'Deve ser um CPF válido')
export const formSchema = z.object({
name: z
.string()
.trim()
.nonempty('Digite seu nome')
.refine(isName, { message: 'Nome inválido' }),
email: z.email('Digite seu email'),
password: z
.string()
.nonempty('Digite sua senha')
.min(6, 'Deve ter no mínimo 6 caracteres'),
cpf: cpf
})
export type Schema = z.infer<typeof formSchema>
export type RegisterContextProps = {
user: User | null
setUser: (user: User) => void
}
export const RegisterContext = createContext<RegisterContextProps | null>(null)

View File

@@ -1,13 +1,10 @@
import type { Route } from './+types'
import type { Route } from '../+types'
import { isValidCPF } from '@brazilian-utils/brazilian-utils'
import { PatternFormat } from 'react-number-format'
import { zodResolver } from '@hookform/resolvers/zod'
import { useState } from 'react'
import { useState, createContext, type ReactNode, use } from 'react'
import { useForm } from 'react-hook-form'
import { Link } from 'react-router'
import { z } from 'zod'
import logo from '@repo/ui/components/logo2.svg'
import { Button } from '@repo/ui/components/ui/button'
import { Checkbox } from '@repo/ui/components/ui/checkbox'
import {
@@ -21,20 +18,8 @@ import {
import { Input } from '@repo/ui/components/ui/input'
import { Label } from '@repo/ui/components/ui/label'
const schema = z.object({
name: z.string().trim().nonempty('Digite seu nome'),
email: z.email('Digite seu email'),
password: z
.string()
.nonempty('Digite sua senha')
.min(6, 'Deve ter no mínimo 6 caracteres'),
cpf: z
.string()
.nonempty('Digite seu CPF')
.refine(isValidCPF, 'Deve ser um CPF válido')
})
type Schema = z.infer<typeof schema>
import { Cpf } from './cpf'
import { formSchema, type Schema, RegisterContext, type User } from './data'
export function meta({}: Route.MetaArgs) {
return [{ title: 'Criar conta · EDUSEG®' }]
@@ -42,8 +27,9 @@ export function meta({}: Route.MetaArgs) {
export default function Signup({}: Route.ComponentProps) {
const [show, setShow] = useState(false)
const [user, setUser] = useState<User | null>(null)
const form = useForm({
resolver: zodResolver(schema)
resolver: zodResolver(formSchema)
})
const { control, handleSubmit, formState } = form
@@ -51,34 +37,17 @@ export default function Signup({}: Route.ComponentProps) {
console.log(data)
}
console.log(user)
return (
<>
<div className="space-y-6">
<div className="flex justify-center">
<div className="border border-white/15 bg-white/5 px-2.5 py-3 rounded-xl">
<img src={logo} alt="EDUSEG®" className="block size-12" />
</div>
</div>
<div className="text-center space-y-1.5">
<h1 className="text-2xl font-semibold font-display text-balance">
Criar conta
</h1>
<p className="text-white/50 text-sm">
tem uma conta?{' '}
<Link to="/" className="font-medium text-white hover:underline">
Faça login
</Link>
.
</p>
</div>
<RegisterContext value={{ user, setUser }}>
{user ? (
<Form {...form}>
<form onSubmit={handleSubmit(onSubmit)} className="grid gap-6">
<FormField
control={control}
name="name"
defaultValue=""
defaultValue={user?.name}
render={({ field }) => (
<FormItem>
<FormLabel>Nome</FormLabel>
@@ -93,6 +62,7 @@ export default function Signup({}: Route.ComponentProps) {
<FormField
control={control}
name="email"
defaultValue={user?.email}
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
@@ -107,12 +77,22 @@ export default function Signup({}: Route.ComponentProps) {
<FormField
control={control}
name="cpf"
defaultValue=""
render={({ field }) => (
defaultValue={user.cpf}
render={({ field: { ref, onChange, ...props } }) => (
<FormItem>
<FormLabel>CPF</FormLabel>
<FormControl>
<Input placeholder="___.___.___-__" {...field} />
<PatternFormat
format="###.###.###-##"
mask="_"
placeholder="___.___.___-__"
customInput={Input}
getInputRef={ref}
onValueChange={({ value }) => {
onChange(value)
}}
{...props}
/>
</FormControl>
<FormMessage />
</FormItem>
@@ -130,6 +110,7 @@ export default function Signup({}: Route.ComponentProps) {
<Input
type={show ? 'text' : 'password'}
placeholder="••••••••"
autoComplete="false"
{...field}
/>
</FormControl>
@@ -155,19 +136,9 @@ export default function Signup({}: Route.ComponentProps) {
</Button>
</form>
</Form>
<p className="text-white/50 text-xs text-center">
Ao fazer login, você concorda com nossa{' '}
<a
href="//eduseg.com.br/politica"
target="_blank"
className="underline hover:no-underline"
>
política de privacidade
</a>
.
</p>
</div>
</>
) : (
<Cpf />
)}
</RegisterContext>
)
}

View File

@@ -0,0 +1,50 @@
import type { Route } from './+types/index'
import { Outlet, Link } from 'react-router'
import logo from '@repo/ui/components/logo2.svg'
export function meta({}: Route.MetaArgs) {
return [{ title: 'Criar conta · EDUSEG®' }]
}
export default function Layout() {
return (
<>
<div className="space-y-6">
<div className="flex justify-center">
<div className="border border-white/15 bg-white/5 px-2.5 py-3 rounded-xl">
<img src={logo} alt="EDUSEG®" className="block size-12" />
</div>
</div>
<div className="text-center space-y-1.5">
<h1 className="text-2xl font-semibold font-display text-balance">
Criar conta
</h1>
<p className="text-white/50 text-sm">
tem uma conta?{' '}
<Link to="/" className="font-medium text-white hover:underline">
Faça login
</Link>
.
</p>
</div>
<Outlet />
<p className="text-white/50 text-xs text-center">
Ao cadastrar, você concorda com nossa{' '}
<a
href="//eduseg.com.br/politica"
target="_blank"
className="underline hover:no-underline"
>
política de privacidade
</a>
.
</p>
</div>
</>
)
}

View File

@@ -9,8 +9,8 @@ async function proxy({
request,
context
}: Route.ActionArgs): Promise<Response> {
const pathname = new URL(request.url).pathname
const url = new URL(pathname, context.cloudflare.env.ISSUER_URL)
const { pathname, search } = new URL(request.url)
const url = new URL(pathname + search, context.cloudflare.env.ISSUER_URL)
const headers = new Headers(request.headers)
const shouldCache =