- Adicionada interface Course em types.ts - Criado CourseForm para cadastro/edição de turmas - Implementada página CourseManagement com tabelas Excel-like - Adicionadas funções CRUD de cursos no DataContext - Integrado dropdown de cursos no EventForm baseado na instituição - Adicionada rota 'courses' no App.tsx - Link 'Gestão de Cursos' inserido no menu principal após 'Equipe & Fotógrafos' - Removido 'Configurações' do menu principal (mantido apenas no dropdown do avatar) - Implementado comportamento de toggle para seleção de universidades - Sistema restrito a SUPERADMIN e BUSINESS_OWNER
534 lines
36 KiB
TypeScript
534 lines
36 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { User, Mail, Phone, MapPin, Lock, Bell, Palette, Globe, Save, Camera, GraduationCap } from 'lucide-react';
|
|
import { Button } from '../components/Button';
|
|
import { useAuth } from '../contexts/AuthContext';
|
|
import { UserRole } from '../types';
|
|
|
|
export const SettingsPage: React.FC = () => {
|
|
const { user } = useAuth();
|
|
const isAdmin = user?.role === UserRole.SUPERADMIN || user?.role === UserRole.BUSINESS_OWNER;
|
|
const [activeTab, setActiveTab] = useState<'profile' | 'account' | 'notifications' | 'appearance' | 'courses'>('profile');
|
|
const [profileData, setProfileData] = useState({
|
|
name: 'João Silva',
|
|
email: 'joao.silva@photum.com',
|
|
phone: '(41) 99999-0000',
|
|
location: 'Curitiba, PR',
|
|
bio: 'Fotógrafo profissional especializado em eventos e formaturas há mais de 10 anos.',
|
|
avatar: 'https://i.pravatar.cc/150?img=68'
|
|
});
|
|
|
|
const [notificationSettings, setNotificationSettings] = useState({
|
|
emailNotifications: true,
|
|
pushNotifications: true,
|
|
smsNotifications: false,
|
|
eventReminders: true,
|
|
paymentAlerts: true,
|
|
teamUpdates: false
|
|
});
|
|
|
|
const [appearanceSettings, setAppearanceSettings] = useState({
|
|
theme: 'light',
|
|
language: 'pt-BR',
|
|
dateFormat: 'DD/MM/YYYY',
|
|
currency: 'BRL'
|
|
});
|
|
|
|
const handleSaveProfile = () => {
|
|
alert('Perfil atualizado com sucesso!');
|
|
};
|
|
|
|
const handleSaveNotifications = () => {
|
|
alert('Configurações de notificações salvas!');
|
|
};
|
|
|
|
const handleSaveAppearance = () => {
|
|
alert('Configurações de aparência salvas!');
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 pt-20 sm:pt-24 md:pt-28 lg:pt-32 pb-8 sm:pb-12">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
{/* Header */}
|
|
<div className="mb-6 sm:mb-8">
|
|
<h1 className="text-2xl sm:text-3xl font-serif font-bold text-brand-black mb-2">
|
|
Configurações
|
|
</h1>
|
|
<p className="text-sm sm:text-base text-gray-600">
|
|
Gerencie suas preferências e informações da conta
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4 sm:gap-6">
|
|
{/* Mobile Tabs - Horizontal */}
|
|
<div className="lg:hidden">
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-2">
|
|
<nav className="flex overflow-x-auto gap-1 scrollbar-hide">
|
|
<button
|
|
onClick={() => setActiveTab('profile')}
|
|
className={`flex items-center gap-2 px-3 py-2 rounded-md transition-colors whitespace-nowrap text-sm ${activeTab === 'profile'
|
|
? 'bg-brand-gold text-white'
|
|
: 'text-gray-700 hover:bg-gray-100'
|
|
}`}
|
|
>
|
|
<User size={18} />
|
|
<span className="font-medium">Perfil</span>
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('account')}
|
|
className={`flex items-center gap-2 px-3 py-2 rounded-md transition-colors whitespace-nowrap text-sm ${activeTab === 'account'
|
|
? 'bg-brand-gold text-white'
|
|
: 'text-gray-700 hover:bg-gray-100'
|
|
}`}
|
|
>
|
|
<Lock size={18} />
|
|
<span className="font-medium">Conta</span>
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('notifications')}
|
|
className={`flex items-center gap-2 px-3 py-2 rounded-md transition-colors whitespace-nowrap text-sm ${activeTab === 'notifications'
|
|
? 'bg-brand-gold text-white'
|
|
: 'text-gray-700 hover:bg-gray-100'
|
|
}`}
|
|
>
|
|
<Bell size={18} />
|
|
<span className="font-medium">Notificações</span>
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('appearance')}
|
|
className={`flex items-center gap-2 px-3 py-2 rounded-md transition-colors whitespace-nowrap text-sm ${activeTab === 'appearance'
|
|
? 'bg-brand-gold text-white'
|
|
: 'text-gray-700 hover:bg-gray-100'
|
|
}`}
|
|
>
|
|
<Palette size={18} />
|
|
<span className="font-medium">Aparência</span>
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Desktop Sidebar - Vertical */}
|
|
<div className="hidden lg:block lg:col-span-1">
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
<nav className="space-y-1">
|
|
<button
|
|
onClick={() => setActiveTab('profile')}
|
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-md transition-colors ${activeTab === 'profile'
|
|
? 'bg-brand-gold text-white'
|
|
: 'text-gray-700 hover:bg-gray-100'
|
|
}`}
|
|
>
|
|
<User size={20} />
|
|
<span className="font-medium">Perfil</span>
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('account')}
|
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-md transition-colors ${activeTab === 'account'
|
|
? 'bg-brand-gold text-white'
|
|
: 'text-gray-700 hover:bg-gray-100'
|
|
}`}
|
|
>
|
|
<Lock size={20} />
|
|
<span className="font-medium">Conta</span>
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('notifications')}
|
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-md transition-colors ${activeTab === 'notifications'
|
|
? 'bg-brand-gold text-white'
|
|
: 'text-gray-700 hover:bg-gray-100'
|
|
}`}
|
|
>
|
|
<Bell size={20} />
|
|
<span className="font-medium">Notificações</span>
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('appearance')}
|
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-md transition-colors ${activeTab === 'appearance'
|
|
? 'bg-brand-gold text-white'
|
|
: 'text-gray-700 hover:bg-gray-100'
|
|
}`}
|
|
>
|
|
<Palette size={20} />
|
|
<span className="font-medium">Aparência</span>
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="lg:col-span-3">
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 sm:p-6 md:p-8">
|
|
{/* Profile Tab */}
|
|
{activeTab === 'profile' && (
|
|
<div>
|
|
<h2 className="text-xl sm:text-2xl font-semibold mb-4 sm:mb-6">Informações do Perfil</h2>
|
|
|
|
<div className="mb-6 sm:mb-8">
|
|
<div className="flex flex-col sm:flex-row items-center sm:items-start gap-4 sm:gap-6">
|
|
<div className="relative">
|
|
<img
|
|
src={profileData.avatar}
|
|
alt="Avatar"
|
|
className="w-20 h-20 sm:w-24 sm:h-24 rounded-full object-cover"
|
|
/>
|
|
<button className="absolute bottom-0 right-0 w-7 h-7 sm:w-8 sm:h-8 bg-brand-gold text-white rounded-full flex items-center justify-center hover:bg-[#a5bd2e] transition-colors">
|
|
<Camera size={14} className="sm:w-4 sm:h-4" />
|
|
</button>
|
|
</div>
|
|
<div className="text-center sm:text-left">
|
|
<h3 className="font-semibold text-base sm:text-lg">{profileData.name}</h3>
|
|
<p className="text-xs sm:text-sm text-gray-600">{profileData.email}</p>
|
|
<button className="text-xs sm:text-sm text-brand-gold hover:underline mt-1">
|
|
Alterar foto
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Nome Completo
|
|
</label>
|
|
<div className="relative">
|
|
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
|
|
<input
|
|
type="text"
|
|
value={profileData.name}
|
|
onChange={(e) => setProfileData({ ...profileData, name: e.target.value })}
|
|
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Email
|
|
</label>
|
|
<div className="relative">
|
|
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
|
|
<input
|
|
type="email"
|
|
value={profileData.email}
|
|
onChange={(e) => setProfileData({ ...profileData, email: e.target.value })}
|
|
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Telefone
|
|
</label>
|
|
<div className="relative">
|
|
<Phone className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
|
|
<input
|
|
type="tel"
|
|
value={profileData.phone}
|
|
onChange={(e) => setProfileData({ ...profileData, phone: e.target.value })}
|
|
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Localização
|
|
</label>
|
|
<div className="relative">
|
|
<MapPin className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
|
|
<input
|
|
type="text"
|
|
value={profileData.location}
|
|
onChange={(e) => setProfileData({ ...profileData, location: e.target.value })}
|
|
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Biografia
|
|
</label>
|
|
<textarea
|
|
value={profileData.bio}
|
|
onChange={(e) => setProfileData({ ...profileData, bio: e.target.value })}
|
|
rows={4}
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
/>
|
|
</div>
|
|
|
|
<div className="pt-4">
|
|
<Button size="lg" variant="secondary" onClick={handleSaveProfile}>
|
|
<Save size={20} className="mr-2" />
|
|
Salvar Alterações
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Account Tab */}
|
|
{activeTab === 'account' && (
|
|
<div>
|
|
<h2 className="text-2xl font-semibold mb-6">Segurança da Conta</h2>
|
|
|
|
<div className="space-y-6">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Senha Atual
|
|
</label>
|
|
<input
|
|
type="password"
|
|
placeholder="Digite sua senha atual"
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Nova Senha
|
|
</label>
|
|
<input
|
|
type="password"
|
|
placeholder="Digite sua nova senha"
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Confirmar Nova Senha
|
|
</label>
|
|
<input
|
|
type="password"
|
|
placeholder="Confirme sua nova senha"
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
/>
|
|
</div>
|
|
|
|
<div className="pt-4">
|
|
<Button size="lg" variant="secondary">
|
|
<Lock size={20} className="mr-2" />
|
|
Atualizar Senha
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="pt-8 border-t border-gray-200">
|
|
<h3 className="text-lg font-semibold mb-4">Autenticação em Dois Fatores</h3>
|
|
<p className="text-gray-600 mb-4">
|
|
Adicione uma camada extra de segurança à sua conta
|
|
</p>
|
|
<Button size="md" variant="outline">
|
|
Ativar 2FA
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Notifications Tab */}
|
|
{activeTab === 'notifications' && (
|
|
<div>
|
|
<h2 className="text-2xl font-semibold mb-6">Preferências de Notificações</h2>
|
|
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between py-4 border-b border-gray-200">
|
|
<div>
|
|
<h3 className="font-medium">Notificações por Email</h3>
|
|
<p className="text-sm text-gray-600">Receba atualizações por email</p>
|
|
</div>
|
|
<label className="relative inline-flex items-center cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={notificationSettings.emailNotifications}
|
|
onChange={(e) => setNotificationSettings({
|
|
...notificationSettings,
|
|
emailNotifications: e.target.checked
|
|
})}
|
|
className="sr-only peer"
|
|
/>
|
|
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-brand-gold/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-brand-gold"></div>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between py-4 border-b border-gray-200">
|
|
<div>
|
|
<h3 className="font-medium">Notificações Push</h3>
|
|
<p className="text-sm text-gray-600">Receba notificações no navegador</p>
|
|
</div>
|
|
<label className="relative inline-flex items-center cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={notificationSettings.pushNotifications}
|
|
onChange={(e) => setNotificationSettings({
|
|
...notificationSettings,
|
|
pushNotifications: e.target.checked
|
|
})}
|
|
className="sr-only peer"
|
|
/>
|
|
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-brand-gold/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-brand-gold"></div>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between py-4 border-b border-gray-200">
|
|
<div>
|
|
<h3 className="font-medium">SMS</h3>
|
|
<p className="text-sm text-gray-600">Receba mensagens de texto</p>
|
|
</div>
|
|
<label className="relative inline-flex items-center cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={notificationSettings.smsNotifications}
|
|
onChange={(e) => setNotificationSettings({
|
|
...notificationSettings,
|
|
smsNotifications: e.target.checked
|
|
})}
|
|
className="sr-only peer"
|
|
/>
|
|
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-brand-gold/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-brand-gold"></div>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between py-4 border-b border-gray-200">
|
|
<div>
|
|
<h3 className="font-medium">Lembretes de Eventos</h3>
|
|
<p className="text-sm text-gray-600">Receba lembretes antes dos eventos</p>
|
|
</div>
|
|
<label className="relative inline-flex items-center cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={notificationSettings.eventReminders}
|
|
onChange={(e) => setNotificationSettings({
|
|
...notificationSettings,
|
|
eventReminders: e.target.checked
|
|
})}
|
|
className="sr-only peer"
|
|
/>
|
|
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-brand-gold/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-brand-gold"></div>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between py-4 border-b border-gray-200">
|
|
<div>
|
|
<h3 className="font-medium">Alertas de Pagamento</h3>
|
|
<p className="text-sm text-gray-600">Notificações sobre pagamentos</p>
|
|
</div>
|
|
<label className="relative inline-flex items-center cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={notificationSettings.paymentAlerts}
|
|
onChange={(e) => setNotificationSettings({
|
|
...notificationSettings,
|
|
paymentAlerts: e.target.checked
|
|
})}
|
|
className="sr-only peer"
|
|
/>
|
|
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-brand-gold/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-brand-gold"></div>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="pt-4">
|
|
<Button size="lg" variant="secondary" onClick={handleSaveNotifications}>
|
|
<Save size={20} className="mr-2" />
|
|
Salvar Preferências
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Appearance Tab */}
|
|
{activeTab === 'appearance' && (
|
|
<div>
|
|
<h2 className="text-2xl font-semibold mb-6">Aparência e Idioma</h2>
|
|
|
|
<div className="space-y-6">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Tema
|
|
</label>
|
|
<select
|
|
value={appearanceSettings.theme}
|
|
onChange={(e) => setAppearanceSettings({
|
|
...appearanceSettings,
|
|
theme: e.target.value
|
|
})}
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
>
|
|
<option value="light">Claro</option>
|
|
<option value="dark">Escuro</option>
|
|
<option value="auto">Automático</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Idioma
|
|
</label>
|
|
<select
|
|
value={appearanceSettings.language}
|
|
onChange={(e) => setAppearanceSettings({
|
|
...appearanceSettings,
|
|
language: e.target.value
|
|
})}
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
>
|
|
<option value="pt-BR">Português (Brasil)</option>
|
|
<option value="en-US">English (US)</option>
|
|
<option value="es-ES">Español</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Formato de Data
|
|
</label>
|
|
<select
|
|
value={appearanceSettings.dateFormat}
|
|
onChange={(e) => setAppearanceSettings({
|
|
...appearanceSettings,
|
|
dateFormat: e.target.value
|
|
})}
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
>
|
|
<option value="DD/MM/YYYY">DD/MM/YYYY</option>
|
|
<option value="MM/DD/YYYY">MM/DD/YYYY</option>
|
|
<option value="YYYY-MM-DD">YYYY-MM-DD</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Moeda
|
|
</label>
|
|
<select
|
|
value={appearanceSettings.currency}
|
|
onChange={(e) => setAppearanceSettings({
|
|
...appearanceSettings,
|
|
currency: e.target.value
|
|
})}
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
|
|
>
|
|
<option value="BRL">Real (R$)</option>
|
|
<option value="USD">Dólar ($)</option>
|
|
<option value="EUR">Euro (€)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div className="pt-4">
|
|
<Button size="lg" variant="secondary" onClick={handleSaveAppearance}>
|
|
<Save size={20} className="mr-2" />
|
|
Salvar Configurações
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|