OpChan/app/src/components/Header.tsx

298 lines
11 KiB
TypeScript
Raw Normal View History

import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useAuth, useForum, useNetwork, useUIState } from '@/hooks';
import { EVerificationStatus } from '@opchan/core';
import { localDatabase } from '@opchan/core';
2025-08-30 18:34:50 +05:30
import {
LogOut,
AlertTriangle,
CheckCircle,
Key,
CircleSlash,
2025-09-10 17:29:50 +05:30
Trash2,
2025-08-30 18:34:50 +05:30
} from 'lucide-react';
import {
2025-09-05 20:26:29 +05:30
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
2025-09-10 17:29:50 +05:30
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
2025-07-30 15:55:13 +05:30
import { useToast } from '@/components/ui/use-toast';
import { useEthereumWallet } from '@opchan/react';
import { WalletWizard } from '@/components/ui/wallet-wizard';
2025-10-29 17:53:59 +05:30
import { CallSignSetupDialog } from '@/components/ui/call-sign-setup-dialog';
2025-11-19 01:37:06 -05:00
import RemixBanner from '@/components/RemixBanner';
2025-04-15 16:28:03 +05:30
const Header = () => {
const { currentUser, delegationInfo } = useAuth();
2025-11-14 14:37:00 -05:00
const { statusMessage, syncStatus, syncDetail } = useNetwork();
2025-10-03 19:00:01 +05:30
const location = useLocation();
2025-07-30 15:55:13 +05:30
const { toast } = useToast();
const { isConnected, disconnect } = useEthereumWallet();
2025-08-30 18:34:50 +05:30
const [walletWizardOpen, setWalletWizardOpen] = useState(false);
2025-10-29 17:53:59 +05:30
const [callSignDialogOpen, setCallSignDialogOpen] = useState(false);
2025-08-30 18:34:50 +05:30
// Use centralized UI state instead of direct LocalDatabase access
2025-10-03 19:00:01 +05:30
const [hasShownWizard, setHasShownWizard] = useUIState(
'hasShownWalletWizard',
false
);
2025-08-30 18:34:50 +05:30
// Auto-open wizard when wallet connects for the first time
React.useEffect(() => {
if (isConnected && !hasShownWizard) {
setWalletWizardOpen(true);
setHasShownWizard(true);
}
}, [isConnected, hasShownWizard, setHasShownWizard]);
2025-08-30 18:34:50 +05:30
2025-04-15 16:28:03 +05:30
const handleConnect = async () => {
setWalletWizardOpen(true);
2025-04-15 16:28:03 +05:30
};
2025-08-30 18:34:50 +05:30
2025-09-05 20:26:29 +05:30
const handleOpenWizard = () => {
setWalletWizardOpen(true);
};
const handleDisconnect = async () => {
2025-10-29 17:53:59 +05:30
// For anonymous users, clear their session
if (currentUser?.verificationStatus === EVerificationStatus.ANONYMOUS) {
await localDatabase.clearUser();
await localDatabase.clearDelegation();
window.location.reload(); // Reload to reset state
return;
}
2025-11-20 15:41:29 -05:00
2025-10-29 17:53:59 +05:30
// For wallet users, disconnect wallet
await disconnect();
setHasShownWizard(false); // Reset so wizard can show again on next connection
toast({
2025-08-30 18:34:50 +05:30
title: 'Wallet Disconnected',
description: 'Your wallet has been disconnected successfully.',
});
2025-04-15 16:28:03 +05:30
};
2025-07-30 15:55:13 +05:30
2025-09-10 17:29:50 +05:30
const handleClearDatabase = async () => {
try {
await localDatabase.clearAll();
toast({
title: 'Database Cleared',
description: 'All local data has been cleared successfully.',
});
} catch (error) {
console.error('Failed to clear database:', error);
toast({
title: 'Error',
description: 'Failed to clear local database. Please try again.',
variant: 'destructive',
});
}
};
const getStatusIcon = () => {
if (!isConnected) return <CircleSlash className="w-4 h-4" />;
if (
2025-10-03 19:00:01 +05:30
currentUser?.verificationStatus ===
2025-11-20 15:41:29 -05:00
EVerificationStatus.ENS_VERIFIED &&
2025-09-05 14:03:29 +05:30
delegationInfo?.isValid
) {
return <CheckCircle className="w-4 h-4" />;
2025-10-03 19:00:01 +05:30
} else if (
currentUser?.verificationStatus === EVerificationStatus.WALLET_CONNECTED
) {
return <AlertTriangle className="w-4 h-4" />;
2025-09-05 13:41:37 +05:30
} else if (
2025-10-03 19:00:01 +05:30
currentUser?.verificationStatus ===
EVerificationStatus.ENS_VERIFIED
2025-09-05 13:41:37 +05:30
) {
return <Key className="w-4 h-4" />;
} else {
return <AlertTriangle className="w-4 h-4" />;
2025-04-24 14:31:00 +05:30
}
};
2025-08-30 18:34:50 +05:30
2025-04-15 16:28:03 +05:30
return (
2025-09-05 20:26:29 +05:30
<>
<header className="bg-cyber-dark border-b border-border sticky top-0 z-40">
2025-11-17 18:29:28 -05:00
<div className="max-w-6xl mx-auto px-2 py-2">
{/* Single Row - Logo, Nav, Status, User */}
2025-11-20 15:41:29 -05:00
<div className="flex items-center justify-between text-sm gap-2">
2025-11-17 18:29:28 -05:00
{/* Logo & Nav */}
<div className="flex items-center gap-3">
<Link to="/" className="font-semibold text-foreground">
OPCHAN
2025-09-05 20:26:29 +05:30
</Link>
2025-11-17 18:29:28 -05:00
<nav className="hidden sm:flex items-center gap-2">
<Link
to="/"
className={location.pathname === '/' ? 'text-primary' : 'text-muted-foreground hover:text-foreground'}
>
HOME
</Link>
<span className="text-muted-foreground">|</span>
<Link
to="/cells"
className={location.pathname === '/cells' ? 'text-primary' : 'text-muted-foreground hover:text-foreground'}
>
CELLS
</Link>
{isConnected && (
<>
<span className="text-muted-foreground">|</span>
<Link
to="/bookmarks"
className={location.pathname === '/bookmarks' ? 'text-primary' : 'text-muted-foreground hover:text-foreground'}
>
BOOKMARKS
</Link>
<span className="text-muted-foreground">|</span>
<Link
to="/following"
className={location.pathname === '/following' ? 'text-primary' : 'text-muted-foreground hover:text-foreground'}
>
FOLLOWING
</Link>
2025-11-17 18:29:28 -05:00
</>
2025-09-05 20:26:29 +05:30
)}
2025-11-17 18:29:28 -05:00
</nav>
2025-09-05 20:26:29 +05:30
</div>
2025-11-17 18:29:28 -05:00
{/* Network Status */}
2025-11-20 15:41:29 -05:00
<div className="hidden md:flex items-center gap-2 text-xs text-muted-foreground">
2025-11-17 18:29:28 -05:00
<span>{statusMessage}</span>
{syncStatus === 'syncing' && syncDetail && syncDetail.missing > 0 && (
<span className="text-yellow-400">SYNCING ({syncDetail.missing})</span>
)}
</div>
2025-09-05 20:26:29 +05:30
2025-11-17 18:29:28 -05:00
{/* User */}
<div className="flex items-center gap-2">
2025-10-29 17:53:59 +05:30
{isConnected || currentUser?.verificationStatus === EVerificationStatus.ANONYMOUS ? (
2025-11-17 18:29:28 -05:00
<div className="flex items-center gap-2">
2025-09-05 20:26:29 +05:30
<DropdownMenu>
<DropdownMenuTrigger asChild>
2025-11-20 15:41:29 -05:00
<button className="text-foreground hover:text-primary text-sm">
2025-11-17 18:29:28 -05:00
{currentUser?.displayName}
</button>
2025-09-05 20:26:29 +05:30
</DropdownMenuTrigger>
2025-11-20 15:41:29 -05:00
<DropdownMenuContent align="end" className="w-48 bg-[#050505] border border-border text-sm">
2025-09-05 20:26:29 +05:30
<DropdownMenuItem asChild>
2025-11-17 18:29:28 -05:00
<Link to="/profile">Profile</Link>
2025-09-05 20:26:29 +05:30
</DropdownMenuItem>
2025-09-08 13:01:36 +05:30
2025-10-29 17:53:59 +05:30
{currentUser?.verificationStatus === EVerificationStatus.ANONYMOUS ? (
2025-11-17 18:29:28 -05:00
<DropdownMenuItem onClick={() => setCallSignDialogOpen(true)}>
{currentUser?.callSign ? 'Update' : 'Set'} Call Sign
2025-10-29 17:53:59 +05:30
</DropdownMenuItem>
) : (
2025-11-17 18:29:28 -05:00
<DropdownMenuItem onClick={handleOpenWizard}>
Setup Wizard
2025-10-29 17:53:59 +05:30
</DropdownMenuItem>
)}
2025-09-08 13:01:36 +05:30
<DropdownMenuSeparator className="bg-border" />
2025-09-08 13:01:36 +05:30
2025-09-10 17:29:50 +05:30
<AlertDialog>
<AlertDialogTrigger asChild>
2025-11-17 18:29:28 -05:00
<DropdownMenuItem onSelect={e => e.preventDefault()} className="text-orange-400 focus:text-orange-400">
Clear Database
2025-09-10 17:29:50 +05:30
</DropdownMenuItem>
</AlertDialogTrigger>
<AlertDialogContent className="bg-[#050505] border border-border text-foreground">
2025-09-10 17:29:50 +05:30
<AlertDialogHeader>
<AlertDialogTitle className="text-foreground uppercase tracking-[0.2em] text-sm">
2025-09-10 17:29:50 +05:30
Clear Local Database
</AlertDialogTitle>
<AlertDialogDescription className="text-muted-foreground">
This will permanently delete all locally stored
data including:
2025-09-10 17:29:50 +05:30
<br /> Posts and comments
<br /> User identities and preferences
<br /> Bookmarks and votes
<br /> UI state and settings
<br />
<br />
<strong>This action cannot be undone.</strong>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="border border-border text-foreground hover:bg-white/5">
2025-09-10 17:29:50 +05:30
Cancel
</AlertDialogCancel>
<AlertDialogAction
onClick={handleClearDatabase}
className="border border-red-600 text-red-400 hover:bg-red-600/10"
2025-09-10 17:29:50 +05:30
>
Clear Database
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
2025-11-17 18:29:28 -05:00
<DropdownMenuItem onClick={handleDisconnect} className="text-red-400 focus:text-red-400">
{currentUser?.verificationStatus === EVerificationStatus.ANONYMOUS ? 'Exit Anonymous' : 'Disconnect'}
2025-09-05 20:26:29 +05:30
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
) : (
2025-11-17 18:29:28 -05:00
<button
2025-09-05 20:26:29 +05:30
onClick={handleConnect}
2025-11-20 15:41:29 -05:00
className="text-primary hover:underline text-sm"
2025-09-05 20:26:29 +05:30
>
2025-11-19 00:31:51 -05:00
LOGIN
2025-11-17 18:29:28 -05:00
</button>
2025-09-05 20:26:29 +05:30
)}
</div>
</div>
2025-04-15 16:28:03 +05:30
</div>
2025-09-05 20:26:29 +05:30
</header>
2025-08-30 18:34:50 +05:30
2025-11-19 01:37:06 -05:00
{/* Remix Banner */}
<RemixBanner />
{/* Wallet Wizard */}
<WalletWizard
open={walletWizardOpen}
onOpenChange={setWalletWizardOpen}
onComplete={() => {
setWalletWizardOpen(false);
toast({
2025-08-30 18:34:50 +05:30
title: 'Setup Complete',
description: 'Your wallet is ready to use!',
});
}}
/>
2025-10-29 17:53:59 +05:30
{/* Call Sign Dialog for Anonymous Users */}
{currentUser?.verificationStatus === EVerificationStatus.ANONYMOUS && (
<CallSignSetupDialog
open={callSignDialogOpen}
onOpenChange={setCallSignDialogOpen}
/>
)}
2025-09-05 20:26:29 +05:30
</>
2025-04-15 16:28:03 +05:30
);
};
export default Header;