reset de senha dos usuários
This commit is contained in:
parent
f5283eba4f
commit
1ca3e2f326
@ -1,82 +1,247 @@
|
|||||||
// Caminho: app/login/page.tsx
|
// Caminho: app/login/page.tsx
|
||||||
|
|
||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
|
||||||
|
import {usersService} from "@/services/usersApi.mjs";
|
||||||
import { LoginForm } from "@/components/LoginForm";
|
import { LoginForm } from "@/components/LoginForm";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ArrowLeft } from "lucide-react"; // Importa o ícone de seta
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { ArrowLeft, X } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import RenderFromTemplateContext from "next/dist/client/components/render-from-template-context";
|
||||||
|
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
return (
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-2">
|
const [email, setEmail] = useState("");
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
{/* PAINEL ESQUERDO: O Formulário */}
|
const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
|
||||||
<div className="relative flex flex-col items-center justify-center p-8 bg-background">
|
|
||||||
|
|
||||||
{/* Link para Voltar */}
|
|
||||||
<div className="absolute top-8 left-8">
|
|
||||||
<Link href="/" className="inline-flex items-center text-muted-foreground hover:text-primary transition-colors font-medium">
|
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
||||||
Voltar à página inicial
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* O contêiner principal que agora terá a sombra e o estilo de card */}
|
|
||||||
<div className="w-full max-w-md bg-card p-10 rounded-2xl shadow-xl">
|
const handleOpenModal = () => {
|
||||||
<div className="text-center mb-8">
|
// Tenta pegar o email do input do formulário de login
|
||||||
<h1 className="text-3xl font-bold text-foreground">Acesse sua conta</h1>
|
const emailInput = document.querySelector('input[type="email"]') as HTMLInputElement;
|
||||||
<p className="text-muted-foreground mt-2">Bem-vindo(a) de volta ao MedConnect!</p>
|
if (emailInput?.value) {
|
||||||
|
setEmail(emailInput.value);
|
||||||
|
}
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleResetPassword = async () => {
|
||||||
|
if (!email.trim()) {
|
||||||
|
setMessage({ type: "error", text: "Por favor, insira um e-mail válido." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
setMessage(null);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Chama o método que já faz o fetch corretamente
|
||||||
|
const data = await usersService.resetPassword(email);
|
||||||
|
|
||||||
|
|
||||||
|
console.log("Resposta resetPassword:", data);
|
||||||
|
|
||||||
|
|
||||||
|
setMessage({
|
||||||
|
type: "success",
|
||||||
|
text: "E-mail de recuperação enviado! Verifique sua caixa de entrada.",
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setMessage(null);
|
||||||
|
setEmail("");
|
||||||
|
}, 2000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro no reset de senha:", error);
|
||||||
|
setMessage({
|
||||||
|
type: "error",
|
||||||
|
text:
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Erro ao enviar e-mail. Tente novamente.",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setMessage(null);
|
||||||
|
setEmail("");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-2">
|
||||||
|
|
||||||
|
{/* PAINEL ESQUERDO: O Formulário */}
|
||||||
|
<div className="relative flex flex-col items-center justify-center p-8 bg-background">
|
||||||
|
|
||||||
|
{/* Link para Voltar */}
|
||||||
|
<div className="absolute top-8 left-8">
|
||||||
|
<Link href="/" className="inline-flex items-center text-muted-foreground hover:text-primary transition-colors font-medium">
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
|
Voltar à página inicial
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<LoginForm>
|
|
||||||
{/* Children para o LoginForm */}
|
{/* O contêiner principal que agora terá a sombra e o estilo de card */}
|
||||||
<div className="mt-4 text-center text-sm">
|
<div className="w-full max-w-md bg-card p-10 rounded-2xl shadow-xl">
|
||||||
<Link href="/esqueci-minha-senha">
|
<div className="text-center mb-8">
|
||||||
<span className="text-muted-foreground hover:text-primary cursor-pointer underline">
|
<h1 className="text-3xl font-bold text-foreground">Acesse sua conta</h1>
|
||||||
|
<p className="text-muted-foreground mt-2">Bem-vindo(a) de volta ao MedConnect!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<LoginForm>
|
||||||
|
{/* Children para o LoginForm */}
|
||||||
|
<div className="mt-4 text-center text-sm">
|
||||||
|
<button
|
||||||
|
onClick={handleOpenModal}
|
||||||
|
className="text-muted-foreground hover:text-primary cursor-pointer underline bg-transparent border-none"
|
||||||
|
>
|
||||||
Esqueceu sua senha?
|
Esqueceu sua senha?
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</LoginForm>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="mt-6 text-center text-sm">
|
||||||
|
<span className="text-muted-foreground">Não tem uma conta de paciente? </span>
|
||||||
|
<Link href="/patient/register">
|
||||||
|
<span className="font-semibold text-primary hover:underline cursor-pointer">
|
||||||
|
Crie uma agora
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</LoginForm>
|
|
||||||
|
|
||||||
<div className="mt-6 text-center text-sm">
|
|
||||||
<span className="text-muted-foreground">Não tem uma conta de paciente? </span>
|
|
||||||
<Link href="/patient/register">
|
|
||||||
<span className="font-semibold text-primary hover:underline cursor-pointer">
|
|
||||||
Crie uma agora
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* PAINEL DIREITO: A Imagem e Branding */}
|
|
||||||
<div className="hidden lg:block relative">
|
{/* PAINEL DIREITO: A Imagem e Branding */}
|
||||||
{/* Usamos o componente <Image> para otimização e performance */}
|
<div className="hidden lg:block relative">
|
||||||
<Image
|
{/* Usamos o componente <Image> para otimização e performance */}
|
||||||
src="https://images.unsplash.com/photo-1576091160550-2173dba999ef?q=80&w=2070" // Uma imagem profissional de alta qualidade
|
<Image
|
||||||
alt="Médica utilizando um tablet na clínica MedConnect"
|
src="https://images.unsplash.com/photo-1576091160550-2173dba999ef?q=80&w=2070"
|
||||||
fill
|
alt="Médica utilizando um tablet na clínica MedConnect"
|
||||||
style={{ objectFit: 'cover' }}
|
fill
|
||||||
priority // Ajuda a carregar a imagem mais rápido
|
style={{ objectFit: 'cover' }}
|
||||||
/>
|
priority
|
||||||
{/* Camada de sobreposição para escurecer a imagem e destacar o texto */}
|
/>
|
||||||
<div className="absolute inset-0 bg-primary/80 flex flex-col items-start justify-end p-12 text-left">
|
{/* Camada de sobreposição para escurecer a imagem e destacar o texto */}
|
||||||
|
<div className="absolute inset-0 bg-primary/80 flex flex-col items-start justify-end p-12 text-left">
|
||||||
{/* BLOCO DE NOME ADICIONADO */}
|
{/* BLOCO DE NOME ADICIONADO */}
|
||||||
<div className="mb-6 border-l-4 border-primary-foreground pl-4">
|
<div className="mb-6 border-l-4 border-primary-foreground pl-4">
|
||||||
<h1 className="text-5xl font-extrabold text-primary-foreground tracking-wider">
|
<h1 className="text-5xl font-extrabold text-primary-foreground tracking-wider">
|
||||||
MedConnect
|
MedConnect
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-4xl font-bold text-primary-foreground leading-tight">
|
<h2 className="text-4xl font-bold text-primary-foreground leading-tight">
|
||||||
Tecnologia e Cuidado a Serviço da Sua Saúde.
|
Tecnologia e Cuidado a Serviço da Sua Saúde.
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-4 text-lg text-primary-foreground/80">
|
<p className="mt-4 text-lg text-primary-foreground/80">
|
||||||
Acesse seu portal para uma experiência de saúde integrada, segura e eficiente.
|
Acesse seu portal para uma experiência de saúde integrada, segura e eficiente.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
{/* Modal de Recuperação de Senha */}
|
||||||
|
{isModalOpen && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||||
|
<div className="relative w-full max-w-md bg-card p-8 rounded-2xl shadow-2xl mx-4">
|
||||||
|
{/* Botão de fechar */}
|
||||||
|
<button
|
||||||
|
onClick={closeModal}
|
||||||
|
className="absolute top-4 right-4 text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Cabeçalho */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h2 className="text-2xl font-bold text-foreground">Recuperar Senha</h2>
|
||||||
|
<p className="text-muted-foreground mt-2">
|
||||||
|
Insira seu e-mail e enviaremos um link para redefinir sua senha.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Input de e-mail */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-foreground mb-2">
|
||||||
|
E-mail
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
placeholder="seu@email.com"
|
||||||
|
disabled={isLoading}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Mensagem de feedback */}
|
||||||
|
{message && (
|
||||||
|
<div
|
||||||
|
className={`p-3 rounded-lg text-sm ${
|
||||||
|
message.type === "success"
|
||||||
|
? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300"
|
||||||
|
: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{message.text}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
{/* Botões */}
|
||||||
|
<div className="flex gap-3 pt-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={closeModal}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleResetPassword}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
{isLoading ? "Enviando..." : "Resetar Senha"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,4 +61,40 @@ export const usersService = {
|
|||||||
permissions,
|
permissions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
async resetPassword(email) {
|
||||||
|
if (!email) throw new Error("Email é obrigatório para resetar a senha.");
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/auth/v1/recover`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error("Erro no resetPassword:", res.status, data);
|
||||||
|
throw new Error(`Erro ${res.status}: ${data.message || "Falha ao resetar senha."}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log("✅ Reset de senha:", data);
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Erro na chamada resetPassword:", err);
|
||||||
|
throw new Error(err.message || "Erro inesperado na recuperação de senha.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user