From 4892614df8f09905ad31177529fac4174a4a9ff2 Mon Sep 17 00:00:00 2001 From: Danish Arora Date: Fri, 5 Sep 2025 20:26:29 +0530 Subject: [PATCH] chore: UI improvements --- src/components/CellList.tsx | 177 +++++--- src/components/Header.tsx | 382 +++++++++++------ src/components/PostCard.tsx | 2 +- src/components/PostDetail.tsx | 2 +- src/components/PostList.tsx | 26 +- src/components/ui/bookmark-card.tsx | 6 +- src/components/ui/wallet-wizard.tsx | 90 ++-- src/hooks/core/useBookmarks.ts | 8 +- src/index.css | 137 +++++- src/lib/waku/constants.ts | 8 +- src/pages/BookmarksPage.tsx | 42 +- src/pages/CellPage.tsx | 6 +- src/pages/FeedPage.tsx | 30 +- src/pages/Index.tsx | 6 +- src/pages/ProfilePage.tsx | 630 ++++++++++++++-------------- 15 files changed, 927 insertions(+), 625 deletions(-) diff --git a/src/components/CellList.tsx b/src/components/CellList.tsx index 812b9a3..c31d4f4 100644 --- a/src/components/CellList.tsx +++ b/src/components/CellList.tsx @@ -8,6 +8,9 @@ import { Loader2, TrendingUp, Clock, + Grid3X3, + Shield, + Hash, } from 'lucide-react'; import { CreateCellDialog } from './CreateCellDialog'; import { Button } from '@/components/ui/button'; @@ -25,6 +28,49 @@ import { sortCells, SortOption } from '@/lib/utils/sorting'; import { Cell } from '@/types/forum'; import { usePending } from '@/hooks/usePending'; +// Empty State Component +const EmptyState: React.FC<{ canCreateCell: boolean }> = ({ canCreateCell }) => { + return ( +
+ {/* Visual Element */} +
+
+ +
+ {/* Floating elements */} +
+ +
+
+ +
+
+ + {/* Content */} +
+

+ No Cells Found +

