Merge pull request #17 from m1guelmcf/pagina-inicial-login

Alteracao da pagina inicial e na pagina de login
This commit is contained in:
DaniloSts 2025-11-18 13:38:50 -03:00 committed by GitHub
commit 361a651412
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 298 additions and 193 deletions

View File

@ -138,7 +138,7 @@ export default function LoginPage() {
Não tem uma conta de paciente?{" "}
</span>
<Link href="/patient/register">
<span className="font-semibold text-primary hover:underline cursor-pointer">
<span className="font-semibold text-blue-600 hover:text-blue-700 hover:underline cursor-pointer">
Crie uma agora
</span>
</Link>
@ -232,18 +232,21 @@ export default function LoginPage() {
{/* Botões */}
<div className="flex gap-3 pt-2">
{/* Botão Cancelar Azul contornado */}
<Button
variant="outline"
onClick={closeModal}
disabled={isLoading}
className="flex-1"
className="flex-1 bg-blue-600 hover:bg-blue-700 text-white"
>
Cancelar
</Button>
{/* Botão Resetar Senha Azul sólido */}
<Button
onClick={handleResetPassword}
disabled={isLoading}
className="flex-1"
className="flex-1 bg-blue-600 hover:bg-blue-700 text-white"
>
{isLoading ? "Enviando..." : "Resetar Senha"}
</Button>

View File

@ -3,50 +3,49 @@
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { useState } from "react";
import { Stethoscope, Baby, Microscope } from "lucide-react";
export default function InicialPage() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
return (
<div className="min-h-screen flex flex-col bg-background">
{/* Barra superior de informações */}
<div className="bg-primary text-primary-foreground text-sm py-2 px-4 md:px-6 flex justify-between items-center">
<div className="min-h-screen flex flex-col bg-white font-sans scroll-smooth text-[#1E2A78]">
{/* Barra superior */}
<div className="bg-[#1E2A78] text-white text-sm py-2 px-4 md:px-6 flex justify-between items-center">
<span className="hidden sm:inline">Horário: 08h00 - 21h00</span>
<span>Email: contato@mediconnect.com</span>
<span className="hover:underline cursor-pointer transition">
Email: contato@mediconnect.com
</span>
</div>
{/* Header principal - Com Logo REAL */}
<header className="bg-card shadow-md py-4 px-4 md:px-6 flex justify-between items-center relative">
{/* Agrupamento do Logo e Nome do Site */}
<a href="#home" className="flex items-center space-x-1 cursor-pointer">
{/* 1. IMAGEM/LOGO REAL: Referenciando o arquivo placeholder-logo.png na pasta public */}
{/* Header */}
<header className="bg-white shadow-md py-4 px-4 md:px-6 flex justify-between items-center relative sticky top-0 z-50 backdrop-blur-md">
<a href="#home" className="flex items-center space-x-2 cursor-pointer">
<img
src="/android-chrome-512x512.png" // O caminho se inicia a partir da pasta 'public'
src="/android-chrome-512x512.png"
alt="Logo MediConnect"
className="w-14 h-14 object-contain" // ALTERADO: Aumentado para w-14 h-14
className="w-20 h-20 object-contain transition-transform hover:scale-105"
/>
{/* 2. NOME DO SITE */}
<h1 className="text-2xl font-bold text-primary">MediConnect</h1>
<h1 className="text-2xl font-extrabold text-[#1E2A78] tracking-tight">
MediConnect
</h1>
</a>
{/* Botão do menu hambúrguer para telas menores */}
{/* Menu Mobile */}
<div className="md:hidden flex items-center space-x-4">
{/* O botão de login agora estará sempre aqui, fora do menu */}
<Link href="/login">
<Button
variant="outline"
className="rounded-full px-4 py-2 text-sm border-2 transition cursor-pointer"
className="rounded-full px-4 py-2 text-sm border-2 border-[#007BFF] text-[#007BFF] hover:bg-[#007BFF] hover:text-white transition"
>
Login
</Button>
</Link>
<button
onClick={() => setIsMenuOpen(!isMenuOpen)}
className="text-primary-foreground focus:outline-none"
className="text-[#1E2A78] focus:outline-none"
>
<svg
className="w-6 h-6 text-primary"
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@ -71,114 +70,140 @@ export default function InicialPage() {
</button>
</div>
{/* Navegação principal */}
{/* Navegação */}
<nav
className={`${
isMenuOpen ? "block" : "hidden"
} absolute top-[76px] left-0 w-full bg-card shadow-md py-4 md:relative md:top-auto md:left-auto md:w-auto md:block md:bg-transparent md:shadow-none z-10`}
} absolute top-[76px] left-0 w-full bg-white shadow-md py-4 md:relative md:top-auto md:left-auto md:w-auto md:block md:bg-transparent md:shadow-none transition-all duration-300 z-10`}
>
<div className="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-6 text-muted-foreground font-medium items-center">
<Link href="#home" className="hover:text-primary">
<div className="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-8 text-gray-600 font-medium items-center">
<Link href="#home" className="hover:text-[#007BFF] transition">
Home
</Link>
<a href="#about" className="hover:text-primary">
<a href="#about" className="hover:text-[#007BFF] transition">
Sobre
</a>
<a href="#departments" className="hover:text-primary">
<a href="#departments" className="hover:text-[#007BFF] transition">
Departamentos
</a>
<a href="#doctors" className="hover:text-primary">
<a href="#doctors" className="hover:text-[#007BFF] transition">
Médicos
</a>
<a href="#contact" className="hover:text-primary">
<a href="#contact" className="hover:text-[#007BFF] transition">
Contato
</a>
</div>
</nav>
{/* Botão de Login para telas maiores (md e acima) */}
{/* Login Desktop */}
<div className="hidden md:flex space-x-4">
<Link href="/login">
<Button
variant="outline"
className="rounded-full px-6 py-2 border-2 transition cursor-pointer"
className="rounded-full px-6 py-2 border-2 border-[#007BFF] text-[#007BFF] hover:bg-[#007BFF] hover:text-white transition"
>
Login
</Button>
</Link>
</div>
</header>
{/* Seção principal de destaque */}
<section className="flex flex-col md:flex-row items-center justify-between px-6 md:px-10 lg:px-20 py-16 bg-background text-center md:text-left">
{/* Hero Section */}
<section className="flex flex-col md:flex-row items-center justify-between px-6 md:px-10 lg:px-20 py-20 bg-gradient-to-r from-[#1E2A78] via-[#007BFF] to-[#00BFFF] text-white">
<div className="max-w-lg mx-auto md:mx-0">
<h2 className="text-muted-foreground uppercase text-sm">
<h2 className="uppercase text-sm tracking-widest opacity-80">
Bem-vindo à Saúde Digital
</h2>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-extrabold text-foreground leading-tight mt-2">
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-extrabold leading-tight mt-2 drop-shadow-lg">
Soluções Médicas <br /> & Cuidados com a Saúde
</h1>
<p className="text-muted-foreground mt-4 text-sm sm:text-base">
<p className="mt-4 text-base leading-relaxed opacity-90">
Excelência em saúde mais de 25 anos. Atendimento médico com
qualidade, segurança e carinho.
</p>
<div className="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4 justify-center md:justify-start">
<Button>Nossos Serviços</Button>
<Button variant="secondary">Saiba Mais</Button>
<div className="mt-8 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4 justify-center md:justify-start">
<Button className="px-8 py-3 text-base font-semibold bg-white text-[#1E2A78] hover:bg-[#EAF4FF] transition-all shadow-md">
Nossos Serviços
</Button>
<Button className="px-8 py-3 text-base font-semibold bg-white text-[#1E2A78] hover:bg-[#EAF4FF] transition-all shadow-md">
Saiba Mais
</Button>
</div>
</div>
<div className="mt-10 md:mt-0 flex justify-center">
<img
src="https://t4.ftcdn.net/jpg/03/20/52/31/360_F_320523164_tx7Rdd7I2XDTvvKfz2oRuRpKOPE5z0ni.jpg"
alt="Médico"
className="w-60 sm:w-80 lg:w-96 h-auto object-cover rounded-lg shadow-lg"
className="w-72 sm:w-96 lg:w-[28rem] h-auto object-cover rounded-2xl shadow-xl "
/>
</div>
</section>
{/* Seção de serviços */}
<section className="py-16 px-6 md:px-10 lg:px-20 bg-card">
<h2 className="text-center text-2xl sm:text-3xl font-bold text-foreground">
{/* Serviços */}
<section
id="departments"
className="py-20 px-6 md:px-10 lg:px-20 bg-[#F8FBFF]"
>
<h2 className="text-center text-3xl sm:text-4xl font-extrabold text-[#1E2A78]">
Cuidados completos para a sua saúde
</h2>
<p className="text-center text-muted-foreground mt-2 text-sm sm:text-base">
<p className="text-center text-gray-600 mt-3 text-base">
Serviços médicos que oferecemos
</p>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mt-10 max-w-5xl mx-auto">
<div className="p-6 bg-background rounded-xl shadow hover:shadow-lg transition">
<h3 className="text-xl font-semibold text-primary">
Clínica Geral
</h3>
<p className="text-muted-foreground mt-2 text-sm">
Seu primeiro passo para o cuidado. Atendimento focado na prevenção
e no diagnóstico inicial.
</p>
<Button className="mt-4 w-full">Agendar</Button>
</div>
<div className="p-6 bg-background rounded-xl shadow hover:shadow-lg transition">
<h3 className="text-xl font-semibold text-primary">Pediatria</h3>
<p className="text-muted-foreground mt-2 text-sm">
Cuidado gentil e especializado para garantir a saúde e o
desenvolvimento de crianças e adolescentes.
</p>
<Button className="mt-4 w-full">Agendar</Button>
</div>
<div className="p-6 bg-background rounded-xl shadow hover:shadow-lg transition">
<h3 className="text-xl font-semibold text-primary">Exames</h3>
<p className="text-muted-foreground mt-2 text-sm">
Resultados rápidos e precisos em exames laboratoriais e de imagem
essenciais para seu diagnóstico.
</p>
<Button className="mt-4 w-full">Agendar</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10 mt-12 max-w-6xl mx-auto">
{/* Card */}
{[
{
title: "Clínica Geral",
desc: "Seu primeiro passo para o cuidado. Atendimento focado na prevenção e no diagnóstico inicial.",
Icon: Stethoscope,
},
{
title: "Pediatria",
desc: "Cuidado gentil e especializado para garantir a saúde e o desenvolvimento de crianças e adolescentes.",
Icon: Baby,
},
{
title: "Exames",
desc: "Resultados rápidos e precisos em exames laboratoriais e de imagem essenciais para seu diagnóstico.",
Icon: Microscope,
},
].map(({ title, desc, Icon }, index) => (
<div
key={index}
className="p-8 bg-white rounded-2xl shadow-md hover:shadow-xl transition-all duration-300 border border-[#E0E9FF] group"
>
<div className="flex items-center space-x-3">
<Icon className="text-[#007BFF] w-6 h-6 group-hover:scale-110 transition-transform" />
<h3 className="text-xl font-semibold">{title}</h3>
</div>
<p className="text-gray-600 mt-3 text-sm leading-relaxed">
{desc}
</p>
<Button className="mt-6 w-full bg-[#007BFF] hover:bg-[#005FCC] text-white transition">
Agendar
</Button>
</div>
))}
</div>
</section>
{/* Footer */}
<footer className="bg-primary text-primary-foreground py-6 text-center text-sm">
<p>© 2025 MediConnect</p>
<footer className="bg-[#1E2A78] text-white py-8 text-center text-sm">
<div className="space-y-2">
<p>© 2025 MediConnect Todos os direitos reservados</p>
<div className="flex justify-center space-x-6 opacity-80">
<a href="#about" className="hover:text-[#00BFFF] transition">
Sobre
</a>
<a href="#departments" className="hover:text-[#00BFFF] transition">
Serviços
</a>
<a href="#contact" className="hover:text-[#00BFFF] transition">
Contato
</a>
</div>
</div>
</footer>
   
</div>
);
}

View File

@ -16,138 +16,215 @@ import { Eye, EyeOff, Mail, Lock, Loader2 } from "lucide-react";
import { usersService } from "@/services/usersApi.mjs";
interface LoginFormProps {
children?: React.ReactNode;
children?: React.ReactNode;
}
interface FormState {
email: string;
password: string;
email: string;
password: string;
}
export function LoginForm({ children }: LoginFormProps) {
const [form, setForm] = useState<FormState>({ email: "", password: "" });
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const { toast } = useToast();
const [form, setForm] = useState<FormState>({ email: "", password: "" });
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const { toast } = useToast();
const [userRoles, setUserRoles] = useState<string[]>([]);
const [authenticatedUser, setAuthenticatedUser] = useState<any>(null);
const [userRoles, setUserRoles] = useState<string[]>([]);
const [authenticatedUser, setAuthenticatedUser] = useState<any>(null);
/**
* --- NOVA FUNÇÃO ---
* Finaliza o login com o perfil de dashboard escolhido e redireciona.
*/
const handleRoleSelection = (selectedDashboardRole: string, user: any) => {
if (!user) {
toast({ title: "Erro de Sessão", description: "Não foi possível encontrar os dados do usuário. Tente novamente.", variant: "destructive" });
setUserRoles([]);
return;
}
/**
* --- NOVA FUNÇÃO ---
* Finaliza o login com o perfil de dashboard escolhido e redireciona.
*/
const handleRoleSelection = (selectedDashboardRole: string, user: any) => {
if (!user) {
toast({
title: "Erro de Sessão",
description:
"Não foi possível encontrar os dados do usuário. Tente novamente.",
variant: "destructive",
});
setUserRoles([]);
return;
}
const roleInLowerCase = selectedDashboardRole.toLowerCase();
console.log("Salvando no localStorage com o perfil:", roleInLowerCase);
const roleInLowerCase = selectedDashboardRole.toLowerCase();
console.log("Salvando no localStorage com o perfil:", roleInLowerCase);
const completeUserInfo = { ...user, user_metadata: { ...user.user_metadata, role: roleInLowerCase } };
localStorage.setItem("user_info", JSON.stringify(completeUserInfo));
let redirectPath = "";
switch (selectedDashboardRole) {
case "gestor": redirectPath = "/manager/dashboard"; break;
case "admin": redirectPath = "/manager/dashboard"; break;
case "medico": redirectPath = "/doctor/dashboard"; break;
case "secretaria": redirectPath = "/secretary/dashboard"; break;
case "paciente": redirectPath = "/patient/dashboard"; break;
}
if (redirectPath) {
toast({ title: `Entrando como ${selectedDashboardRole}...` });
router.push(redirectPath);
} else {
toast({ title: "Erro", description: "Perfil selecionado inválido.", variant: "destructive" });
}
const completeUserInfo = {
...user,
user_metadata: { ...user.user_metadata, role: roleInLowerCase },
};
localStorage.setItem("user_info", JSON.stringify(completeUserInfo));
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
localStorage.removeItem("token");
localStorage.removeItem("user_info");
let redirectPath = "";
switch (selectedDashboardRole) {
case "gestor":
redirectPath = "/manager/dashboard";
break;
case "admin":
redirectPath = "/manager/dashboard";
break;
case "medico":
redirectPath = "/doctor/dashboard";
break;
case "secretaria":
redirectPath = "/secretary/dashboard";
break;
case "paciente":
redirectPath = "/patient/dashboard";
break;
}
try {
const authData = await login(form.email, form.password);
const user = authData.user;
if (!user || !user.id) {
throw new Error("Resposta de autenticação inválida.");
}
if (redirectPath) {
toast({ title: `Entrando como ${selectedDashboardRole}...` });
router.push(redirectPath);
} else {
toast({
title: "Erro",
description: "Perfil selecionado inválido.",
variant: "destructive",
});
}
};
const rolesData = await api.get(`/rest/v1/user_roles?user_id=eq.${user.id}&select=role`);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
localStorage.removeItem("token");
localStorage.removeItem("user_info");
const me = await usersService.getMeSimple()
console.log(me.roles)
try {
const authData = await login(form.email, form.password);
const user = authData.user;
if (!user || !user.id) {
throw new Error("Resposta de autenticação inválida.");
}
if (!me.roles || me.roles.length === 0) {
throw new Error("Nenhum perfil de acesso foi encontrado para este usuário.");
}
const rolesData = await api.get(
`/rest/v1/user_roles?user_id=eq.${user.id}&select=role`
);
handleRoleSelection(me.roles[0], user);
const me = await usersService.getMeSimple();
console.log(me.roles);
} catch (error) {
localStorage.removeItem("token");
localStorage.removeItem("user_info");
toast({
title: "Erro no Login",
description: error instanceof Error ? error.message : "Ocorreu um erro inesperado.",
variant: "destructive",
});
setIsLoading(false);
}
};
if (!me.roles || me.roles.length === 0) {
throw new Error(
"Nenhum perfil de acesso foi encontrado para este usuário."
);
}
// Estado para guardar os botões de seleção de perfil
const [roleSelectionUI, setRoleSelectionUI] = useState<React.ReactNode | null>(null);
handleRoleSelection(me.roles[0], user);
} catch (error) {
localStorage.removeItem("token");
localStorage.removeItem("user_info");
toast({
title: "Erro no Login",
description:
error instanceof Error
? error.message
: "Ocorreu um erro inesperado.",
variant: "destructive",
});
setIsLoading(false);
}
};
return (
<Card className="w-full bg-transparent border-0 shadow-none">
<CardContent className="p-0">
{!roleSelectionUI ? (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-2">
<Label htmlFor="email">E-mail</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-5 h-5" />
<Input id="email" type="email" placeholder="seu.email@exemplo.com" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} className="pl-10 h-11" required disabled={isLoading} autoComplete="username" />
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password">Senha</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-5 h-5" />
<Input id="password" type={showPassword ? "text" : "password"} placeholder="Digite sua senha" value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })} className="pl-10 pr-12 h-11" required disabled={isLoading} autoComplete="current-password" />
<button type="button" onClick={() => setShowPassword(!showPassword)} className="absolute right-2 top-1/2 -translate-y-1/2 h-8 w-8 p-0 text-muted-foreground hover:text-foreground" disabled={isLoading}>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
</div>
<Button type="submit" className="w-full h-11 text-base font-semibold" disabled={isLoading}>
{isLoading ? <Loader2 className="w-5 h-5 animate-spin" /> : "Entrar"}
</Button>
</form>
) : (
<div className="space-y-4 animate-in fade-in-50">
<h3 className="text-lg font-medium text-center text-foreground">Você tem múltiplos perfis</h3>
<p className="text-sm text-muted-foreground text-center">Selecione com qual perfil deseja entrar:</p>
<div className="flex flex-col space-y-3 pt-2">
{userRoles.map((role) => (
<Button key={role} variant="outline" className="h-11 text-base" onClick={() => handleRoleSelection(role, authenticatedUser)}>
Entrar como: {role.charAt(0).toUpperCase() + role.slice(1)}
</Button>
))}
</div>
</div>
)}
{children}
</CardContent>
</Card>
);
}
// Estado para guardar os botões de seleção de perfil
const [roleSelectionUI, setRoleSelectionUI] =
useState<React.ReactNode | null>(null);
return (
<Card className="w-full bg-transparent border-0 shadow-none">
<CardContent className="p-0">
{!roleSelectionUI ? (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-2">
<Label htmlFor="email">E-mail</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-5 h-5" />
<Input
id="email"
type="email"
placeholder="seu.email@exemplo.com"
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
className="pl-10 h-11 focus-visible:ring-blue-600 focus-visible:ring-2"
required
disabled={isLoading}
autoComplete="username"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password">Senha</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-5 h-5" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Digite sua senha"
value={form.password}
onChange={(e) =>
setForm({ ...form, password: e.target.value })
}
className="pl-10 pr-12 h-11 focus-visible:ring-blue-600 focus-visible:ring-2"
required
disabled={isLoading}
autoComplete="current-password"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-2 top-1/2 -translate-y-1/2 h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
disabled={isLoading}
>
{showPassword ? (
<EyeOff className="w-5 h-5" />
) : (
<Eye className="w-5 h-5" />
)}
</button>
</div>
</div>
<Button
type="submit"
className="w-full h-11 bg-blue-600 hover:bg-blue-700 text-white"
disabled={isLoading}
>
{isLoading ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : (
"Entrar"
)}
</Button>
</form>
) : (
<div className="space-y-4 animate-in fade-in-50">
<h3 className="text-lg font-medium text-center text-foreground">
Você tem múltiplos perfis
</h3>
<p className="text-sm text-muted-foreground text-center">
Selecione com qual perfil deseja entrar:
</p>
<div className="flex flex-col space-y-3 pt-2">
{userRoles.map((role) => (
<Button
key={role}
variant="outline"
className="h-11 text-base"
onClick={() => handleRoleSelection(role, authenticatedUser)}
>
Entrar como: {role.charAt(0).toUpperCase() + role.slice(1)}
</Button>
))}
</div>
</div>
)}
{children}
</CardContent>
</Card>
);
}