296 lines
11 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { Lock, Eye, EyeOff, CheckCircle } from "lucide-react";
import toast from "react-hot-toast";
import { useNavigate } from "react-router-dom";
import { authService } from "../services";
const ResetPassword: React.FC = () => {
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [accessToken, setAccessToken] = useState<string | null>(null);
const navigate = useNavigate();
useEffect(() => {
// Extrair access_token do hash da URL (#access_token=...)
// OU do query string (?token=...&type=recovery)
let token: string | null = null;
let type: string | null = null;
// Primeiro tenta no hash (formato padrão do Supabase após redirect)
const hash = window.location.hash;
console.log("[ResetPassword] Hash completo:", hash);
if (hash) {
const hashParams = new URLSearchParams(hash.substring(1));
token = hashParams.get("access_token");
type = hashParams.get("type");
console.log("[ResetPassword] Token do hash:", token ? token.substring(0, 20) + "..." : "null");
console.log("[ResetPassword] Type do hash:", type);
}
// Se não encontrou no hash, tenta no query string
if (!token) {
const search = window.location.search;
console.log("[ResetPassword] Query string completo:", search);
if (search) {
const queryParams = new URLSearchParams(search);
token = queryParams.get("token");
type = queryParams.get("type");
console.log("[ResetPassword] Token do query:", token ? token.substring(0, 20) + "..." : "null");
console.log("[ResetPassword] Type do query:", type);
}
}
if (token) {
setAccessToken(token);
console.log("[ResetPassword] ✅ Token de recuperação detectado e armazenado");
console.log("[ResetPassword] Type:", type);
} else {
console.error("[ResetPassword] ❌ Token não encontrado no hash nem no query string");
console.log("[ResetPassword] URL completa:", window.location.href);
toast.error("Link de recuperação inválido ou expirado");
setTimeout(() => navigate("/"), 3000);
}
}, [navigate]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Validações
if (!password.trim()) {
toast.error("Digite a nova senha");
return;
}
if (password.length < 6) {
toast.error("A senha deve ter pelo menos 6 caracteres");
return;
}
if (password !== confirmPassword) {
toast.error("As senhas não coincidem");
return;
}
if (!accessToken) {
toast.error("Token de recuperação não encontrado");
return;
}
setLoading(true);
try {
console.log("[ResetPassword] Atualizando senha...");
// Atualizar senha usando o token de recuperação
await authService.updatePassword(accessToken, password);
console.log("[ResetPassword] Senha atualizada com sucesso!");
toast.success("Senha atualizada com sucesso!");
// Limpar formulário
setPassword("");
setConfirmPassword("");
// Redirecionar para login após 2 segundos
setTimeout(() => {
navigate("/");
}, 2000);
} catch (error: unknown) {
console.error("[ResetPassword] Erro ao atualizar senha:", error);
const err = error as {
response?: {
data?: {
error_description?: string;
message?: string;
msg?: string;
error_code?: string;
}
};
message?: string;
};
// Mensagem específica para senha igual
if (err?.response?.data?.error_code === "same_password") {
toast.error("A nova senha deve ser diferente da senha atual");
setLoading(false);
return;
}
const errorMessage =
err?.response?.data?.msg ||
err?.response?.data?.error_description ||
err?.response?.data?.message ||
err?.message ||
"Erro ao atualizar senha. Tente novamente.";
toast.error(errorMessage);
} finally {
setLoading(false);
}
};
// Validações em tempo real
const hasMinLength = password.length >= 6;
const passwordsMatch = password === confirmPassword && confirmPassword !== "";
// Se não tiver token ainda, mostrar loading
if (!accessToken) {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-white dark:from-gray-900 dark:to-gray-950 flex items-center justify-center p-4">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600 dark:text-gray-400">
Verificando link de recuperação...
</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-white dark:from-gray-900 dark:to-gray-950 flex items-center justify-center p-4 transition-colors">
<div className="max-w-md w-full">
{/* Header */}
<div className="text-center mb-8">
<div className="bg-gradient-to-r from-blue-600 to-blue-400 dark:from-blue-700 dark:to-blue-500 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4 shadow-md">
<Lock className="w-8 h-8 text-white" />
</div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-2">
Redefinir Senha
</h1>
<p className="text-gray-600 dark:text-gray-400">
Digite sua nova senha abaixo
</p>
</div>
{/* Formulário */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8 border border-transparent dark:border-gray-700 transition-colors">
<form onSubmit={handleSubmit} className="space-y-6">
{/* Nova Senha */}
<div>
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
>
Nova Senha
</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
id="password"
type={showPassword ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full pl-10 pr-12 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-gray-100"
placeholder="Digite sua nova senha"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
{showPassword ? (
<EyeOff className="w-5 h-5" />
) : (
<Eye className="w-5 h-5" />
)}
</button>
</div>
</div>
{/* Confirmar Senha */}
<div>
<label
htmlFor="confirmPassword"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
>
Confirmar Nova Senha
</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
id="confirmPassword"
type={showConfirmPassword ? "text" : "password"}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full pl-10 pr-12 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:text-gray-100"
placeholder="Confirme sua nova senha"
required
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
{showConfirmPassword ? (
<EyeOff className="w-5 h-5" />
) : (
<Eye className="w-5 h-5" />
)}
</button>
</div>
</div>
{/* Indicadores de Validação */}
{password && (
<div className="space-y-2 text-sm">
<div
className={`flex items-center gap-2 ${
hasMinLength ? "text-green-600" : "text-gray-500"
}`}
>
<CheckCircle className="w-4 h-4" />
<span>Mínimo de 6 caracteres</span>
</div>
{confirmPassword && (
<div
className={`flex items-center gap-2 ${
passwordsMatch ? "text-green-600" : "text-red-600"
}`}
>
<CheckCircle className="w-4 h-4" />
<span>
{passwordsMatch
? "As senhas coincidem"
: "As senhas não coincidem"}
</span>
</div>
)}
</div>
)}
{/* Botão Submit */}
<button
type="submit"
disabled={loading || !hasMinLength || !passwordsMatch}
className="w-full bg-blue-600 text-white py-3 px-4 rounded-lg font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
>
{loading ? "Atualizando..." : "Redefinir Senha"}
</button>
</form>
{/* Link para voltar */}
<div className="mt-6 text-center">
<button
onClick={() => navigate("/")}
className="text-sm text-blue-600 dark:text-blue-400 hover:underline"
>
Voltar para o login
</button>
</div>
</div>
</div>
</div>
);
};
export default ResetPassword;