403Webshell
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 :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/app.copyrightchain.it/copyrightchain_app/App.tsx.bak_remove_mock_anchoring
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';
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 { setAuthToken } from './services/apiClient';
import { AuthProvider, useAuth } from './context/AuthContext';
import { Shield, Layout, FileText, Archive, MessageSquare, User as UserIcon, Gavel, Monitor, Menu, X, Coins, Lock, Award, Scale, Home } from 'lucide-react';

const GATEWAY_BASE = 'https://app.copyrightchain.it/api/ai';

type ClientUserApi = {
  id: string;
  email: string;
  name: string;
  wallet?: string;
  plan?: string;
  ai_limit_monthly?: number;
  updated_at?: string;
  created_at?: string;
  must_change_password?: boolean;
};

type ClientAuthResponse = {
  ok: boolean;
  token: string;
  user: ClientUserApi;
  balance_abo: number;
  pricing?: any;
  must_change_password?: boolean;
};

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;
};

function buildEmptyUser(role: 'admin' | 'client'): AppUser {
  return {
    id: '',
    username: 'User',
    name: '',
    email: '',
    role,
    identity: { verified: false },
    registrationDate: new Date().toISOString(),
    credits: { balance: 0 },
    wallet: '',
    plan: 'free',
    ai_limit_monthly: 0,
    must_change_password: false,
    created_at: '',
    updated_at: '',
    isAuthenticated: false
  };
}

