fix: correcoes no logout, edicao de perfil, favicons e botao de limpar notificacoes
Build and Deploy (Gitea Actions) / build-and-deploy (push) Successful in 2m37s Details

This commit is contained in:
Sidney 2026-06-07 20:48:52 -03:00
parent baaa36f0a2
commit 6f2bc80b0c
6 changed files with 58 additions and 62 deletions

View File

@ -4,6 +4,9 @@ import "./globals.css";
export const metadata: Metadata = {
title: "AgendaPRO Admin — Gerenciador de Assinaturas",
description: "Painel administrativo para gerenciamento de assinaturas, tenants e planos do AgendaPRO SaaS.",
icons: {
icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">🛡️</text></svg>'
}
};
export default function RootLayout({ children }: { children: React.ReactNode }) {

View File

@ -196,11 +196,18 @@ export default function AdminPage() {
}}>
<div className="flex-between" style={{ padding: '12px 16px', borderBottom: '1px solid var(--border-color)' }}>
<h3 style={{ fontSize: '14px', fontWeight: 600 }}>Notificações</h3>
{unreadCount > 0 && (
<button className="btn-ghost" style={{ fontSize: '12px', padding: '4px 8px' }} onClick={markAllAsRead}>
Marcar lidas
</button>
)}
<div style={{ display: 'flex', gap: '8px' }}>
{unreadCount > 0 && (
<button className="btn-ghost" style={{ fontSize: '12px', padding: '4px 8px' }} onClick={markAllAsRead}>
Marcar lidas
</button>
)}
{notifications.length > 0 && (
<button className="btn-ghost" style={{ fontSize: '12px', padding: '4px 8px', color: 'var(--danger)' }} onClick={() => setNotifications([])}>
Limpar
</button>
)}
</div>
</div>
<div style={{ maxHeight: '300px', overflowY: 'auto' }}>
{notifications.map(n => (

View File

@ -4,6 +4,9 @@ import "./globals.css";
export const metadata: Metadata = {
title: "AgendaPRO — Sistema de Agendamento SaaS",
description: "Plataforma completa de agendamento para barbearias, manicures, massagens e estética. Gerencie sua agenda, clientes e pagamentos.",
icons: {
icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">📅</text></svg>'
}
};
export default function RootLayout({ children }: { children: React.ReactNode }) {

View File

@ -143,6 +143,7 @@ export default function Home() {
title={pages[currentPage]?.title || 'Dashboard'}
subtitle={pages[currentPage]?.subtitle || ''}
onMenuClick={() => setSidebarOpen(true)}
onNavigate={setCurrentPage}
/>
<div className="page-content fade-in" key={currentPage}>
{renderPage()}

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
import { Store, Clock, Globe, Bell, Palette, Shield, Save, Copy, ExternalLink, Check, Link, Eye, EyeOff } from 'lucide-react';
import { Store, Clock, Globe, Bell, Palette, Shield, Save, Copy, ExternalLink, Check, Link } from 'lucide-react';
const tabs = [
{ id: 'general', label: 'Geral', icon: Store },
@ -397,54 +397,25 @@ export default function Settings() {
)}
{activeTab === 'security' && (
<SecurityTab handleSave={handleSave} saved={saved} />
<div className="card">
<h3 className="card-title mb-4">Segurança</h3>
<div className="form-group">
<label className="form-label">Senha Atual</label>
<input className="form-input" type="password" placeholder="••••••••" />
</div>
<div className="grid-2">
<div className="form-group">
<label className="form-label">Nova Senha</label>
<input className="form-input" type="password" placeholder="••••••••" />
</div>
<div className="form-group">
<label className="form-label">Confirmar Nova Senha</label>
<input className="form-input" type="password" placeholder="••••••••" />
</div>
</div>
<button className="btn btn-primary mt-4" onClick={handleSave}><Save size={16} /> {saved ? '✓ Salvo!' : 'Salvar'}</button>
</div>
)}
</div>
);
}
function SecurityTab({ handleSave, saved }: { handleSave: () => void; saved: boolean }) {
const [showCurrent, setShowCurrent] = useState(false);
const [showNew, setShowNew] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
// Re-importing specific lucide icons dynamically if possible, or we can use a generic button.
// Actually, we can use Eye and EyeOff if we import them at the top. Let's assume they are imported.
// Wait, I need to add the import at the top of the file!
// For now, I'll just use a button that says "Mostrar" or "Ocultar", or try to use Eye/EyeOff.
return (
<div className="card">
<h3 className="card-title mb-4">Segurança</h3>
<div className="form-group">
<label className="form-label">Senha Atual</label>
<div style={{ position: 'relative' }}>
<input className="form-input" type={showCurrent ? "text" : "password"} placeholder="••••••••" style={{ paddingRight: '40px' }} />
<button type="button" className="btn-ghost btn-icon" onClick={() => setShowCurrent(!showCurrent)} style={{ position: 'absolute', right: '4px', top: '50%', transform: 'translateY(-50%)', padding: '4px' }}>
{showCurrent ? <EyeOff size={16} color="var(--text-muted)" /> : <Eye size={16} color="var(--text-muted)" />}
</button>
</div>
</div>
<div className="grid-2">
<div className="form-group">
<label className="form-label">Nova Senha</label>
<div style={{ position: 'relative' }}>
<input className="form-input" type={showNew ? "text" : "password"} placeholder="••••••••" style={{ paddingRight: '40px' }} />
<button type="button" className="btn-ghost btn-icon" onClick={() => setShowNew(!showNew)} style={{ position: 'absolute', right: '4px', top: '50%', transform: 'translateY(-50%)', padding: '4px' }}>
{showNew ? <EyeOff size={16} color="var(--text-muted)" /> : <Eye size={16} color="var(--text-muted)" />}
</button>
</div>
</div>
<div className="form-group">
<label className="form-label">Confirmar Nova Senha</label>
<div style={{ position: 'relative' }}>
<input className="form-input" type={showConfirm ? "text" : "password"} placeholder="••••••••" style={{ paddingRight: '40px' }} />
<button type="button" className="btn-ghost btn-icon" onClick={() => setShowConfirm(!showConfirm)} style={{ position: 'absolute', right: '4px', top: '50%', transform: 'translateY(-50%)', padding: '4px' }}>
{showConfirm ? <EyeOff size={16} color="var(--text-muted)" /> : <Eye size={16} color="var(--text-muted)" />}
</button>
</div>
</div>
</div>
<button className="btn btn-primary mt-4" onClick={handleSave}><Save size={16} /> {saved ? '✓ Salvo!' : 'Salvar'}</button>
</div>
);
}

View File

@ -5,6 +5,7 @@ interface TopbarProps {
title: string;
subtitle: string;
onMenuClick: () => void;
onNavigate?: (page: string) => void;
}
const mockNotifications = [
@ -13,10 +14,10 @@ const mockNotifications = [
{ id: 3, title: 'Estoque Baixo', message: 'Pomada modeladora abaixo de 5 unidades', type: 'warning', time: '1 dia atrás', read: true },
];
export default function Topbar({ title, subtitle, onMenuClick }: TopbarProps) {
export default function Topbar({ title, subtitle, onMenuClick, onNavigate }: TopbarProps) {
const [avatarInitials, setAvatarInitials] = useState('AD');
const [tenantName, setTenantName] = useState('Administrador');
const [email, setEmail] = useState('');
const [email, setEmail] = useState('admin@agendapro.com');
const [showNotifications, setShowNotifications] = useState(false);
const [showProfile, setShowProfile] = useState(false);
@ -88,9 +89,12 @@ export default function Topbar({ title, subtitle, onMenuClick }: TopbarProps) {
};
const logout = () => {
localStorage.removeItem('agendapro_token');
localStorage.removeItem('agendapro_tenant_slug');
localStorage.removeItem('agendapro_tenant_name');
window.location.href = '/';
localStorage.removeItem('agendapro_email');
localStorage.removeItem('agendapro_role');
window.location.href = '/login';
};
const unreadCount = notifications.filter(n => !n.read).length;
@ -133,11 +137,18 @@ export default function Topbar({ title, subtitle, onMenuClick }: TopbarProps) {
}}>
<div className="flex-between" style={{ padding: '12px 16px', borderBottom: '1px solid var(--border-color)' }}>
<h3 style={{ fontSize: '14px', fontWeight: 600 }}>Notificações</h3>
{unreadCount > 0 && (
<button className="btn-ghost" style={{ fontSize: '12px', padding: '4px 8px' }} onClick={markAllAsRead}>
Marcar lidas
</button>
)}
<div style={{ display: 'flex', gap: '8px' }}>
{unreadCount > 0 && (
<button className="btn-ghost" style={{ fontSize: '12px', padding: '4px 8px' }} onClick={markAllAsRead}>
Marcar lidas
</button>
)}
{notifications.length > 0 && (
<button className="btn-ghost" style={{ fontSize: '12px', padding: '4px 8px', color: 'var(--danger)' }} onClick={() => setNotifications([])}>
Limpar
</button>
)}
</div>
</div>
<div style={{ maxHeight: '300px', overflowY: 'auto' }}>
{notifications.map(n => (
@ -181,7 +192,7 @@ export default function Topbar({ title, subtitle, onMenuClick }: TopbarProps) {
<div style={{ color: 'var(--text-muted)', fontSize: '12px', marginTop: '2px' }}>{email}</div>
</div>
<button className="btn-ghost" style={{ width: '100%', justifyContent: 'flex-start', padding: '10px 12px', fontSize: '13px' }}>
<button className="btn-ghost" onClick={() => { if(onNavigate) onNavigate('settings'); setShowProfile(false); }} style={{ width: '100%', justifyContent: 'flex-start', padding: '10px 12px', fontSize: '13px' }}>
<User size={16} style={{ marginRight: '8px' }} /> Meu Perfil
</button>
<button className="btn-ghost" onClick={logout} style={{ width: '100%', justifyContent: 'flex-start', padding: '10px 12px', fontSize: '13px', color: 'var(--danger)' }}>