mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-17 05:47:52 +00:00
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:
parent
c8420421eb
commit
7430ae15a8
42
src/components/header/Header.tsx
Normal file
42
src/components/header/Header.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
98
src/components/header/ProfileMenu.tsx
Normal file
98
src/components/header/ProfileMenu.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -5,6 +5,9 @@ import { supabase } from '../lib/supabase';
|
||||
interface AuthContextType {
|
||||
user: any;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
signIn: (email: string, password: string) => Promise<any>;
|
||||
signUp: (email: string, password: string) => Promise<any>;
|
||||
signOut: () => Promise<void>;
|
||||
userRole: string | null;
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ export function UserManagementPage() {
|
||||
try {
|
||||
const { data: { users }, error } = await supabase.auth.admin.listUsers();
|
||||
if (error) throw error;
|
||||
setUsers(users || []);
|
||||
setUsers(users?.filter(user => user.email) || []);
|
||||
} catch (err) {
|
||||
console.error('Erro ao buscar usuários:', err);
|
||||
setError('Não foi possível carregar os usuários');
|
||||
|
||||
@ -129,6 +129,8 @@ export function StoryPage() {
|
||||
<AudioRecorder
|
||||
storyId={story.id}
|
||||
studentId={story.student_id}
|
||||
schoolId={story.school_id}
|
||||
classId={story.class_id}
|
||||
onAudioUploaded={(audioUrl) => {
|
||||
console.log('Áudio gravado:', audioUrl);
|
||||
}}
|
||||
|
||||
@ -117,8 +117,8 @@ export function StudentDashboardPage() {
|
||||
{student?.avatar_url ? (
|
||||
<img
|
||||
src={student.avatar_url}
|
||||
alt={student?.name}
|
||||
className="w-full h-full rounded-full object-cover"
|
||||
alt={student.name}
|
||||
className="w-10 h-10 rounded-full"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-2xl font-bold text-purple-600">
|
||||
@ -128,8 +128,8 @@ export function StudentDashboardPage() {
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">{student?.name}</h1>
|
||||
<p className="text-gray-500">
|
||||
{(student?.class as any)?.grade} • {(student?.class as any)?.name} • {(student?.school as any)?.name}
|
||||
<p className="text-xs text-gray-500 truncate">
|
||||
{student?.class?.name} - {student?.class?.grade} • {student?.school?.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -33,6 +33,7 @@ export interface Class {
|
||||
export interface Student {
|
||||
id: string;
|
||||
class_id: string;
|
||||
school_id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
birth_date?: string;
|
||||
@ -41,6 +42,18 @@ export interface Student {
|
||||
guardian_email?: string;
|
||||
created_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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user