Skip to content

Commit 2bf1abb

Browse files
authored
Feat/api conection (#118)
2 parents 90c15d1 + d05f655 commit 2bf1abb

25 files changed

Lines changed: 3235 additions & 13474 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ backend/coverage/
2121

2222
# Dist-electron
2323
dist-electron/.sfdx/
24+
pnpm-lock.yaml

backend/drizzle/meta/_journal.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@
2929
"when": 1779191875793,
3030
"tag": "0003_hot_miek",
3131
"breakpoints": true
32+
},
33+
{
34+
"idx": 4,
35+
"version": "7",
36+
"when": 1780927293836,
37+
"tag": "0004_small_psylocke",
38+
"breakpoints": true
3239
}
3340
]
3441
}

backend/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"cheerio": "^1.2.0",
4040
"cors": "^2.8.6",
4141
"dotenv": "^17.4.2",
42+
"drizzle-orm": "^0.45.2",
4243
"express": "^5.2.1",
4344
"iron-session": "^8.0.4",
4445
"pdfkit": "^0.18.0",
@@ -56,8 +57,10 @@
5657
"@types/pg": "^8.20.0",
5758
"@types/supertest": "^7.2.0",
5859
"@vitest/coverage-v8": "^4.1.8",
60+
"drizzle-kit": "^0.31.10",
5961
"nodemon": "^3.1.14",
6062
"supertest": "^7.2.2",
63+
"tsx": "^4.22.4",
6164
"typescript": "^6.0.3",
6265
"vitest": "^4.1.8"
6366
}

frontend/src/components/login/RegisterSide.tsx

Lines changed: 135 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import { useState, FormEvent } from "react";
2-
import { Eye, EyeOff, ArrowLeft } from "lucide-react";
2+
import { Eye, EyeOff, ArrowLeft, Github, Linkedin } from "lucide-react";
33
import { Image } from "@unpic/react";
44
import { motion } from "framer-motion";
55
import PhoneInput from "react-phone-number-input";
6+
7+
import { api } from "@/services/api";
68
import "react-phone-number-input/style.css";
9+
import { useNavigate } from "react-router-dom";
10+
11+
interface ValidationError {
12+
_errors?: string[];
13+
}
714

815
const 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

4956
export 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

Comments
 (0)