+ + {canCreateCell ? ( +
+

+ Ready to start your own decentralized community? +

+ +
+ ) : ( +
+

+ Connect your wallet and verify ownership to create cells +

+
+ )} +
+
+ ); +}; + // Separate component to properly use hooks const CellItem: React.FC<{ cell: Cell }> = ({ cell }) => { const pending = usePending(cell.id); @@ -32,7 +78,7 @@ const CellItem: React.FC<{ cell: Cell }> = ({ cell }) => { return (
{ ); } + const hasCells = sortedCells.length > 0; + return ( -
-
-
-

- Decentralized Cells -

-

- Discover communities built on Bitcoin Ordinals -

-
+
+
+
+
+

+ Decentralized Cells +

+

+ Discover communities built on Bitcoin Ordinals +

+
-
- + {/* Only show controls when cells exist */} + {hasCells && ( +
+ - + - - + + + {canCreateCell && ( + + )} +
+ )}
- {sortedCells.length === 0 ? ( -
-
- No cells found. Be the first to create one! -
-
+ {!hasCells ? ( + ) : ( sortedCells.map(cell => ) )}
- - {canCreateCell && ( -
-

- Ready to start your own community? -

- -
- )}
); }; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index bed914c..f608d6a 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -7,6 +7,7 @@ import { useForum } from '@/contexts/useForum'; import { localDatabase } from '@/lib/database/LocalDatabase'; import { DelegationFullStatus } from '@/lib/delegation'; import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; import { LogOut, @@ -19,12 +20,18 @@ import { Grid3X3, User, Bookmark, + Settings, + Menu, + X, + Clock, } from 'lucide-react'; import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from '@/components/ui/tooltip'; + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; import { useToast } from '@/components/ui/use-toast'; import { useAppKitAccount, useDisconnect } from '@reown/appkit/react'; import { WalletWizard } from '@/components/ui/wallet-wizard'; @@ -58,6 +65,7 @@ const Header = () => { : undefined; const [walletWizardOpen, setWalletWizardOpen] = useState(false); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); // ✅ Get display name from enhanced hook const { displayName } = useUserDisplay(address || ''); @@ -101,6 +109,10 @@ const Header = () => { setWalletWizardOpen(true); }; + const handleOpenWizard = () => { + setWalletWizardOpen(true); + }; + const handleDisconnect = async () => { await disconnect(); await setHasShownWizard(false); // Reset so wizard can show again on next connection @@ -110,36 +122,6 @@ const Header = () => { }); }; - const getAccountStatusText = () => { - if (!isConnected) return 'Connect Wallet'; - - if (verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED) { - return delegationInfo?.isValid ? 'Ready to Post' : 'Delegation Expired'; - } else if (verificationStatus === EVerificationStatus.WALLET_CONNECTED) { - return 'Verified (Read-only)'; - } else { - return 'Verify Wallet'; - } - }; - - const getStatusColor = () => { - if (!isConnected) return 'text-red-400'; - - if ( - verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED && - delegationInfo?.isValid - ) { - return 'text-green-400'; - } else if (verificationStatus === EVerificationStatus.WALLET_CONNECTED) { - return 'text-yellow-400'; - } else if ( - verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED - ) { - return 'text-orange-400'; - } else { - return 'text-red-400'; - } - }; const getStatusIcon = () => { if (!isConnected) return ; @@ -161,140 +143,278 @@ const Header = () => { }; return ( -
-
-
- {/* Logo and Navigation */} -
- - - opchan - - - + + {/* Mobile Network Status */} +
+
+ + {wakuHealth.statusMessage} + {forum.lastSync && ( + + {new Date(forum.lastSync).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + })} + + )} +
- ) : ( - - )} -
+
+ )}
-
+ {/* Wallet Wizard */} { }); }} /> - + ); }; diff --git a/src/components/PostCard.tsx b/src/components/PostCard.tsx index 0b20bf0..e290222 100644 --- a/src/components/PostCard.tsx +++ b/src/components/PostCard.tsx @@ -99,7 +99,7 @@ const PostCard: React.FC = ({ post, commentCount = 0 }) => { }; return ( -
+
{/* Voting column */}
diff --git a/src/components/PostDetail.tsx b/src/components/PostDetail.tsx index a6d78c0..79e3364 100644 --- a/src/components/PostDetail.tsx +++ b/src/components/PostDetail.tsx @@ -48,7 +48,7 @@ const PostDetail = () => { isBookmarked, loading: bookmarkLoading, toggleBookmark, - } = usePostBookmark(post!, post?.cellId); + } = usePostBookmark(post, post?.cellId); // ✅ Move ALL hook calls to the top, before any conditional logic const postPending = usePending(post?.id); diff --git a/src/components/PostList.tsx b/src/components/PostList.tsx index 9971dc3..01ebbe9 100644 --- a/src/components/PostList.tsx +++ b/src/components/PostList.tsx @@ -151,8 +151,8 @@ const PostList = () => { }; return ( -
-
+
+
{
-
+
{ />
-

{cell.name}

+

{cell.name}

-

{cell.description}

+

{cell.description}

{canPost && ( -
+

@@ -228,7 +228,7 @@ const PostList = () => { {!canPost && verificationStatus === EVerificationStatus.WALLET_CONNECTED && ( -
+

Read-Only Mode

@@ -244,7 +244,7 @@ const PostList = () => { )} {!canPost && !currentUser && ( -
+

Connect wallet and verify Ordinal ownership to post

@@ -256,10 +256,10 @@ const PostList = () => {
{visiblePosts.length === 0 ? ( -
- -

No Threads Yet

-

+

+ +

No Threads Yet

+

{canPost ? 'Be the first to post in this cell!' : 'Connect your wallet and verify Ordinal ownership to start a thread.'} @@ -269,7 +269,7 @@ const PostList = () => { visiblePosts.map(post => (

diff --git a/src/components/ui/bookmark-card.tsx b/src/components/ui/bookmark-card.tsx index 6148da6..13594f0 100644 --- a/src/components/ui/bookmark-card.tsx +++ b/src/components/ui/bookmark-card.tsx @@ -12,6 +12,7 @@ import { Bookmark, BookmarkType } from '@/types/forum'; import { useUserDisplay } from '@/hooks'; import { cn } from '@/lib/utils'; import { formatDistanceToNow } from 'date-fns'; +import { useNavigate } from 'react-router-dom'; interface BookmarkCardProps { bookmark: Bookmark; @@ -27,6 +28,7 @@ export function BookmarkCard({ className, }: BookmarkCardProps) { const authorInfo = useUserDisplay(bookmark.author || ''); + const navigate = useNavigate(); const handleNavigate = () => { if (onNavigate) { @@ -34,9 +36,9 @@ export function BookmarkCard({ } else { // Default navigation behavior if (bookmark.type === BookmarkType.POST) { - window.location.href = `/post/${bookmark.targetId}`; + navigate(`/post/${bookmark.targetId}`); } else if (bookmark.type === BookmarkType.COMMENT && bookmark.postId) { - window.location.href = `/post/${bookmark.postId}#comment-${bookmark.targetId}`; + navigate(`/post/${bookmark.postId}#comment-${bookmark.targetId}`); } } }; diff --git a/src/components/ui/wallet-wizard.tsx b/src/components/ui/wallet-wizard.tsx index 12d0811..231c226 100644 --- a/src/components/ui/wallet-wizard.tsx +++ b/src/components/ui/wallet-wizard.tsx @@ -9,9 +9,8 @@ import { import { Button } from '@/components/ui/button'; import { CheckCircle, Circle, Loader2 } from 'lucide-react'; import { useAuth } from '@/hooks'; -import { useAuth as useAuthContext } from '@/contexts/useAuth'; +import { useDelegation } from '@/hooks/useDelegation'; import { EVerificationStatus } from '@/types/identity'; -import { DelegationFullStatus } from '@/lib/delegation'; import { WalletConnectionStep } from './wallet-connection-step'; import { VerificationStep } from './verification-step'; import { DelegationStep } from './delegation-step'; @@ -32,43 +31,15 @@ export function WalletWizard({ const [currentStep, setCurrentStep] = React.useState(1); const [isLoading, setIsLoading] = React.useState(false); const { isAuthenticated, verificationStatus } = useAuth(); - const { getDelegationStatus } = useAuthContext(); - const [delegationInfo, setDelegationInfo] = - React.useState(null); - const hasInitialized = React.useRef(false); + const { delegationStatus } = useDelegation(); - // Load delegation status + // Reset wizard when opened - always start at step 1 for simplicity React.useEffect(() => { - getDelegationStatus().then(setDelegationInfo).catch(console.error); - }, [getDelegationStatus]); - - // Reset wizard when opened and determine starting step - React.useEffect(() => { - if (open && !hasInitialized.current) { - // Determine the appropriate starting step based on current state - if (!isAuthenticated) { - setCurrentStep(1); // Start at connection step if not authenticated - } else if ( - isAuthenticated && - verificationStatus === EVerificationStatus.WALLET_UNCONNECTED - ) { - setCurrentStep(2); // Start at verification step if authenticated but not verified - } else if ( - isAuthenticated && - (verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED || - verificationStatus === EVerificationStatus.WALLET_CONNECTED) && - !delegationInfo?.isValid - ) { - setCurrentStep(3); // Start at delegation step if verified but no valid delegation - } else { - setCurrentStep(3); // Default to step 3 if everything is complete - } + if (open) { + setCurrentStep(1); setIsLoading(false); - hasInitialized.current = true; - } else if (!open) { - hasInitialized.current = false; } - }, [open, isAuthenticated, verificationStatus, delegationInfo]); + }, [open]); const handleStepComplete = (step: WizardStep) => { if (step < 3) { @@ -84,30 +55,36 @@ export function WalletWizard({ onOpenChange(false); }; + // Business logic: determine step status based on current wizard step const getStepStatus = (step: WizardStep) => { - if (step === 1) { - return isAuthenticated ? 'complete' : 'current'; - } else if (step === 2) { - if (!isAuthenticated) return 'disabled'; - return verificationStatus !== EVerificationStatus.WALLET_UNCONNECTED - ? 'complete' - : 'current'; - } else if (step === 3) { - if ( - !isAuthenticated || - verificationStatus === EVerificationStatus.WALLET_UNCONNECTED - ) { - return 'disabled'; - } - return delegationInfo?.isValid ? 'complete' : 'current'; + if (step < currentStep) { + return 'complete'; + } else if (step === currentStep) { + return 'current'; + } else { + return 'disabled'; } - return 'disabled'; }; + const renderStepIcon = (step: WizardStep) => { const status = getStepStatus(step); + + // Check if step is actually completed based on auth state + const isActuallyComplete = (step: WizardStep): boolean => { + switch (step) { + case 1: + return isAuthenticated; + case 2: + return verificationStatus !== EVerificationStatus.WALLET_UNCONNECTED; + case 3: + return delegationStatus.isValid; + default: + return false; + } + }; - if (status === 'complete') { + if (status === 'complete' || isActuallyComplete(step)) { return ; } else if (status === 'current') { return ; @@ -149,7 +126,10 @@ export function WalletWizard({ className={`text-sm ${ getStepStatus(step as WizardStep) === 'current' ? 'text-blue-500 font-medium' - : getStepStatus(step as WizardStep) === 'complete' + : (getStepStatus(step as WizardStep) === 'complete' || + (step === 1 && isAuthenticated) || + (step === 2 && verificationStatus !== EVerificationStatus.WALLET_UNCONNECTED) || + (step === 3 && delegationStatus.isValid)) ? 'text-green-500' : 'text-gray-400' }`} @@ -160,7 +140,9 @@ export function WalletWizard({ {step < 3 && (
{ - if (currentUser?.address) { + if (currentUser?.address && post?.id) { const bookmarked = BookmarkService.isPostBookmarked( currentUser.address, post.id @@ -177,10 +177,10 @@ export function usePostBookmark(post: Post, cellId?: string) { } else { setIsBookmarked(false); } - }, [currentUser?.address, post.id]); + }, [currentUser?.address, post?.id]); const toggleBookmark = useCallback(async () => { - if (!currentUser?.address) return false; + if (!currentUser?.address || !post) return false; setLoading(true); try { diff --git a/src/index.css b/src/index.css index 3c3aac7..b872cea 100644 --- a/src/index.css +++ b/src/index.css @@ -113,17 +113,150 @@ } @layer components { + /* Page Layout Components */ + .page-container { + @apply min-h-screen flex flex-col bg-cyber-dark text-white; + } + + .page-header { + @apply mb-8; + } + + .page-title { + @apply text-3xl font-mono font-bold text-white mb-2; + } + + .page-subtitle { + @apply text-cyber-neutral; + } + + .page-content { + @apply flex-1 pt-16; + } + + .page-main { + @apply container mx-auto px-4 py-8 max-w-6xl; + } + + .page-footer { + @apply border-t border-cyber-muted py-4 text-center text-xs text-cyber-neutral; + } + + /* Card Components */ + .card-base { + @apply bg-cyber-muted/20 border border-cyber-muted/30 rounded-sm transition-all duration-200; + } + + .card-hover { + @apply hover:bg-cyber-muted/30 hover:border-cyber-accent/50; + } + + .card-padding { + @apply p-6; + } + + .card-padding-sm { + @apply p-4; + } + .thread-card { - @apply border border-cyber-muted bg-cyber-dark hover:bg-cyber-muted/20 transition-colors rounded-sm p-4 mb-4; + @apply card-base card-hover card-padding-sm mb-4; } .board-card { - @apply border border-cyber-muted bg-cyber-dark hover:bg-cyber-muted/20 transition-colors rounded-sm p-3 mb-3; + @apply card-base card-hover card-padding-sm mb-3; } .comment-card { @apply border-l-2 border-muted pl-3 py-2 my-2 hover:border-primary transition-colors; } + + /* Content Cards */ + .content-card { + @apply card-base card-hover card-padding; + } + + .content-card-sm { + @apply card-base card-hover card-padding-sm; + } + + /* Status Indicators */ + .status-ready { + @apply bg-green-500/20 text-green-400 border-green-500/30; + } + + .status-warning { + @apply bg-orange-500/20 text-orange-400 border-orange-500/30; + } + + .status-error { + @apply bg-red-500/20 text-red-400 border-red-500/30; + } + + .status-neutral { + @apply bg-cyber-muted/20 text-cyber-neutral border-cyber-muted/30; + } + + /* Button Variants */ + .btn-cyber { + @apply bg-cyber-accent hover:bg-cyber-accent/80 text-black font-mono font-medium; + } + + .btn-cyber-outline { + @apply border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30; + } + + /* Typography */ + .text-glow { + text-shadow: 0 0 8px rgba(15, 160, 206, 0.5); + } + + .text-glow-subtle { + text-shadow: 0 0 4px rgba(15, 160, 206, 0.3); + } + + /* Spacing Utilities */ + .section-spacing { + @apply mb-8; + } + + .content-spacing { + @apply mb-6; + } + + .item-spacing { + @apply mb-4; + } + + /* Grid Layouts */ + .grid-main-sidebar { + @apply grid grid-cols-1 lg:grid-cols-3 gap-6; + } + + .grid-main-content { + @apply lg:col-span-2; + } + + .grid-sidebar { + @apply lg:col-span-1; + } + + /* Empty States */ + .empty-state { + @apply flex flex-col items-center justify-center py-16 px-4 text-center; + } + + .empty-state-icon { + @apply w-16 h-16 text-cyber-accent/50 mb-4; + } + + .empty-state-title { + @apply text-xl font-mono font-bold text-white mb-2; + } + + .empty-state-description { + @apply text-cyber-neutral mb-4; + } } @keyframes cyber-flicker { diff --git a/src/lib/waku/constants.ts b/src/lib/waku/constants.ts index cb8aa17..72dbcf1 100644 --- a/src/lib/waku/constants.ts +++ b/src/lib/waku/constants.ts @@ -2,7 +2,7 @@ * Single content topic for all message types * Different message types are parsed from the message content itself */ -export const CONTENT_TOPIC = '/opchan-sds-ab/1/messages/proto'; +export const CONTENT_TOPIC = '/opchan-test-1/1/messages/proto'; /** * Bootstrap nodes for the Waku network @@ -14,8 +14,4 @@ export const BOOTSTRAP_NODES = { '/dns4/node-01.do-ams3.waku.sandbox.status.im/tcp/30303/p2p/16Uiu2HAmNaeL4p3WEYzC9mgXBmBWSgWjPHRvatZTXnp8Jgv3iKsb', '/dns4/vps-aaa00d52.vps.ovh.ca/tcp/8000/wss/p2p/16Uiu2HAm9PftGgHZwWE3wzdMde4m3kT2eYJFXLZfGoSED3gysofk', ], -}; - -export const LOCAL_STORAGE_KEYS = { - KEY_DELEGATION: 'opchan-key-delegation', -}; +}; \ No newline at end of file diff --git a/src/pages/BookmarksPage.tsx b/src/pages/BookmarksPage.tsx index dcebf6d..6cc47d5 100644 --- a/src/pages/BookmarksPage.tsx +++ b/src/pages/BookmarksPage.tsx @@ -1,4 +1,5 @@ import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import Header from '@/components/Header'; import { BookmarkList } from '@/components/ui/bookmark-card'; import { Button } from '@/components/ui/button'; @@ -27,6 +28,7 @@ import { useAuth } from '@/contexts/useAuth'; const BookmarksPage = () => { const { currentUser } = useAuth(); + const navigate = useNavigate(); const { bookmarks, loading, @@ -75,9 +77,9 @@ const BookmarksPage = () => { const handleNavigate = (bookmark: Bookmark) => { if (bookmark.type === BookmarkType.POST) { - window.location.href = `/post/${bookmark.targetId}`; + navigate(`/post/${bookmark.targetId}`); } else if (bookmark.type === BookmarkType.COMMENT && bookmark.postId) { - window.location.href = `/post/${bookmark.postId}#comment-${bookmark.targetId}`; + navigate(`/post/${bookmark.postId}#comment-${bookmark.targetId}`); } }; @@ -117,19 +119,20 @@ const BookmarksPage = () => { } return ( -
+
-
- {/* Header Section */} -
-
-
- -

- My Bookmarks -

-
+
+
+ {/* Header Section */} +
+
+
+ +

+ My Bookmarks +

+
{bookmarks.length > 0 && ( @@ -165,11 +168,11 @@ const BookmarksPage = () => { )}
-

- Your saved posts and comments. Bookmarks are stored locally and - won't be shared. -

-
+

+ Your saved posts and comments. Bookmarks are stored locally and + won't be shared. +

+
{/* Stats */} {bookmarks.length > 0 && ( @@ -248,9 +251,10 @@ const BookmarksPage = () => { /> +
-
+

OpChan - A decentralized forum built on Waku & Bitcoin Ordinals

diff --git a/src/pages/CellPage.tsx b/src/pages/CellPage.tsx index 211f742..1e2798c 100644 --- a/src/pages/CellPage.tsx +++ b/src/pages/CellPage.tsx @@ -3,12 +3,12 @@ import PostList from '@/components/PostList'; const CellPage = () => { return ( -
+
-
+
-
+

OpChan - A decentralized forum built on Waku & Bitcoin Ordinals

diff --git a/src/pages/FeedPage.tsx b/src/pages/FeedPage.tsx index b42f228..3560954 100644 --- a/src/pages/FeedPage.tsx +++ b/src/pages/FeedPage.tsx @@ -96,18 +96,19 @@ const FeedPage: React.FC = () => { } return ( -
-
+
+
{/* Page Header */} -
-
-

- Popular Posts -

-

- Latest posts from all cells -

-
+
+
+
+

+ Popular Posts +

+

+ Latest posts from all cells +

+
@@ -147,6 +148,7 @@ const FeedPage: React.FC = () => { Refresh
+
@@ -155,12 +157,12 @@ const FeedPage: React.FC = () => { {/* Posts Feed */}
{allPosts.length === 0 ? ( -
+
-

+

No posts yet

-

+

Be the first to create a post in a cell!

{verificationStatus !== diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index cc81afc..40e81ca 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -9,9 +9,9 @@ const Index = () => { const { refreshData } = useForumActions(); return ( -
+
-
+
{!health.isConnected && (
@@ -26,7 +26,7 @@ const Index = () => {
)}
-
+

OpChan - A decentralized forum built on Waku & Bitcoin Ordinals

diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 147e1f0..c8b70be 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { useAuth, useUserActions, useForumActions } from '@/hooks'; import { useAuth as useAuthContext } from '@/contexts/useAuth'; import { useUserDisplay } from '@/hooks'; +import { useDelegation } from '@/hooks/useDelegation'; import { DelegationFullStatus } from '@/lib/delegation'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -15,17 +16,21 @@ import { } from '@/components/ui/select'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; -import { Separator } from '@/components/ui/separator'; import { WalletWizard } from '@/components/ui/wallet-wizard'; +import Header from '@/components/Header'; import { Loader2, - Wallet, - Hash, User, Shield, CheckCircle, AlertTriangle, XCircle, + Settings, + Copy, + Globe, + Edit3, + Save, + X, } from 'lucide-react'; import { EDisplayPreference, EVerificationStatus } from '@/types/identity'; import { useToast } from '@/hooks/use-toast'; @@ -36,8 +41,9 @@ export default function ProfilePage() { const { toast } = useToast(); // Get current user from auth context for the address - const { currentUser } = useAuth(); + const { currentUser, verificationStatus } = useAuth(); const { getDelegationStatus } = useAuthContext(); + const { delegationStatus } = useDelegation(); const [delegationInfo, setDelegationInfo] = useState(null); const address = currentUser?.address; @@ -68,16 +74,38 @@ export default function ProfilePage() { } }, [currentUser]); + // 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 ( -
- - -
- Please connect your wallet to view your profile. -
-
-
+
+
+
+ + +
+ +

Connect Required

+

Please connect your wallet to view your profile.

+
+
+
+
); } @@ -146,7 +174,7 @@ export default function ProfilePage() { }; const getVerificationIcon = () => { - switch (userInfo.verificationLevel) { + switch (verificationStatus) { case EVerificationStatus.ENS_ORDINAL_VERIFIED: return ; case EVerificationStatus.WALLET_CONNECTED: @@ -159,7 +187,7 @@ export default function ProfilePage() { }; const getVerificationText = () => { - switch (userInfo.verificationLevel) { + switch (verificationStatus) { case EVerificationStatus.ENS_ORDINAL_VERIFIED: return 'Owns ENS or Ordinal'; case EVerificationStatus.WALLET_CONNECTED: @@ -172,7 +200,7 @@ export default function ProfilePage() { }; const getVerificationColor = () => { - switch (userInfo.verificationLevel) { + switch (verificationStatus) { case EVerificationStatus.ENS_ORDINAL_VERIFIED: return 'bg-green-100 text-green-800 border-green-200'; case EVerificationStatus.WALLET_CONNECTED: @@ -185,316 +213,308 @@ export default function ProfilePage() { }; return ( -
- - - - - Profile - - - - {/* Wallet Information */} -
-

- - Wallet Information -

-
-
- -
- {currentUser.address} -
-
-
- -
- - {currentUser.walletType} - -
-
-
+
+
+
+
+ {/* Page Header */} +
+

Profile

+

Manage your account settings and preferences

- - - {/* Identity Information */} -
-

- - Identity -

-
-
- -
- {currentUser.ensDetails?.ensName || 'N/A'} -
-
-
- -
- {userInfo.displayName} -
-
-
-
- - - - {/* Editable Profile Fields */} -
-

Profile Settings

- -
-
- - {isEditing ? ( - setCallSign(e.target.value)} - placeholder="Enter your call sign" - className="mt-1" - disabled={isSubmitting} - /> - ) : ( -
- {userInfo.callSign || currentUser.callSign || 'Not set'} -
- )} -

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

-
- -
- - {isEditing ? ( - - ) : ( -
- {(userInfo.displayPreference || displayPreference) === - EDisplayPreference.CALL_SIGN - ? 'Call Sign (when available)' - : 'Wallet Address'} -
- )} -
-
-
- - - - {/* Verification Status */} -
-

- - Verification Status -

-
- {getVerificationIcon()} - - {getVerificationText()} - -
-
- - - - {/* Delegation Details */} -
-

- - Key Delegation -

-
- {/* Delegation Status */} -
- - {delegationInfo?.isValid ? 'Active' : 'Inactive'} - - {delegationInfo?.isValid && delegationInfo?.timeRemaining && ( - - {delegationInfo.timeRemaining} remaining - - )} - {!delegationInfo?.isValid && ( - - Renewal Recommended - - )} - {!delegationInfo?.isValid && ( - Expired - )} -
- - {/* Delegation Details Grid */} -
-
- -
- {currentUser.browserPubKey - ? `${currentUser.browserPubKey.slice(0, 12)}...${currentUser.browserPubKey.slice(-8)}` - : 'Not delegated'} -
-
- -
- -
- {currentUser.delegationSignature === 'valid' ? ( - + {/* User Profile Card - Primary (2/3 width) */} +
+ + + +
+ + User Profile +
+ {!isEditing && ( + )} -
-
- - {currentUser.delegationExpiry && ( -
- -
- {new Date(currentUser.delegationExpiry).toLocaleString()} + + + + {/* Identity Section */} +
+
+
+ +
+
+
+ {userInfo.displayName} +
+
+ {currentUser.ensDetails?.ensName || 'No ENS name'} +
+
+ {getVerificationIcon()} + + {getVerificationText()} + +
+
- )} -
- -
- {currentUser.lastChecked - ? new Date(currentUser.lastChecked).toLocaleString() - : 'Never'} + {/* Wallet Section */} +
+

+ Wallet Information +

+
+
+ +
+
+ {currentUser.address.slice(0, 8)}...{currentUser.address.slice(-6)} +
+ +
+
+
+ +
+ + + {currentUser.walletType} + +
+
+
-
-
- -
- {delegationInfo?.hasDelegation ? ( - +

+ 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} + /> + ) : ( +
+ {userInfo.callSign || currentUser.callSign || 'Not set'} +
+ )} +

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

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