diff --git a/TODO.md b/TODO.md
index d9518ca..9d3e504 100644
--- a/TODO.md
+++ b/TODO.md
@@ -5,6 +5,7 @@ This document outlines the features and improvements that still need to be imple
## 🚨 High Priority (1-2 weeks)
### 1. Bookmarking System
+
- **Requirement**: "Users can bookmark posts and topics; local only"
- **Status**: ❌ Not implemented
- **Missing**:
@@ -16,6 +17,7 @@ This document outlines the features and improvements that still need to be imple
- **Estimated Effort**: 2-3 days
### 2. Call Sign Setup & Display
+
- **Requirement**: "Users can setup a call sign; bitcoin identity operator unique name - remains - ordinal used as avatar"
- **Status**: ⚠️ Partially implemented
- **Missing**:
@@ -27,6 +29,7 @@ This document outlines the features and improvements that still need to be imple
- **Estimated Effort**: 3-4 days
### 3. Cell Icon System
+
- **Requirement**: "Cell can be created with a name, description, icon; icon size will be restricted"
- **Status**: ❌ Not implemented
- **Missing**:
@@ -40,6 +43,7 @@ This document outlines the features and improvements that still need to be imple
## 🔶 Medium Priority (2-3 weeks)
### 4. Enhanced Sorting Options
+
- **Requirement**: "Users can sort topics per new or top"
- **Status**: ⚠️ Basic implementation exists
- **Missing**:
@@ -51,6 +55,7 @@ This document outlines the features and improvements that still need to be imple
- **Estimated Effort**: 1-2 days
### 5. Active Member Count Display
+
- **Requirement**: "A user can see the number of active members per cell; deduced from retrievable activity"
- **Status**: ⚠️ Calculated in backend but not shown
- **Missing**:
@@ -61,6 +66,7 @@ This document outlines the features and improvements that still need to be imple
- **Estimated Effort**: 1 day
### 6. IndexedDB Integration
+
- **Requirement**: "store message cache in indexedDB -- make app local-first"
- **Status**: ❌ In-memory caching only
- **Missing**:
@@ -72,6 +78,7 @@ This document outlines the features and improvements that still need to be imple
- **Estimated Effort**: 3-4 days
### 7. Enhanced Moderation UI
+
- **Requirement**: "Cell admin can mark posts and comments as moderated"
- **Status**: ⚠️ Backend logic exists, basic UI
- **Missing**:
@@ -86,6 +93,7 @@ This document outlines the features and improvements that still need to be imple
## 🔵 Low Priority (3-4 weeks)
### 8. Anonymous User Experience
+
- **Requirement**: "Anonymous users can upvote, comments and post"
- **Status**: ⚠️ Basic support but limited UX
- **Missing**:
@@ -97,6 +105,7 @@ This document outlines the features and improvements that still need to be imple
- **Estimated Effort**: 2-3 days
### 9. Relevance Score Visibility
+
- **Requirement**: "The relevance index is used to push most relevant posts and comments on top"
- **Status**: ⚠️ Calculated but limited visibility
- **Missing**:
@@ -108,6 +117,7 @@ This document outlines the features and improvements that still need to be imple
- **Estimated Effort**: 1-2 days
### 10. Mobile Responsiveness
+
- **Requirement**: "Users do not need any software beyond a browser to use the forum"
- **Status**: ❌ Basic responsive design
- **Missing**:
@@ -121,18 +131,21 @@ This document outlines the features and improvements that still need to be imple
## 🛠️ Technical Debt & Infrastructure
### 11. Performance Optimizations
+
- [ ] Implement virtual scrolling for large lists
- [ ] Add message pagination
- [ ] Optimize relevance calculations
- [ ] Implement lazy loading for images
### 12. Testing & Quality
+
- [ ] Add comprehensive unit tests
- [ ] Implement integration tests
- [ ] Add end-to-end testing
- [ ] Performance testing and monitoring
### 13. Documentation
+
- [ ] API documentation
- [ ] User guide
- [ ] Developer setup guide
@@ -141,11 +154,13 @@ This document outlines the features and improvements that still need to be imple
## 📋 Implementation Notes
### Dependencies
+
- Bookmarking system depends on IndexedDB integration
- Call sign setup depends on user profile system completion
- Enhanced moderation depends on existing moderation backend
### Technical Considerations
+
- Use React Query for state management
- Implement proper error boundaries
- Add loading states for all async operations
@@ -153,6 +168,7 @@ This document outlines the features and improvements that still need to be imple
- Follow existing code patterns and conventions
### Testing Strategy
+
- Unit tests for utility functions
- Integration tests for hooks and contexts
- Component tests for UI elements
@@ -169,11 +185,11 @@ This document outlines the features and improvements that still need to be imple
## 📅 Timeline Estimate
- **Phase 1 (High Priority)**: 1-2 weeks
-- **Phase 2 (Medium Priority)**: 2-3 weeks
+- **Phase 2 (Medium Priority)**: 2-3 weeks
- **Phase 3 (Low Priority)**: 3-4 weeks
- **Total Estimated Time**: 6-9 weeks
---
-*Last updated: [Current Date]*
-*Based on FURPS requirements analysis and codebase review*
+_Last updated: [Current Date]_
+_Based on FURPS requirements analysis and codebase review_
diff --git a/src/App.tsx b/src/App.tsx
index 6026da9..5f8b7f5 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -23,6 +23,7 @@ import PostPage from './pages/PostPage';
import NotFound from './pages/NotFound';
import Dashboard from './pages/Dashboard';
import Index from './pages/Index';
+import ProfilePage from './pages/ProfilePage';
import { appkitConfig } from './lib/wallet/config';
import { WagmiProvider } from 'wagmi';
import { config } from './lib/wallet/config';
@@ -46,6 +47,7 @@ const App = () => (
} />
} />
} />
+ } />
} />
diff --git a/src/components/CellList.tsx b/src/components/CellList.tsx
index 164da6f..2747278 100644
--- a/src/components/CellList.tsx
+++ b/src/components/CellList.tsx
@@ -21,8 +21,71 @@ import {
import { CypherImage } from './ui/CypherImage';
import { RelevanceIndicator } from './ui/relevance-indicator';
import { sortCells, SortOption } from '@/lib/utils/sorting';
+import { Cell } from '@/types/forum';
import { usePending } from '@/hooks/usePending';
+// Separate component to properly use hooks
+const CellItem: React.FC<{ cell: Cell }> = ({ cell }) => {
+ const pending = usePending(cell.id);
+
+ return (
+
+
+
+
+
+
+
+ {cell.name}
+
+ {cell.relevanceScore !== undefined && (
+
+ )}
+
+ {pending.isPending && (
+
+
+ syncing…
+
+
+ )}
+
+
+ {cell.description}
+
+
+
+
+
+
+ {cell.postCount || 0} posts
+
+
+
+ {cell.activeMemberCount || 0} members
+
+
+
+
+
+
+ );
+};
+
const CellList = () => {
const { cellsWithStats, isInitialLoading } = useForumData();
const { refreshData } = useForumActions();
@@ -109,63 +172,7 @@ const CellList = () => {
) : (
- sortedCells.map(cell => (
-
-
-
-
-
-
-
- {cell.name}
-
- {cell.relevanceScore !== undefined && (
-
- )}
-
- {usePending(cell.id).isPending && (
-
-
- syncing…
-
-
- )}
-
-
- {cell.description}
-
-
-
-
-
-
- {cell.postCount || 0} posts
-
-
-
- {cell.activeMemberCount || 0} members
-
-
-
-
-
-
- ))
+ sortedCells.map(cell => )
)}
diff --git a/src/components/FeedSidebar.tsx b/src/components/FeedSidebar.tsx
index 2025a8b..dfd4be4 100644
--- a/src/components/FeedSidebar.tsx
+++ b/src/components/FeedSidebar.tsx
@@ -1,17 +1,11 @@
-import React, { useState } from 'react';
+import React from 'react';
import { Link } from 'react-router-dom';
-import { Plus, TrendingUp, Users, Eye } from 'lucide-react';
-import { Button } from '@/components/ui/button';
+import { TrendingUp, Users, Eye } from 'lucide-react';
+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
-import {
- useForumData,
- useForumSelectors,
- useAuth,
- usePermissions,
-} from '@/hooks';
+import { useForumData, useForumSelectors, useAuth } from '@/hooks';
import { CypherImage } from '@/components/ui/CypherImage';
-import { CreateCellDialog } from '@/components/CreateCellDialog';
import { useUserDisplay } from '@/hooks';
const FeedSidebar: React.FC = () => {
@@ -19,11 +13,9 @@ const FeedSidebar: React.FC = () => {
const forumData = useForumData();
const selectors = useForumSelectors(forumData);
const { currentUser, verificationStatus } = useAuth();
- const { canCreateCell } = usePermissions();
- const [showCreateCell, setShowCreateCell] = useState(false);
// Get user display information using the hook
- const { displayName, hasENS, hasOrdinal } = useUserDisplay(
+ const { displayName, ensName, ordinalDetails } = useUserDisplay(
currentUser?.address || ''
);
@@ -41,9 +33,9 @@ const FeedSidebar: React.FC = () => {
return { text: 'Verified Owner', color: 'bg-green-500' };
} else if (verificationStatus.level === 'verified-basic') {
return { text: 'Verified', color: 'bg-blue-500' };
- } else if (hasENS) {
+ } else if (ensName) {
return { text: 'ENS User', color: 'bg-purple-500' };
- } else if (hasOrdinal) {
+ } else if (ordinalDetails) {
return { text: 'Ordinal User', color: 'bg-orange-500' };
}
return { text: 'Unverified', color: 'bg-gray-500' };
@@ -82,12 +74,13 @@ const FeedSidebar: React.FC = () => {
)}
- {verificationStatus.level === 'verified-basic' && !hasOrdinal && (
-
-
- Read-only mode. Acquire Ordinals to post.
-
- )}
+ {verificationStatus.level === 'verified-basic' &&
+ !ordinalDetails && (
+
+
+ Read-only mode. Acquire Ordinals to post.
+
+ )}
)}
@@ -158,31 +151,6 @@ const FeedSidebar: React.FC = () => {
)}
-
- {/* Quick Actions */}
- {canCreateCell && (
-
-
- Quick Actions
-
-
- setShowCreateCell(true)}
- className="w-full bg-cyber-accent hover:bg-cyber-accent/80"
- size="sm"
- >
-
- Create Cell
-
-
-
- )}
-
- {/* Create Cell Dialog */}
-
);
};
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index 14379db..eb4cdd3 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -13,6 +13,7 @@ import {
CircleSlash,
Home,
Grid3X3,
+ User,
} from 'lucide-react';
import {
Tooltip,
@@ -177,6 +178,19 @@ const Header = () => {
Cells
+ {isConnected && (
+
+
+ Profile
+
+ )}
@@ -196,7 +210,12 @@ const Header = () => {
{forum.lastSync && (
- Last updated {new Date(forum.lastSync).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
+ Last updated{' '}
+ {new Date(forum.lastSync).toLocaleTimeString([], {
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ })}
{forum.isSyncing ? ' • syncing…' : ''}
)}
diff --git a/src/components/PostDetail.tsx b/src/components/PostDetail.tsx
index d4fafce..17e6083 100644
--- a/src/components/PostDetail.tsx
+++ b/src/components/PostDetail.tsx
@@ -185,7 +185,9 @@ const PostDetail = () => {
{postVotePending.isPending && (
- syncing…
+
+ syncing…
+
)}
diff --git a/src/components/examples/HookDemoComponent.tsx b/src/components/examples/HookDemoComponent.tsx
index 90e76b1..f8fea2b 100644
--- a/src/components/examples/HookDemoComponent.tsx
+++ b/src/components/examples/HookDemoComponent.tsx
@@ -1,7 +1,6 @@
import {
useForumData,
useAuth,
- useUserDisplay,
useUserVotes,
useForumActions,
useUserActions,
@@ -23,7 +22,6 @@ export function HookDemoComponent() {
// Core data hooks - reactive and optimized
const forumData = useForumData();
const auth = useAuth();
- const userDisplay = useUserDisplay(auth.currentUser?.address || '');
// Derived hooks for specific data
const userVotes = useUserVotes();
@@ -142,17 +140,6 @@ export function HookDemoComponent() {
- {userDisplay.badges.length > 0 && (
-
- Badges:
- {userDisplay.badges.map((badge, index) => (
-
- {badge.icon} {badge.label}
-
- ))}
-
- )}
-
@@ -28,12 +28,12 @@ export function AuthorDisplay({
variant="secondary"
className="text-xs px-1.5 py-0.5 h-auto bg-green-900/20 border-green-500/30 text-green-400"
>
- {hasCallSign ? (
+ {callSign ? (
<>
Call Sign
>
- ) : hasENS ? (
+ ) : ensName ? (
<>
ENS
diff --git a/src/components/ui/verification-step.tsx b/src/components/ui/verification-step.tsx
index 700ad79..ce0089b 100644
--- a/src/components/ui/verification-step.tsx
+++ b/src/components/ui/verification-step.tsx
@@ -46,6 +46,42 @@ export function VerificationStep({
details?: OrdinalDetails | EnsDetails;
} | null>(null);
+ // Watch for changes in user state after verification
+ React.useEffect(() => {
+ if (
+ verificationResult?.success &&
+ verificationResult.message.includes('Checking ownership')
+ ) {
+ // Check if actual ownership was verified
+ const hasOwnership =
+ walletType === 'bitcoin'
+ ? !!currentUser?.ordinalDetails
+ : !!currentUser?.ensDetails;
+
+ if (hasOwnership) {
+ setVerificationResult({
+ success: true,
+ message:
+ walletType === 'bitcoin'
+ ? 'Ordinal ownership verified successfully!'
+ : 'ENS ownership verified successfully!',
+ details:
+ walletType === 'bitcoin'
+ ? currentUser?.ordinalDetails
+ : currentUser?.ensDetails,
+ });
+ } else {
+ setVerificationResult({
+ success: false,
+ message:
+ walletType === 'bitcoin'
+ ? 'No Ordinal ownership found. You can still participate in the forum with your connected wallet!'
+ : 'No ENS ownership found. You can still participate in the forum with your connected wallet!',
+ });
+ }
+ }
+ }, [currentUser, verificationResult, walletType]);
+
const handleVerify = async () => {
if (!currentUser) return;
@@ -56,16 +92,15 @@ export function VerificationStep({
const success = await verifyWallet();
if (success) {
+ // For now, just show success - the actual ownership check will be done
+ // by the useEffect when the user state updates
setVerificationResult({
success: true,
message:
walletType === 'bitcoin'
- ? 'Ordinal ownership verified successfully!'
- : 'ENS ownership verified successfully!',
- details:
- walletType === 'bitcoin'
- ? currentUser.ordinalDetails
- : currentUser.ensDetails,
+ ? 'Verification process completed. Checking ownership...'
+ : 'Verification process completed. Checking ownership...',
+ details: undefined,
});
} else {
setVerificationResult({
diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx
index eec7d44..651884b 100644
--- a/src/contexts/AuthContext.tsx
+++ b/src/contexts/AuthContext.tsx
@@ -1,7 +1,11 @@
import React, { createContext, useState, useEffect, useMemo } from 'react';
import { useToast } from '@/components/ui/use-toast';
import { OpchanMessage } from '@/types/forum';
-import { User, EVerificationStatus, EDisplayPreference } from '@/types/identity';
+import {
+ User,
+ EVerificationStatus,
+ EDisplayPreference,
+} from '@/types/identity';
import { WalletManager } from '@/lib/wallet';
import {
DelegationManager,
diff --git a/src/contexts/ForumContext.tsx b/src/contexts/ForumContext.tsx
index db296a8..cb7dfdc 100644
--- a/src/contexts/ForumContext.tsx
+++ b/src/contexts/ForumContext.tsx
@@ -7,7 +7,11 @@ import React, {
useRef,
} from 'react';
import { Cell, Post, Comment } from '@/types/forum';
-import { User, EVerificationStatus, EDisplayPreference } from '@/types/identity';
+import {
+ User,
+ EVerificationStatus,
+ EDisplayPreference,
+} from '@/types/identity';
import { useToast } from '@/components/ui/use-toast';
import { ForumActions } from '@/lib/forum/ForumActions';
@@ -122,7 +126,6 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
// Transform message cache data to the expected types
const updateStateFromCache = useCallback(async () => {
-
// Build user verification status for relevance calculation
const relevanceCalculator = new RelevanceCalculator();
const allUsers: User[] = [];
@@ -260,8 +263,14 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
Object.assign(messageManager.messageCache.posts, seeded.posts);
Object.assign(messageManager.messageCache.comments, seeded.comments);
Object.assign(messageManager.messageCache.votes, seeded.votes);
- Object.assign(messageManager.messageCache.moderations, seeded.moderations);
- Object.assign(messageManager.messageCache.userIdentities, seeded.userIdentities);
+ Object.assign(
+ messageManager.messageCache.moderations,
+ seeded.moderations
+ );
+ Object.assign(
+ messageManager.messageCache.userIdentities,
+ seeded.userIdentities
+ );
// Determine if we have any cached content
const hasSeedData =
@@ -274,7 +283,7 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
await updateStateFromCache();
// Initialize network and let incoming messages update LocalDatabase/Cache
- await initializeNetwork(toast, updateStateFromCache, setError);
+ await initializeNetwork(toast, setError);
if (hasSeedData) {
setIsInitialLoading(false);
@@ -288,7 +297,7 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
} catch (e) {
console.warn('LocalDatabase warm-start failed, continuing cold:', e);
// Initialize network even if local DB failed, keep loader until first message
- await initializeNetwork(toast, updateStateFromCache, setError);
+ await initializeNetwork(toast, setError);
const unsubscribe = messageManager.onMessageReceived(() => {
setIsInitialLoading(false);
unsubscribe();
diff --git a/src/hooks/actions/useAuthActions.ts b/src/hooks/actions/useAuthActions.ts
index 688b2a1..6fa4932 100644
--- a/src/hooks/actions/useAuthActions.ts
+++ b/src/hooks/actions/useAuthActions.ts
@@ -2,6 +2,7 @@ import { useCallback, useState } from 'react';
import { useAuth } from '@/hooks/core/useEnhancedAuth';
import { DelegationDuration } from '@/lib/delegation';
import { useToast } from '@/components/ui/use-toast';
+import { useAuth as useAuthContext } from '@/contexts/useAuth';
export interface AuthActionStates {
isConnecting: boolean;
@@ -38,6 +39,7 @@ export function useAuthActions(): AuthActions {
verificationStatus,
} = useAuth();
+ const { verifyOwnership, delegateKey: delegateKeyFromContext } = useAuthContext();
const { toast } = useToast();
const [isConnecting, setIsConnecting] = useState(false);
@@ -153,23 +155,24 @@ export function useAuthActions(): AuthActions {
setIsVerifying(true);
- try {
- toast({
- title: 'Verifying...',
- description: 'Please sign the verification message in your wallet.',
- });
-
- // This would trigger the verification process
- // The actual implementation would depend on the verification system
-
- // Simulate verification process
- await new Promise(resolve => setTimeout(resolve, 3000));
-
- toast({
- title: 'Verification Complete',
- description: 'Your wallet has been verified successfully.',
- });
- return true;
+ try {
+ // Call the real verification function from AuthContext
+ const success = await verifyOwnership();
+
+ if (success) {
+ toast({
+ title: 'Verification Complete',
+ description: 'Your wallet has been verified successfully.',
+ });
+ } else {
+ toast({
+ title: 'Verification Failed',
+ description: 'Failed to verify wallet ownership. Please try again.',
+ variant: 'destructive',
+ });
+ }
+
+ return success;
} catch (error) {
console.error('Failed to verify wallet:', error);
toast({
@@ -181,7 +184,7 @@ export function useAuthActions(): AuthActions {
} finally {
setIsVerifying(false);
}
- }, [isAuthenticated, verificationStatus.level, toast]);
+ }, [isAuthenticated, verificationStatus.level, verifyOwnership, toast]);
// Delegate key
const delegateKey = useCallback(
@@ -207,21 +210,24 @@ export function useAuthActions(): AuthActions {
setIsDelegating(true);
try {
- toast({
- title: 'Delegating Key...',
- description: 'Please sign the delegation message in your wallet.',
- });
-
- // This would trigger the key delegation process
- // The actual implementation would use the DelegationManager
-
- const durationLabel = duration === '7days' ? '1 week' : '30 days';
-
- toast({
- title: 'Key Delegated',
- description: `Your signing key has been delegated for ${durationLabel}.`,
- });
- return true;
+ // Call the real delegation function from AuthContext
+ const success = await delegateKeyFromContext(duration);
+
+ if (success) {
+ const durationLabel = duration === '7days' ? '1 week' : '30 days';
+ toast({
+ title: 'Key Delegated',
+ description: `Your signing key has been delegated for ${durationLabel}.`,
+ });
+ } else {
+ toast({
+ title: 'Delegation Failed',
+ description: 'Failed to delegate signing key. Please try again.',
+ variant: 'destructive',
+ });
+ }
+
+ return success;
} catch (error) {
console.error('Failed to delegate key:', error);
toast({
@@ -234,7 +240,7 @@ export function useAuthActions(): AuthActions {
setIsDelegating(false);
}
},
- [isAuthenticated, verificationStatus.level, toast]
+ [isAuthenticated, verificationStatus.level, delegateKeyFromContext, toast]
);
// Clear delegation
diff --git a/src/hooks/core/useEnhancedUserDisplay.ts b/src/hooks/core/useEnhancedUserDisplay.ts
index c7e8d89..11a9424 100644
--- a/src/hooks/core/useEnhancedUserDisplay.ts
+++ b/src/hooks/core/useEnhancedUserDisplay.ts
@@ -2,20 +2,13 @@ import { useState, useEffect, useMemo } from 'react';
import { useForum } from '@/contexts/useForum';
import { EDisplayPreference, EVerificationStatus } from '@/types/identity';
-export interface Badge {
- type: 'verification' | 'ens' | 'ordinal' | 'callsign';
- label: string;
- icon: string;
- color: string;
-}
-
export interface UserDisplayInfo {
displayName: string;
- hasCallSign: boolean;
- hasENS: boolean;
- hasOrdinal: boolean;
+ callSign: string | null;
+ ensName: string | null;
+ ordinalDetails: string | null;
verificationLevel: EVerificationStatus;
- badges: Badge[];
+ displayPreference: EDisplayPreference | null;
isLoading: boolean;
error: string | null;
}
@@ -27,11 +20,11 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
const { userIdentityService, userVerificationStatus } = useForum();
const [displayInfo, setDisplayInfo] = useState({
displayName: `${address.slice(0, 6)}...${address.slice(-4)}`,
- hasCallSign: false,
- hasENS: false,
- hasOrdinal: false,
+ callSign: null,
+ ensName: null,
+ ordinalDetails: null,
verificationLevel: EVerificationStatus.UNVERIFIED,
- badges: [],
+ displayPreference: null,
isLoading: true,
error: null,
});
@@ -41,8 +34,7 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
return (
userVerificationStatus[address] || {
isVerified: false,
- hasENS: false,
- hasOrdinal: false,
+ ensName: null,
verificationStatus: EVerificationStatus.UNVERIFIED,
}
);
@@ -66,13 +58,13 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
);
setDisplayInfo({
displayName: `${address.slice(0, 6)}...${address.slice(-4)}`,
- hasCallSign: false,
- hasENS: false,
- hasOrdinal: false,
+ callSign: null,
+ ensName: verificationInfo.ensName || null,
+ ordinalDetails: null,
verificationLevel:
verificationInfo.verificationStatus ||
EVerificationStatus.UNVERIFIED,
- badges: [],
+ displayPreference: null,
isLoading: false,
error: null,
});
@@ -95,102 +87,30 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
displayName = identity.ensName;
}
- // Generate badges
- const badges: Badge[] = [];
-
- // Verification badge
- if (
- identity.verificationStatus === EVerificationStatus.VERIFIED_OWNER
- ) {
- badges.push({
- type: 'verification',
- label: 'Verified Owner',
- icon: '🔑',
- color: 'text-cyber-accent',
- });
- } else if (
- identity.verificationStatus === EVerificationStatus.VERIFIED_BASIC
- ) {
- badges.push({
- type: 'verification',
- label: 'Verified',
- icon: '✅',
- color: 'text-green-400',
- });
- }
-
- // ENS badge
- if (identity.ensName) {
- badges.push({
- type: 'ens',
- label: 'ENS',
- icon: '🏷️',
- color: 'text-blue-400',
- });
- }
-
- // Ordinal badge
- if (identity.ordinalDetails) {
- badges.push({
- type: 'ordinal',
- label: 'Ordinal',
- icon: '⚡',
- color: 'text-orange-400',
- });
- }
-
- // Call sign badge
- if (identity.callSign) {
- badges.push({
- type: 'callsign',
- label: 'Call Sign',
- icon: '📻',
- color: 'text-purple-400',
- });
- }
-
setDisplayInfo({
displayName,
- hasCallSign: Boolean(identity.callSign),
- hasENS: Boolean(identity.ensName),
- hasOrdinal: Boolean(identity.ordinalDetails),
+ callSign: identity.callSign || null,
+ ensName: identity.ensName || null,
+ ordinalDetails: identity.ordinalDetails
+ ? identity.ordinalDetails.ordinalDetails
+ : null,
verificationLevel: identity.verificationStatus,
- badges,
+ displayPreference: identity.displayPreference || null,
isLoading: false,
error: null,
});
} else {
-
- // Use verification info from forum context
- const badges: Badge[] = [];
- if (verificationInfo.hasENS) {
- badges.push({
- type: 'ens',
- label: 'ENS',
- icon: '🏷️',
- color: 'text-blue-400',
- });
- }
- if (verificationInfo.hasOrdinal) {
- badges.push({
- type: 'ordinal',
- label: 'Ordinal',
- icon: '⚡',
- color: 'text-orange-400',
- });
- }
-
setDisplayInfo({
displayName:
verificationInfo.ensName ||
`${address.slice(0, 6)}...${address.slice(-4)}`,
- hasCallSign: false,
- hasENS: verificationInfo.hasENS,
- hasOrdinal: verificationInfo.hasOrdinal,
+ callSign: null,
+ ensName: verificationInfo.ensName || null,
+ ordinalDetails: null,
verificationLevel:
verificationInfo.verificationStatus ||
EVerificationStatus.UNVERIFIED,
- badges,
+ displayPreference: null,
isLoading: false,
error: null,
});
@@ -202,11 +122,11 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
);
setDisplayInfo({
displayName: `${address.slice(0, 6)}...${address.slice(-4)}`,
- hasCallSign: false,
- hasENS: false,
- hasOrdinal: false,
+ callSign: null,
+ ensName: null,
+ ordinalDetails: null,
verificationLevel: EVerificationStatus.UNVERIFIED,
- badges: [],
+ displayPreference: null,
isLoading: false,
error: error instanceof Error ? error.message : 'Unknown error',
});
@@ -221,19 +141,16 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
if (!displayInfo.isLoading && verificationInfo) {
setDisplayInfo(prev => ({
...prev,
- hasENS: verificationInfo.hasENS || prev.hasENS,
- hasOrdinal: verificationInfo.hasOrdinal || prev.hasOrdinal,
+ ensName: verificationInfo.ensName || prev.ensName,
verificationLevel:
verificationInfo.verificationStatus || prev.verificationLevel,
}));
}
}, [
verificationInfo.ensName,
- verificationInfo.hasENS,
- verificationInfo.hasOrdinal,
verificationInfo.verificationStatus,
displayInfo.isLoading,
- verificationInfo
+ verificationInfo,
]);
return displayInfo;
diff --git a/src/hooks/core/useUserDisplay.ts b/src/hooks/core/useUserDisplay.ts
index fae41b3..db3efe9 100644
--- a/src/hooks/core/useUserDisplay.ts
+++ b/src/hooks/core/useUserDisplay.ts
@@ -1,3 +1,3 @@
// Re-export the enhanced user display hook as the main useUserDisplay
export { useEnhancedUserDisplay as useUserDisplay } from './useEnhancedUserDisplay';
-export type { Badge, UserDisplayInfo } from './useEnhancedUserDisplay';
+export type { UserDisplayInfo } from './useEnhancedUserDisplay';
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index 5851cb3..687e36c 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -18,7 +18,7 @@ export type {
EnhancedAuthState,
} from './core/useEnhancedAuth';
-export type { Badge, UserDisplayInfo } from './core/useEnhancedUserDisplay';
+export type { UserDisplayInfo } from './core/useEnhancedUserDisplay';
// Derived hooks
export { useCell } from './derived/useCell';
diff --git a/src/hooks/usePending.ts b/src/hooks/usePending.ts
index ab2c44e..8c47dfe 100644
--- a/src/hooks/usePending.ts
+++ b/src/hooks/usePending.ts
@@ -44,5 +44,3 @@ export function usePendingVote(targetId: string | undefined) {
return { isPending };
}
-
-
diff --git a/src/lib/database/LocalDatabase.ts b/src/lib/database/LocalDatabase.ts
index 8951064..9910662 100644
--- a/src/lib/database/LocalDatabase.ts
+++ b/src/lib/database/LocalDatabase.ts
@@ -113,26 +113,38 @@ export class LocalDatabase {
private storeMessage(message: OpchanMessage): void {
switch (message.type) {
case MessageType.CELL:
- if (!this.cache.cells[message.id] || this.cache.cells[message.id]?.timestamp !== message.timestamp) {
+ if (
+ !this.cache.cells[message.id] ||
+ this.cache.cells[message.id]?.timestamp !== message.timestamp
+ ) {
this.cache.cells[message.id] = message;
this.put(STORE.CELLS, message);
}
break;
case MessageType.POST:
- if (!this.cache.posts[message.id] || this.cache.posts[message.id]?.timestamp !== message.timestamp) {
+ if (
+ !this.cache.posts[message.id] ||
+ this.cache.posts[message.id]?.timestamp !== message.timestamp
+ ) {
this.cache.posts[message.id] = message;
this.put(STORE.POSTS, message);
}
break;
case MessageType.COMMENT:
- if (!this.cache.comments[message.id] || this.cache.comments[message.id]?.timestamp !== message.timestamp) {
+ if (
+ !this.cache.comments[message.id] ||
+ this.cache.comments[message.id]?.timestamp !== message.timestamp
+ ) {
this.cache.comments[message.id] = message;
this.put(STORE.COMMENTS, message);
}
break;
case MessageType.VOTE: {
const voteKey = `${message.targetId}:${message.author}`;
- if (!this.cache.votes[voteKey] || this.cache.votes[voteKey]?.timestamp !== message.timestamp) {
+ if (
+ !this.cache.votes[voteKey] ||
+ this.cache.votes[voteKey]?.timestamp !== message.timestamp
+ ) {
this.cache.votes[voteKey] = message;
this.put(STORE.VOTES, { key: voteKey, ...message });
}
@@ -140,7 +152,11 @@ export class LocalDatabase {
}
case MessageType.MODERATE: {
const modMsg = message as ModerateMessage;
- if (!this.cache.moderations[modMsg.targetId] || this.cache.moderations[modMsg.targetId]?.timestamp !== modMsg.timestamp) {
+ if (
+ !this.cache.moderations[modMsg.targetId] ||
+ this.cache.moderations[modMsg.targetId]?.timestamp !==
+ modMsg.timestamp
+ ) {
this.cache.moderations[modMsg.targetId] = modMsg;
this.put(STORE.MODERATIONS, modMsg);
}
@@ -150,7 +166,10 @@ export class LocalDatabase {
const profileMsg = message as UserProfileUpdateMessage;
const { author, callSign, displayPreference, timestamp } = profileMsg;
- if (!this.cache.userIdentities[author] || this.cache.userIdentities[author]?.lastUpdated !== timestamp) {
+ if (
+ !this.cache.userIdentities[author] ||
+ this.cache.userIdentities[author]?.lastUpdated !== timestamp
+ ) {
this.cache.userIdentities[author] = {
ensName: undefined,
ordinalDetails: undefined,
@@ -182,14 +201,7 @@ export class LocalDatabase {
private async hydrateFromIndexedDB(): Promise {
if (!this.db) return;
- const [
- cells,
- posts,
- comments,
- votes,
- moderations,
- identities,
- ]: [
+ const [cells, posts, comments, votes, moderations, identities]: [
CellMessage[],
PostMessage[],
CommentMessage[],
@@ -217,7 +229,9 @@ export class LocalDatabase {
return [key, vote];
})
);
- this.cache.moderations = Object.fromEntries(moderations.map(m => [m.targetId, m]));
+ this.cache.moderations = Object.fromEntries(
+ moderations.map(m => [m.targetId, m])
+ );
this.cache.userIdentities = Object.fromEntries(
identities.map(u => {
const { address, ...record } = u;
@@ -232,7 +246,10 @@ export class LocalDatabase {
STORE.META
);
meta
- .filter(entry => typeof entry.key === 'string' && entry.key.startsWith('pending:'))
+ .filter(
+ entry =>
+ typeof entry.key === 'string' && entry.key.startsWith('pending:')
+ )
.forEach(entry => {
const id = (entry.key as string).substring('pending:'.length);
this.pendingIds.add(id);
@@ -313,5 +330,3 @@ export class LocalDatabase {
}
export const localDatabase = new LocalDatabase();
-
-
diff --git a/src/lib/database/schema.ts b/src/lib/database/schema.ts
index 9b94b68..ef16345 100644
--- a/src/lib/database/schema.ts
+++ b/src/lib/database/schema.ts
@@ -60,5 +60,3 @@ export function openLocalDB(): Promise {
};
});
}
-
-
diff --git a/src/lib/delegation/index.ts b/src/lib/delegation/index.ts
index 554ae73..5829646 100644
--- a/src/lib/delegation/index.ts
+++ b/src/lib/delegation/index.ts
@@ -85,7 +85,10 @@ export class DelegationManager {
*/
signMessage(message: UnsignedMessage): OpchanMessage | null {
const now = Date.now();
- if (!this.cachedDelegation || now - this.cachedAt > DelegationManager.CACHE_TTL_MS) {
+ if (
+ !this.cachedDelegation ||
+ now - this.cachedAt > DelegationManager.CACHE_TTL_MS
+ ) {
this.cachedDelegation = DelegationStorage.retrieve();
this.cachedAt = now;
}
@@ -164,7 +167,10 @@ export class DelegationManager {
currentWalletType?: 'bitcoin' | 'ethereum'
): DelegationFullStatus {
const now = Date.now();
- if (!this.cachedDelegation || now - this.cachedAt > DelegationManager.CACHE_TTL_MS) {
+ if (
+ !this.cachedDelegation ||
+ now - this.cachedAt > DelegationManager.CACHE_TTL_MS
+ ) {
this.cachedDelegation = DelegationStorage.retrieve();
this.cachedAt = now;
}
diff --git a/src/lib/forum/__tests__/relevance.test.ts b/src/lib/forum/__tests__/relevance.test.ts
index 7dff338..31a4a18 100644
--- a/src/lib/forum/__tests__/relevance.test.ts
+++ b/src/lib/forum/__tests__/relevance.test.ts
@@ -1,6 +1,10 @@
import { RelevanceCalculator } from '../RelevanceCalculator';
import { Post, Comment, UserVerificationStatus } from '@/types/forum';
-import { User, EVerificationStatus, EDisplayPreference } from '@/types/identity';
+import {
+ User,
+ EVerificationStatus,
+ EDisplayPreference,
+} from '@/types/identity';
import { VoteMessage, MessageType } from '@/types/waku';
import { expect, describe, beforeEach, it } from 'vitest';
diff --git a/src/lib/services/UserIdentityService.ts b/src/lib/services/UserIdentityService.ts
index 03c888a..56fd446 100644
--- a/src/lib/services/UserIdentityService.ts
+++ b/src/lib/services/UserIdentityService.ts
@@ -84,7 +84,8 @@ export class UserIdentityService {
}
// Fallback: Check Waku message cache
- const cacheServiceData = messageManager.messageCache.userIdentities[address];
+ const cacheServiceData =
+ messageManager.messageCache.userIdentities[address];
if (cacheServiceData) {
if (import.meta.env?.DEV) {
@@ -191,7 +192,10 @@ export class UserIdentityService {
await this.messageService.signAndBroadcastMessage(unsignedMessage);
if (import.meta.env?.DEV) {
- console.debug('UserIdentityService: message broadcast result', !!signedMessage);
+ console.debug(
+ 'UserIdentityService: message broadcast result',
+ !!signedMessage
+ );
}
return !!signedMessage;
@@ -248,9 +252,15 @@ export class UserIdentityService {
}
try {
- // For now, return null - ENS resolution can be added later
- // This would typically call an ENS resolver API
- return null;
+ // Import the ENS resolver from wagmi
+ const { getEnsName } = await import('@wagmi/core');
+ const { config } = await import('@/lib/wallet/config');
+
+ const ensName = await getEnsName(config, {
+ address: address as `0x${string}`,
+ });
+
+ return ensName || null;
} catch (error) {
console.error('Failed to resolve ENS name:', error);
return null;
@@ -263,13 +273,9 @@ export class UserIdentityService {
private async resolveOrdinalDetails(
address: string
): Promise<{ ordinalId: string; ordinalDetails: string } | null> {
- if (address.startsWith('0x')) {
- return null; // Not a Bitcoin address
- }
-
try {
- // For now, return null - Ordinal resolution can be added later
- // This would typically call an Ordinal API
+ //TODO: add Ordinal API call
+ console.log('resolveOrdinalDetails', address);
return null;
} catch (error) {
console.error('Failed to resolve Ordinal details:', error);
@@ -290,7 +296,9 @@ export class UserIdentityService {
ordinalDetails: undefined,
callSign: undefined,
displayPreference:
- displayPreference === EDisplayPreference.CALL_SIGN ? EDisplayPreference.CALL_SIGN : EDisplayPreference.WALLET_ADDRESS,
+ displayPreference === EDisplayPreference.CALL_SIGN
+ ? EDisplayPreference.CALL_SIGN
+ : EDisplayPreference.WALLET_ADDRESS,
lastUpdated: timestamp,
verificationStatus: EVerificationStatus.UNVERIFIED,
};
diff --git a/src/lib/waku/index.ts b/src/lib/waku/index.ts
index 6797d02..0d50633 100644
--- a/src/lib/waku/index.ts
+++ b/src/lib/waku/index.ts
@@ -15,8 +15,7 @@ class MessageManager {
private messageService: MessageService | null = null;
private reliableMessaging: ReliableMessaging | null = null;
- constructor() {
- }
+ constructor() {}
public static async create(): Promise {
const manager = new MessageManager();
diff --git a/src/lib/waku/network.ts b/src/lib/waku/network.ts
index cff2ce5..54191a5 100644
--- a/src/lib/waku/network.ts
+++ b/src/lib/waku/network.ts
@@ -47,7 +47,6 @@ export const refreshData = async (
export const initializeNetwork = async (
toast: ToastFunction,
- updateStateFromCache: () => void,
setError: (error: string | null) => void
): Promise => {
try {
diff --git a/src/lib/wallet/index.ts b/src/lib/wallet/index.ts
index 03758b1..9951f4a 100644
--- a/src/lib/wallet/index.ts
+++ b/src/lib/wallet/index.ts
@@ -207,7 +207,10 @@ export class WalletManager {
const bitcoinMessage = await loadBitcoinMessage();
const result = bitcoinMessage.verify(message, walletAddress, signature);
if (import.meta.env?.DEV) {
- console.debug('WalletManager.verifySignature (bitcoin) result', result);
+ console.debug(
+ 'WalletManager.verifySignature (bitcoin) result',
+ result
+ );
}
return result;
}
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx
new file mode 100644
index 0000000..10f55c9
--- /dev/null
+++ b/src/pages/ProfilePage.tsx
@@ -0,0 +1,482 @@
+import { useState, useEffect } from 'react';
+import { useAuth, useUserActions, useForumActions } from '@/hooks';
+import { useUserDisplay } from '@/hooks';
+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 { Separator } from '@/components/ui/separator';
+import { WalletWizard } from '@/components/ui/wallet-wizard';
+import {
+ Loader2,
+ Wallet,
+ Hash,
+ User,
+ Shield,
+ CheckCircle,
+ AlertTriangle,
+ XCircle,
+} from 'lucide-react';
+import { EDisplayPreference, EVerificationStatus } from '@/types/identity';
+import { useToast } from '@/hooks/use-toast';
+
+export default function ProfilePage() {
+ const { updateProfile } = useUserActions();
+ const { refreshData } = useForumActions();
+ const { toast } = useToast();
+
+ // Get current user from auth context for the address
+ const { currentUser, delegationInfo } = useAuth();
+ const address = currentUser?.address;
+
+ // Get comprehensive user information from the unified hook
+ const userInfo = useUserDisplay(address || '');
+
+ const [isEditing, setIsEditing] = useState(false);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [callSign, setCallSign] = useState(currentUser?.callSign || '');
+ const [displayPreference, setDisplayPreference] = useState(
+ currentUser?.displayPreference || EDisplayPreference.WALLET_ADDRESS
+ );
+ const [walletWizardOpen, setWalletWizardOpen] = useState(false);
+
+ // Update local state when user data changes
+ useEffect(() => {
+ if (currentUser) {
+ setCallSign(currentUser.callSign || '');
+ setDisplayPreference(
+ currentUser.displayPreference || EDisplayPreference.WALLET_ADDRESS
+ );
+ }
+ }, [currentUser]);
+
+ if (!currentUser) {
+ return (
+
+
+
+
+ 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 refreshData();
+ 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 = () => {
+ setCallSign(currentUser.callSign || '');
+ setDisplayPreference(currentUser.displayPreference);
+ setIsEditing(false);
+ };
+
+ const getVerificationIcon = () => {
+ switch (userInfo.verificationLevel) {
+ case EVerificationStatus.VERIFIED_OWNER:
+ return ;
+ case EVerificationStatus.VERIFIED_BASIC:
+ return ;
+ case EVerificationStatus.UNVERIFIED:
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getVerificationText = () => {
+ switch (userInfo.verificationLevel) {
+ case EVerificationStatus.VERIFIED_OWNER:
+ return 'Fully Verified';
+ case EVerificationStatus.VERIFIED_BASIC:
+ return 'Basic Verification';
+ case EVerificationStatus.UNVERIFIED:
+ return 'Unverified';
+ default:
+ return 'Unknown';
+ }
+ };
+
+ const getVerificationColor = () => {
+ switch (userInfo.verificationLevel) {
+ case EVerificationStatus.VERIFIED_OWNER:
+ return 'bg-green-100 text-green-800 border-green-200';
+ case EVerificationStatus.VERIFIED_BASIC:
+ return 'bg-blue-100 text-blue-800 border-blue-200';
+ case EVerificationStatus.UNVERIFIED:
+ return 'bg-yellow-100 text-yellow-800 border-yellow-200';
+ default:
+ return 'bg-gray-100 text-gray-800 border-gray-200';
+ }
+ };
+
+ return (
+
+
+
+
+
+ Profile
+
+
+
+ {/* Wallet Information */}
+
+
+
+ Wallet Information
+
+
+
+
+ Address
+
+
+ {currentUser.address}
+
+
+
+
+ Network
+
+
+
+ {currentUser.walletType}
+
+
+
+
+
+
+
+
+ {/* Identity Information */}
+
+
+
+ Identity
+
+
+
+
+ ENS Name
+
+
+ {currentUser.ensDetails?.ensName || 'N/A'}
+
+
+
+
+ Current Display Name
+
+
+ {userInfo.displayName}
+
+
+
+
+
+
+
+ {/* Editable Profile Fields */}
+
+
Profile Settings
+
+
+
+
+ Call Sign
+
+ {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
+
+
+
+
+
+ Display Preference
+
+ {isEditing ? (
+
+ setDisplayPreference(value as EDisplayPreference)
+ }
+ disabled={isSubmitting}
+ >
+
+
+
+
+
+ Call Sign (when available)
+
+
+ Wallet Address
+
+
+
+ ) : (
+
+ {(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.isActive ? 'Active' : 'Inactive'}
+
+ {delegationInfo.isActive && delegationInfo.timeRemaining && (
+
+ {delegationInfo.timeRemaining} remaining
+
+ )}
+ {delegationInfo.needsRenewal && !delegationInfo.isExpired && (
+
+ Renewal Recommended
+
+ )}
+ {delegationInfo.isExpired && (
+
+ Expired
+
+ )}
+
+
+ {/* Delegation Details Grid */}
+
+
+
+ Browser Public Key
+
+
+ {currentUser.browserPubKey ? (
+ `${currentUser.browserPubKey.slice(0, 12)}...${currentUser.browserPubKey.slice(-8)}`
+ ) : 'Not delegated'}
+
+
+
+
+
+ Delegation Signature
+
+
+ {currentUser.delegationSignature === 'valid' ? (
+
+ Valid
+
+ ) : (
+ 'Not signed'
+ )}
+
+
+
+ {currentUser.delegationExpiry && (
+
+
+ Expires At
+
+
+ {new Date(currentUser.delegationExpiry).toLocaleString()}
+
+
+ )}
+
+
+
+ Last Updated
+
+
+ {currentUser.lastChecked ?
+ new Date(currentUser.lastChecked).toLocaleString() :
+ 'Never'
+ }
+
+
+
+
+
+ Can Delegate
+
+
+ {delegationInfo.canDelegate ? (
+
+ Yes
+
+ ) : (
+
+ No
+
+ )}
+
+
+
+
+ {/* Delegation Actions */}
+ {delegationInfo.canDelegate && (
+
+ setWalletWizardOpen(true)}
+ >
+ {delegationInfo.isActive ? 'Renew Delegation' : 'Delegate Key'}
+
+
+ )}
+
+
+
+
+
+ {/* Action Buttons */}
+
+ {isEditing ? (
+ <>
+
+ Cancel
+
+
+ {isSubmitting && (
+
+ )}
+ Save Changes
+
+ >
+ ) : (
+ setIsEditing(true)}>Edit Profile
+ )}
+
+
+
+
+ {/* Wallet Wizard */}
+
setWalletWizardOpen(false)}
+ />
+
+ );
+}
diff --git a/src/types/waku.ts b/src/types/waku.ts
index 2061f4b..f5d3c96 100644
--- a/src/types/waku.ts
+++ b/src/types/waku.ts
@@ -1,4 +1,4 @@
-import { EDisplayPreference, EVerificationStatus } from "./identity";
+import { EDisplayPreference, EVerificationStatus } from './identity';
/**
* Message types for Waku communication
@@ -162,8 +162,8 @@ export interface UserIdentityCache {
ordinalDetails: string;
};
callSign?: string;
- displayPreference: EDisplayPreference
+ displayPreference: EDisplayPreference;
lastUpdated: number;
- verificationStatus: EVerificationStatus
+ verificationStatus: EVerificationStatus;
};
}