adição de recuperação de senha e limpeza de remanescentes

This commit is contained in:
joao.herculano
2026-08-06 11:53:09 -03:00
parent 4e1ebe861c
commit 5b10e46e62
20 changed files with 183 additions and 925 deletions
+109 -22
View File
@@ -1,12 +1,12 @@
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { Boxes, Loader2, LogIn, UserPlus } from "lucide-react";
import { Boxes, Eye, EyeOff, KeyRound, Loader2, LogIn, UserPlus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { criarConta, entrar, obterSessaoAtual, normalizarNome } from "@/lib/auth";
import { criarConta, entrar, obterSessaoAtual, normalizarNome, recuperarSenha } from "@/lib/auth";
export const Route = createFileRoute("/auth")({
head: () => ({
@@ -32,11 +32,14 @@ export const Route = createFileRoute("/auth")({
function AuthPage() {
const navigate = useNavigate();
const [modo, setModo] = useState<"entrar" | "criar">("entrar");
const [modo, setModo] = useState<"entrar" | "criar" | "recuperar">("entrar");
const [nome, setNome] = useState("");
const [senha, setSenha] = useState("");
const [confirmarSenha, setConfirmarSenha] = useState("");
const [mostrarSenha, setMostrarSenha] = useState(false);
const [carregando, setCarregando] = useState(false);
const [verificando, setVerificando] = useState(true);
const precisaConfirmarSenha = modo !== "entrar";
useEffect(() => {
let cancelado = false;
@@ -57,6 +60,10 @@ function AuthPage() {
toast.error("Informe um nome com pelo menos 3 caracteres e senha com 6 ou mais.");
return;
}
if (precisaConfirmarSenha && senha !== confirmarSenha) {
toast.error("As senhas digitadas não coincidem.");
return;
}
setCarregando(true);
try {
if (modo === "criar") {
@@ -66,6 +73,10 @@ function AuthPage() {
? "Conta criada e liberada. Faça login."
: "Conta criada! Aguarde a liberação de um administrador.",
);
} else if (modo === "recuperar") {
await recuperarSenha({ data: { nome: usuario, senha } });
toast.success("Senha redefinida. Aguarde a aprovação de um administrador.");
setModo("entrar");
} else {
await entrar({ data: { nome: usuario, senha } });
void navigate({ to: "/coleta", replace: true });
@@ -76,6 +87,8 @@ function AuthPage() {
toast.error(
msg.includes("incorretos")
? "Nome de usuário ou senha incorretos."
: msg.includes("não encontrado")
? "Nome de usuário não encontrado."
: msg.includes("já existe")
? "Este nome de usuário já existe. Faça login."
: "Não foi possível concluir. Tente novamente.",
@@ -104,7 +117,9 @@ function AuthPage() {
<p className="text-sm text-muted-foreground">
{modo === "entrar"
? "Entre com seu nome de usuário e senha."
: "Crie sua conta — o acesso é liberado por um administrador."}
: modo === "criar"
? "Crie sua conta — o acesso é liberado por um administrador."
: "Defina uma nova senha. Depois disso, a conta volta para aprovação do administrador."}
</p>
</div>
@@ -124,17 +139,52 @@ function AuthPage() {
</div>
<div className="space-y-2">
<Label htmlFor="senha">Senha</Label>
<Input
id="senha"
type="password"
autoComplete={modo === "entrar" ? "current-password" : "new-password"}
value={senha}
onChange={(e) => setSenha(e.target.value)}
placeholder="Mínimo de 6 caracteres"
className="h-12 rounded-xl"
/>
<div className="relative">
<Input
id="senha"
type={mostrarSenha ? "text" : "password"}
autoComplete={modo === "entrar" ? "current-password" : "new-password"}
value={senha}
onChange={(e) => setSenha(e.target.value)}
placeholder="Mínimo de 6 caracteres"
className="h-12 rounded-xl pr-12"
/>
<button
type="button"
className="absolute top-[14px] right-3 text-muted-foreground"
onClick={() => setMostrarSenha((atual) => !atual)}
aria-label={mostrarSenha ? "Ocultar senha" : "Mostrar senha"}
>
{mostrarSenha ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
</div>
</div>
{precisaConfirmarSenha ? (
<div className="space-y-2">
<Label htmlFor="confirmar-senha">Confirmar senha</Label>
<div className="relative">
<Input
id="confirmar-senha"
type={mostrarSenha ? "text" : "password"}
autoComplete="new-password"
value={confirmarSenha}
onChange={(e) => setConfirmarSenha(e.target.value)}
placeholder="Repita a senha"
className="h-12 rounded-xl pr-12"
/>
<button
type="button"
className="absolute top-[14px] right-3 text-muted-foreground"
onClick={() => setMostrarSenha((atual) => !atual)}
aria-label={mostrarSenha ? "Ocultar senha" : "Mostrar senha"}
>
{mostrarSenha ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
</div>
</div>
) : null}
<Button
type="submit"
className="h-12 w-full rounded-xl text-base font-semibold"
@@ -144,20 +194,57 @@ function AuthPage() {
<Loader2 className="h-5 w-5 animate-spin" />
) : modo === "entrar" ? (
<LogIn className="h-5 w-5" />
) : (
) : modo === "criar" ? (
<UserPlus className="h-5 w-5" />
) : (
<KeyRound className="h-5 w-5" />
)}
{modo === "entrar" ? "Entrar" : "Criar conta"}
{modo === "entrar"
? "Entrar"
: modo === "criar"
? "Criar conta"
: "Recuperar senha"}
</Button>
</form>
<button
type="button"
className="w-full text-center text-sm text-muted-foreground underline-offset-4 hover:underline"
onClick={() => setModo(modo === "entrar" ? "criar" : "entrar")}
>
{modo === "entrar" ? "Não tenho conta — criar agora" : "Já tenho conta — entrar"}
</button>
<div className="space-y-2 text-center text-sm">
{modo !== "entrar" ? (
<button
type="button"
className="w-full text-muted-foreground underline-offset-4 hover:underline"
onClick={() => {
setModo("entrar");
setConfirmarSenha("");
}}
>
tenho conta entrar
</button>
) : null}
{modo !== "criar" ? (
<button
type="button"
className="w-full text-muted-foreground underline-offset-4 hover:underline"
onClick={() => {
setModo("criar");
setConfirmarSenha("");
}}
>
Não tenho conta criar agora
</button>
) : null}
{modo !== "recuperar" ? (
<button
type="button"
className="w-full text-muted-foreground underline-offset-4 hover:underline"
onClick={() => {
setModo("recuperar");
setConfirmarSenha("");
}}
>
Esqueci minha senha
</button>
) : null}
</div>
</div>
</div>
);