function mapClientUserToAppUser(raw: ClientUserApi, balance: number, role: 'admin' | 'client'): AppUser {
  const name = String(raw?.name || '').trim();
  return {
    id: String(raw?.id || ''),
    username: name || 'User',
    name,
    email: String(raw?.email || ''),
    role,
    identity: { verified: false },
    registrationDate: String(raw?.created_at || new Date().toISOString()),
    credits: { balance: typeof balance === 'number' ? balance : 0 },
    wallet: String(raw?.wallet || ''),
    plan: String(raw?.plan || 'free'),
    ai_limit_monthly: Number(raw?.ai_limit_monthly || 0),
    must_change_password: Boolean(raw?.must_change_password),
    created_at: String(raw?.created_at || ''),
    updated_at: String(raw?.updated_at || ''),
    isAuthenticated: true
  };
}

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');
  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>(() => sessionStorage.getItem('ccp_client_token'));
  const [isBootstrappingClient, setIsBootstrappingClient] = useState<boolean>(!isAdminHost && !!sessionStorage.getItem('ccp_client_token'));
  const [user, setUser] = useState<AppUser>(() => {
    const saved = sessionStorage.getItem('app_user_data');
    if (saved) {
      try {
        const parsed = JSON.parse(saved);
        return {
          ...buildEmptyUser(forcedRole),
          ...parsed,
          role: forcedRole,
          isAuthenticated: Boolean(parsed?.isAuthenticated && sessionStorage.getItem('ccp_client_token'))
        };
      } catch (_) {}
    }
    return buildEmptyUser(forcedRole);
  });

  const t = translations[lang];

  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);
  };

  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]);

  useEffect(() => {
    sessionStorage.setItem('app_user_data', JSON.stringify(user));
    if (clientToken) sessionStorage.setItem('ccp_client_token', clientToken);
    else sessionStorage.removeItem('ccp_client_token');
  }, [user, clientToken]);

  useEffect(() => {
    setAuthToken(clientToken);
  }, [clientToken]);


  useEffect(() => {
    if (isAdminHost) return;

    if (!clientToken) {
      setIsBootstrappingClient(false);
      setUser(prev => ({ ...buildEmptyUser(forcedRole), role: forcedRole, credits: { balance: prev.credits?.balance || 0 } }));
      return;
    }

    let cancelled = false;

    const bootstrapClient = async () => {
      setIsBootstrappingClient(true);
      try {
        const res = await fetch(`${GATEWAY_BASE}/client/me`, {
          method: 'GET',
          headers: {
            'X-CCP-Token': clientToken
          }
        });

        const data = await res.json().catch(() => null);

        if (!res.ok || !data?.ok || !data?.user) {
          throw new Error(data?.error || 'session_invalid');
        }

        if (cancelled) return;

        const nextUser = mapClientUserToAppUser(data.user, Number(data.balance_abo || 0), forcedRole);
        setUser(nextUser);

        const sp = new URLSearchParams(window.location.search);
        const billing = (sp.get('billing') || '').toLowerCase().trim();
        if (billing === 'success') {
          notify(lang === 'it' ? 'Pagamento confermato. Wallet aggiornato.' : 'Payment confirmed. Wallet updated.', 'success');
          sp.delete('billing');
          const u = new URL(window.location.href);
          u.search = sp.toString() ? `?${sp.toString()}` : '';
          window.history.replaceState({}, '', u.toString());
        } else if (billing === 'cancel') {
          notify(lang === 'it' ? 'Pagamento annullato.' : 'Payment cancelled.', 'warning');
          sp.delete('billing');
          const u = new URL(window.location.href);
          u.search = sp.toString() ? `?${sp.toString()}` : '';
          window.history.replaceState({}, '', u.toString());
        }
      } catch (_) {
        if (cancelled) return;
        setClientToken(null);
        setUser(buildEmptyUser(forcedRole));
        setActiveTab('auth');
      } finally {
        if (!cancelled) setIsBootstrappingClient(false);
      }
    };

    bootstrapClient();
    return () => { cancelled = true; };
  }, [clientToken, isAdminHost, forcedRole, lang]);

  useEffect(() => {
    if (isAdminHost || isBootstrappingClient) return;
    const isPublic = ['landing', 'verify', 'auth'].includes(activeTab);
    if (!user.isAuthenticated && !isPublic) {
      notify(lang === 'it'
        ? 'Per accedere all’area cliente devi effettuare il login.'
        : 'You must login to access the client area.',
        'info'
      );
      setActiveTab('auth');
    }
  }, [activeTab, user.isAuthenticated, isAdminHost, isBootstrappingClient, lang]);

  useEffect(() => {
    const pendingCerts = certificates.filter(c => c.bitcoinEvidence?.status === 'pending');

    pendingCerts.forEach(cert => {
      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));

        if (selectedCert?.id === cert.id) {
          setSelectedCert(updatedCert);
        }

        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, selectedCert, 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 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");
  };

  const setUserRole = (role: 'admin' | 'client') => {
    setUser(prev => ({ ...prev, role }));
    notify(`Modalità ${role === 'admin' ? 'Admin' : 'Client'}`, "info");
  };

  const handleUpdateUser = (updatedUser: AppUser) => {
    setUser(updatedUser);
  };

  const handleClientAuthSuccess = (payload: ClientAuthResponse) => {
    const nextUser = mapClientUserToAppUser(payload.user, Number(payload.balance_abo || 0), forcedRole);
    setClientToken(payload.token);
    setUser(nextUser);
    setActiveTab('dashboard');
  };

  const handleClientLogout = async () => {
    try {
      if (clientToken) {
        await fetch(`${GATEWAY_BASE}/client/logout`, {
          method: 'POST',
          headers: {
            'X-CCP-Token': clientToken
          }
        }).catch(() => null);
      }
    } finally {
      setClientToken(null);
      setUser(buildEmptyUser(forcedRole));
      setActiveTab('auth');
      notify(lang === 'it' ? 'Sessione chiusa.' : 'Session closed.', 'info');
    }
  };

  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 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 = () => {
    if (!isAdminHost && isBootstrappingClient) {
      return (
        <div className="max-w-3xl mx-auto p-12">
          <div className="bg-white border border-slate-100 shadow-2xl rounded-[2rem] p-12 text-center">
            <p className="text-sm font-black uppercase tracking-widest text-slate-400">Sessione cliente</p>
            <h2 className="text-3xl font-black text-slate-900 mt-4">Caricamento area riservata…</h2>
          </div>
        </div>
      );
    }

    switch (activeTab) {
      case 'auth':
        return <AuthPage onAuthSuccess={handleClientAuthSuccess} />;
      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-40">{user.username || 'User'}</p>
                  <p className="text-[9px] font-bold text-slate-400 uppercase tracking-widest">{user.role}</p>
                </div>
              </button>
              {!isAdminHost && user.isAuthenticated && (
                <button onClick={handleClientLogout} className="w-full px-4 py-3 rounded-2xl bg-slate-50 border border-slate-100 text-slate-700 text-[10px] font-black uppercase tracking-widest">
                  {lang === 'it' ? 'Esci' : 'Logout'}
                </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>
                {!isAdminHost && user.isAuthenticated && (
                  <button onClick={() => { setIsMobileMenuOpen(false); handleClientLogout(); }} className="mt-6 w-full px-4 py-3 rounded-2xl bg-slate-50 border border-slate-100 text-slate-700 text-[10px] font-black uppercase tracking-widest">
                    {lang === 'it' ? 'Esci' : 'Logout'}
                  </button>
                )}
              </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 in modo persistente sul dispositivo, serve il consenso (GDPR). Senza consenso i dati restano solo nella sessione corrente.</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({ onAuthSuccess }: { onAuthSuccess: (payload: ClientAuthResponse) => void }) {
  const sp = new URLSearchParams(window.location.search);
  const initialMode = (sp.get('auth') || '').toLowerCase() === 'register' ? 'register' : 'login';

  const [mode, setMode] = useState<'login' | 'register'>(initialMode);
  const [fullName, setFullName] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [wallet, setWallet] = useState('');
  const [isSubmitting, setIsSubmitting] = useState(false);

  useEffect(() => {
    const current = new URLSearchParams(window.location.search);
    const auth = (current.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());
  };

  const submit = async () => {
    try {
      setIsSubmitting(true);

      if (!email.trim() || !password.trim()) {
        throw new Error('Inserisci email e password.');
      }

      if (mode === 'register') {
        if (!fullName.trim()) throw new Error('Inserisci il nome.');
        if (password.length < 8) throw new Error('La password deve avere almeno 8 caratteri.');
        if (password !== confirmPassword) throw new Error('Le password non coincidono.');

        const res = await fetch(`${GATEWAY_BASE}/client/register`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            name: fullName.trim(),
            email: email.trim(),
            password,
            wallet: wallet.trim()
          })
        });

        const data = await res.json().catch(() => null);
        if (!res.ok || !data?.ok || !data?.token) {
          throw new Error(
            data?.error === 'email_exists' ? 'Email già registrata.' :
            data?.error === 'weak_password' ? 'Password troppo debole.' :
            data?.error === 'invalid_email' ? 'Email non valida.' :
            data?.error || 'register_failed'
          );
        }

        onAuthSuccess(data as ClientAuthResponse);
      } else {
        const res = await fetch(`${GATEWAY_BASE}/client/login`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            email: email.trim(),
            password
          })
        });

        const data = await res.json().catch(() => null);
        if (!res.ok || !data?.ok || !data?.token) {
          throw new Error(
            data?.error === 'unauthorized' ? 'Credenziali non valide.' :
            data?.error || 'login_failed'
          );
        }

        onAuthSuccess(data as ClientAuthResponse);
      }
    } catch (e: any) {
      alert(`Errore autenticazione: ${e?.message || 'unknown_error'}`);
    } finally {
      setIsSubmitting(false);
    }
  };

  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.'
            : 'Compila il form per creare il tuo account cliente.'}
        </p>

        <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
          {mode === 'register' && (
            <div className="space-y-2 md:col-span-2">
              <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Nome</label>
              <input
                value={fullName}
                onChange={(e) => setFullName(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">Conferma Password</label>
                <input
                  type="password"
                  value={confirmPassword}
                  onChange={(e) => setConfirmPassword(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>

              <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={submit}
          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'
            ? 'Registrazione: nome, email, password, conferma password, wallet opzionale.'
            : 'Login: email e password.'}
        </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;

Youez - 2016 - github.com/yon3zu
LinuXploit