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 {
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
@ -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);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user