Skip to content

Commit 34e0dd5

Browse files
author
Jovinull
committed
feat(ui): unifica formulario com abas de admin e usuario
1 parent 62a380d commit 34e0dd5

2 files changed

Lines changed: 66 additions & 40 deletions

File tree

frontend/src/components/login/RegisterSide.tsx

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ function StarsBackground() {
5757
}
5858

5959
export default function RegisterSide() {
60+
const [role, setRole] = useState<"user" | "admin">("user");
6061
const [showPassword, setShowPassword] = useState(false);
6162
const [nome, setNome] = useState("");
6263
const [email, setEmail] = useState("");
@@ -153,14 +154,16 @@ export default function RegisterSide() {
153154
isValid = false;
154155
}
155156

156-
if (!level) {
157-
setLevelError("A seleção de nível é obrigatória.");
158-
isValid = false;
159-
}
157+
if (role === "admin") {
158+
if (!level) {
159+
setLevelError("A seleção de nível é obrigatória.");
160+
isValid = false;
161+
}
160162

161-
if (selectedTechnologies.length === 0) {
162-
setTechnologiesError("Selecione ao menos uma tecnologia.");
163-
isValid = false;
163+
if (selectedTechnologies.length === 0) {
164+
setTechnologiesError("Selecione ao menos uma tecnologia.");
165+
isValid = false;
166+
}
164167
}
165168

166169
if (isValid) {
@@ -172,8 +175,9 @@ export default function RegisterSide() {
172175
name: nome,
173176
phone: telefone,
174177
cpf: cpf,
175-
level: level,
176-
technologies: selectedTechnologies,
178+
level: role === "admin" ? level : undefined,
179+
technologies: role === "admin" ? selectedTechnologies : undefined,
180+
role: role,
177181
});
178182
window.location.href = "/login?registered=true";
179183
} catch (error: any) {
@@ -213,6 +217,22 @@ export default function RegisterSide() {
213217
</div>
214218
</div>
215219
<form className="relative z-10 space-y-5 w-full max-w-2xl mx-auto my-8" onSubmit={handleSubmit}>
220+
<div className="flex bg-gray-100 dark:bg-neutral-800/80 backdrop-blur-sm p-1.5 rounded-xl mb-4 border border-gray-200 dark:border-neutral-700">
221+
<button
222+
type="button"
223+
onClick={() => { setRole("user"); setLevelError(""); setTechnologiesError(""); }}
224+
className={`flex-1 py-3 text-sm font-semibold rounded-lg transition-all duration-200 ${role === "user" ? "bg-white dark:bg-neutral-700 text-blue-600 dark:text-blue-400 shadow-sm" : "text-gray-500 dark:text-neutral-400 hover:text-gray-700 dark:hover:text-neutral-200"}`}
225+
>
226+
Sou Usuário
227+
</button>
228+
<button
229+
type="button"
230+
onClick={() => setRole("admin")}
231+
className={`flex-1 py-3 text-sm font-semibold rounded-lg transition-all duration-200 ${role === "admin" ? "bg-white dark:bg-neutral-700 text-blue-600 dark:text-blue-400 shadow-sm" : "text-gray-500 dark:text-neutral-400 hover:text-gray-700 dark:hover:text-neutral-200"}`}
232+
>
233+
Sou Admin
234+
</button>
235+
</div>
216236
{apiError && (
217237
<div className="p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl">
218238
<p className="text-sm text-red-600 dark:text-red-400 text-center">{apiError}</p>
@@ -250,37 +270,41 @@ export default function RegisterSide() {
250270
<input id="cpf" type="text" value={cpf} onChange={(e) => setCpf(formatCpf(e.target.value))} placeholder="091.000.000-00" disabled={isLoading} className={`w-full px-4 py-3.5 rounded-xl border bg-white/80 dark:bg-neutral-800/80 backdrop-blur-sm text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-neutral-500 focus:outline-none focus:ring-2 transition-all shadow-sm ${cpfError ? "border-red-500 focus:ring-red-500" : "border-gray-200 dark:border-neutral-700 focus:ring-blue-500 focus:border-transparent"} ${isLoading ? "opacity-50 cursor-not-allowed" : ""}`} />
251271
{cpfError && <p className="mt-1.5 text-xs text-red-500 font-medium">{cpfError}</p>}
252272
</div>
253-
<div>
254-
<label htmlFor="level" className="block text-sm font-semibold text-gray-700 dark:text-neutral-300 mb-1.5">Nível Profissional</label>
255-
<select id="level" value={level} onChange={(e) => setLevel(e.target.value)} disabled={isLoading} className={`w-full px-4 py-3.5 rounded-xl border bg-white/80 dark:bg-neutral-800/80 backdrop-blur-sm text-gray-900 dark:text-white focus:outline-none focus:ring-2 transition-all shadow-sm ${levelError ? "border-red-500 focus:ring-red-500" : "border-gray-200 dark:border-neutral-700 focus:ring-blue-500 focus:border-transparent"} ${isLoading ? "opacity-50 cursor-not-allowed" : ""}`}>
256-
<option value="" disabled hidden>Selecione seu nível</option>
257-
<option value="Estágio">Estágio</option>
258-
<option value="Júnior">Júnior</option>
259-
<option value="Pleno">Pleno</option>
260-
<option value="Sênior">Sênior</option>
261-
<option value="Especialista">Especialista</option>
262-
</select>
263-
{levelError && <p className="mt-1.5 text-xs text-red-500 font-medium">{levelError}</p>}
264-
</div>
265-
<div>
266-
<label className="block text-sm font-semibold text-gray-700 dark:text-neutral-300 mb-1.5">Tecnologias que domina</label>
267-
<div className={`flex flex-wrap gap-2.5 p-4 rounded-xl border bg-white/80 dark:bg-neutral-800/80 backdrop-blur-sm shadow-sm transition-all ${technologiesError ? "border-red-500" : "border-gray-200 dark:border-neutral-700"} ${isLoading ? "opacity-50" : ""}`}>
268-
{POPULAR_TECHNOLOGIES.map((tech) => {
269-
const isSelected = selectedTechnologies.includes(tech);
270-
return (
271-
<Badge
272-
key={tech}
273-
variant={isSelected ? "default" : "secondary"}
274-
className={`cursor-pointer text-sm py-1.5 px-3.5 transition-all duration-200 hover:scale-105 active:scale-95 border-0 ${isSelected ? "bg-gradient-to-r from-blue-600 to-purple-600 hover:opacity-90 text-white shadow-md shadow-blue-500/20" : "bg-gray-100 dark:bg-neutral-700 text-gray-700 dark:text-neutral-300 hover:bg-gray-200 dark:hover:bg-neutral-600"}`}
275-
onClick={() => toggleTechnology(tech)}
276-
>
277-
{tech}
278-
</Badge>
279-
);
280-
})}
281-
</div>
282-
{technologiesError && <p className="mt-1.5 text-xs text-red-500 font-medium">{technologiesError}</p>}
283-
</div>
273+
{role === "admin" && (
274+
<>
275+
<div>
276+
<label htmlFor="level" className="block text-sm font-semibold text-gray-700 dark:text-neutral-300 mb-1.5">Nível Profissional</label>
277+
<select id="level" value={level} onChange={(e) => setLevel(e.target.value)} disabled={isLoading} className={`w-full px-4 py-3.5 rounded-xl border bg-white/80 dark:bg-neutral-800/80 backdrop-blur-sm text-gray-900 dark:text-white focus:outline-none focus:ring-2 transition-all shadow-sm ${levelError ? "border-red-500 focus:ring-red-500" : "border-gray-200 dark:border-neutral-700 focus:ring-blue-500 focus:border-transparent"} ${isLoading ? "opacity-50 cursor-not-allowed" : ""}`}>
278+
<option value="" disabled hidden>Selecione seu nível</option>
279+
<option value="Estágio">Estágio</option>
280+
<option value="Júnior">Júnior</option>
281+
<option value="Pleno">Pleno</option>
282+
<option value="Sênior">Sênior</option>
283+
<option value="Especialista">Especialista</option>
284+
</select>
285+
{levelError && <p className="mt-1.5 text-xs text-red-500 font-medium">{levelError}</p>}
286+
</div>
287+
<div>
288+
<label className="block text-sm font-semibold text-gray-700 dark:text-neutral-300 mb-1.5">Tecnologias que domina</label>
289+
<div className={`flex flex-wrap gap-2.5 p-4 rounded-xl border bg-white/80 dark:bg-neutral-800/80 backdrop-blur-sm shadow-sm transition-all ${technologiesError ? "border-red-500" : "border-gray-200 dark:border-neutral-700"} ${isLoading ? "opacity-50" : ""}`}>
290+
{POPULAR_TECHNOLOGIES.map((tech) => {
291+
const isSelected = selectedTechnologies.includes(tech);
292+
return (
293+
<Badge
294+
key={tech}
295+
variant={isSelected ? "default" : "secondary"}
296+
className={`cursor-pointer text-sm py-1.5 px-3.5 transition-all duration-200 hover:scale-105 active:scale-95 border-0 ${isSelected ? "bg-gradient-to-r from-blue-600 to-purple-600 hover:opacity-90 text-white shadow-md shadow-blue-500/20" : "bg-gray-100 dark:bg-neutral-700 text-gray-700 dark:text-neutral-300 hover:bg-gray-200 dark:hover:bg-neutral-600"}`}
297+
onClick={() => toggleTechnology(tech)}
298+
>
299+
{tech}
300+
</Badge>
301+
);
302+
})}
303+
</div>
304+
{technologiesError && <p className="mt-1.5 text-xs text-red-500 font-medium">{technologiesError}</p>}
305+
</div>
306+
</>
307+
)}
284308
<motion.button type="submit" disabled={isLoading} whileHover={{ scale: isLoading ? 1 : 1.01 }} whileTap={{ scale: isLoading ? 1 : 0.99 }} 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">
285309
{isLoading ? "Cadastrando..." : "Cadastrar"}
286310
</motion.button>

frontend/src/services/authService.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export interface RegisterData {
5555
cpf?: string;
5656
technologies?: string[];
5757
level?: string;
58+
role?: "user" | "admin";
5859
}
5960

6061
export async function login(credentials: LoginCredentials) {
@@ -86,6 +87,7 @@ export async function register(userData: RegisterData) {
8687
cpf: userData.cpf,
8788
technologies: userData.technologies,
8889
level: userData.level,
90+
role: userData.role,
8991
}),
9092
credentials: "include",
9193
});

0 commit comments

Comments
 (0)