chore: persist profile state on edit

This commit is contained in:
Danish Arora 2025-09-08 13:01:36 +05:30
parent 4892614df8
commit a746af365a
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
8 changed files with 388 additions and 296 deletions

View File

@ -29,7 +29,9 @@ import { Cell } from '@/types/forum';
import { usePending } from '@/hooks/usePending'; import { usePending } from '@/hooks/usePending';
// Empty State Component // Empty State Component
const EmptyState: React.FC<{ canCreateCell: boolean }> = ({ canCreateCell }) => { const EmptyState: React.FC<{ canCreateCell: boolean }> = ({
canCreateCell,
}) => {
return ( return (
<div className="col-span-2 flex flex-col items-center justify-center py-16 px-4"> <div className="col-span-2 flex flex-col items-center justify-center py-16 px-4">
{/* Visual Element */} {/* Visual Element */}
@ -76,10 +78,7 @@ const CellItem: React.FC<{ cell: Cell }> = ({ cell }) => {
const pending = usePending(cell.id); const pending = usePending(cell.id);
return ( return (
<Link <Link to={`/cell/${cell.id}`} className="group block board-card">
to={`/cell/${cell.id}`}
className="group block board-card"
>
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<CypherImage <CypherImage
src={cell.icon} src={cell.icon}
@ -162,64 +161,72 @@ const CellList = () => {
<div className="page-header"> <div className="page-header">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div> <div>
<h1 className="page-title"> <h1 className="page-title">Decentralized Cells</h1>
Decentralized Cells
</h1>
<p className="page-subtitle"> <p className="page-subtitle">
Discover communities built on Bitcoin Ordinals Discover communities built on Bitcoin Ordinals
</p> </p>
</div> </div>
{/* Only show controls when cells exist */} {/* Only show controls when cells exist */}
{hasCells && ( {hasCells && (
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<ModerationToggle /> <ModerationToggle />
<Select <Select
value={sortOption} value={sortOption}
onValueChange={(value: SortOption) => setSortOption(value)} onValueChange={(value: SortOption) => setSortOption(value)}
> >
<SelectTrigger className="w-40 bg-cyber-muted/50 border-cyber-muted text-cyber-light"> <SelectTrigger className="w-40 bg-cyber-muted/50 border-cyber-muted text-cyber-light">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent className="bg-cyber-dark border-cyber-muted/30"> <SelectContent className="bg-cyber-dark border-cyber-muted/30">
<SelectItem value="relevance" className="text-cyber-light hover:bg-cyber-muted/30"> <SelectItem
<TrendingUp className="w-4 h-4 mr-2 inline" /> value="relevance"
Relevance className="text-cyber-light hover:bg-cyber-muted/30"
</SelectItem> >
<SelectItem value="activity" className="text-cyber-light hover:bg-cyber-muted/30"> <TrendingUp className="w-4 h-4 mr-2 inline" />
<MessageSquare className="w-4 h-4 mr-2 inline" /> Relevance
Activity </SelectItem>
</SelectItem> <SelectItem
<SelectItem value="newest" className="text-cyber-light hover:bg-cyber-muted/30"> value="activity"
<Clock className="w-4 h-4 mr-2 inline" /> className="text-cyber-light hover:bg-cyber-muted/30"
Newest >
</SelectItem> <MessageSquare className="w-4 h-4 mr-2 inline" />
<SelectItem value="alphabetical" className="text-cyber-light hover:bg-cyber-muted/30"> Activity
<Layout className="w-4 h-4 mr-2 inline" /> </SelectItem>
A-Z <SelectItem
</SelectItem> value="newest"
</SelectContent> className="text-cyber-light hover:bg-cyber-muted/30"
</Select> >
<Clock className="w-4 h-4 mr-2 inline" />
Newest
</SelectItem>
<SelectItem
value="alphabetical"
className="text-cyber-light hover:bg-cyber-muted/30"
>
<Layout className="w-4 h-4 mr-2 inline" />
A-Z
</SelectItem>
</SelectContent>
</Select>
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"
onClick={refreshData} onClick={refreshData}
disabled={isInitialLoading} disabled={isInitialLoading}
title="Refresh data" title="Refresh data"
className="px-3 border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30" className="px-3 border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30"
> >
<RefreshCw <RefreshCw
className={`w-4 h-4 ${isInitialLoading ? 'animate-spin' : ''}`} className={`w-4 h-4 ${isInitialLoading ? 'animate-spin' : ''}`}
/> />
</Button> </Button>
{canCreateCell && ( {canCreateCell && <CreateCellDialog />}
<CreateCellDialog /> </div>
)} )}
</div>
)}
</div> </div>
</div> </div>

View File

@ -122,7 +122,6 @@ const Header = () => {
}); });
}; };
const getStatusIcon = () => { const getStatusIcon = () => {
if (!isConnected) return <CircleSlash className="w-4 h-4" />; if (!isConnected) return <CircleSlash className="w-4 h-4" />;
@ -194,21 +193,26 @@ const Header = () => {
<Badge <Badge
variant="outline" variant="outline"
className={`font-mono text-xs border-0 ${ className={`font-mono text-xs border-0 ${
verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED && delegationInfo?.isValid verificationStatus ===
EVerificationStatus.ENS_ORDINAL_VERIFIED &&
delegationInfo?.isValid
? 'bg-green-500/20 text-green-400 border-green-500/30' ? 'bg-green-500/20 text-green-400 border-green-500/30'
: verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED : verificationStatus ===
? 'bg-orange-500/20 text-orange-400 border-orange-500/30' EVerificationStatus.ENS_ORDINAL_VERIFIED
: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30' ? 'bg-orange-500/20 text-orange-400 border-orange-500/30'
: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30'
}`} }`}
> >
{getStatusIcon()} {getStatusIcon()}
<span className="ml-1"> <span className="ml-1">
{verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED && delegationInfo?.isValid {verificationStatus ===
EVerificationStatus.ENS_ORDINAL_VERIFIED &&
delegationInfo?.isValid
? 'READY' ? 'READY'
: verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED : verificationStatus ===
? 'EXPIRED' EVerificationStatus.ENS_ORDINAL_VERIFIED
: 'VERIFY' ? 'EXPIRED'
} : 'VERIFY'}
</span> </span>
</Badge> </Badge>
@ -224,21 +228,34 @@ const Header = () => {
<Settings className="w-4 h-4" /> <Settings className="w-4 h-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56 bg-black/95 border-cyber-muted/30"> <DropdownMenuContent
align="end"
className="w-56 bg-black/95 border-cyber-muted/30"
>
<div className="px-3 py-2 border-b border-cyber-muted/30"> <div className="px-3 py-2 border-b border-cyber-muted/30">
<div className="text-sm font-medium text-white">{displayName}</div> <div className="text-sm font-medium text-white">
<div className="text-xs text-cyber-neutral">{address?.slice(0, 8)}...{address?.slice(-4)}</div> {displayName}
</div>
<div className="text-xs text-cyber-neutral">
{address?.slice(0, 8)}...{address?.slice(-4)}
</div>
</div> </div>
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link to="/profile" className="flex items-center space-x-2"> <Link
to="/profile"
className="flex items-center space-x-2"
>
<User className="w-4 h-4" /> <User className="w-4 h-4" />
<span>Profile</span> <span>Profile</span>
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link to="/bookmarks" className="flex items-center space-x-2"> <Link
to="/bookmarks"
className="flex items-center space-x-2"
>
<Bookmark className="w-4 h-4" /> <Bookmark className="w-4 h-4" />
<span>Bookmarks</span> <span>Bookmarks</span>
</Link> </Link>
@ -246,7 +263,10 @@ const Header = () => {
<DropdownMenuSeparator className="bg-cyber-muted/30" /> <DropdownMenuSeparator className="bg-cyber-muted/30" />
<DropdownMenuItem onClick={handleOpenWizard} className="flex items-center space-x-2"> <DropdownMenuItem
onClick={handleOpenWizard}
className="flex items-center space-x-2"
>
<Settings className="w-4 h-4" /> <Settings className="w-4 h-4" />
<span>Setup Wizard</span> <span>Setup Wizard</span>
</DropdownMenuItem> </DropdownMenuItem>
@ -279,7 +299,11 @@ const Header = () => {
onClick={() => setMobileMenuOpen(!mobileMenuOpen)} onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden text-white hover:bg-cyber-muted/30" className="md:hidden text-white hover:bg-cyber-muted/30"
> >
{mobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />} {mobileMenuOpen ? (
<X className="w-5 h-5" />
) : (
<Menu className="w-5 h-5" />
)}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -267,10 +267,7 @@ const PostList = () => {
</div> </div>
) : ( ) : (
visiblePosts.map(post => ( visiblePosts.map(post => (
<div <div key={post.id} className="thread-card">
key={post.id}
className="thread-card"
>
<div className="flex gap-4"> <div className="flex gap-4">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<button <button

View File

@ -66,7 +66,6 @@ export function WalletWizard({
} }
}; };
const renderStepIcon = (step: WizardStep) => { const renderStepIcon = (step: WizardStep) => {
const status = getStepStatus(step); const status = getStepStatus(step);
@ -126,10 +125,12 @@ export function WalletWizard({
className={`text-sm ${ className={`text-sm ${
getStepStatus(step as WizardStep) === 'current' getStepStatus(step as WizardStep) === 'current'
? 'text-blue-500 font-medium' ? 'text-blue-500 font-medium'
: (getStepStatus(step as WizardStep) === 'complete' || : getStepStatus(step as WizardStep) === 'complete' ||
(step === 1 && isAuthenticated) || (step === 1 && isAuthenticated) ||
(step === 2 && verificationStatus !== EVerificationStatus.WALLET_UNCONNECTED) || (step === 2 &&
(step === 3 && delegationStatus.isValid)) verificationStatus !==
EVerificationStatus.WALLET_UNCONNECTED) ||
(step === 3 && delegationStatus.isValid)
? 'text-green-500' ? 'text-green-500'
: 'text-gray-400' : 'text-gray-400'
}`} }`}
@ -142,7 +143,9 @@ export function WalletWizard({
className={`w-8 h-px mx-2 ${ className={`w-8 h-px mx-2 ${
getStepStatus(step as WizardStep) === 'complete' || getStepStatus(step as WizardStep) === 'complete' ||
(step === 1 && isAuthenticated) || (step === 1 && isAuthenticated) ||
(step === 2 && verificationStatus !== EVerificationStatus.WALLET_UNCONNECTED) (step === 2 &&
verificationStatus !==
EVerificationStatus.WALLET_UNCONNECTED)
? 'bg-green-500' ? 'bg-green-500'
: 'bg-gray-600' : 'bg-gray-600'
}`} }`}

View File

@ -129,44 +129,42 @@ const BookmarksPage = () => {
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<BookmarkIcon className="text-cyber-accent" size={32} /> <BookmarkIcon className="text-cyber-accent" size={32} />
<h1 className="page-title"> <h1 className="page-title">My Bookmarks</h1>
My Bookmarks
</h1>
</div> </div>
{bookmarks.length > 0 && ( {bookmarks.length > 0 && (
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
className="text-red-400 border-red-400/30 hover:bg-red-400/10" className="text-red-400 border-red-400/30 hover:bg-red-400/10"
>
<Trash2 size={16} className="mr-2" />
Clear All
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Clear All Bookmarks</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to remove all your bookmarks? This
action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleClearAll}
className="bg-red-600 hover:bg-red-700"
> >
<Trash2 size={16} className="mr-2" />
Clear All Clear All
</AlertDialogAction> </Button>
</AlertDialogFooter> </AlertDialogTrigger>
</AlertDialogContent> <AlertDialogContent>
</AlertDialog> <AlertDialogHeader>
)} <AlertDialogTitle>Clear All Bookmarks</AlertDialogTitle>
</div> <AlertDialogDescription>
Are you sure you want to remove all your bookmarks? This
action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleClearAll}
className="bg-red-600 hover:bg-red-700"
>
Clear All
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
</div>
<p className="page-subtitle"> <p className="page-subtitle">
Your saved posts and comments. Bookmarks are stored locally and Your saved posts and comments. Bookmarks are stored locally and
@ -174,83 +172,83 @@ const BookmarksPage = () => {
</p> </p>
</div> </div>
{/* Stats */} {/* Stats */}
{bookmarks.length > 0 && ( {bookmarks.length > 0 && (
<div className="flex gap-4 mb-6"> <div className="flex gap-4 mb-6">
<Badge <Badge
variant="outline" variant="outline"
className="border-cyber-accent/30 text-cyber-accent" className="border-cyber-accent/30 text-cyber-accent"
> >
<FileText size={14} className="mr-1" /> <FileText size={14} className="mr-1" />
{postBookmarks.length} Posts {postBookmarks.length} Posts
</Badge> </Badge>
<Badge <Badge
variant="outline" variant="outline"
className="border-cyber-accent/30 text-cyber-accent" className="border-cyber-accent/30 text-cyber-accent"
> >
<MessageSquare size={14} className="mr-1" /> <MessageSquare size={14} className="mr-1" />
{commentBookmarks.length} Comments {commentBookmarks.length} Comments
</Badge> </Badge>
<Badge <Badge
variant="outline" variant="outline"
className="border-cyber-accent/30 text-cyber-accent" className="border-cyber-accent/30 text-cyber-accent"
> >
<BookmarkIcon size={14} className="mr-1" /> <BookmarkIcon size={14} className="mr-1" />
{bookmarks.length} Total {bookmarks.length} Total
</Badge> </Badge>
</div> </div>
)} )}
{/* Tabs */} {/* Tabs */}
<Tabs <Tabs
value={activeTab} value={activeTab}
onValueChange={value => onValueChange={value =>
setActiveTab(value as 'all' | 'posts' | 'comments') setActiveTab(value as 'all' | 'posts' | 'comments')
} }
className="w-full" className="w-full"
> >
<TabsList className="grid w-full grid-cols-3 mb-6"> <TabsList className="grid w-full grid-cols-3 mb-6">
<TabsTrigger value="all" className="flex items-center gap-2"> <TabsTrigger value="all" className="flex items-center gap-2">
<BookmarkIcon size={16} /> <BookmarkIcon size={16} />
All ({bookmarks.length}) All ({bookmarks.length})
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="posts" className="flex items-center gap-2"> <TabsTrigger value="posts" className="flex items-center gap-2">
<FileText size={16} /> <FileText size={16} />
Posts ({postBookmarks.length}) Posts ({postBookmarks.length})
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="comments" className="flex items-center gap-2"> <TabsTrigger value="comments" className="flex items-center gap-2">
<MessageSquare size={16} /> <MessageSquare size={16} />
Comments ({commentBookmarks.length}) Comments ({commentBookmarks.length})
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="all"> <TabsContent value="all">
<BookmarkList <BookmarkList
bookmarks={getFilteredBookmarks()} bookmarks={getFilteredBookmarks()}
onRemove={removeBookmark} onRemove={removeBookmark}
onNavigate={handleNavigate} onNavigate={handleNavigate}
emptyMessage="No bookmarks yet" emptyMessage="No bookmarks yet"
/> />
</TabsContent> </TabsContent>
<TabsContent value="posts"> <TabsContent value="posts">
<BookmarkList <BookmarkList
bookmarks={getFilteredBookmarks()} bookmarks={getFilteredBookmarks()}
onRemove={removeBookmark} onRemove={removeBookmark}
onNavigate={handleNavigate} onNavigate={handleNavigate}
emptyMessage="No bookmarked posts yet" emptyMessage="No bookmarked posts yet"
/> />
</TabsContent> </TabsContent>
<TabsContent value="comments"> <TabsContent value="comments">
<BookmarkList <BookmarkList
bookmarks={getFilteredBookmarks()} bookmarks={getFilteredBookmarks()}
onRemove={removeBookmark} onRemove={removeBookmark}
onNavigate={handleNavigate} onNavigate={handleNavigate}
emptyMessage="No bookmarked comments yet" emptyMessage="No bookmarked comments yet"
/> />
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</div> </div>
</main> </main>

View File

@ -105,49 +105,47 @@ const FeedPage: React.FC = () => {
<h1 className="page-title text-glow text-cyber-accent"> <h1 className="page-title text-glow text-cyber-accent">
Popular Posts Popular Posts
</h1> </h1>
<p className="page-subtitle"> <p className="page-subtitle">Latest posts from all cells</p>
Latest posts from all cells
</p>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<ModerationToggle /> <ModerationToggle />
<Select <Select
value={sortOption} value={sortOption}
onValueChange={(value: SortOption) => setSortOption(value)} onValueChange={(value: SortOption) => setSortOption(value)}
> >
<SelectTrigger className="w-40"> <SelectTrigger className="w-40">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="relevance"> <SelectItem value="relevance">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<TrendingUp className="w-4 h-4" /> <TrendingUp className="w-4 h-4" />
<span>Relevance</span> <span>Relevance</span>
</div> </div>
</SelectItem> </SelectItem>
<SelectItem value="time"> <SelectItem value="time">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Clock className="w-4 h-4" /> <Clock className="w-4 h-4" />
<span>Newest</span> <span>Newest</span>
</div> </div>
</SelectItem> </SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={refreshData} onClick={refreshData}
disabled={isRefreshing} disabled={isRefreshing}
className="flex items-center space-x-2" className="flex items-center space-x-2"
> >
<RefreshCw <RefreshCw
className={`w-4 h-4 ${isRefreshing ? 'animate-spin' : ''}`} className={`w-4 h-4 ${isRefreshing ? 'animate-spin' : ''}`}
/> />
<span>Refresh</span> <span>Refresh</span>
</Button> </Button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -58,21 +58,21 @@ export default function ProfilePage() {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [callSign, setCallSign] = useState(currentUser?.callSign || ''); const [callSign, setCallSign] = useState('');
const [displayPreference, setDisplayPreference] = useState( const [displayPreference, setDisplayPreference] = useState(EDisplayPreference.WALLET_ADDRESS);
currentUser?.displayPreference || EDisplayPreference.WALLET_ADDRESS
);
const [walletWizardOpen, setWalletWizardOpen] = useState(false); const [walletWizardOpen, setWalletWizardOpen] = useState(false);
// Update local state when user data changes // Initialize and update local state when user data changes
useEffect(() => { useEffect(() => {
if (currentUser) { if (currentUser) {
setCallSign(currentUser.callSign || ''); // Use the same data source as the display (userInfo) for consistency
setDisplayPreference( const currentCallSign = userInfo.callSign || currentUser.callSign || '';
currentUser.displayPreference || EDisplayPreference.WALLET_ADDRESS const currentDisplayPreference = userInfo.displayPreference || currentUser.displayPreference || EDisplayPreference.WALLET_ADDRESS;
);
setCallSign(currentCallSign);
setDisplayPreference(currentDisplayPreference);
} }
}, [currentUser]); }, [currentUser, userInfo.callSign, userInfo.displayPreference]);
// Copy to clipboard function // Copy to clipboard function
const copyToClipboard = async (text: string, label: string) => { const copyToClipboard = async (text: string, label: string) => {
@ -100,8 +100,12 @@ export default function ProfilePage() {
<CardContent className="pt-6"> <CardContent className="pt-6">
<div className="text-center text-cyber-neutral"> <div className="text-center text-cyber-neutral">
<User className="w-12 h-12 mx-auto mb-4 text-cyber-accent" /> <User className="w-12 h-12 mx-auto mb-4 text-cyber-accent" />
<h2 className="text-xl font-mono font-bold mb-2">Connect Required</h2> <h2 className="text-xl font-mono font-bold mb-2">
<p className="text-sm">Please connect your wallet to view your profile.</p> Connect Required
</h2>
<p className="text-sm">
Please connect your wallet to view your profile.
</p>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@ -168,8 +172,12 @@ export default function ProfilePage() {
}; };
const handleCancel = () => { const handleCancel = () => {
setCallSign(currentUser.callSign || ''); // Reset to the same data source as display for consistency
setDisplayPreference(currentUser.displayPreference); const currentCallSign = userInfo.callSign || currentUser.callSign || '';
const currentDisplayPreference = userInfo.displayPreference || currentUser.displayPreference || EDisplayPreference.WALLET_ADDRESS;
setCallSign(currentCallSign);
setDisplayPreference(currentDisplayPreference);
setIsEditing(false); setIsEditing(false);
}; };
@ -220,7 +228,9 @@ export default function ProfilePage() {
{/* Page Header */} {/* Page Header */}
<div className="page-header"> <div className="page-header">
<h1 className="page-title">Profile</h1> <h1 className="page-title">Profile</h1>
<p className="page-subtitle">Manage your account settings and preferences</p> <p className="page-subtitle">
Manage your account settings and preferences
</p>
</div> </div>
{/* Two-Card Layout: User Profile + Security Status */} {/* Two-Card Layout: User Profile + Security Status */}
@ -283,12 +293,15 @@ export default function ProfilePage() {
</Label> </Label>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex-1 font-mono text-sm bg-cyber-dark/50 border border-cyber-muted/30 px-3 py-2 rounded-md text-cyber-light"> <div className="flex-1 font-mono text-sm bg-cyber-dark/50 border border-cyber-muted/30 px-3 py-2 rounded-md text-cyber-light">
{currentUser.address.slice(0, 8)}...{currentUser.address.slice(-6)} {currentUser.address.slice(0, 8)}...
{currentUser.address.slice(-6)}
</div> </div>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => copyToClipboard(currentUser.address, 'Address')} onClick={() =>
copyToClipboard(currentUser.address, 'Address')
}
className="border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30" className="border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30"
> >
<Copy className="w-4 h-4" /> <Copy className="w-4 h-4" />
@ -301,7 +314,10 @@ export default function ProfilePage() {
</Label> </Label>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Globe className="w-4 h-4 text-cyber-neutral" /> <Globe className="w-4 h-4 text-cyber-neutral" />
<Badge variant="outline" className="capitalize bg-cyber-accent/20 text-cyber-accent border-cyber-accent/30"> <Badge
variant="outline"
className="capitalize bg-cyber-accent/20 text-cyber-accent border-cyber-accent/30"
>
{currentUser.walletType} {currentUser.walletType}
</Badge> </Badge>
</div> </div>
@ -316,7 +332,10 @@ export default function ProfilePage() {
</h3> </h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="callSign" className="text-sm font-medium text-cyber-neutral"> <Label
htmlFor="callSign"
className="text-sm font-medium text-cyber-neutral"
>
Call Sign Call Sign
</Label> </Label>
{isEditing ? ( {isEditing ? (
@ -330,16 +349,22 @@ export default function ProfilePage() {
/> />
) : ( ) : (
<div className="text-sm bg-cyber-dark/50 border border-cyber-muted/30 px-3 py-2 rounded-md text-cyber-light"> <div className="text-sm bg-cyber-dark/50 border border-cyber-muted/30 px-3 py-2 rounded-md text-cyber-light">
{userInfo.callSign || currentUser.callSign || 'Not set'} {userInfo.callSign ||
currentUser.callSign ||
'Not set'}
</div> </div>
)} )}
<p className="text-xs text-cyber-neutral"> <p className="text-xs text-cyber-neutral">
3-20 characters, letters, numbers, underscores, and hyphens only 3-20 characters, letters, numbers, underscores, and
hyphens only
</p> </p>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="displayPreference" className="text-sm font-medium text-cyber-neutral"> <Label
htmlFor="displayPreference"
className="text-sm font-medium text-cyber-neutral"
>
Display Preference Display Preference
</Label> </Label>
{isEditing ? ( {isEditing ? (
@ -354,17 +379,24 @@ export default function ProfilePage() {
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent className="bg-cyber-dark border-cyber-muted/30"> <SelectContent className="bg-cyber-dark border-cyber-muted/30">
<SelectItem value={EDisplayPreference.CALL_SIGN} className="text-cyber-light hover:bg-cyber-muted/30"> <SelectItem
value={EDisplayPreference.CALL_SIGN}
className="text-cyber-light hover:bg-cyber-muted/30"
>
Call Sign (when available) Call Sign (when available)
</SelectItem> </SelectItem>
<SelectItem value={EDisplayPreference.WALLET_ADDRESS} className="text-cyber-light hover:bg-cyber-muted/30"> <SelectItem
value={EDisplayPreference.WALLET_ADDRESS}
className="text-cyber-light hover:bg-cyber-muted/30"
>
Wallet Address Wallet Address
</SelectItem> </SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
) : ( ) : (
<div className="text-sm bg-cyber-dark/50 border border-cyber-muted/30 px-3 py-2 rounded-md text-cyber-light"> <div className="text-sm bg-cyber-dark/50 border border-cyber-muted/30 px-3 py-2 rounded-md text-cyber-light">
{(userInfo.displayPreference || displayPreference) === {(userInfo.displayPreference ||
displayPreference) ===
EDisplayPreference.CALL_SIGN EDisplayPreference.CALL_SIGN
? 'Call Sign (when available)' ? 'Call Sign (when available)'
: 'Wallet Address'} : 'Wallet Address'}
@ -413,7 +445,8 @@ export default function ProfilePage() {
<Shield className="h-5 w-5 text-cyber-accent" /> <Shield className="h-5 w-5 text-cyber-accent" />
Security Security
</div> </div>
{(delegationStatus.hasDelegation || delegationInfo?.hasDelegation) && ( {(delegationStatus.hasDelegation ||
delegationInfo?.hasDelegation) && (
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
@ -421,7 +454,9 @@ export default function ProfilePage() {
className="border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30" className="border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30"
> >
<Settings className="w-4 h-4 mr-2" /> <Settings className="w-4 h-4 mr-2" />
{(delegationStatus.isValid || delegationInfo?.isValid) ? 'Renew' : 'Setup'} {delegationStatus.isValid || delegationInfo?.isValid
? 'Renew'
: 'Setup'}
</Button> </Button>
)} )}
</CardTitle> </CardTitle>
@ -430,41 +465,61 @@ export default function ProfilePage() {
{/* Delegation Status */} {/* Delegation Status */}
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm font-medium text-cyber-neutral">Delegation</span> <span className="text-sm font-medium text-cyber-neutral">
Delegation
</span>
<Badge <Badge
variant={(delegationStatus.isValid || delegationInfo?.isValid) ? 'default' : 'secondary'} variant={
delegationStatus.isValid || delegationInfo?.isValid
? 'default'
: 'secondary'
}
className={ className={
(delegationStatus.isValid || delegationInfo?.isValid) delegationStatus.isValid || delegationInfo?.isValid
? 'bg-green-500/20 text-green-400 border-green-500/30' ? 'bg-green-500/20 text-green-400 border-green-500/30'
: 'bg-red-500/20 text-red-400 border-red-500/30' : 'bg-red-500/20 text-red-400 border-red-500/30'
} }
> >
{(delegationStatus.isValid || delegationInfo?.isValid) ? 'Active' : 'Inactive'} {delegationStatus.isValid || delegationInfo?.isValid
? 'Active'
: 'Inactive'}
</Badge> </Badge>
</div> </div>
{/* Expiry Date */} {/* Expiry Date */}
{(delegationStatus.expiresAt || currentUser.delegationExpiry) && ( {(delegationStatus.expiresAt ||
currentUser.delegationExpiry) && (
<div className="space-y-1"> <div className="space-y-1">
<span className="text-xs text-cyber-neutral">Valid until</span> <span className="text-xs text-cyber-neutral">
Valid until
</span>
<div className="text-sm font-mono text-cyber-light"> <div className="text-sm font-mono text-cyber-light">
{(delegationStatus.expiresAt || new Date(currentUser.delegationExpiry!)).toLocaleDateString()} {(
delegationStatus.expiresAt ||
new Date(currentUser.delegationExpiry!)
).toLocaleDateString()}
</div> </div>
</div> </div>
)} )}
{/* Signature Status */} {/* Signature Status */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm font-medium text-cyber-neutral">Signature</span> <span className="text-sm font-medium text-cyber-neutral">
Signature
</span>
<Badge <Badge
variant="outline" variant="outline"
className={ className={
(delegationStatus.isValid || currentUser.delegationSignature === 'valid') delegationStatus.isValid ||
currentUser.delegationSignature === 'valid'
? 'text-green-400 border-green-500/30 bg-green-500/10' ? 'text-green-400 border-green-500/30 bg-green-500/10'
: 'text-red-400 border-red-500/30 bg-red-500/10' : 'text-red-400 border-red-500/30 bg-red-500/10'
} }
> >
{(delegationStatus.isValid || currentUser.delegationSignature === 'valid') ? 'Valid' : 'Not signed'} {delegationStatus.isValid ||
currentUser.delegationSignature === 'valid'
? 'Valid'
: 'Not signed'}
</Badge> </Badge>
</div> </div>
</div> </div>
@ -476,15 +531,22 @@ export default function ProfilePage() {
</Label> </Label>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex-1 font-mono text-xs bg-cyber-dark/50 border border-cyber-muted/30 px-2 py-1 rounded text-cyber-light"> <div className="flex-1 font-mono text-xs bg-cyber-dark/50 border border-cyber-muted/30 px-2 py-1 rounded text-cyber-light">
{(delegationStatus.publicKey || currentUser.browserPubKey) {delegationStatus.publicKey || currentUser.browserPubKey
? `${(delegationStatus.publicKey || currentUser.browserPubKey!).slice(0, 12)}...${(delegationStatus.publicKey || currentUser.browserPubKey!).slice(-8)}` ? `${(delegationStatus.publicKey || currentUser.browserPubKey!).slice(0, 12)}...${(delegationStatus.publicKey || currentUser.browserPubKey!).slice(-8)}`
: 'Not delegated'} : 'Not delegated'}
</div> </div>
{(delegationStatus.publicKey || currentUser.browserPubKey) && ( {(delegationStatus.publicKey ||
currentUser.browserPubKey) && (
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => copyToClipboard(delegationStatus.publicKey || currentUser.browserPubKey!, 'Public Key')} onClick={() =>
copyToClipboard(
delegationStatus.publicKey ||
currentUser.browserPubKey!,
'Public Key'
)
}
className="border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30" className="border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30"
> >
<Copy className="w-3 h-3" /> <Copy className="w-3 h-3" />
@ -494,21 +556,24 @@ export default function ProfilePage() {
</div> </div>
{/* Warning for expired delegation */} {/* Warning for expired delegation */}
{(!delegationStatus.isValid && delegationStatus.hasDelegation) || (!delegationInfo?.isValid && delegationInfo?.hasDelegation) && ( {(!delegationStatus.isValid &&
<div className="p-3 bg-orange-500/10 border border-orange-500/30 rounded-md"> delegationStatus.hasDelegation) ||
<div className="flex items-center gap-2 text-orange-400"> (!delegationInfo?.isValid &&
<AlertTriangle className="w-4 h-4" /> delegationInfo?.hasDelegation && (
<span className="text-xs font-medium"> <div className="p-3 bg-orange-500/10 border border-orange-500/30 rounded-md">
Delegation expired. Renew to continue using your browser key. <div className="flex items-center gap-2 text-orange-400">
</span> <AlertTriangle className="w-4 h-4" />
</div> <span className="text-xs font-medium">
</div> Delegation expired. Renew to continue using your
)} browser key.
</span>
</div>
</div>
))}
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
</div> </div>
</div> </div>
</main> </main>