OpChan/app/src/components/Header.tsx

494 lines
19 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-04-15 16:28:03 +05:30
import { Button } from '@/components/ui/button';
2025-09-05 20:26:29 +05:30
import { Badge } from '@/components/ui/badge';
2025-08-30 18:34:50 +05:30
import {
LogOut,
Terminal,
AlertTriangle,
CheckCircle,
Key,
CircleSlash,
Home,
Grid3X3,
2025-09-05 12:53:15 +05:30
User,
2025-09-05 17:24:29 +05:30
Bookmark,
2025-09-05 20:26:29 +05:30
Settings,
Menu,
X,
Clock,
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-09-05 16:06:30 +05:30
import { WakuHealthDot } from '@/components/ui/waku-health-indicator';
2025-04-15 16:28:03 +05:30
const Header = () => {
const { currentUser, delegationInfo } = useAuth();
2025-10-03 19:00:01 +05:30
const { statusMessage } = useNetwork();
2025-10-03 19:00:01 +05:30
const location = useLocation();
2025-07-30 15:55:13 +05:30
const { toast } = useToast();
const { content } = useForum();
const { isConnected, disconnect } = useEthereumWallet();
2025-08-30 18:34:50 +05:30
const [walletWizardOpen, setWalletWizardOpen] = useState(false);
2025-09-05 20:26:29 +05:30
const [mobileMenuOpen, setMobileMenuOpen] = 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;
}
// 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 ===
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">
<div className="max-w-6xl mx-auto px-3 sm:px-4">
2025-09-05 20:26:29 +05:30
{/* Top Row - Logo, Network Status, User Actions */}
<div className="flex items-center justify-between h-12 sm:h-14 md:h-16">
2025-09-05 20:26:29 +05:30
{/* Left: Logo */}
<div className="flex items-center min-w-0">
2025-09-05 20:26:29 +05:30
<Link
to="/"
className="flex items-center space-x-1 sm:space-x-2 text-xs sm:text-sm font-mono font-semibold uppercase tracking-[0.3em] sm:tracking-[0.4em] text-foreground truncate"
2025-09-05 20:26:29 +05:30
>
<Terminal className="w-4 h-4 sm:w-5 sm:h-5 flex-shrink-0" />
<span className="truncate">opchan</span>
2025-09-05 20:26:29 +05:30
</Link>
</div>
2025-08-30 18:34:50 +05:30
2025-09-05 20:26:29 +05:30
{/* Center: Network Status (Desktop) */}
<div className="hidden lg:flex items-center space-x-3">
<div className="flex items-center space-x-2 px-3 py-1 border border-border text-[10px] uppercase tracking-[0.2em]">
2025-09-05 20:26:29 +05:30
<WakuHealthDot />
<span className="text-[10px] text-muted-foreground">
{statusMessage}
2025-09-05 20:26:29 +05:30
</span>
{content.lastSync && (
<div className="flex items-center space-x-1 text-[10px] text-muted-foreground">
2025-09-05 20:26:29 +05:30
<Clock className="w-3 h-3" />
<span>
{new Date(content.lastSync).toLocaleTimeString([], {
2025-09-05 20:26:29 +05:30
hour: '2-digit',
minute: '2-digit',
})}
</span>
</div>
)}
</div>
</div>
{/* Right: User Actions */}
<div className="flex items-center space-x-2 sm:space-x-3 flex-shrink-0">
2025-09-05 20:26:29 +05:30
{/* Network Status (Mobile) */}
<div className="lg:hidden">
<WakuHealthDot />
</div>
{/* User Status & Actions */}
2025-10-29 17:53:59 +05:30
{isConnected || currentUser?.verificationStatus === EVerificationStatus.ANONYMOUS ? (
<div className="flex items-center space-x-1 sm:space-x-2">
{/* Status Badge - hidden for anonymous sessions */}
{currentUser?.verificationStatus !== EVerificationStatus.ANONYMOUS && (
<Badge
variant="outline"
className={`hidden sm:flex items-center gap-1 text-[9px] sm:text-[10px] px-1.5 sm:px-2 py-0.5 ${
currentUser?.verificationStatus ===
EVerificationStatus.ENS_VERIFIED &&
delegationInfo?.isValid
? 'border-green-500 text-green-300'
2025-10-03 19:00:01 +05:30
: currentUser?.verificationStatus ===
EVerificationStatus.ENS_VERIFIED
? 'border-orange-500 text-orange-300'
: 'border-yellow-500 text-yellow-300'
}`}
>
{getStatusIcon()}
<span className="hidden md:inline">
{currentUser?.verificationStatus ===
EVerificationStatus.WALLET_UNCONNECTED
? 'CONNECT'
: delegationInfo?.isValid
? 'READY'
: currentUser?.verificationStatus ===
EVerificationStatus.ENS_VERIFIED
? 'EXPIRED'
: 'DELEGATE'}
</span>
</Badge>
)}
2025-09-05 20:26:29 +05:30
{/* User Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="flex items-center space-x-1 sm:space-x-2 text-foreground border-border px-2 sm:px-3"
2025-09-05 20:26:29 +05:30
>
<div className="text-[10px] sm:text-[11px] uppercase tracking-[0.15em] sm:tracking-[0.2em] truncate max-w-[80px] sm:max-w-none">
2025-10-03 19:00:01 +05:30
{currentUser?.displayName}
</div>
<Settings className="w-3 h-3 sm:w-4 sm:h-4 flex-shrink-0" />
2025-09-05 20:26:29 +05:30
</Button>
</DropdownMenuTrigger>
2025-09-08 13:01:36 +05:30
<DropdownMenuContent
align="end"
className="w-56 bg-[#050505] border border-border text-sm"
2025-09-08 13:01:36 +05:30
>
2025-09-05 20:26:29 +05:30
<DropdownMenuItem asChild>
2025-09-08 13:01:36 +05:30
<Link
to="/profile"
className="flex items-center space-x-2"
>
2025-09-05 20:26:29 +05:30
<User className="w-4 h-4" />
<span>Profile</span>
</Link>
</DropdownMenuItem>
2025-09-08 13:01:36 +05:30
2025-10-29 17:53:59 +05:30
{currentUser?.verificationStatus === EVerificationStatus.ANONYMOUS ? (
<DropdownMenuItem
onClick={() => setCallSignDialogOpen(true)}
className="flex items-center space-x-2"
>
<User className="w-4 h-4" />
<span>{currentUser?.callSign ? 'Update' : 'Set'} Call Sign</span>
</DropdownMenuItem>
) : (
<DropdownMenuItem
onClick={handleOpenWizard}
className="flex items-center space-x-2"
>
<Settings className="w-4 h-4" />
<span>Setup Wizard</span>
</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>
<DropdownMenuItem
onSelect={e => e.preventDefault()}
2025-09-10 17:29:50 +05:30
className="flex items-center space-x-2 text-orange-400 focus:text-orange-400"
>
<Trash2 className="w-4 h-4" />
<span>Clear Database</span>
</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-09-08 13:01:36 +05:30
<DropdownMenuItem
onClick={handleDisconnect}
2025-09-05 20:26:29 +05:30
className="flex items-center space-x-2 text-red-400 focus:text-red-400"
>
<LogOut className="w-4 h-4" />
2025-10-29 17:53:59 +05:30
<span>{currentUser?.verificationStatus === EVerificationStatus.ANONYMOUS ? 'Exit Anonymous' : 'Disconnect'}</span>
2025-09-05 20:26:29 +05:30
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
) : (
<Button
onClick={handleConnect}
className="text-primary border-primary hover:bg-primary/10 text-[10px] sm:text-[11px] px-2 sm:px-3"
2025-09-05 20:26:29 +05:30
>
<span className="hidden sm:inline">Connect</span>
<span className="sm:hidden">CON</span>
2025-09-05 20:26:29 +05:30
</Button>
)}
{/* Mobile Menu Toggle */}
<Button
variant="ghost"
size="sm"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden border-border text-foreground p-2"
2025-09-05 20:26:29 +05:30
>
2025-09-08 13:01:36 +05:30
{mobileMenuOpen ? (
<X className="w-4 h-4 sm:w-5 sm:h-5" />
2025-09-08 13:01:36 +05:30
) : (
<Menu className="w-4 h-4 sm:w-5 sm:h-5" />
2025-09-08 13:01:36 +05:30
)}
2025-09-05 20:26:29 +05:30
</Button>
</div>
</div>
{/* Navigation Bar (Desktop) */}
<div className="hidden md:flex items-center justify-center border-t border-border py-2">
<nav className="flex items-center space-x-0.5 text-[11px] uppercase tracking-[0.2em]">
<Link
to="/"
className={`flex items-center space-x-2 px-4 py-2 border-b ${
2025-08-30 18:34:50 +05:30
location.pathname === '/'
? 'border-primary text-primary'
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
<Home className="w-4 h-4" />
2025-09-05 20:26:29 +05:30
<span>HOME</span>
</Link>
<Link
to="/cells"
className={`flex items-center space-x-2 px-4 py-2 border-b ${
2025-08-30 18:34:50 +05:30
location.pathname === '/cells'
? 'border-primary text-primary'
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
<Grid3X3 className="w-4 h-4" />
2025-09-05 20:26:29 +05:30
<span>CELLS</span>
</Link>
2025-09-05 12:53:15 +05:30
{isConnected && (
2025-09-05 17:24:29 +05:30
<>
<Link
to="/bookmarks"
className={`flex items-center space-x-2 px-4 py-2 border-b ${
2025-09-05 17:24:29 +05:30
location.pathname === '/bookmarks'
? 'border-primary text-primary'
: 'border-transparent text-muted-foreground hover:text-foreground'
2025-09-05 17:24:29 +05:30
}`}
>
<Bookmark className="w-4 h-4" />
2025-09-05 20:26:29 +05:30
<span>BOOKMARKS</span>
2025-09-05 17:24:29 +05:30
</Link>
</>
2025-09-05 12:53:15 +05:30
)}
</nav>
</div>
2025-08-30 18:34:50 +05:30
2025-09-05 20:26:29 +05:30
{/* Mobile Navigation */}
{mobileMenuOpen && (
<div className="md:hidden border-t border-border py-4 space-y-2">
2025-09-05 20:26:29 +05:30
<nav className="space-y-1">
<Link
to="/"
className={`flex items-center space-x-3 px-4 py-3 border ${
2025-09-05 20:26:29 +05:30
location.pathname === '/'
? 'border-primary text-primary'
: 'border-border text-muted-foreground'
2025-09-05 20:26:29 +05:30
}`}
onClick={() => setMobileMenuOpen(false)}
>
2025-09-05 20:26:29 +05:30
<Home className="w-4 h-4" />
<span>HOME</span>
</Link>
<Link
to="/cells"
className={`flex items-center space-x-3 px-4 py-3 border ${
2025-09-05 20:26:29 +05:30
location.pathname === '/cells'
? 'border-primary text-primary'
: 'border-border text-muted-foreground'
2025-09-05 20:26:29 +05:30
}`}
onClick={() => setMobileMenuOpen(false)}
>
<Grid3X3 className="w-4 h-4" />
<span>CELLS</span>
</Link>
{isConnected && (
<>
<Link
to="/bookmarks"
className={`flex items-center space-x-3 px-4 py-3 border ${
2025-09-05 20:26:29 +05:30
location.pathname === '/bookmarks'
? 'border-primary text-primary'
: 'border-border text-muted-foreground'
2025-09-05 20:26:29 +05:30
}`}
onClick={() => setMobileMenuOpen(false)}
>
<Bookmark className="w-4 h-4" />
<span>BOOKMARKS</span>
</Link>
<Link
to="/profile"
className={`flex items-center space-x-3 px-4 py-3 border ${
2025-09-05 20:26:29 +05:30
location.pathname === '/profile'
? 'border-primary text-primary'
: 'border-border text-muted-foreground'
2025-09-05 20:26:29 +05:30
}`}
onClick={() => setMobileMenuOpen(false)}
>
<User className="w-4 h-4" />
<span>PROFILE</span>
</Link>
</>
)}
</nav>
2025-09-08 13:01:36 +05:30
2025-09-05 20:26:29 +05:30
{/* Mobile Network Status */}
<div className="px-4 py-3 border-t border-border">
<div className="flex items-center space-x-2 text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
2025-09-05 20:26:29 +05:30
<WakuHealthDot />
<span>{statusMessage}</span>
{content.lastSync && (
2025-09-05 20:26:29 +05:30
<span className="ml-auto">
{new Date(content.lastSync).toLocaleTimeString([], {
2025-09-05 20:26:29 +05:30
hour: '2-digit',
minute: '2-digit',
})}
</span>
)}
</div>
</div>
2025-09-05 20:26:29 +05:30
</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
{/* 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;