feat: adiciona menu de perfil no header

- Cria componente ProfileMenu com dropdown
- Implementa navegação contextual baseada no role do usuário
- Adiciona opções de acesso ao dashboard, perfil e logout
- Atualiza Header para mostrar/esconder botões baseado no estado de autenticação
- Adiciona detecção de clique fora do menu para fechá-lo
This commit is contained in:
Lucas Santana 2024-12-20 13:45:29 -03:00
parent c8420421eb
commit 7430ae15a8
7 changed files with 163 additions and 5 deletions

View File

@ -0,0 +1,42 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { useAuth } from '../../hooks/useAuth';
import { ProfileMenu } from './ProfileMenu';
export function Header() {
const { user } = useAuth();
return (
<header className="bg-white border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<Link to="/" className="flex items-center gap-2">
<img src="/logo.svg" alt="Logo" className="h-8 w-8" />
<span className="font-semibold text-gray-900">Histórias Mágicas</span>
</Link>
<div className="flex items-center gap-4">
{user ? (
<ProfileMenu />
) : (
<>
<Link
to="/login/school"
className="text-gray-600 hover:text-gray-900"
>
Entrar
</Link>
<Link
to="/register/school"
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition"
>
Cadastrar Escola
</Link>
</>
)}
</div>
</div>
</div>
</header>
);
}

View File

@ -0,0 +1,98 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { LogOut, Settings, LayoutDashboard } from 'lucide-react';
import { useAuth } from '../../hooks/useAuth';
export function ProfileMenu() {
const { user, userRole, signOut } = useAuth();
const navigate = useNavigate();
const [isOpen, setIsOpen] = React.useState(false);
const menuRef = React.useRef<HTMLDivElement>(null);
// Fecha o menu quando clicar fora dele
React.useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const getDashboardPath = () => {
switch (userRole) {
case 'school':
return '/dashboard';
case 'teacher':
return '/professor';
case 'student':
return '/aluno';
default:
return '/';
}
};
const getProfilePath = () => {
switch (userRole) {
case 'school':
return '/dashboard/configuracoes';
case 'teacher':
return '/professor/perfil';
case 'student':
return '/aluno/perfil';
default:
return '/';
}
};
return (
<div className="relative" ref={menuRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center hover:bg-purple-200 transition"
>
<span className="text-lg font-medium text-purple-600">
{user?.user_metadata?.name?.[0]?.toUpperCase() || user?.email?.[0]?.toUpperCase()}
</span>
</button>
{isOpen && (
<div className="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg py-1 border border-gray-200">
<button
onClick={() => {
navigate(getDashboardPath());
setIsOpen(false);
}}
className="w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2"
>
<LayoutDashboard className="h-4 w-4" />
Acessar Dashboard
</button>
<button
onClick={() => {
navigate(getProfilePath());
setIsOpen(false);
}}
className="w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2"
>
<Settings className="h-4 w-4" />
Meu Perfil
</button>
<hr className="my-1" />
<button
onClick={signOut}
className="w-full px-4 py-2 text-left text-sm text-red-600 hover:bg-gray-100 flex items-center gap-2"
>
<LogOut className="h-4 w-4" />
Sair
</button>
</div>
)}
</div>
);
}

View File

@ -5,6 +5,9 @@ import { supabase } from '../lib/supabase';
interface AuthContextType { interface AuthContextType {
user: any; user: any;
loading: boolean; loading: boolean;
error: string | null;
signIn: (email: string, password: string) => Promise<any>;
signUp: (email: string, password: string) => Promise<any>;
signOut: () => Promise<void>; signOut: () => Promise<void>;
userRole: string | null; userRole: string | null;
} }

View File

@ -23,7 +23,7 @@ export function UserManagementPage() {
try { try {
const { data: { users }, error } = await supabase.auth.admin.listUsers(); const { data: { users }, error } = await supabase.auth.admin.listUsers();
if (error) throw error; if (error) throw error;
setUsers(users || []); setUsers(users?.filter(user => user.email) || []);
} catch (err) { } catch (err) {
console.error('Erro ao buscar usuários:', err); console.error('Erro ao buscar usuários:', err);
setError('Não foi possível carregar os usuários'); setError('Não foi possível carregar os usuários');

View File

@ -129,6 +129,8 @@ export function StoryPage() {
<AudioRecorder <AudioRecorder
storyId={story.id} storyId={story.id}
studentId={story.student_id} studentId={story.student_id}
schoolId={story.school_id}
classId={story.class_id}
onAudioUploaded={(audioUrl) => { onAudioUploaded={(audioUrl) => {
console.log('Áudio gravado:', audioUrl); console.log('Áudio gravado:', audioUrl);
}} }}

View File

@ -117,8 +117,8 @@ export function StudentDashboardPage() {
{student?.avatar_url ? ( {student?.avatar_url ? (
<img <img
src={student.avatar_url} src={student.avatar_url}
alt={student?.name} alt={student.name}
className="w-full h-full rounded-full object-cover" className="w-10 h-10 rounded-full"
/> />
) : ( ) : (
<span className="text-2xl font-bold text-purple-600"> <span className="text-2xl font-bold text-purple-600">
@ -128,8 +128,8 @@ export function StudentDashboardPage() {
</div> </div>
<div> <div>
<h1 className="text-2xl font-bold text-gray-900">{student?.name}</h1> <h1 className="text-2xl font-bold text-gray-900">{student?.name}</h1>
<p className="text-gray-500"> <p className="text-xs text-gray-500 truncate">
{(student?.class as any)?.grade} {(student?.class as any)?.name} {(student?.school as any)?.name} {student?.class?.name} - {student?.class?.grade} {student?.school?.name}
</p> </p>
</div> </div>
</div> </div>

View File

@ -33,6 +33,7 @@ export interface Class {
export interface Student { export interface Student {
id: string; id: string;
class_id: string; class_id: string;
school_id: string;
name: string; name: string;
email: string; email: string;
birth_date?: string; birth_date?: string;
@ -41,6 +42,18 @@ export interface Student {
guardian_email?: string; guardian_email?: string;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
status?: string;
avatar_url?: string;
// Relacionamentos
class?: {
id: string;
name: string;
grade: string;
};
school?: {
id: string;
name: string;
};
} }
export interface TeacherClass { export interface TeacherClass {