11import { useState , FormEvent } from "react" ;
2- import { Eye , EyeOff , ArrowLeft } from "lucide-react" ;
2+ import { Eye , EyeOff , ArrowLeft , Github , Linkedin } from "lucide-react" ;
33import { Image } from "@unpic/react" ;
44import { motion } from "framer-motion" ;
55import PhoneInput from "react-phone-number-input" ;
6+
7+ import { api } from "@/services/api" ;
68import "react-phone-number-input/style.css" ;
9+ import { useNavigate } from "react-router-dom" ;
10+
11+ interface ValidationError {
12+ _errors ?: string [ ] ;
13+ }
714
815const STATIC_STARS = Array . from ( { length : 40 } ) . map ( ( _ , i ) => {
916 const random = ( min : number , max : number ) => Math . random ( ) * ( max - min ) + min ;
@@ -47,6 +54,8 @@ function StarsBackground() {
4754}
4855
4956export default function RegisterSide ( ) {
57+ const navigate = useNavigate ( ) ;
58+
5059 const [ showPassword , setShowPassword ] = useState ( false ) ;
5160 const [ nome , setNome ] = useState ( "" ) ;
5261 const [ email , setEmail ] = useState ( "" ) ;
@@ -59,6 +68,8 @@ export default function RegisterSide() {
5968 const [ telefoneError , setTelefoneError ] = useState ( "" ) ;
6069 const [ passwordError , setPasswordError ] = useState ( "" ) ;
6170 const [ cpfError , setCpfError ] = useState ( "" ) ;
71+ const [ globalError , setGlobalError ] = useState ( "" ) ;
72+ const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
6273
6374 const handleRevealPassword = ( ) => setShowPassword ( ( prev ) => ! prev ) ;
6475
@@ -70,13 +81,44 @@ export default function RegisterSide() {
7081 return `${ digits . slice ( 0 , 3 ) } .${ digits . slice ( 3 , 6 ) } .${ digits . slice ( 6 , 9 ) } -${ digits . slice ( 9 ) } ` ;
7182 } ;
7283
73- const handleSubmit = ( e : FormEvent ) => {
84+ const handleOAuthLogin = async ( provider : "google" | "github" | "linkedin" ) => {
85+ try {
86+ setGlobalError ( "" ) ;
87+ setIsSubmitting ( true ) ;
88+
89+ const response = await api . get ( `/auth/${ provider } /url` ) ;
90+
91+ if ( response . data ?. url ) {
92+ window . location . href = response . data . url ;
93+ } else {
94+ throw new Error ( "URL de redirecionamento não retornada pelo servidor." ) ;
95+ }
96+ } catch ( error : unknown ) {
97+ console . error ( "Erro ao iniciar fluxo OAuth:" , error ) ;
98+
99+ let apiError = `Não foi possível conectar ao provedor ${ provider } .` ;
100+
101+ // Verifica de forma segura se o erro contém um objeto response vindo do Axios
102+ if ( error && typeof error === "object" && "response" in error ) {
103+ const axiosError = error as { response ?: { data ?: { error ?: string } } } ;
104+ if ( typeof axiosError . response ?. data ?. error === "string" ) {
105+ apiError = axiosError . response . data . error ;
106+ }
107+ }
108+
109+ setGlobalError ( apiError ) ;
110+ setIsSubmitting ( false ) ;
111+ }
112+ } ;
113+
114+ const handleSubmit = async ( e : FormEvent ) => {
74115 e . preventDefault ( ) ;
75116 setNomeError ( "" ) ;
76117 setEmailError ( "" ) ;
77118 setTelefoneError ( "" ) ;
78119 setPasswordError ( "" ) ;
79120 setCpfError ( "" ) ;
121+ setGlobalError ( "" ) ;
80122
81123 let isValid = true ;
82124
@@ -103,8 +145,57 @@ export default function RegisterSide() {
103145 setCpfError ( "Por favor, insira um CPF válido." ) ; isValid = false ;
104146 }
105147
106- if ( isValid ) {
107- console . log ( "Cadastro válido!" , { nome, email, telefone, password, cpf } ) ;
148+ if ( ! isValid ) return ;
149+
150+ setIsSubmitting ( true ) ;
151+
152+ try {
153+ const response = await api . post ( "/auth/register" , {
154+ name : nome ,
155+ email : email ,
156+ phone : telefone ,
157+ password : password ,
158+ cpf : cpf . replace ( / \D / g, "" ) ,
159+ } ) ;
160+
161+ console . log ( "Cadastro efetuado com sucesso!" , response . data ) ;
162+ navigate ( "/app" ) ;
163+
164+ } catch ( error : unknown ) {
165+ console . error ( "Erro na requisição de cadastro:" , error ) ;
166+ setIsSubmitting ( false ) ;
167+
168+ if ( error && typeof error === "object" && "response" in error ) {
169+ const axiosError = error as {
170+ response ?: {
171+ data ?: {
172+ error ?: string | Record < string , ValidationError > ;
173+ message ?: string ;
174+ } ;
175+ } ;
176+ } ;
177+
178+ const data = axiosError . response ?. data ;
179+
180+ if ( data && typeof data . error === "object" ) {
181+ const validationErrors = data . error as Record < string , ValidationError > ;
182+
183+ if ( validationErrors . name ?. _errors ?. [ 0 ] ) setNomeError ( validationErrors . name . _errors [ 0 ] ) ;
184+ if ( validationErrors . email ?. _errors ?. [ 0 ] ) setEmailError ( validationErrors . email . _errors [ 0 ] ) ;
185+ if ( validationErrors . phone ?. _errors ?. [ 0 ] ) setTelefoneError ( validationErrors . phone . _errors [ 0 ] ) ;
186+ if ( validationErrors . password ?. _errors ?. [ 0 ] ) setPasswordError ( validationErrors . password . _errors [ 0 ] ) ;
187+ if ( validationErrors . cpf ?. _errors ?. [ 0 ] ) setCpfError ( validationErrors . cpf . _errors [ 0 ] ) ;
188+ } else {
189+ const fallbackMsg = data ?. message || ( typeof data ?. error === "string" ? data . error : "" ) || "Erro ao efetuar o cadastro." ;
190+ if ( fallbackMsg . toLowerCase ( ) . includes ( "email" ) ) {
191+ setEmailError ( "Este endereço de e-mail já está em uso." ) ;
192+ } else {
193+ setGlobalError ( fallbackMsg ) ;
194+ }
195+ }
196+ } else {
197+ setGlobalError ( "Ocorreu um erro inesperado de conexão." ) ;
198+ }
108199 }
109200 } ;
110201
@@ -144,13 +235,20 @@ export default function RegisterSide() {
144235
145236 < form className = "relative z-10 space-y-5 w-full max-w-2xl mx-auto my-8" onSubmit = { handleSubmit } >
146237
238+ { globalError && (
239+ < div className = "p-3.5 bg-red-500/10 border border-red-500/30 text-red-500 rounded-xl text-sm font-semibold text-center backdrop-blur-sm" >
240+ { globalError }
241+ </ div >
242+ ) }
243+
147244 < div >
148245 < label htmlFor = "nome" className = "block text-sm font-semibold text-gray-700 dark:text-neutral-300 mb-1.5" >
149246 Nome
150247 </ label >
151248 < input
152249 id = "nome"
153250 type = "text"
251+ disabled = { isSubmitting }
154252 value = { nome }
155253 onChange = { ( e ) => setNome ( e . target . value ) }
156254 placeholder = "Bene Santos"
@@ -168,6 +266,7 @@ export default function RegisterSide() {
168266 < input
169267 id = "email"
170268 type = "email"
269+ disabled = { isSubmitting }
171270 value = { email }
172271 onChange = { ( e ) => setEmail ( e . target . value ) }
173272 placeholder = "lbene17@gmail.com"
@@ -188,6 +287,7 @@ export default function RegisterSide() {
188287 < PhoneInput
189288 international
190289 defaultCountry = "BR"
290+ disabled = { isSubmitting }
191291 value = { telefone }
192292 onChange = { setTelefone }
193293 className = "w-full px-4 py-3.5 text-gray-900 dark:text-white bg-transparent focus:outline-none phone-input-custom"
@@ -205,6 +305,7 @@ export default function RegisterSide() {
205305 < input
206306 id = "senha"
207307 type = { showPassword ? "text" : "password" }
308+ disabled = { isSubmitting }
208309 value = { password }
209310 onChange = { ( e ) => setPassword ( e . target . value ) }
210311 placeholder = "Ex: ••••••••••••"
@@ -214,6 +315,7 @@ export default function RegisterSide() {
214315 />
215316 < button
216317 type = "button"
318+ disabled = { isSubmitting }
217319 onClick = { handleRevealPassword }
218320 className = "absolute inset-y-0 right-0 pr-4 flex items-center text-gray-400 dark:text-neutral-500 hover:text-blue-500 dark:hover:text-blue-400 transition-colors"
219321 >
@@ -230,6 +332,7 @@ export default function RegisterSide() {
230332 < input
231333 id = "cpf"
232334 type = "text"
335+ disabled = { isSubmitting }
233336 value = { cpf }
234337 onChange = { ( e ) => setCpf ( formatCpf ( e . target . value ) ) }
235338 placeholder = "091.000.000-00"
@@ -242,12 +345,13 @@ export default function RegisterSide() {
242345
243346 < motion . button
244347 type = "submit"
245- whileHover = { { scale : 1.01 } }
246- whileTap = { { scale : 0.99 } }
247- className = "w-full bg-gradient-to-r from-blue-600 via-purple-600 to-teal-600 hover:opacity-95 text-white py-3.5 px-4 rounded-xl font-bold text-base focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all shadow-md shadow-blue-500/10 cursor-pointer"
348+ disabled = { isSubmitting }
349+ whileHover = { { scale : isSubmitting ? 1 : 1.01 } }
350+ whileTap = { { scale : isSubmitting ? 1 : 0.99 } }
351+ className = "w-full bg-gradient-to-r from-blue-600 via-purple-600 to-teal-600 hover:opacity-95 text-white py-3.5 px-4 rounded-xl font-bold text-base focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all shadow-md shadow-blue-500/10 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
248352 >
249- Cadastrar
250- </ motion . button >
353+ { isSubmitting ? "Cadastrando..." : " Cadastrar" }
354+ </ motion . button >
251355 </ form >
252356
253357 < div className = "relative z-10 w-full max-w-2xl mx-auto" >
@@ -260,18 +364,32 @@ export default function RegisterSide() {
260364 </ div >
261365 </ div >
262366
263- { /* Botões Sociais Atualizados com Hover translúcido e bordas combinando */ }
264367 < div className = "grid grid-cols-3 gap-4" >
265- < button className = "flex justify-center items-center py-3 px-4 border border-gray-200 dark:border-neutral-800 rounded-xl bg-white/50 dark:bg-neutral-800/50 hover:bg-gray-50 dark:hover:bg-neutral-700/50 transition-all shadow-sm cursor-pointer" >
368+ < button
369+ type = "button"
370+ disabled = { isSubmitting }
371+ onClick = { ( ) => handleOAuthLogin ( "google" ) }
372+ className = "flex justify-center items-center py-3 px-4 border border-gray-200 dark:border-neutral-800 rounded-xl bg-white/50 dark:bg-neutral-800/50 hover:bg-gray-50 dark:hover:bg-neutral-700/50 transition-all shadow-sm cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
373+ >
266374 < Image src = "/google.png" alt = "Google" width = { 20 } height = { 20 } className = "object-contain" />
267375 </ button >
268- < button className = "flex justify-center items-center py-3 px-4 border border-gray-200 dark:border-neutral-800 rounded-xl bg-white/50 dark:bg-neutral-800/50 hover:bg-gray-50 dark:hover:bg-neutral-700/50 transition-all shadow-sm cursor-pointer" >
269- < Image src = "/facebook.png" alt = "Facebook" width = { 20 } height = { 20 } className = "object-contain" />
376+
377+ < button
378+ type = "button"
379+ disabled = { isSubmitting }
380+ onClick = { ( ) => handleOAuthLogin ( "linkedin" ) }
381+ className = "flex justify-center items-center py-3 px-4 border border-gray-200 dark:border-neutral-800 rounded-xl bg-white/50 dark:bg-neutral-800/50 hover:bg-gray-50 dark:hover:bg-neutral-700/50 transition-all shadow-sm cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
382+ >
383+ < Linkedin className = "h-5 text-blue-500" />
270384 </ button >
271- < button className = "flex justify-center items-center py-3 px-4 border border-gray-200 dark:border-neutral-800 rounded-xl bg-white/50 dark:bg-neutral-800/50 hover:bg-gray-50 dark:hover:bg-neutral-700/50 transition-all shadow-sm cursor-pointer" >
272- < svg className = "h-5 w-5 fill-gray-900 dark:fill-white transition-colors" viewBox = "0 0 24 24" >
273- < path d = "M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M15.97 4.17c.66-.81 1.11-1.93.99-3.06-1 .04-2.21.67-2.93 1.49-.62.69-1.16 1.84-1.01 2.96 1.12.09 2.27-.58 2.95-1.39z" />
274- </ svg >
385+
386+ < button
387+ type = "button"
388+ disabled = { isSubmitting }
389+ onClick = { ( ) => handleOAuthLogin ( "github" ) }
390+ className = "flex justify-center items-center py-3 px-4 border border-gray-200 dark:border-neutral-800 rounded-xl bg-white/50 dark:bg-neutral-800/50 hover:bg-gray-50 dark:hover:bg-neutral-700/50 transition-all shadow-sm cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
391+ >
392+ < Github className = "h-5 text-gray-900 dark:text-white" />
275393 </ button >
276394 </ div >
277395 </ div >
0 commit comments