| Server IP : 54.37.205.81 / Your IP : 216.73.216.76 Web Server : nginx/1.22.1 System : Linux vps-249481fa 6.1.0-50-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.176-1 (2026-07-02) x86_64 User : debian ( 1000) PHP Version : 8.2.32 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/app.copyrightchain.it/copyrightchain_app/ |
Upload File : |
import React, { useState, createContext, useContext, useMemo, useEffect, Suspense } from 'react';
import LandingPage from './components/LandingPage';
import Dashboard from './components/Dashboard';
import FileUploader from './components/FileUploader';
import CertificateView from './components/CertificateView';
import VerificationPortal from './components/VerificationPortal';
const AdminDashboard = React.lazy(() => import('./components/AdminDashboard'));
import AdminLogin from './components/AdminLogin';
import { AuthProvider } from './context/AuthContext';
const LegalVault = React.lazy(() => import('./components/LegalVault'));
const AIAssistant = React.lazy(() => import('./components/AIAssistant'));
import UserProfile from './components/UserProfile';
const LegalDefense = React.lazy(() => import('./components/LegalDefense'));
import IPMonitor from './components/IPMonitor';
const BillingCenter = React.lazy(() => import('./components/BillingCenter'));
const LicenseGenerator = React.lazy(() => import('./components/LicenseGenerator'));
import { Certificate, Language, AppUser, IdentityData } from './types';
import { translations } from './translations';
import { draftNotificationEmail, simulateEmailSend } from './emailService';
import { AuthProvider, useAuth } from './context/AuthContext';
import { Shield, Layout, FileText, Search, Settings, Archive, MessageSquare, User as UserIcon, Gavel, Monitor, Globe, CreditCard, Menu, X, Zap, Coins, Lock, Award, Scale, Home, RefreshCw } from 'lucide-react';
export interface NotificationContextType {
notify: (message: string, type?: 'success' | 'error' | 'info' | 'warning') => void;
user: AppUser;
deductCredits: (amount: number) => boolean;
addCredits: (amount: number) => void;
setUserRole: (role: 'admin' | 'client') => void;
onUpdateUser: (updatedUser: AppUser) => void;
}
const NotificationContext = createContext<NotificationContextType | undefined>(undefined);
export const useNotification = () => {
const context = useContext(NotificationContext);
if (!context) throw new Error("useNotification must be used within a NotificationProvider");
return context;
};
const App: React.FC = () => {
const isAdminHost = window.location.hostname.startsWith('admin.');
const forcedRole: 'admin' | 'client' = isAdminHost ? 'admin' : 'client';
const [lang, setLang] = useState<Language>('it');
const [activeTab, setActiveTab] = useState<'landing' | 'dashboard' | 'upload' | 'verify' | 'certificates' | 'admin' | 'ai-assistant' | 'profile' | 'defense' | 'monitor' | 'billing' | 'licenses' | 'auth'>(isAdminHost ? 'admin' : 'landing');
useEffect(() => {
if (isAdminHost) return;
const sp = new URLSearchParams(window.location.search);
const auth = (sp.get('auth') || '').toLowerCase().trim();
if (auth === 'login' || auth === 'register') {
setActiveTab('auth');
}
}, [isAdminHost]);
const [certificates, setCertificates] = useState<Certificate[]>([]);
const [selectedCert, setSelectedCert] = useState<Certificate | null>(null);
const [notifications, setNotifications] = useState<{ id: number, message: string, type: string }[]>([]);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [logoClickCount, setLogoClickCount] = useState(0);
const [hasConsent, setHasConsent] = useState<boolean>(() => localStorage.getItem('cc_consent') === 'true');
const [clientToken, setClientToken] = useState<string | null>(() => {
const consent = localStorage.getItem('cc_consent') === 'true';
return consent ? sessionStorage.getItem('ccp_client_token') : null;
});
const [user, setUser] = useState<AppUser>(() => {
const consent = localStorage.getItem('cc_consent') === 'true';
const saved = consent ? sessionStorage.getItem('app_user_data') : null;
if (saved) return { ...JSON.parse(saved), role: forcedRole, isAuthenticated: false };
return {
id: 'USR-123456',
username: 'User',
email: '',
role: forcedRole,
identity: { verified: false },
registrationDate: new Date().toISOString(),
isAuthenticated: false,
credits: { balance: 0 }
};
});
// CLIENT_GATE_PRIVATE_TABS: su host client blocca le tab private finché l'utente non è autenticato
useEffect(() => {
if (isAdminHost) return;
const publicTabs = ['landing', 'verify'] as const;
const isPublic = ['landing','verify','auth'].includes(activeTab);
if (!user.isAuthenticated && !isPublic) {
notify(lang === 'it'
? 'Per accedere all’area cliente devi registrarti / effettuare il login.'
: 'To access the client area you must register / login.',
'info'
);
setActiveTab('auth');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeTab, user.isAuthenticated, isAdminHost]);
// GDPR: persist personal data only after explicit consent.
useEffect(() => {
if (!hasConsent) return;
sessionStorage.setItem('app_user_data', JSON.stringify(user));
if (clientToken) sessionStorage.setItem('ccp_client_token', clientToken);
else sessionStorage.removeItem('ccp_client_token');
}, [user, hasConsent, clientToken]);
// Gestione background anchoring
useEffect(() => {
const pendingCerts = certificates.filter(c => c.bitcoinEvidence?.status === 'pending');
pendingCerts.forEach(cert => {
// Simula il tempo di attesa della blockchain (per la demo: 20 secondi)
const timer = setTimeout(async () => {
const updatedCert: Certificate = {
...cert,
bitcoinEvidence: {
...cert.bitcoinEvidence!,
status: 'anchored',
txId: '0e3e' + Math.random().toString(16).substr(2, 8),
blockHeight: 842512 + Math.floor(Math.random() * 100)
}
};
setCertificates(prev => prev.map(c => c.id === cert.id ? updatedCert : c));
// Se il certificato è correntemente visualizzato, aggiornalo
if (selectedCert?.id === cert.id) {
setSelectedCert(updatedCert);
}
// Invia notifica email (simulata)
const email = await draftNotificationEmail('anchoring_complete', updatedCert, lang);
await simulateEmailSend(email);
notify(`${lang === 'it' ? 'Ancoraggio Blockchain completato per' : 'Blockchain Anchoring complete for'}: ${cert.title}`, "success");
}, 20000);
return () => clearTimeout(timer);
});
}, [certificates, lang]);
function handleLogoClick() {
if (user.role !== 'admin') return;
setLogoClickCount(prev => {
const next = prev + 1;
if (next === 5) {
setActiveTab('admin');
notify("Admin Portal Unlocked", "success");
return 0;
}
return next;
});
setTimeout(() => setLogoClickCount(0), 2000);
}
const notify = (message: string, type: 'success' | 'error' | 'info' | 'warning' = 'info') => {
const id = Date.now();
setNotifications(prev => [...prev, { id, message, type }]);
setTimeout(() => setNotifications(prev => prev.filter(n => n.id !== id)), 5000);
};
const deductCredits = (amount: number): boolean => {
if (user.credits.balance < amount) {
notify(translations[lang].insufficientCredits, "error");
setActiveTab('billing');
return false;
}
setUser(prev => ({
...prev,
credits: { balance: prev.credits.balance - amount }
}));
return true;
};
const addCredits = (_amount: number) => {
notify("Ricarica temporaneamente disattivata: accredito solo dopo conferma pagamento Stripe.", "error");
return;
};
useEffect(() => {
if (!clientToken) return;
const syncBalance = async () => {
try {
const res = await fetch('https://app.copyrightchain.it/api/ai/me/balance', {
method: 'GET',
headers: {
'X-CCP-Token': clientToken
}
});
const data = await res.json();
if (res.ok && data?.ok && typeof data.balance_abo === 'number') {
setUser(prev => ({
...prev,
credits: { balance: data.balance_abo }
}));
}
} catch (_) {}
};
syncBalance();
}, [clientToken]);
const setUserRole = (role: 'admin' | 'client') => {
setUser(prev => ({ ...prev, role }));
notify(`Modalità ${role === 'admin' ? 'Admin' : 'Client'}`, "info");
if (role === 'client') setIsAdminAuthenticated(false);
};
const handleUpdateUser = (updatedUser: AppUser) => {
setUser(updatedUser);
};
const handleAddCertificate = (cert: Certificate) => {
setCertificates(prev => [cert, ...prev]);
setSelectedCert(cert);
setActiveTab('certificates');
notify(lang === 'it' ? "Certificato emesso con successo. In fase di ancoraggio..." : "Certificate issued successfully. Anchoring in progress...", "success");
};
const t = translations[lang];
const AdminArea: React.FC = () => {
const { token, isAdmin } = useAuth();
if (!token || !isAdmin) return <AdminLogin lang={lang} />;
return <AdminDashboard certificates={certificates} lang={lang} users={[user]} onUpdateUser={handleUpdateUser} />;
};
const renderContent = () => {
switch (activeTab) {
case 'auth':
return <AuthPage onLogin={(username, email, token) => { setClientToken(token); setUser(prev => ({ ...prev, username, email, isAuthenticated: true })); setActiveTab('dashboard'); }} />;
case 'landing': return <LandingPage lang={lang} onStart={() => setActiveTab('auth')} />;
case 'dashboard': return <Dashboard certificates={certificates} onNavigate={setActiveTab} lang={lang} user={user} />;
case 'upload': return <FileUploader onComplete={handleAddCertificate} lang={lang} />;
case 'verify': return <VerificationPortal certificates={certificates} lang={lang} />;
case 'certificates':
if (selectedCert) return (
<div className="space-y-6">
<button onClick={() => setSelectedCert(null)} className="flex items-center gap-2 text-slate-400 hover:text-blue-600 font-black text-[10px] uppercase tracking-widest">
← {lang === 'it' ? 'Torna al Vault' : 'Back to Vault'}
</button>
<CertificateView certificate={selectedCert} lang={lang} />
</div>
);
return <LegalVault certificates={certificates} onViewCert={setSelectedCert} lang={lang} userIdentity={user.identity} onUserIdentityUpdate={(data) => setUser(prev => ({...prev, identity: data}))} />;
case 'admin':
return <AdminArea />;
case 'ai-assistant': return <AIAssistant lang={lang} certificates={certificates} />;
case 'profile': return <UserProfile user={user} lang={lang} onNavigateAdmin={() => setActiveTab('admin')} />;
case 'defense': return <LegalDefense certificates={certificates} lang={lang} />;
case 'monitor': return <IPMonitor certificates={certificates} lang={lang} onNavigate={setActiveTab} />;
case 'billing': return <BillingCenter lang={lang} user={user} onPurchase={addCredits} clientToken={clientToken} />;
case 'licenses': return <LicenseGenerator certificates={certificates} lang={lang} />;
default: return <LandingPage lang={lang} onStart={() => setActiveTab('auth')} />;
}
};
const navItems = [
{ id: 'landing', icon: <Home size={20} />, label: lang === 'it' ? "Home" : "Home" },
{ id: 'dashboard', icon: <Layout size={20} />, label: t.dashboard },
{ id: 'upload', icon: <FileText size={20} />, label: t.newDeposit },
{ id: 'certificates', icon: <Archive size={20} />, label: t.certificates },
{ id: 'licenses', icon: <Award size={20} />, label: lang === 'it' ? "Licenze" : "Licenses", group: 'legal' },
{ id: 'ai-assistant', icon: <MessageSquare size={20} />, label: t.aiAssistant, group: 'legal' },
{ id: 'defense', icon: <Gavel size={20} />, label: t.legalDefense, group: 'legal' },
{ id: 'monitor', icon: <Monitor size={20} />, label: t.ipMonitor, group: 'legal' },
{ id: 'billing', icon: <Coins size={20} />, label: t.billing, group: 'billing' }
];
return (
<AuthProvider>
<NotificationContext.Provider value={{ notify, user, deductCredits, addCredits, setUserRole, onUpdateUser: handleUpdateUser }}>
<div className="min-h-screen bg-[#f8fafc] flex font-sans text-slate-900 overflow-x-hidden">
<aside className="w-80 bg-white border-r border-slate-100 p-8 flex flex-col hidden lg:flex sticky top-0 h-screen z-40">
<div className="flex items-center gap-3 mb-12 cursor-pointer select-none" onClick={handleLogoClick}>
<div className="bg-blue-600 text-white p-2.5 rounded-2xl shadow-lg shadow-blue-100"><Shield size={28} /></div>
<h1 className="text-2xl font-black tracking-tighter text-slate-900 uppercase">{t.appName}</h1>
</div>
<nav className="flex-1 space-y-1 overflow-y-auto custom-scrollbar pr-2">
{navItems.map(item => (
<React.Fragment key={item.id}>
{item.group === 'legal' && navItems.findIndex(i => i.id === item.id) === 4 && (
<div className="pt-8 pb-4"><p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] ml-4">Legal Tools</p></div>
)}
{item.group === 'billing' && (
<div className="pt-8 pb-4"><p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] ml-4">Account</p></div>
)}
<NavItem active={activeTab === item.id} onClick={() => setActiveTab(item.id as any)} icon={item.icon} label={item.label} />
</React.Fragment>
))}
</nav>
<div className="mt-auto pt-8 border-t border-slate-50 space-y-2">
<button onClick={() => setActiveTab('profile')} className={`w-full flex items-center gap-4 p-4 rounded-2xl hover:bg-slate-50 transition-all group ${activeTab === 'profile' ? 'bg-blue-50 text-blue-600' : ''}`}>
<div className="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center text-blue-600">
<UserIcon size={20} />
</div>
<div className="text-left">
<p className="text-xs font-black text-slate-900 uppercase truncate w-32">{user.username}</p>
<p className="text-[9px] font-bold text-slate-400 uppercase tracking-widest">{user.role}</p>
</div>
</button>
</div>
</aside>
<main className="flex-1 flex flex-col h-screen overflow-hidden relative">
<header className="bg-white/80 backdrop-blur-md border-b border-slate-100 p-4 md:p-6 flex justify-between items-center z-30 sticky top-0">
<div className="flex items-center gap-4">
<button onClick={() => setIsMobileMenuOpen(true)} className="lg:hidden p-3 -ml-2 text-slate-900 hover:bg-slate-100 rounded-2xl transition-all">
<Menu size={28} />
</button>
<div className="flex items-center gap-4 bg-slate-900 px-6 py-2.5 rounded-2xl border border-white/10 shadow-2xl">
<div className="flex items-center gap-3">
<div className="bg-yellow-500/20 p-1 rounded-lg"><Coins size={16} className="text-yellow-400" /></div>
<div>
<p className="text-[8px] font-black text-slate-400 uppercase leading-none">ABO Wallet</p>
<p className="text-sm font-black text-white">{user.credits.balance} <span className="text-[8px] opacity-60">COINS</span></p>
</div>
</div>
</div>
</div>
<div className="flex items-center gap-2 md:gap-4">
{user.role === 'admin' && (
<button onClick={() => setActiveTab('admin')} className="p-2.5 bg-slate-50 text-slate-400 hover:text-slate-900 rounded-xl border border-slate-100">
<Lock size={18} />
</button>
)}
<select value={lang} onChange={(e) => setLang(e.target.value as Language)} className="bg-transparent text-[10px] font-black uppercase tracking-widest outline-none border-none cursor-pointer">
<option value="it">IT</option>
<option value="en">EN</option>
</select>
<button onClick={() => setActiveTab('profile')} className="w-10 h-10 rounded-xl bg-slate-50 flex items-center justify-center text-slate-400 hover:text-blue-600 transition-all border border-slate-100">
<UserIcon size={20} />
</button>
</div>
</header>
<div className="flex-1 overflow-y-auto p-0 custom-scrollbar">
<div className="max-w-full mx-auto pb-24">
<Suspense fallback={<div className="p-10 text-slate-400 text-sm font-black uppercase tracking-widest">Loading…</div>}>
{renderContent()}
</Suspense>
</div>
</div>
</main>
{isMobileMenuOpen && (
<div className="fixed inset-0 z-[100] lg:hidden">
<div className="absolute inset-0 bg-slate-900/60 backdrop-blur-sm" onClick={() => setIsMobileMenuOpen(false)}></div>
<aside className="absolute inset-y-0 left-0 w-80 bg-white shadow-2xl p-8 flex flex-col">
<div className="flex justify-between items-center mb-10">
<div className="flex items-center gap-3"><Shield size={20} className="text-blue-600" /><h2 className="font-black uppercase tracking-tighter text-xl">{t.appName}</h2></div>
<button onClick={() => setIsMobileMenuOpen(false)} className="p-2.5 bg-slate-50 text-slate-400 rounded-xl"><X size={24} /></button>
</div>
<nav className="space-y-1 flex-1 overflow-y-auto custom-scrollbar">
{navItems.map(item => (
<button key={item.id} onClick={() => { setActiveTab(item.id as any); setIsMobileMenuOpen(false); }} className={`w-full flex items-center gap-4 px-6 py-4 rounded-2xl transition-all ${activeTab === item.id ? 'bg-blue-600 text-white shadow-xl' : 'text-slate-500 hover:bg-slate-50'}`}>
{item.icon} <span className="text-sm font-black uppercase tracking-tight">{item.label}</span>
</button>
))}
</nav>
</aside>
</div>
)}
<div className="fixed bottom-24 lg:bottom-8 right-4 lg:right-8 z-[110] space-y-4 max-w-sm w-[calc(100%-2rem)] pointer-events-none">
{notifications.map(n => (
<div key={n.id} className={`p-6 rounded-[2rem] shadow-2xl border flex items-center gap-4 animate-in slide-in-from-right duration-300 bg-white pointer-events-auto ${n.type === 'error' ? 'border-red-100' : 'border-blue-100'}`}>
<div className={`p-2 rounded-xl ${n.type === 'error' ? 'bg-red-500' : 'bg-blue-500'} text-white`}><Shield size={20} /></div>
<p className="text-xs font-bold leading-relaxed">{n.message}</p>
</div>
))}
</div>
</div>
{!hasConsent && (
<div className="fixed bottom-0 left-0 right-0 z-[90] p-4">
<div className="max-w-3xl mx-auto bg-white border border-slate-100 shadow-2xl rounded-[2rem] p-6 flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<p className="text-xs font-black uppercase tracking-widest text-slate-400">Privacy</p>
<p className="text-sm font-bold text-slate-700">Per salvare impostazioni e dati di profilo sul dispositivo, serve il consenso (GDPR). Senza consenso, i dati restano solo in sessione.</p>
</div>
<div className="flex gap-3">
<button onClick={() => { localStorage.setItem('cc_consent','false'); setHasConsent(false); }} className="px-6 py-3 rounded-2xl bg-slate-50 border border-slate-100 text-slate-700 text-[10px] font-black uppercase tracking-widest">Rifiuta</button>
<button onClick={() => { localStorage.setItem('cc_consent','true'); setHasConsent(true); }} className="px-6 py-3 rounded-2xl bg-slate-900 text-white text-[10px] font-black uppercase tracking-widest">Accetta</button>
</div>
</div>
</div>
)}
</NotificationContext.Provider>
</AuthProvider>
);
};
function AuthPage({ onLogin }: { onLogin: (username: string, email: string, token: string) => void }) {
const gatewayBase = 'https://app.copyrightchain.it/api/ai';
const sp = new URLSearchParams(window.location.search);
const initialMode = (sp.get('auth') || '').toLowerCase() === 'register' ? 'register' : 'login';
const [mode, setMode] = useState<'login' | 'register'>(initialMode);
const [username, setUsername] = useState('User');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [wallet, setWallet] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
const sp = new URLSearchParams(window.location.search);
const auth = (sp.get('auth') || '').toLowerCase().trim();
if (auth === 'register') setMode('register');
else if (auth === 'login') setMode('login');
}, []);
const switchMode = (nextMode: 'login' | 'register') => {
setMode(nextMode);
const u = new URL(window.location.href);
u.searchParams.set('auth', nextMode);
window.history.replaceState({}, '', u.toString());
};
return (
<div className="max-w-4xl mx-auto p-8">
<div className="bg-white border border-slate-100 shadow-2xl rounded-[2rem] p-10 space-y-6">
<div className="grid grid-cols-2 gap-3 bg-slate-50 rounded-[1.5rem] p-2">
<button
onClick={() => switchMode('login')}
className={`py-4 rounded-[1rem] font-black uppercase tracking-widest text-xs transition-all ${mode === 'login' ? 'bg-slate-900 text-white shadow-lg' : 'text-slate-500'}`}
>
Login
</button>
<button
onClick={() => switchMode('register')}
className={`py-4 rounded-[1rem] font-black uppercase tracking-widest text-xs transition-all ${mode === 'register' ? 'bg-blue-600 text-white shadow-lg shadow-blue-100' : 'text-slate-500'}`}
>
Registrati
</button>
</div>
<h2 className="text-3xl font-black uppercase tracking-tight text-slate-900">
{mode === 'login' ? 'Accesso Area Cliente' : 'Crea Account Cliente'}
</h2>
<p className="text-sm font-bold text-slate-500">
{mode === 'login'
? 'Accedi con email e password per entrare nell’area cliente.'
: 'Inserisci i tuoi dati per creare l’account CopyrightChain.'}
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Nome Utente</label>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full px-6 py-4 rounded-2xl bg-slate-50 border border-slate-100 font-black text-xs outline-none focus:ring-4 focus:ring-blue-500/10"
placeholder="Mario Rossi"
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Email</label>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-6 py-4 rounded-2xl bg-slate-50 border border-slate-100 font-black text-xs outline-none focus:ring-4 focus:ring-blue-500/10"
placeholder="email@dominio.it"
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-6 py-4 rounded-2xl bg-slate-50 border border-slate-100 font-black text-xs outline-none focus:ring-4 focus:ring-blue-500/10"
placeholder="••••••••"
/>
</div>
{mode === 'register' && (
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Wallet (opzionale)</label>
<input
value={wallet}
onChange={(e) => setWallet(e.target.value)}
className="w-full px-6 py-4 rounded-2xl bg-slate-50 border border-slate-100 font-black text-xs outline-none focus:ring-4 focus:ring-blue-500/10"
placeholder="0x..."
/>
</div>
)}
</div>
<button
onClick={async () => {
try {
setIsSubmitting(true);
if (mode === 'register') {
const res = await fetch(`${gatewayBase}/client/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: username || 'User',
email,
password,
wallet
})
});
const data = await res.json();
if (!res.ok || !data?.ok || !data?.token) {
throw new Error(data?.error || 'register_failed');
}
onLogin((data.user?.name || username || 'User'), (data.user?.email || email), data.token);
} else {
const res = await fetch(`${gatewayBase}/client/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email,
password
})
});
const data = await res.json();
if (!res.ok || !data?.ok || !data?.token) {
throw new Error(data?.error || 'login_failed');
}
onLogin((data.user?.name || username || 'User'), (data.user?.email || email), data.token);
}
} catch (e: any) {
alert(`Errore autenticazione: ${e?.message || 'unknown_error'}`);
} finally {
setIsSubmitting(false);
}
}}
disabled={isSubmitting}
className={`w-full py-5 rounded-[2rem] font-black uppercase tracking-[0.2em] shadow-2xl ${mode === 'register' ? 'bg-blue-600 text-white shadow-blue-100' : 'bg-slate-900 text-white'} disabled:opacity-60`}
>
{isSubmitting ? 'Attendere...' : (mode === 'register' ? 'Crea account' : 'Entra')}
</button>
<div className="text-[10px] font-bold text-slate-400">
{mode === 'register'
? 'Apertura diretta registrazione: usa ?auth=register'
: 'Apertura diretta login: usa ?auth=login'}
</div>
</div>
</div>
);
}
const NavItem = ({ active, onClick, icon, label }: any) => (
<button onClick={onClick} className={`w-full flex items-center gap-4 px-6 py-4 rounded-3xl transition-all ${active ? 'bg-blue-600 text-white shadow-xl shadow-blue-100' : 'text-slate-400 hover:text-slate-900 hover:bg-slate-50'}`}>
{icon} <span className="text-sm font-black uppercase tracking-tight">{label}</span>
</button>
);
export default App;