OpChan/src/components/Header.tsx

506 lines
19 KiB
TypeScript
Raw Normal View History

import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
2025-09-05 16:06:30 +05:30
import { useAuth, useWakuHealthStatus } from '@/hooks';
2025-09-05 13:41:37 +05:30
import { useAuth as useAuthContext } from '@/contexts/useAuth';
import { EVerificationStatus } from '@/types/identity';
2025-09-04 13:27:47 +05:30
import { useForum } from '@/contexts/useForum';
2025-09-05 14:03:29 +05:30
import { localDatabase } from '@/lib/database/LocalDatabase';
import { DelegationFullStatus } from '@/lib/delegation';
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 { useAppKitAccount, useDisconnect } from '@reown/appkit/react';
import { WalletWizard } from '@/components/ui/wallet-wizard';
import { useUserDisplay } from '@/hooks';
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 = () => {
2025-09-05 13:41:37 +05:30
const { verificationStatus } = useAuth();
const { getDelegationStatus } = useAuthContext();
2025-09-05 14:03:29 +05:30
const [delegationInfo, setDelegationInfo] =
useState<DelegationFullStatus | null>(null);
2025-09-05 16:06:30 +05:30
const wakuHealth = useWakuHealthStatus();
const location = useLocation();
2025-07-30 15:55:13 +05:30
const { toast } = useToast();
2025-09-04 13:27:47 +05:30
const forum = useForum();
// Use AppKit hooks for multi-chain support
2025-08-30 18:34:50 +05:30
const bitcoinAccount = useAppKitAccount({ namespace: 'bip122' });
const ethereumAccount = useAppKitAccount({ namespace: 'eip155' });
const { disconnect } = useDisconnect();
2025-08-30 18:34:50 +05:30
// Determine which account is connected
const isBitcoinConnected = bitcoinAccount.isConnected;
const isEthereumConnected = ethereumAccount.isConnected;
const isConnected = isBitcoinConnected || isEthereumConnected;
2025-08-30 18:34:50 +05:30
const address = isConnected
? isBitcoinConnected
? bitcoinAccount.address
: ethereumAccount.address
: undefined;
const [walletWizardOpen, setWalletWizardOpen] = useState(false);
2025-09-05 20:26:29 +05:30
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
2025-08-30 18:34:50 +05:30
// ✅ Get display name from enhanced hook
2025-09-03 15:01:57 +05:30
const { displayName } = useUserDisplay(address || '');
2025-09-05 14:03:29 +05:30
// Load delegation status
React.useEffect(() => {
getDelegationStatus().then(setDelegationInfo).catch(console.error);
}, [getDelegationStatus]);
// Use LocalDatabase to persist wizard state across navigation
const getHasShownWizard = async (): Promise<boolean> => {
try {
2025-09-05 14:03:29 +05:30
const value = await localDatabase.loadUIState('hasShownWalletWizard');
return value === true;
} catch {
return false;
}
};
2025-08-30 18:34:50 +05:30
2025-09-05 14:03:29 +05:30
const setHasShownWizard = async (value: boolean): Promise<void> => {
try {
2025-09-05 14:03:29 +05:30
await localDatabase.storeUIState('hasShownWalletWizard', value);
} catch (e) {
console.error('Failed to store wizard state', e);
}
};
2025-08-30 18:34:50 +05:30
// Auto-open wizard when wallet connects for the first time
React.useEffect(() => {
2025-09-05 14:03:29 +05:30
if (isConnected) {
getHasShownWizard().then(hasShown => {
if (!hasShown) {
setWalletWizardOpen(true);
setHasShownWizard(true).catch(console.error);
}
});
}
}, [isConnected]);
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 () => {
await disconnect();
2025-09-05 14:03:29 +05:30
await 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-09-05 13:41:37 +05:30
verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED &&
2025-09-05 14:03:29 +05:30
delegationInfo?.isValid
) {
return <CheckCircle className="w-4 h-4" />;
2025-09-05 13:41:37 +05:30
} else if (verificationStatus === EVerificationStatus.WALLET_CONNECTED) {
return <AlertTriangle className="w-4 h-4" />;
2025-09-05 13:41:37 +05:30
} else if (
verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED
) {
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-black/80 border-b border-cyber-muted/30 sticky top-0 z-50 backdrop-blur-md">
<div className="container mx-auto px-4">
{/* Top Row - Logo, Network Status, User Actions */}
<div className="flex items-center justify-between h-16">
{/* Left: Logo */}
<div className="flex items-center">
<Link
to="/"
className="flex items-center space-x-2 text-xl font-mono font-bold text-white hover:text-cyber-accent transition-colors"
>
<Terminal className="w-6 h-6" />
<span className="tracking-wider">opchan</span>
</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 bg-cyber-muted/20 rounded-full border border-cyber-muted/30">
<WakuHealthDot />
<span className="text-xs font-mono text-cyber-neutral">
{wakuHealth.statusMessage}
</span>
{forum.lastSync && (
<div className="flex items-center space-x-1 text-xs text-cyber-neutral/70">
<Clock className="w-3 h-3" />
<span>
{new Date(forum.lastSync).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
})}
</span>
</div>
)}
</div>
</div>
{/* Right: User Actions */}
<div className="flex items-center space-x-3">
{/* Network Status (Mobile) */}
<div className="lg:hidden">
<WakuHealthDot />
</div>
{/* User Status & Actions */}
{isConnected ? (
<div className="flex items-center space-x-2">
{/* Status Badge */}
2025-09-08 13:01:36 +05:30
<Badge
variant="outline"
2025-09-05 20:26:29 +05:30
className={`font-mono text-xs border-0 ${
2025-09-08 13:01:36 +05:30
verificationStatus ===
EVerificationStatus.ENS_ORDINAL_VERIFIED &&
delegationInfo?.isValid
2025-09-05 20:26:29 +05:30
? 'bg-green-500/20 text-green-400 border-green-500/30'
2025-09-08 13:01:36 +05:30
: verificationStatus ===
EVerificationStatus.ENS_ORDINAL_VERIFIED
? 'bg-orange-500/20 text-orange-400 border-orange-500/30'
: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30'
2025-09-05 20:26:29 +05:30
}`}
>
{getStatusIcon()}
<span className="ml-1">
2025-09-15 14:24:52 +05:30
{verificationStatus === EVerificationStatus.WALLET_UNCONNECTED
? 'CONNECT'
: delegationInfo?.isValid
? 'READY'
: verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED
? 'EXPIRED'
: 'DELEGATE'}
2025-09-05 20:26:29 +05:30
</span>
</Badge>
{/* User Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="flex items-center space-x-2 text-white hover:bg-cyber-muted/30"
>
<div className="text-sm font-mono">{displayName}</div>
<Settings className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
2025-09-08 13:01:36 +05:30
<DropdownMenuContent
align="end"
className="w-56 bg-black/95 border-cyber-muted/30"
>
2025-09-05 20:26:29 +05:30
<div className="px-3 py-2 border-b border-cyber-muted/30">
2025-09-08 13:01:36 +05:30
<div className="text-sm font-medium text-white">
{displayName}
</div>
<div className="text-xs text-cyber-neutral">
{address?.slice(0, 8)}...{address?.slice(-4)}
</div>
2025-09-05 20:26:29 +05:30
</div>
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
<DropdownMenuItem
onClick={handleOpenWizard}
className="flex items-center space-x-2"
>
2025-09-05 20:26:29 +05:30
<Settings className="w-4 h-4" />
<span>Setup Wizard</span>
</DropdownMenuItem>
2025-09-08 13:01:36 +05:30
2025-09-05 20:26:29 +05:30
<DropdownMenuSeparator className="bg-cyber-muted/30" />
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-black/95 border-cyber-muted/30">
<AlertDialogHeader>
<AlertDialogTitle className="text-white">
Clear Local Database
</AlertDialogTitle>
<AlertDialogDescription className="text-cyber-neutral">
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="bg-cyber-muted/20 border-cyber-muted/30 text-white hover:bg-cyber-muted/30">
Cancel
</AlertDialogCancel>
<AlertDialogAction
onClick={handleClearDatabase}
className="bg-red-600 hover:bg-red-700 text-white"
>
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" />
<span>Disconnect</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
) : (
<Button
onClick={handleConnect}
className="bg-cyber-accent hover:bg-cyber-accent/80 text-black font-mono font-medium"
>
Connect
</Button>
)}
{/* Mobile Menu Toggle */}
<Button
variant="ghost"
size="sm"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden text-white hover:bg-cyber-muted/30"
>
2025-09-08 13:01:36 +05:30
{mobileMenuOpen ? (
<X className="w-5 h-5" />
) : (
<Menu className="w-5 h-5" />
)}
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-cyber-muted/20 py-2">
<nav className="flex items-center space-x-1">
<Link
to="/"
2025-09-05 20:26:29 +05:30
className={`flex items-center space-x-2 px-4 py-2 rounded-md text-sm font-mono transition-all ${
2025-08-30 18:34:50 +05:30
location.pathname === '/'
2025-09-05 20:26:29 +05:30
? 'bg-cyber-accent/20 text-cyber-accent border border-cyber-accent/30'
: 'text-cyber-neutral hover:text-white hover:bg-cyber-muted/20'
}`}
>
<Home className="w-4 h-4" />
2025-09-05 20:26:29 +05:30
<span>HOME</span>
</Link>
<Link
to="/cells"
2025-09-05 20:26:29 +05:30
className={`flex items-center space-x-2 px-4 py-2 rounded-md text-sm font-mono transition-all ${
2025-08-30 18:34:50 +05:30
location.pathname === '/cells'
2025-09-05 20:26:29 +05:30
? 'bg-cyber-accent/20 text-cyber-accent border border-cyber-accent/30'
: 'text-cyber-neutral hover:text-white hover:bg-cyber-muted/20'
}`}
>
<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"
2025-09-05 20:26:29 +05:30
className={`flex items-center space-x-2 px-4 py-2 rounded-md text-sm font-mono transition-all ${
2025-09-05 17:24:29 +05:30
location.pathname === '/bookmarks'
2025-09-05 20:26:29 +05:30
? 'bg-cyber-accent/20 text-cyber-accent border border-cyber-accent/30'
: 'text-cyber-neutral hover:text-white hover:bg-cyber-muted/20'
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-cyber-muted/20 py-4 space-y-2">
<nav className="space-y-1">
<Link
to="/"
className={`flex items-center space-x-3 px-4 py-3 rounded-md text-sm font-mono transition-all ${
location.pathname === '/'
? 'bg-cyber-accent/20 text-cyber-accent border border-cyber-accent/30'
: 'text-cyber-neutral hover:text-white hover:bg-cyber-muted/20'
}`}
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 rounded-md text-sm font-mono transition-all ${
location.pathname === '/cells'
? 'bg-cyber-accent/20 text-cyber-accent border border-cyber-accent/30'
: 'text-cyber-neutral hover:text-white hover:bg-cyber-muted/20'
}`}
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 rounded-md text-sm font-mono transition-all ${
location.pathname === '/bookmarks'
? 'bg-cyber-accent/20 text-cyber-accent border border-cyber-accent/30'
: 'text-cyber-neutral hover:text-white hover:bg-cyber-muted/20'
}`}
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 rounded-md text-sm font-mono transition-all ${
location.pathname === '/profile'
? 'bg-cyber-accent/20 text-cyber-accent border border-cyber-accent/30'
: 'text-cyber-neutral hover:text-white hover:bg-cyber-muted/20'
}`}
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-cyber-muted/20">
<div className="flex items-center space-x-2 text-xs text-cyber-neutral">
<WakuHealthDot />
<span>{wakuHealth.statusMessage}</span>
{forum.lastSync && (
<span className="ml-auto">
{new Date(forum.lastSync).toLocaleTimeString([], {
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-09-05 20:26:29 +05:30
</>
2025-04-15 16:28:03 +05:30
);
};
export default Header;