import { useState } from 'react'; import { useForum } from '@opchan/react'; import { useAuth } from '@opchan/react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { WalletWizard } from '@/components/ui/wallet-wizard'; import Header from '@/components/Header'; import { Loader2, User, Shield, CheckCircle, AlertTriangle, XCircle, Settings, Copy, Globe, Edit3, Save, X, } from 'lucide-react'; import { EDisplayPreference, EVerificationStatus } from '@opchan/core'; import { useToast } from '@/hooks/use-toast'; export default function ProfilePage() { const forum = useForum(); const { updateProfile } = forum.user; const { refresh } = forum.content; const { toast } = useToast(); // Get current user from auth context for the address const { currentUser, delegationInfo } = useAuth(); const [isEditing, setIsEditing] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [callSign, setCallSign] = useState(''); const [displayPreference, setDisplayPreference] = useState( EDisplayPreference.WALLET_ADDRESS ); const [walletWizardOpen, setWalletWizardOpen] = useState(false); // Copy to clipboard function const copyToClipboard = async (text: string, label: string) => { try { await navigator.clipboard.writeText(text); toast({ title: 'Copied!', description: `${label} copied to clipboard`, }); } catch { toast({ title: 'Copy Failed', description: 'Failed to copy to clipboard', variant: 'destructive', }); } }; if (!currentUser) { return (

Connect Required

Please connect your wallet to view your profile.

); } const handleSave = async () => { if (!callSign.trim()) { toast({ title: 'Invalid Input', description: 'Call sign cannot be empty.', variant: 'destructive', }); return; } // Basic validation for call sign if (callSign.length < 3 || callSign.length > 20) { toast({ title: 'Invalid Call Sign', description: 'Call sign must be between 3 and 20 characters.', variant: 'destructive', }); return; } if (!/^[a-zA-Z0-9_-]+$/.test(callSign)) { toast({ title: 'Invalid Call Sign', description: 'Call sign can only contain letters, numbers, underscores, and hyphens.', variant: 'destructive', }); return; } setIsSubmitting(true); try { const success = await updateProfile({ callSign: callSign.trim(), displayPreference, }); if (success) { await refresh(); setIsEditing(false); toast({ title: 'Profile Updated', description: 'Your profile has been updated successfully.', }); } } catch { toast({ title: 'Update Failed', description: 'Failed to update profile. Please try again.', variant: 'destructive', }); } finally { setIsSubmitting(false); } }; const handleCancel = () => { // Reset to the same data source as display for consistency const currentCallSign = currentUser.callSign || currentUser.callSign || ''; const currentDisplayPreference = currentUser.displayPreference || currentUser.displayPreference || EDisplayPreference.WALLET_ADDRESS; setCallSign(currentCallSign); setDisplayPreference(currentDisplayPreference); setIsEditing(false); }; const getVerificationIcon = () => { // Use verification level from UserIdentityService (central database store) switch (currentUser.verificationStatus) { case EVerificationStatus.ENS_ORDINAL_VERIFIED: return ; case EVerificationStatus.WALLET_CONNECTED: return ; case EVerificationStatus.WALLET_UNCONNECTED: return ; default: return ; } }; const getVerificationText = () => { // Use verification level from UserIdentityService (central database store) switch (currentUser.verificationStatus) { case EVerificationStatus.ENS_ORDINAL_VERIFIED: return 'Owns ENS or Ordinal'; case EVerificationStatus.WALLET_CONNECTED: return 'Connected Wallet'; case EVerificationStatus.WALLET_UNCONNECTED: return 'Unconnected Wallet'; default: return 'Unknown'; } }; const getVerificationColor = () => { // Use verification level from UserIdentityService (central database store) switch (currentUser.verificationStatus) { case EVerificationStatus.ENS_ORDINAL_VERIFIED: return 'bg-green-100 text-green-800 border-green-200'; case EVerificationStatus.WALLET_CONNECTED: return 'bg-blue-100 text-blue-800 border-blue-200'; case EVerificationStatus.WALLET_UNCONNECTED: return 'bg-yellow-100 text-yellow-800 border-yellow-200'; default: return 'bg-gray-100 text-gray-800 border-gray-200'; } }; return (
{/* Page Header */}

Profile

Manage your account settings and preferences

{/* Two-Card Layout: User Profile + Security Status */}
{/* User Profile Card - Primary (2/3 width) */}
User Profile
{!isEditing && ( )}
{/* Identity Section */}
{currentUser.displayName}
{/* Show ENS name if available */} {currentUser.ensDetails?.ensName && (
ENS: {currentUser.ensDetails?.ensName}
)} {/* Show Ordinal details if available */} {currentUser.ordinalDetails && (
Ordinal:{' '} {currentUser.ordinalDetails.ordinalDetails}
)} {/* Show fallback if neither ENS nor Ordinal */} {!currentUser.ensDetails?.ensName && !currentUser.ordinalDetails?.ordinalDetails && (
No ENS or Ordinal verification
)}
{getVerificationIcon()} {getVerificationText()}
{/* Wallet Section */}

Wallet Information

{currentUser.address.slice(0, 8)}... {currentUser.address.slice(-6)}
{currentUser.walletType}
{/* Settings Section */}

Profile Settings

{isEditing ? ( setCallSign(e.target.value)} placeholder="Enter your call sign" className="bg-cyber-dark/50 border-cyber-muted/30 text-cyber-light" disabled={isSubmitting} /> ) : (
{currentUser.callSign || currentUser.callSign || 'Not set'}
)}

3-20 characters, letters, numbers, underscores, and hyphens only

{isEditing ? ( ) : (
{(currentUser.displayPreference || displayPreference) === EDisplayPreference.CALL_SIGN ? 'Call Sign (when available)' : 'Wallet Address'}
)}
{/* Action Buttons */} {isEditing && (
)}
{/* Security Status Card - Secondary (1/3 width) */}
Security
{delegationInfo.hasDelegation && ( )}
{/* Delegation Status */}
Delegation {delegationInfo.isValid ? 'Active' : 'Inactive'}
{/* Expiry Date */} {delegationInfo.expiresAt && (
Valid until
{delegationInfo.expiresAt.toLocaleDateString()}
)} {/* Signature Status */}
Signature {delegationInfo.isValid ? 'Valid' : 'Not signed'}
{/* Browser Public Key */}
{delegationInfo.publicKey ? `${delegationInfo.publicKey.slice(0, 12)}...${delegationInfo.publicKey.slice(-8)}` : 'Not delegated'}
{delegationInfo.publicKey && ( )}
{/* Warning for expired delegation */} {!delegationInfo.isValid && delegationInfo.hasDelegation && (
Delegation expired. Renew to continue using your browser key.
)}

OpChan - A decentralized forum built on Waku & Bitcoin Ordinals

{/* Wallet Wizard */} setWalletWizardOpen(false)} />
); }