mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-07 15:23:05 +00:00
chore: move all storages to indexedDB
This commit is contained in:
parent
9e6be6156f
commit
aa17bda249
@ -4,6 +4,8 @@ import { useAuth, useNetworkStatus } from '@/hooks';
|
|||||||
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
||||||
import { EVerificationStatus } from '@/types/identity';
|
import { EVerificationStatus } from '@/types/identity';
|
||||||
import { useForum } from '@/contexts/useForum';
|
import { useForum } from '@/contexts/useForum';
|
||||||
|
import { localDatabase } from '@/lib/database/LocalDatabase';
|
||||||
|
import { DelegationFullStatus } from '@/lib/delegation';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -31,7 +33,8 @@ import { useUserDisplay } from '@/hooks';
|
|||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { verificationStatus } = useAuth();
|
const { verificationStatus } = useAuth();
|
||||||
const { getDelegationStatus } = useAuthContext();
|
const { getDelegationStatus } = useAuthContext();
|
||||||
const delegationInfo = getDelegationStatus();
|
const [delegationInfo, setDelegationInfo] =
|
||||||
|
useState<DelegationFullStatus | null>(null);
|
||||||
const networkStatus = useNetworkStatus();
|
const networkStatus = useNetworkStatus();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@ -57,28 +60,38 @@ const Header = () => {
|
|||||||
// ✅ Get display name from enhanced hook
|
// ✅ Get display name from enhanced hook
|
||||||
const { displayName } = useUserDisplay(address || '');
|
const { displayName } = useUserDisplay(address || '');
|
||||||
|
|
||||||
// Use sessionStorage to persist wizard state across navigation
|
// Load delegation status
|
||||||
const getHasShownWizard = () => {
|
React.useEffect(() => {
|
||||||
|
getDelegationStatus().then(setDelegationInfo).catch(console.error);
|
||||||
|
}, [getDelegationStatus]);
|
||||||
|
|
||||||
|
// Use LocalDatabase to persist wizard state across navigation
|
||||||
|
const getHasShownWizard = async (): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
return sessionStorage.getItem('hasShownWalletWizard') === 'true';
|
const value = await localDatabase.loadUIState('hasShownWalletWizard');
|
||||||
|
return value === true;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setHasShownWizard = (value: boolean) => {
|
const setHasShownWizard = async (value: boolean): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
sessionStorage.setItem('hasShownWalletWizard', value.toString());
|
await localDatabase.storeUIState('hasShownWalletWizard', value);
|
||||||
} catch {
|
} catch (e) {
|
||||||
// Fallback if sessionStorage is not available
|
console.error('Failed to store wizard state', e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Auto-open wizard when wallet connects for the first time
|
// Auto-open wizard when wallet connects for the first time
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isConnected && !getHasShownWizard()) {
|
if (isConnected) {
|
||||||
setWalletWizardOpen(true);
|
getHasShownWizard().then(hasShown => {
|
||||||
setHasShownWizard(true);
|
if (!hasShown) {
|
||||||
|
setWalletWizardOpen(true);
|
||||||
|
setHasShownWizard(true).catch(console.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [isConnected]);
|
}, [isConnected]);
|
||||||
|
|
||||||
@ -88,7 +101,7 @@ const Header = () => {
|
|||||||
|
|
||||||
const handleDisconnect = async () => {
|
const handleDisconnect = async () => {
|
||||||
await disconnect();
|
await disconnect();
|
||||||
setHasShownWizard(false); // Reset so wizard can show again on next connection
|
await setHasShownWizard(false); // Reset so wizard can show again on next connection
|
||||||
toast({
|
toast({
|
||||||
title: 'Wallet Disconnected',
|
title: 'Wallet Disconnected',
|
||||||
description: 'Your wallet has been disconnected successfully.',
|
description: 'Your wallet has been disconnected successfully.',
|
||||||
@ -99,7 +112,7 @@ const Header = () => {
|
|||||||
if (!isConnected) return 'Connect Wallet';
|
if (!isConnected) return 'Connect Wallet';
|
||||||
|
|
||||||
if (verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED) {
|
if (verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED) {
|
||||||
return delegationInfo.isValid ? 'Ready to Post' : 'Delegation Expired';
|
return delegationInfo?.isValid ? 'Ready to Post' : 'Delegation Expired';
|
||||||
} else if (verificationStatus === EVerificationStatus.WALLET_CONNECTED) {
|
} else if (verificationStatus === EVerificationStatus.WALLET_CONNECTED) {
|
||||||
return 'Verified (Read-only)';
|
return 'Verified (Read-only)';
|
||||||
} else {
|
} else {
|
||||||
@ -112,7 +125,7 @@ const Header = () => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED &&
|
verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED &&
|
||||||
delegationInfo.isValid
|
delegationInfo?.isValid
|
||||||
) {
|
) {
|
||||||
return 'text-green-400';
|
return 'text-green-400';
|
||||||
} else if (verificationStatus === EVerificationStatus.WALLET_CONNECTED) {
|
} else if (verificationStatus === EVerificationStatus.WALLET_CONNECTED) {
|
||||||
@ -131,7 +144,7 @@ const Header = () => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED &&
|
verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED &&
|
||||||
delegationInfo.isValid
|
delegationInfo?.isValid
|
||||||
) {
|
) {
|
||||||
return <CheckCircle className="w-4 h-4" />;
|
return <CheckCircle className="w-4 h-4" />;
|
||||||
} else if (verificationStatus === EVerificationStatus.WALLET_CONNECTED) {
|
} else if (verificationStatus === EVerificationStatus.WALLET_CONNECTED) {
|
||||||
@ -244,7 +257,7 @@ const Header = () => {
|
|||||||
<div className="text-xs">
|
<div className="text-xs">
|
||||||
<div>Address: {address?.slice(0, 8)}...</div>
|
<div>Address: {address?.slice(0, 8)}...</div>
|
||||||
<div>Status: {getAccountStatusText()}</div>
|
<div>Status: {getAccountStatusText()}</div>
|
||||||
{delegationInfo.timeRemaining && (
|
{delegationInfo?.timeRemaining && (
|
||||||
<div>
|
<div>
|
||||||
Delegation: {delegationInfo.timeRemaining} remaining
|
Delegation: {delegationInfo.timeRemaining} remaining
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
useForumData,
|
useForumData,
|
||||||
useAuth,
|
useAuth,
|
||||||
@ -10,6 +11,7 @@ import {
|
|||||||
useForumSelectors,
|
useForumSelectors,
|
||||||
} from '@/hooks';
|
} from '@/hooks';
|
||||||
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
||||||
|
import { DelegationFullStatus } from '@/lib/delegation';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -24,6 +26,12 @@ export function HookDemoComponent() {
|
|||||||
const forumData = useForumData();
|
const forumData = useForumData();
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const { getDelegationStatus } = useAuthContext();
|
const { getDelegationStatus } = useAuthContext();
|
||||||
|
const [delegationStatus, setDelegationStatus] = useState<DelegationFullStatus | null>(null);
|
||||||
|
|
||||||
|
// Load delegation status
|
||||||
|
useEffect(() => {
|
||||||
|
getDelegationStatus().then(setDelegationStatus).catch(console.error);
|
||||||
|
}, [getDelegationStatus]);
|
||||||
|
|
||||||
// Derived hooks for specific data
|
// Derived hooks for specific data
|
||||||
const userVotes = useUserVotes();
|
const userVotes = useUserVotes();
|
||||||
@ -137,7 +145,7 @@ export function HookDemoComponent() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Delegation Active:</strong>{' '}
|
<strong>Delegation Active:</strong>{' '}
|
||||||
{getDelegationStatus().isValid ? 'Yes' : 'No'}
|
{delegationStatus?.isValid ? 'Yes' : 'No'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Button } from './button';
|
import { Button } from './button';
|
||||||
import { useAuth, useAuthActions } from '@/hooks';
|
import { useAuth, useAuthActions } from '@/hooks';
|
||||||
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
||||||
import { CheckCircle, AlertCircle, Trash2 } from 'lucide-react';
|
import { CheckCircle, AlertCircle, Trash2 } from 'lucide-react';
|
||||||
import { DelegationDuration } from '@/lib/delegation';
|
import { DelegationDuration, DelegationFullStatus } from '@/lib/delegation';
|
||||||
|
|
||||||
interface DelegationStepProps {
|
interface DelegationStepProps {
|
||||||
onComplete: () => void;
|
onComplete: () => void;
|
||||||
@ -20,9 +20,15 @@ export function DelegationStep({
|
|||||||
}: DelegationStepProps) {
|
}: DelegationStepProps) {
|
||||||
const { currentUser, isAuthenticating } = useAuth();
|
const { currentUser, isAuthenticating } = useAuth();
|
||||||
const { getDelegationStatus } = useAuthContext();
|
const { getDelegationStatus } = useAuthContext();
|
||||||
const delegationInfo = getDelegationStatus();
|
const [delegationInfo, setDelegationInfo] =
|
||||||
|
useState<DelegationFullStatus | null>(null);
|
||||||
const { delegateKey, clearDelegation } = useAuthActions();
|
const { delegateKey, clearDelegation } = useAuthActions();
|
||||||
|
|
||||||
|
// Load delegation status
|
||||||
|
useEffect(() => {
|
||||||
|
getDelegationStatus().then(setDelegationInfo).catch(console.error);
|
||||||
|
}, [getDelegationStatus]);
|
||||||
|
|
||||||
const [selectedDuration, setSelectedDuration] =
|
const [selectedDuration, setSelectedDuration] =
|
||||||
React.useState<DelegationDuration>('7days');
|
React.useState<DelegationDuration>('7days');
|
||||||
const [delegationResult, setDelegationResult] = React.useState<{
|
const [delegationResult, setDelegationResult] = React.useState<{
|
||||||
@ -128,19 +134,19 @@ export function DelegationStep({
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Status */}
|
{/* Status */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{delegationInfo.isValid ? (
|
{delegationInfo?.isValid ? (
|
||||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||||
) : (
|
) : (
|
||||||
<AlertCircle className="h-4 w-4 text-yellow-500" />
|
<AlertCircle className="h-4 w-4 text-yellow-500" />
|
||||||
)}
|
)}
|
||||||
<span
|
<span
|
||||||
className={`text-sm font-medium ${
|
className={`text-sm font-medium ${
|
||||||
delegationInfo.isValid ? 'text-green-400' : 'text-yellow-400'
|
delegationInfo?.isValid ? 'text-green-400' : 'text-yellow-400'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{delegationInfo.isValid ? 'Delegated' : 'Required'}
|
{delegationInfo?.isValid ? 'Delegated' : 'Required'}
|
||||||
</span>
|
</span>
|
||||||
{delegationInfo.isValid && delegationInfo.timeRemaining && (
|
{delegationInfo?.isValid && delegationInfo?.timeRemaining && (
|
||||||
<span className="text-xs text-neutral-400">
|
<span className="text-xs text-neutral-400">
|
||||||
{delegationInfo.timeRemaining} remaining
|
{delegationInfo.timeRemaining} remaining
|
||||||
</span>
|
</span>
|
||||||
@ -148,7 +154,7 @@ export function DelegationStep({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Duration Selection */}
|
{/* Duration Selection */}
|
||||||
{!delegationInfo.isValid && (
|
{!delegationInfo?.isValid && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<label className="text-sm font-medium text-neutral-300">
|
<label className="text-sm font-medium text-neutral-300">
|
||||||
Delegation Duration:
|
Delegation Duration:
|
||||||
@ -187,7 +193,7 @@ export function DelegationStep({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Delegated Browser Public Key */}
|
{/* Delegated Browser Public Key */}
|
||||||
{delegationInfo.isValid && currentUser?.browserPubKey && (
|
{delegationInfo?.isValid && currentUser?.browserPubKey && (
|
||||||
<div className="text-xs text-neutral-400">
|
<div className="text-xs text-neutral-400">
|
||||||
<div className="font-mono break-all bg-neutral-800 p-2 rounded">
|
<div className="font-mono break-all bg-neutral-800 p-2 rounded">
|
||||||
{currentUser.browserPubKey}
|
{currentUser.browserPubKey}
|
||||||
@ -203,7 +209,7 @@ export function DelegationStep({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Delete Button for Active Delegations */}
|
{/* Delete Button for Active Delegations */}
|
||||||
{delegationInfo.isValid && (
|
{delegationInfo?.isValid && (
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button
|
<Button
|
||||||
onClick={clearDelegation}
|
onClick={clearDelegation}
|
||||||
@ -221,7 +227,7 @@ export function DelegationStep({
|
|||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
<div className="mt-auto space-y-2">
|
<div className="mt-auto space-y-2">
|
||||||
{delegationInfo.isValid ? (
|
{delegationInfo?.isValid ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleComplete}
|
onClick={handleComplete}
|
||||||
className="w-full bg-green-600 hover:bg-green-700 text-white"
|
className="w-full bg-green-600 hover:bg-green-700 text-white"
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { CheckCircle, Circle, Loader2 } from 'lucide-react';
|
|||||||
import { useAuth } from '@/hooks';
|
import { useAuth } from '@/hooks';
|
||||||
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
||||||
import { EVerificationStatus } from '@/types/identity';
|
import { EVerificationStatus } from '@/types/identity';
|
||||||
|
import { DelegationFullStatus } from '@/lib/delegation';
|
||||||
import { WalletConnectionStep } from './wallet-connection-step';
|
import { WalletConnectionStep } from './wallet-connection-step';
|
||||||
import { VerificationStep } from './verification-step';
|
import { VerificationStep } from './verification-step';
|
||||||
import { DelegationStep } from './delegation-step';
|
import { DelegationStep } from './delegation-step';
|
||||||
@ -32,9 +33,15 @@ export function WalletWizard({
|
|||||||
const [isLoading, setIsLoading] = React.useState(false);
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
const { isAuthenticated, verificationStatus } = useAuth();
|
const { isAuthenticated, verificationStatus } = useAuth();
|
||||||
const { getDelegationStatus } = useAuthContext();
|
const { getDelegationStatus } = useAuthContext();
|
||||||
const delegationInfo = getDelegationStatus();
|
const [delegationInfo, setDelegationInfo] =
|
||||||
|
React.useState<DelegationFullStatus | null>(null);
|
||||||
const hasInitialized = React.useRef(false);
|
const hasInitialized = React.useRef(false);
|
||||||
|
|
||||||
|
// Load delegation status
|
||||||
|
React.useEffect(() => {
|
||||||
|
getDelegationStatus().then(setDelegationInfo).catch(console.error);
|
||||||
|
}, [getDelegationStatus]);
|
||||||
|
|
||||||
// Reset wizard when opened and determine starting step
|
// Reset wizard when opened and determine starting step
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (open && !hasInitialized.current) {
|
if (open && !hasInitialized.current) {
|
||||||
@ -50,7 +57,7 @@ export function WalletWizard({
|
|||||||
isAuthenticated &&
|
isAuthenticated &&
|
||||||
(verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED ||
|
(verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED ||
|
||||||
verificationStatus === EVerificationStatus.WALLET_CONNECTED) &&
|
verificationStatus === EVerificationStatus.WALLET_CONNECTED) &&
|
||||||
!delegationInfo.isValid
|
!delegationInfo?.isValid
|
||||||
) {
|
) {
|
||||||
setCurrentStep(3); // Start at delegation step if verified but no valid delegation
|
setCurrentStep(3); // Start at delegation step if verified but no valid delegation
|
||||||
} else {
|
} else {
|
||||||
@ -92,7 +99,7 @@ export function WalletWizard({
|
|||||||
) {
|
) {
|
||||||
return 'disabled';
|
return 'disabled';
|
||||||
}
|
}
|
||||||
return delegationInfo.isValid ? 'complete' : 'current';
|
return delegationInfo?.isValid ? 'complete' : 'current';
|
||||||
}
|
}
|
||||||
return 'disabled';
|
return 'disabled';
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
DelegationDuration,
|
DelegationDuration,
|
||||||
DelegationFullStatus,
|
DelegationFullStatus,
|
||||||
} from '@/lib/delegation';
|
} from '@/lib/delegation';
|
||||||
|
import { localDatabase } from '@/lib/database/LocalDatabase';
|
||||||
import { useAppKitAccount, useDisconnect, modal } from '@reown/appkit/react';
|
import { useAppKitAccount, useDisconnect, modal } from '@reown/appkit/react';
|
||||||
|
|
||||||
// Removed VerificationStatus type - using EVerificationStatus enum directly
|
// Removed VerificationStatus type - using EVerificationStatus enum directly
|
||||||
@ -25,8 +26,8 @@ interface AuthContextType {
|
|||||||
disconnectWallet: () => void;
|
disconnectWallet: () => void;
|
||||||
verifyOwnership: () => Promise<boolean>;
|
verifyOwnership: () => Promise<boolean>;
|
||||||
delegateKey: (duration?: DelegationDuration) => Promise<boolean>;
|
delegateKey: (duration?: DelegationDuration) => Promise<boolean>;
|
||||||
getDelegationStatus: () => DelegationFullStatus;
|
getDelegationStatus: () => Promise<DelegationFullStatus>;
|
||||||
clearDelegation: () => void;
|
clearDelegation: () => Promise<void>;
|
||||||
signMessage: (message: OpchanMessage) => Promise<OpchanMessage | null>;
|
signMessage: (message: OpchanMessage) => Promise<OpchanMessage | null>;
|
||||||
verifyMessage: (message: OpchanMessage) => Promise<boolean>;
|
verifyMessage: (message: OpchanMessage) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
@ -73,30 +74,21 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
}, [bitcoinAccount, ethereumAccount]);
|
}, [bitcoinAccount, ethereumAccount]);
|
||||||
|
|
||||||
// Helper functions for user persistence
|
// Helper functions for user persistence
|
||||||
const loadStoredUser = (): User | null => {
|
const loadStoredUser = async (): Promise<User | null> => {
|
||||||
const storedUser = localStorage.getItem('opchan-user');
|
|
||||||
if (!storedUser) return null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = JSON.parse(storedUser);
|
return await localDatabase.loadUser();
|
||||||
const lastChecked = user.lastChecked || 0;
|
|
||||||
const expiryTime = 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
if (Date.now() - lastChecked < expiryTime) {
|
|
||||||
return user;
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem('opchan-user');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse stored user data', e);
|
console.error('Failed to load stored user data', e);
|
||||||
localStorage.removeItem('opchan-user');
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveUser = (user: User): void => {
|
const saveUser = async (user: User): Promise<void> => {
|
||||||
localStorage.setItem('opchan-user', JSON.stringify(user));
|
try {
|
||||||
|
await localDatabase.storeUser(user);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to save user data', e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function for ownership verification
|
// Helper function for ownership verification
|
||||||
@ -188,83 +180,83 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isConnected && address) {
|
if (isConnected && address) {
|
||||||
// Check if we have a stored user for this address
|
// Check if we have a stored user for this address
|
||||||
const storedUser = loadStoredUser();
|
loadStoredUser().then(async storedUser => {
|
||||||
|
if (storedUser && storedUser.address === address) {
|
||||||
|
// Use stored user data
|
||||||
|
setCurrentUser(storedUser);
|
||||||
|
setVerificationStatus(getVerificationStatus(storedUser));
|
||||||
|
} else {
|
||||||
|
// Create new user from AppKit wallet
|
||||||
|
const newUser: User = {
|
||||||
|
address,
|
||||||
|
walletType: isBitcoinConnected ? 'bitcoin' : 'ethereum',
|
||||||
|
verificationStatus: EVerificationStatus.WALLET_CONNECTED, // Connected wallets get basic verification by default
|
||||||
|
displayPreference: EDisplayPreference.WALLET_ADDRESS,
|
||||||
|
lastChecked: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
if (storedUser && storedUser.address === address) {
|
// For Ethereum wallets, try to check ENS ownership immediately
|
||||||
// Use stored user data
|
if (isEthereumConnected) {
|
||||||
setCurrentUser(storedUser);
|
try {
|
||||||
setVerificationStatus(getVerificationStatus(storedUser));
|
const walletManager = WalletManager.getInstance();
|
||||||
} else {
|
walletManager
|
||||||
// Create new user from AppKit wallet
|
.getWalletInfo()
|
||||||
const newUser: User = {
|
.then(async walletInfo => {
|
||||||
address,
|
if (walletInfo?.ensName) {
|
||||||
walletType: isBitcoinConnected ? 'bitcoin' : 'ethereum',
|
const updatedUser = {
|
||||||
verificationStatus: EVerificationStatus.WALLET_CONNECTED, // Connected wallets get basic verification by default
|
...newUser,
|
||||||
displayPreference: EDisplayPreference.WALLET_ADDRESS,
|
ensDetails: { ensName: walletInfo.ensName },
|
||||||
lastChecked: Date.now(),
|
verificationStatus:
|
||||||
};
|
EVerificationStatus.ENS_ORDINAL_VERIFIED,
|
||||||
|
};
|
||||||
// For Ethereum wallets, try to check ENS ownership immediately
|
setCurrentUser(updatedUser);
|
||||||
if (isEthereumConnected) {
|
setVerificationStatus(
|
||||||
try {
|
EVerificationStatus.ENS_ORDINAL_VERIFIED
|
||||||
const walletManager = WalletManager.getInstance();
|
);
|
||||||
walletManager
|
await saveUser(updatedUser);
|
||||||
.getWalletInfo()
|
} else {
|
||||||
.then(walletInfo => {
|
setCurrentUser(newUser);
|
||||||
if (walletInfo?.ensName) {
|
setVerificationStatus(EVerificationStatus.WALLET_CONNECTED);
|
||||||
const updatedUser = {
|
await saveUser(newUser);
|
||||||
...newUser,
|
}
|
||||||
ensDetails: { ensName: walletInfo.ensName },
|
})
|
||||||
verificationStatus:
|
.catch(async () => {
|
||||||
EVerificationStatus.ENS_ORDINAL_VERIFIED,
|
// Fallback to basic verification if ENS check fails
|
||||||
};
|
|
||||||
setCurrentUser(updatedUser);
|
|
||||||
setVerificationStatus(
|
|
||||||
EVerificationStatus.ENS_ORDINAL_VERIFIED
|
|
||||||
);
|
|
||||||
saveUser(updatedUser);
|
|
||||||
} else {
|
|
||||||
setCurrentUser(newUser);
|
setCurrentUser(newUser);
|
||||||
setVerificationStatus(EVerificationStatus.WALLET_CONNECTED);
|
setVerificationStatus(EVerificationStatus.WALLET_CONNECTED);
|
||||||
saveUser(newUser);
|
await saveUser(newUser);
|
||||||
}
|
});
|
||||||
})
|
} catch {
|
||||||
.catch(() => {
|
// WalletManager not ready, fallback to basic verification
|
||||||
// Fallback to basic verification if ENS check fails
|
setCurrentUser(newUser);
|
||||||
setCurrentUser(newUser);
|
setVerificationStatus(EVerificationStatus.WALLET_CONNECTED);
|
||||||
setVerificationStatus(EVerificationStatus.WALLET_CONNECTED);
|
await saveUser(newUser);
|
||||||
saveUser(newUser);
|
}
|
||||||
});
|
} else {
|
||||||
} catch {
|
|
||||||
// WalletManager not ready, fallback to basic verification
|
|
||||||
setCurrentUser(newUser);
|
setCurrentUser(newUser);
|
||||||
setVerificationStatus(EVerificationStatus.WALLET_CONNECTED);
|
setVerificationStatus(EVerificationStatus.WALLET_CONNECTED);
|
||||||
saveUser(newUser);
|
await saveUser(newUser);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setCurrentUser(newUser);
|
const chainName = isBitcoinConnected ? 'Bitcoin' : 'Ethereum';
|
||||||
setVerificationStatus(EVerificationStatus.WALLET_CONNECTED);
|
// Note: We can't use useUserDisplay hook here since this is not a React component
|
||||||
saveUser(newUser);
|
// This is just for toast messages, so simple truncation is acceptable
|
||||||
|
const displayName = `${address.slice(0, 6)}...${address.slice(-4)}`;
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: 'Wallet Connected',
|
||||||
|
description: `Connected to ${chainName} with ${displayName}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const verificationType = isBitcoinConnected
|
||||||
|
? 'Ordinal ownership'
|
||||||
|
: 'ENS ownership';
|
||||||
|
toast({
|
||||||
|
title: 'Action Required',
|
||||||
|
description: `You can participate in the forum now! Verify your ${verificationType} for premium features and delegate a signing key for better UX.`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
const chainName = isBitcoinConnected ? 'Bitcoin' : 'Ethereum';
|
|
||||||
// Note: We can't use useUserDisplay hook here since this is not a React component
|
|
||||||
// This is just for toast messages, so simple truncation is acceptable
|
|
||||||
const displayName = `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: 'Wallet Connected',
|
|
||||||
description: `Connected to ${chainName} with ${displayName}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const verificationType = isBitcoinConnected
|
|
||||||
? 'Ordinal ownership'
|
|
||||||
: 'ENS ownership';
|
|
||||||
toast({
|
|
||||||
title: 'Action Required',
|
|
||||||
description: `You can participate in the forum now! Verify your ${verificationType} for premium features and delegate a signing key for better UX.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Wallet disconnected
|
// Wallet disconnected
|
||||||
setCurrentUser(null);
|
setCurrentUser(null);
|
||||||
@ -327,7 +319,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const updatedUser = await verifyUserOwnership(currentUser);
|
const updatedUser = await verifyUserOwnership(currentUser);
|
||||||
setCurrentUser(updatedUser);
|
setCurrentUser(updatedUser);
|
||||||
saveUser(updatedUser);
|
await saveUser(updatedUser);
|
||||||
|
|
||||||
// Update verification status
|
// Update verification status
|
||||||
setVerificationStatus(getVerificationStatus(updatedUser));
|
setVerificationStatus(getVerificationStatus(updatedUser));
|
||||||
@ -411,7 +403,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update user with delegation info
|
// Update user with delegation info
|
||||||
const delegationStatus = delegationManager.getStatus(
|
const delegationStatus = await delegationManager.getStatus(
|
||||||
currentUser.address,
|
currentUser.address,
|
||||||
currentUser.walletType
|
currentUser.walletType
|
||||||
);
|
);
|
||||||
@ -426,7 +418,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setCurrentUser(updatedUser);
|
setCurrentUser(updatedUser);
|
||||||
saveUser(updatedUser);
|
await saveUser(updatedUser);
|
||||||
|
|
||||||
// Format date for user-friendly display
|
// Format date for user-friendly display
|
||||||
const expiryDate = new Date(updatedUser.delegationExpiry!);
|
const expiryDate = new Date(updatedUser.delegationExpiry!);
|
||||||
@ -458,15 +450,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDelegationStatus = (): DelegationFullStatus => {
|
const getDelegationStatus = async (): Promise<DelegationFullStatus> => {
|
||||||
return delegationManager.getStatus(
|
return await delegationManager.getStatus(
|
||||||
currentUser?.address,
|
currentUser?.address,
|
||||||
currentUser?.walletType
|
currentUser?.walletType
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearDelegation = (): void => {
|
const clearDelegation = async (): Promise<void> => {
|
||||||
delegationManager.clear();
|
await delegationManager.clear();
|
||||||
|
|
||||||
// Update the current user to remove delegation info
|
// Update the current user to remove delegation info
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
@ -476,7 +468,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
browserPublicKey: undefined,
|
browserPublicKey: undefined,
|
||||||
};
|
};
|
||||||
setCurrentUser(updatedUser);
|
setCurrentUser(updatedUser);
|
||||||
saveUser(updatedUser);
|
await saveUser(updatedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
@ -490,7 +482,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
signMessage: async (
|
signMessage: async (
|
||||||
message: OpchanMessage
|
message: OpchanMessage
|
||||||
): Promise<OpchanMessage | null> => {
|
): Promise<OpchanMessage | null> => {
|
||||||
return delegationManager.signMessage(message);
|
return await delegationManager.signMessage(message);
|
||||||
},
|
},
|
||||||
verifyMessage: async (message: OpchanMessage): Promise<boolean> => {
|
verifyMessage: async (message: OpchanMessage): Promise<boolean> => {
|
||||||
return await delegationManager.verify(message);
|
return await delegationManager.verify(message);
|
||||||
|
|||||||
@ -245,7 +245,7 @@ export function useAuthActions(): AuthActions {
|
|||||||
|
|
||||||
// Clear delegation
|
// Clear delegation
|
||||||
const clearDelegation = useCallback(async (): Promise<boolean> => {
|
const clearDelegation = useCallback(async (): Promise<boolean> => {
|
||||||
const delegationInfo = getDelegationStatus();
|
const delegationInfo = await getDelegationStatus();
|
||||||
if (!delegationInfo.isValid) {
|
if (!delegationInfo.isValid) {
|
||||||
toast({
|
toast({
|
||||||
title: 'No Active Delegation',
|
title: 'No Active Delegation',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useContext, useMemo } from 'react';
|
import { useCallback, useContext, useState, useEffect } from 'react';
|
||||||
import { AuthContext } from '@/contexts/AuthContext';
|
import { AuthContext } from '@/contexts/AuthContext';
|
||||||
import { DelegationDuration } from '@/lib/delegation';
|
import { DelegationDuration } from '@/lib/delegation';
|
||||||
|
|
||||||
@ -27,20 +27,36 @@ export const useDelegation = () => {
|
|||||||
contextClearDelegation();
|
contextClearDelegation();
|
||||||
}, [contextClearDelegation]);
|
}, [contextClearDelegation]);
|
||||||
|
|
||||||
const delegationStatus = useMemo(() => {
|
const [delegationStatus, setDelegationStatus] = useState<{
|
||||||
const status = contextGetDelegationStatus();
|
hasDelegation: boolean;
|
||||||
|
isValid: boolean;
|
||||||
|
timeRemaining?: number;
|
||||||
|
expiresAt?: Date;
|
||||||
|
publicKey?: string;
|
||||||
|
address?: string;
|
||||||
|
walletType?: 'bitcoin' | 'ethereum';
|
||||||
|
}>({
|
||||||
|
hasDelegation: false,
|
||||||
|
isValid: false,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
// Load delegation status
|
||||||
hasDelegation: status.hasDelegation,
|
useEffect(() => {
|
||||||
isValid: status.isValid,
|
contextGetDelegationStatus()
|
||||||
timeRemaining: status.timeRemaining,
|
.then(status => {
|
||||||
expiresAt: status.timeRemaining
|
setDelegationStatus({
|
||||||
? new Date(Date.now() + status.timeRemaining)
|
hasDelegation: status.hasDelegation,
|
||||||
: undefined,
|
isValid: status.isValid,
|
||||||
publicKey: status.publicKey,
|
timeRemaining: status.timeRemaining,
|
||||||
address: status.address,
|
expiresAt: status.timeRemaining
|
||||||
walletType: status.walletType,
|
? new Date(Date.now() + status.timeRemaining)
|
||||||
};
|
: undefined,
|
||||||
|
publicKey: status.publicKey,
|
||||||
|
address: status.address,
|
||||||
|
walletType: status.walletType,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
}, [contextGetDelegationStatus]);
|
}, [contextGetDelegationStatus]);
|
||||||
|
|
||||||
const formatTimeRemaining = useCallback((timeMs: number): string => {
|
const formatTimeRemaining = useCallback((timeMs: number): string => {
|
||||||
|
|||||||
@ -18,7 +18,8 @@ export const useMessageSigning = () => {
|
|||||||
const signMessage = useCallback(
|
const signMessage = useCallback(
|
||||||
async (message: OpchanMessage): Promise<OpchanMessage | null> => {
|
async (message: OpchanMessage): Promise<OpchanMessage | null> => {
|
||||||
// Check if we have a valid delegation before attempting to sign
|
// Check if we have a valid delegation before attempting to sign
|
||||||
if (!getDelegationStatus().isValid) {
|
const delegationStatus = await getDelegationStatus();
|
||||||
|
if (!delegationStatus.isValid) {
|
||||||
console.warn('No valid delegation found. Cannot sign message.');
|
console.warn('No valid delegation found. Cannot sign message.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -35,8 +36,9 @@ export const useMessageSigning = () => {
|
|||||||
[contextVerifyMessage]
|
[contextVerifyMessage]
|
||||||
);
|
);
|
||||||
|
|
||||||
const canSignMessages = useCallback((): boolean => {
|
const canSignMessages = useCallback(async (): Promise<boolean> => {
|
||||||
return getDelegationStatus().isValid;
|
const delegationStatus = await getDelegationStatus();
|
||||||
|
return delegationStatus.isValid;
|
||||||
}, [getDelegationStatus]);
|
}, [getDelegationStatus]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo, useState, useEffect } from 'react';
|
||||||
import { useForum } from '@/contexts/useForum';
|
import { useForum } from '@/contexts/useForum';
|
||||||
import { useAuth } from '@/hooks/core/useAuth';
|
import { useAuth } from '@/hooks/core/useAuth';
|
||||||
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
||||||
|
import { DelegationFullStatus } from '@/lib/delegation';
|
||||||
|
|
||||||
export interface NetworkHealth {
|
export interface NetworkHealth {
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
@ -64,7 +65,13 @@ export function useNetworkStatus(): NetworkStatusData {
|
|||||||
|
|
||||||
const { isAuthenticated, currentUser } = useAuth();
|
const { isAuthenticated, currentUser } = useAuth();
|
||||||
const { getDelegationStatus } = useAuthContext();
|
const { getDelegationStatus } = useAuthContext();
|
||||||
const delegationInfo = getDelegationStatus();
|
const [delegationInfo, setDelegationInfo] =
|
||||||
|
useState<DelegationFullStatus | null>(null);
|
||||||
|
|
||||||
|
// Load delegation status
|
||||||
|
useEffect(() => {
|
||||||
|
getDelegationStatus().then(setDelegationInfo).catch(console.error);
|
||||||
|
}, [getDelegationStatus]);
|
||||||
|
|
||||||
// Network health assessment
|
// Network health assessment
|
||||||
const health = useMemo((): NetworkHealth => {
|
const health = useMemo((): NetworkHealth => {
|
||||||
@ -78,7 +85,7 @@ export function useNetworkStatus(): NetworkStatusData {
|
|||||||
issues.push(`Forum error: ${error}`);
|
issues.push(`Forum error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAuthenticated && !delegationInfo.isValid) {
|
if (isAuthenticated && !delegationInfo?.isValid) {
|
||||||
issues.push('Key delegation expired');
|
issues.push('Key delegation expired');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +100,7 @@ export function useNetworkStatus(): NetworkStatusData {
|
|||||||
syncAge,
|
syncAge,
|
||||||
issues,
|
issues,
|
||||||
};
|
};
|
||||||
}, [isNetworkConnected, error, isAuthenticated, delegationInfo.isValid]);
|
}, [isNetworkConnected, error, isAuthenticated, delegationInfo?.isValid]);
|
||||||
|
|
||||||
// Sync status
|
// Sync status
|
||||||
const sync = useMemo((): SyncStatus => {
|
const sync = useMemo((): SyncStatus => {
|
||||||
@ -124,9 +131,9 @@ export function useNetworkStatus(): NetworkStatusData {
|
|||||||
status: isAuthenticated ? 'connected' : 'disconnected',
|
status: isAuthenticated ? 'connected' : 'disconnected',
|
||||||
},
|
},
|
||||||
delegation: {
|
delegation: {
|
||||||
active: delegationInfo.isValid,
|
active: delegationInfo?.isValid || false,
|
||||||
expires: delegationInfo.timeRemaining || null,
|
expires: delegationInfo?.timeRemaining || null,
|
||||||
status: delegationInfo.isValid ? 'active' : 'expired',
|
status: delegationInfo?.isValid ? 'active' : 'expired',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [isNetworkConnected, isAuthenticated, currentUser, delegationInfo]);
|
}, [isNetworkConnected, isAuthenticated, currentUser, delegationInfo]);
|
||||||
@ -134,7 +141,7 @@ export function useNetworkStatus(): NetworkStatusData {
|
|||||||
// Status assessment
|
// Status assessment
|
||||||
const canRefresh = !isRefreshing && !isInitialLoading;
|
const canRefresh = !isRefreshing && !isInitialLoading;
|
||||||
const canSync = isNetworkConnected && !isRefreshing;
|
const canSync = isNetworkConnected && !isRefreshing;
|
||||||
const needsAttention = !health.isHealthy || !delegationInfo.isValid;
|
const needsAttention = !health.isHealthy || !delegationInfo?.isValid;
|
||||||
|
|
||||||
// Helper methods
|
// Helper methods
|
||||||
const getStatusMessage = useMemo(() => {
|
const getStatusMessage = useMemo(() => {
|
||||||
@ -157,10 +164,15 @@ export function useNetworkStatus(): NetworkStatusData {
|
|||||||
const getHealthColor = useMemo(() => {
|
const getHealthColor = useMemo(() => {
|
||||||
return (): 'green' | 'yellow' | 'red' => {
|
return (): 'green' | 'yellow' | 'red' => {
|
||||||
if (!isNetworkConnected || error) return 'red';
|
if (!isNetworkConnected || error) return 'red';
|
||||||
if (health.issues.length > 0 || !delegationInfo.isValid) return 'yellow';
|
if (health.issues.length > 0 || !delegationInfo?.isValid) return 'yellow';
|
||||||
return 'green';
|
return 'green';
|
||||||
};
|
};
|
||||||
}, [isNetworkConnected, error, health.issues.length, delegationInfo.isValid]);
|
}, [
|
||||||
|
isNetworkConnected,
|
||||||
|
error,
|
||||||
|
health.issues.length,
|
||||||
|
delegationInfo?.isValid,
|
||||||
|
]);
|
||||||
|
|
||||||
const getRecommendedActions = useMemo(() => {
|
const getRecommendedActions = useMemo(() => {
|
||||||
return (): string[] => {
|
return (): string[] => {
|
||||||
@ -175,13 +187,13 @@ export function useNetworkStatus(): NetworkStatusData {
|
|||||||
actions.push('Connect your wallet');
|
actions.push('Connect your wallet');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!delegationInfo.isValid) {
|
if (!delegationInfo?.isValid) {
|
||||||
actions.push('Renew key delegation');
|
actions.push('Renew key delegation');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
delegationInfo.isValid &&
|
delegationInfo?.isValid &&
|
||||||
delegationInfo.timeRemaining &&
|
delegationInfo?.timeRemaining &&
|
||||||
delegationInfo.timeRemaining < 3600
|
delegationInfo.timeRemaining < 3600
|
||||||
) {
|
) {
|
||||||
actions.push('Consider renewing key delegation soon');
|
actions.push('Consider renewing key delegation soon');
|
||||||
|
|||||||
@ -14,7 +14,8 @@ import {
|
|||||||
} from '@/types/waku';
|
} from '@/types/waku';
|
||||||
import { OpchanMessage } from '@/types/forum';
|
import { OpchanMessage } from '@/types/forum';
|
||||||
import { MessageValidator } from '@/lib/utils/MessageValidator';
|
import { MessageValidator } from '@/lib/utils/MessageValidator';
|
||||||
import { EVerificationStatus } from '@/types/identity';
|
import { EVerificationStatus, User } from '@/types/identity';
|
||||||
|
import { DelegationInfo } from '@/lib/delegation/types';
|
||||||
import { openLocalDB, STORE, StoreName } from '@/lib/database/schema';
|
import { openLocalDB, STORE, StoreName } from '@/lib/database/schema';
|
||||||
|
|
||||||
export interface LocalDatabaseCache {
|
export interface LocalDatabaseCache {
|
||||||
@ -277,6 +278,9 @@ export class LocalDatabase {
|
|||||||
| ModerateMessage
|
| ModerateMessage
|
||||||
| ({ address: string } & UserIdentityCache[string])
|
| ({ address: string } & UserIdentityCache[string])
|
||||||
| { key: string; value: unknown }
|
| { key: string; value: unknown }
|
||||||
|
| { key: string; value: User; timestamp: number }
|
||||||
|
| { key: string; value: DelegationInfo; timestamp: number }
|
||||||
|
| { key: string; value: unknown; timestamp: number }
|
||||||
): void {
|
): void {
|
||||||
if (!this.db) return;
|
if (!this.db) return;
|
||||||
const tx = this.db.transaction(storeName, 'readwrite');
|
const tx = this.db.transaction(storeName, 'readwrite');
|
||||||
@ -327,6 +331,159 @@ export class LocalDatabase {
|
|||||||
this.pendingListeners.add(listener);
|
this.pendingListeners.add(listener);
|
||||||
return () => this.pendingListeners.delete(listener);
|
return () => this.pendingListeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== User Authentication Storage =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store user authentication data
|
||||||
|
*/
|
||||||
|
public async storeUser(user: User): Promise<void> {
|
||||||
|
const userData = {
|
||||||
|
key: 'current',
|
||||||
|
value: user,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
this.put(STORE.USER_AUTH, userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load user authentication data
|
||||||
|
*/
|
||||||
|
public async loadUser(): Promise<User | null> {
|
||||||
|
if (!this.db) return null;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = this.db!.transaction(STORE.USER_AUTH, 'readonly');
|
||||||
|
const store = tx.objectStore(STORE.USER_AUTH);
|
||||||
|
const request = store.get('current');
|
||||||
|
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
request.onsuccess = () => {
|
||||||
|
const result = request.result as
|
||||||
|
| { key: string; value: User; timestamp: number }
|
||||||
|
| undefined;
|
||||||
|
if (!result) {
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = result.value;
|
||||||
|
const lastChecked = user.lastChecked || 0;
|
||||||
|
const expiryTime = 24 * 60 * 60 * 1000; // 24 hours
|
||||||
|
|
||||||
|
if (Date.now() - lastChecked < expiryTime) {
|
||||||
|
resolve(user);
|
||||||
|
} else {
|
||||||
|
// User data expired, clear it
|
||||||
|
this.clearUser();
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear user authentication data
|
||||||
|
*/
|
||||||
|
public async clearUser(): Promise<void> {
|
||||||
|
if (!this.db) return;
|
||||||
|
|
||||||
|
const tx = this.db.transaction(STORE.USER_AUTH, 'readwrite');
|
||||||
|
const store = tx.objectStore(STORE.USER_AUTH);
|
||||||
|
store.delete('current');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Delegation Storage =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store delegation information
|
||||||
|
*/
|
||||||
|
public async storeDelegation(delegation: DelegationInfo): Promise<void> {
|
||||||
|
const delegationData = {
|
||||||
|
key: 'current',
|
||||||
|
value: delegation,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
this.put(STORE.DELEGATION, delegationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load delegation information
|
||||||
|
*/
|
||||||
|
public async loadDelegation(): Promise<DelegationInfo | null> {
|
||||||
|
if (!this.db) return null;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = this.db!.transaction(STORE.DELEGATION, 'readonly');
|
||||||
|
const store = tx.objectStore(STORE.DELEGATION);
|
||||||
|
const request = store.get('current');
|
||||||
|
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
request.onsuccess = () => {
|
||||||
|
const result = request.result as
|
||||||
|
| { key: string; value: DelegationInfo; timestamp: number }
|
||||||
|
| undefined;
|
||||||
|
resolve(result?.value || null);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear delegation information
|
||||||
|
*/
|
||||||
|
public async clearDelegation(): Promise<void> {
|
||||||
|
if (!this.db) return;
|
||||||
|
|
||||||
|
const tx = this.db.transaction(STORE.DELEGATION, 'readwrite');
|
||||||
|
const store = tx.objectStore(STORE.DELEGATION);
|
||||||
|
store.delete('current');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== UI State Storage =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store UI state value
|
||||||
|
*/
|
||||||
|
public async storeUIState(key: string, value: unknown): Promise<void> {
|
||||||
|
const stateData = {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
this.put(STORE.UI_STATE, stateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load UI state value
|
||||||
|
*/
|
||||||
|
public async loadUIState(key: string): Promise<unknown> {
|
||||||
|
if (!this.db) return null;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = this.db!.transaction(STORE.UI_STATE, 'readonly');
|
||||||
|
const store = tx.objectStore(STORE.UI_STATE);
|
||||||
|
const request = store.get(key);
|
||||||
|
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
request.onsuccess = () => {
|
||||||
|
const result = request.result as
|
||||||
|
| { key: string; value: unknown; timestamp: number }
|
||||||
|
| undefined;
|
||||||
|
resolve(result?.value || null);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear UI state value
|
||||||
|
*/
|
||||||
|
public async clearUIState(key: string): Promise<void> {
|
||||||
|
if (!this.db) return;
|
||||||
|
|
||||||
|
const tx = this.db.transaction(STORE.UI_STATE, 'readwrite');
|
||||||
|
const store = tx.objectStore(STORE.UI_STATE);
|
||||||
|
store.delete(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const localDatabase = new LocalDatabase();
|
export const localDatabase = new LocalDatabase();
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export const DB_NAME = 'opchan-local';
|
export const DB_NAME = 'opchan-local';
|
||||||
export const DB_VERSION = 1;
|
export const DB_VERSION = 2;
|
||||||
|
|
||||||
export const STORE = {
|
export const STORE = {
|
||||||
CELLS: 'cells',
|
CELLS: 'cells',
|
||||||
@ -8,6 +8,9 @@ export const STORE = {
|
|||||||
VOTES: 'votes',
|
VOTES: 'votes',
|
||||||
MODERATIONS: 'moderations',
|
MODERATIONS: 'moderations',
|
||||||
USER_IDENTITIES: 'userIdentities',
|
USER_IDENTITIES: 'userIdentities',
|
||||||
|
USER_AUTH: 'userAuth',
|
||||||
|
DELEGATION: 'delegation',
|
||||||
|
UI_STATE: 'uiState',
|
||||||
META: 'meta',
|
META: 'meta',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -53,6 +56,18 @@ export function openLocalDB(): Promise<IDBDatabase> {
|
|||||||
// User identities keyed by address
|
// User identities keyed by address
|
||||||
db.createObjectStore(STORE.USER_IDENTITIES, { keyPath: 'address' });
|
db.createObjectStore(STORE.USER_IDENTITIES, { keyPath: 'address' });
|
||||||
}
|
}
|
||||||
|
if (!db.objectStoreNames.contains(STORE.USER_AUTH)) {
|
||||||
|
// User authentication data with single key 'current'
|
||||||
|
db.createObjectStore(STORE.USER_AUTH, { keyPath: 'key' });
|
||||||
|
}
|
||||||
|
if (!db.objectStoreNames.contains(STORE.DELEGATION)) {
|
||||||
|
// Key delegation information with single key 'current'
|
||||||
|
db.createObjectStore(STORE.DELEGATION, { keyPath: 'key' });
|
||||||
|
}
|
||||||
|
if (!db.objectStoreNames.contains(STORE.UI_STATE)) {
|
||||||
|
// UI state like wizard flags, preferences
|
||||||
|
db.createObjectStore(STORE.UI_STATE, { keyPath: 'key' });
|
||||||
|
}
|
||||||
if (!db.objectStoreNames.contains(STORE.META)) {
|
if (!db.objectStoreNames.contains(STORE.META)) {
|
||||||
// Misc metadata like lastSync timestamps
|
// Misc metadata like lastSync timestamps
|
||||||
db.createObjectStore(STORE.META, { keyPath: 'key' });
|
db.createObjectStore(STORE.META, { keyPath: 'key' });
|
||||||
|
|||||||
@ -72,7 +72,7 @@ export class DelegationManager {
|
|||||||
nonce,
|
nonce,
|
||||||
};
|
};
|
||||||
|
|
||||||
DelegationStorage.store(delegationInfo);
|
await DelegationStorage.store(delegationInfo);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating delegation:', error);
|
console.error('Error creating delegation:', error);
|
||||||
@ -83,13 +83,13 @@ export class DelegationManager {
|
|||||||
/**
|
/**
|
||||||
* Sign a message with delegated key
|
* Sign a message with delegated key
|
||||||
*/
|
*/
|
||||||
signMessage(message: UnsignedMessage): OpchanMessage | null {
|
async signMessage(message: UnsignedMessage): Promise<OpchanMessage | null> {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (
|
if (
|
||||||
!this.cachedDelegation ||
|
!this.cachedDelegation ||
|
||||||
now - this.cachedAt > DelegationManager.CACHE_TTL_MS
|
now - this.cachedAt > DelegationManager.CACHE_TTL_MS
|
||||||
) {
|
) {
|
||||||
this.cachedDelegation = DelegationStorage.retrieve();
|
this.cachedDelegation = await DelegationStorage.retrieve();
|
||||||
this.cachedAt = now;
|
this.cachedAt = now;
|
||||||
}
|
}
|
||||||
const delegation = this.cachedDelegation;
|
const delegation = this.cachedDelegation;
|
||||||
@ -162,16 +162,16 @@ export class DelegationManager {
|
|||||||
/**
|
/**
|
||||||
* Get delegation status
|
* Get delegation status
|
||||||
*/
|
*/
|
||||||
getStatus(
|
async getStatus(
|
||||||
currentAddress?: string,
|
currentAddress?: string,
|
||||||
currentWalletType?: 'bitcoin' | 'ethereum'
|
currentWalletType?: 'bitcoin' | 'ethereum'
|
||||||
): DelegationFullStatus {
|
): Promise<DelegationFullStatus> {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (
|
if (
|
||||||
!this.cachedDelegation ||
|
!this.cachedDelegation ||
|
||||||
now - this.cachedAt > DelegationManager.CACHE_TTL_MS
|
now - this.cachedAt > DelegationManager.CACHE_TTL_MS
|
||||||
) {
|
) {
|
||||||
this.cachedDelegation = DelegationStorage.retrieve();
|
this.cachedDelegation = await DelegationStorage.retrieve();
|
||||||
this.cachedAt = now;
|
this.cachedAt = now;
|
||||||
}
|
}
|
||||||
const delegation = this.cachedDelegation;
|
const delegation = this.cachedDelegation;
|
||||||
@ -202,8 +202,8 @@ export class DelegationManager {
|
|||||||
/**
|
/**
|
||||||
* Clear stored delegation
|
* Clear stored delegation
|
||||||
*/
|
*/
|
||||||
clear(): void {
|
async clear(): Promise<void> {
|
||||||
DelegationStorage.clear();
|
await DelegationStorage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@ -1,39 +1,35 @@
|
|||||||
import { LOCAL_STORAGE_KEYS } from '@/lib/waku/constants';
|
import { localDatabase } from '@/lib/database/LocalDatabase';
|
||||||
import { DelegationInfo } from './types';
|
import { DelegationInfo } from './types';
|
||||||
|
|
||||||
export class DelegationStorage {
|
export class DelegationStorage {
|
||||||
private static readonly STORAGE_KEY = LOCAL_STORAGE_KEYS.KEY_DELEGATION;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store delegation information in localStorage
|
* Store delegation information in IndexedDB
|
||||||
*/
|
*/
|
||||||
static store(delegation: DelegationInfo): void {
|
static async store(delegation: DelegationInfo): Promise<void> {
|
||||||
// Reduce verbose logging in production; keep minimal signal
|
// Reduce verbose logging in production; keep minimal signal
|
||||||
if (import.meta.env?.MODE !== 'production') {
|
if (import.meta.env?.MODE !== 'production') {
|
||||||
console.log('DelegationStorage.store');
|
console.log('DelegationStorage.store');
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem(
|
try {
|
||||||
DelegationStorage.STORAGE_KEY,
|
await localDatabase.storeDelegation(delegation);
|
||||||
JSON.stringify(delegation)
|
} catch (e) {
|
||||||
);
|
console.error('Failed to store delegation information', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve delegation information from localStorage
|
* Retrieve delegation information from IndexedDB
|
||||||
*/
|
*/
|
||||||
static retrieve(): DelegationInfo | null {
|
static async retrieve(): Promise<DelegationInfo | null> {
|
||||||
const delegationJson = localStorage.getItem(DelegationStorage.STORAGE_KEY);
|
|
||||||
if (!delegationJson) return null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const delegation = JSON.parse(delegationJson);
|
const delegation = await localDatabase.loadDelegation();
|
||||||
if (import.meta.env?.MODE !== 'production') {
|
if (import.meta.env?.MODE !== 'production') {
|
||||||
console.log('DelegationStorage.retrieve');
|
console.log('DelegationStorage.retrieve');
|
||||||
}
|
}
|
||||||
return delegation;
|
return delegation;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse delegation information', e);
|
console.error('Failed to retrieve delegation information', e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,7 +37,11 @@ export class DelegationStorage {
|
|||||||
/**
|
/**
|
||||||
* Clear stored delegation information
|
* Clear stored delegation information
|
||||||
*/
|
*/
|
||||||
static clear(): void {
|
static async clear(): Promise<void> {
|
||||||
localStorage.removeItem(DelegationStorage.STORAGE_KEY);
|
try {
|
||||||
|
await localDatabase.clearDelegation();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to clear delegation information', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -100,9 +100,9 @@ export class ForumActions {
|
|||||||
author: currentUser!.address, // Safe after validation
|
author: currentUser!.address, // Safe after validation
|
||||||
};
|
};
|
||||||
|
|
||||||
const signed = this.delegationManager.signMessage(unsignedPost);
|
const signed = await this.delegationManager.signMessage(unsignedPost);
|
||||||
if (!signed) {
|
if (!signed) {
|
||||||
const status = this.delegationManager.getStatus(
|
const status = await this.delegationManager.getStatus(
|
||||||
currentUser!.address,
|
currentUser!.address,
|
||||||
currentUser!.walletType
|
currentUser!.walletType
|
||||||
);
|
);
|
||||||
@ -169,9 +169,9 @@ export class ForumActions {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Optimistic path: sign locally, write to cache, mark pending, render immediately
|
// Optimistic path: sign locally, write to cache, mark pending, render immediately
|
||||||
const signed = this.delegationManager.signMessage(unsignedComment);
|
const signed = await this.delegationManager.signMessage(unsignedComment);
|
||||||
if (!signed) {
|
if (!signed) {
|
||||||
const status = this.delegationManager.getStatus(
|
const status = await this.delegationManager.getStatus(
|
||||||
currentUser!.address,
|
currentUser!.address,
|
||||||
currentUser!.walletType
|
currentUser!.walletType
|
||||||
);
|
);
|
||||||
@ -241,9 +241,9 @@ export class ForumActions {
|
|||||||
author: currentUser!.address,
|
author: currentUser!.address,
|
||||||
};
|
};
|
||||||
|
|
||||||
const signed = this.delegationManager.signMessage(unsignedCell);
|
const signed = await this.delegationManager.signMessage(unsignedCell);
|
||||||
if (!signed) {
|
if (!signed) {
|
||||||
const status = this.delegationManager.getStatus(
|
const status = await this.delegationManager.getStatus(
|
||||||
currentUser!.address,
|
currentUser!.address,
|
||||||
currentUser!.walletType
|
currentUser!.walletType
|
||||||
);
|
);
|
||||||
@ -313,9 +313,9 @@ export class ForumActions {
|
|||||||
author: currentUser!.address,
|
author: currentUser!.address,
|
||||||
};
|
};
|
||||||
|
|
||||||
const signed = this.delegationManager.signMessage(unsignedVote);
|
const signed = await this.delegationManager.signMessage(unsignedVote);
|
||||||
if (!signed) {
|
if (!signed) {
|
||||||
const status = this.delegationManager.getStatus(
|
const status = await this.delegationManager.getStatus(
|
||||||
currentUser!.address,
|
currentUser!.address,
|
||||||
currentUser!.walletType
|
currentUser!.walletType
|
||||||
);
|
);
|
||||||
@ -384,9 +384,9 @@ export class ForumActions {
|
|||||||
author: currentUser!.address,
|
author: currentUser!.address,
|
||||||
};
|
};
|
||||||
|
|
||||||
const signed = this.delegationManager.signMessage(unsignedMod);
|
const signed = await this.delegationManager.signMessage(unsignedMod);
|
||||||
if (!signed) {
|
if (!signed) {
|
||||||
const status = this.delegationManager.getStatus(
|
const status = await this.delegationManager.getStatus(
|
||||||
currentUser!.address,
|
currentUser!.address,
|
||||||
currentUser!.walletType
|
currentUser!.walletType
|
||||||
);
|
);
|
||||||
@ -457,9 +457,9 @@ export class ForumActions {
|
|||||||
author: currentUser!.address,
|
author: currentUser!.address,
|
||||||
};
|
};
|
||||||
|
|
||||||
const signed = this.delegationManager.signMessage(unsignedMod);
|
const signed = await this.delegationManager.signMessage(unsignedMod);
|
||||||
if (!signed) {
|
if (!signed) {
|
||||||
const status = this.delegationManager.getStatus(
|
const status = await this.delegationManager.getStatus(
|
||||||
currentUser!.address,
|
currentUser!.address,
|
||||||
currentUser!.walletType
|
currentUser!.walletType
|
||||||
);
|
);
|
||||||
@ -530,9 +530,9 @@ export class ForumActions {
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const signed = this.delegationManager.signMessage(unsignedMod);
|
const signed = await this.delegationManager.signMessage(unsignedMod);
|
||||||
if (!signed) {
|
if (!signed) {
|
||||||
const status = this.delegationManager.getStatus(
|
const status = await this.delegationManager.getStatus(
|
||||||
currentUser!.address,
|
currentUser!.address,
|
||||||
currentUser!.walletType
|
currentUser!.walletType
|
||||||
);
|
);
|
||||||
|
|||||||
@ -29,11 +29,11 @@ export class MessageService implements MessageServiceInterface {
|
|||||||
*/
|
*/
|
||||||
async sendMessage(message: UnsignedMessage): Promise<MessageResult> {
|
async sendMessage(message: UnsignedMessage): Promise<MessageResult> {
|
||||||
try {
|
try {
|
||||||
const signedMessage = this.delegationManager.signMessage(message);
|
const signedMessage = await this.delegationManager.signMessage(message);
|
||||||
|
|
||||||
if (!signedMessage) {
|
if (!signedMessage) {
|
||||||
// Check if delegation exists but is expired
|
// Check if delegation exists but is expired
|
||||||
const delegationStatus = this.delegationManager.getStatus();
|
const delegationStatus = await this.delegationManager.getStatus();
|
||||||
const isDelegationExpired = !delegationStatus.isValid;
|
const isDelegationExpired = !delegationStatus.isValid;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -87,7 +87,7 @@ export class MessageService implements MessageServiceInterface {
|
|||||||
message: UnsignedMessage
|
message: UnsignedMessage
|
||||||
): Promise<OpchanMessage | null> {
|
): Promise<OpchanMessage | null> {
|
||||||
try {
|
try {
|
||||||
const signedMessage = this.delegationManager.signMessage(message);
|
const signedMessage = await this.delegationManager.signMessage(message);
|
||||||
if (!signedMessage) {
|
if (!signedMessage) {
|
||||||
console.error('Failed to sign message');
|
console.error('Failed to sign message');
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -9,10 +9,17 @@ interface ValidationReport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MessageValidator {
|
export class MessageValidator {
|
||||||
private delegationManager: DelegationManager;
|
private delegationManager?: DelegationManager;
|
||||||
|
|
||||||
constructor(delegationManager?: DelegationManager) {
|
constructor(delegationManager?: DelegationManager) {
|
||||||
this.delegationManager = delegationManager || new DelegationManager();
|
this.delegationManager = delegationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDelegationManager(): DelegationManager {
|
||||||
|
if (!this.delegationManager) {
|
||||||
|
this.delegationManager = new DelegationManager();
|
||||||
|
}
|
||||||
|
return this.delegationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,7 +33,7 @@ export class MessageValidator {
|
|||||||
|
|
||||||
// Verify signature and delegation proof - we know it's safe to cast here since hasRequiredFields passed
|
// Verify signature and delegation proof - we know it's safe to cast here since hasRequiredFields passed
|
||||||
try {
|
try {
|
||||||
return await this.delegationManager.verify(
|
return await this.getDelegationManager().verify(
|
||||||
message as unknown as OpchanMessage
|
message as unknown as OpchanMessage
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
@ -80,7 +87,7 @@ export class MessageValidator {
|
|||||||
|
|
||||||
// Verify signature
|
// Verify signature
|
||||||
try {
|
try {
|
||||||
const isValid = await this.delegationManager.verify(
|
const isValid = await this.getDelegationManager().verify(
|
||||||
message as unknown as OpchanMessage
|
message as unknown as OpchanMessage
|
||||||
);
|
);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
@ -120,7 +127,7 @@ export class MessageValidator {
|
|||||||
|
|
||||||
// Verify signature and delegation proof
|
// Verify signature and delegation proof
|
||||||
try {
|
try {
|
||||||
const isValid = await this.delegationManager.verify(
|
const isValid = await this.getDelegationManager().verify(
|
||||||
message as unknown as OpchanMessage
|
message as unknown as OpchanMessage
|
||||||
);
|
);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { useAuth, useUserActions, useForumActions } from '@/hooks';
|
import { useAuth, useUserActions, useForumActions } from '@/hooks';
|
||||||
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
import { useAuth as useAuthContext } from '@/contexts/useAuth';
|
||||||
import { useUserDisplay } from '@/hooks';
|
import { useUserDisplay } from '@/hooks';
|
||||||
|
import { DelegationFullStatus } from '@/lib/delegation';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
@ -37,9 +38,15 @@ export default function ProfilePage() {
|
|||||||
// Get current user from auth context for the address
|
// Get current user from auth context for the address
|
||||||
const { currentUser } = useAuth();
|
const { currentUser } = useAuth();
|
||||||
const { getDelegationStatus } = useAuthContext();
|
const { getDelegationStatus } = useAuthContext();
|
||||||
const delegationInfo = getDelegationStatus();
|
const [delegationInfo, setDelegationInfo] =
|
||||||
|
useState<DelegationFullStatus | null>(null);
|
||||||
const address = currentUser?.address;
|
const address = currentUser?.address;
|
||||||
|
|
||||||
|
// Load delegation status
|
||||||
|
useEffect(() => {
|
||||||
|
getDelegationStatus().then(setDelegationInfo).catch(console.error);
|
||||||
|
}, [getDelegationStatus]);
|
||||||
|
|
||||||
// Get comprehensive user information from the unified hook
|
// Get comprehensive user information from the unified hook
|
||||||
const userInfo = useUserDisplay(address || '');
|
const userInfo = useUserDisplay(address || '');
|
||||||
|
|
||||||
@ -341,21 +348,21 @@ export default function ProfilePage() {
|
|||||||
{/* Delegation Status */}
|
{/* Delegation Status */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Badge
|
<Badge
|
||||||
variant={delegationInfo.isValid ? 'default' : 'secondary'}
|
variant={delegationInfo?.isValid ? 'default' : 'secondary'}
|
||||||
className={
|
className={
|
||||||
delegationInfo.isValid
|
delegationInfo?.isValid
|
||||||
? 'bg-green-600 hover:bg-green-700'
|
? 'bg-green-600 hover:bg-green-700'
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{delegationInfo.isValid ? 'Active' : 'Inactive'}
|
{delegationInfo?.isValid ? 'Active' : 'Inactive'}
|
||||||
</Badge>
|
</Badge>
|
||||||
{delegationInfo.isValid && delegationInfo.timeRemaining && (
|
{delegationInfo?.isValid && delegationInfo?.timeRemaining && (
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{delegationInfo.timeRemaining} remaining
|
{delegationInfo.timeRemaining} remaining
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{!delegationInfo.isValid && (
|
{!delegationInfo?.isValid && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="text-yellow-600 border-yellow-600"
|
className="text-yellow-600 border-yellow-600"
|
||||||
@ -363,7 +370,7 @@ export default function ProfilePage() {
|
|||||||
Renewal Recommended
|
Renewal Recommended
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{!delegationInfo.isValid && (
|
{!delegationInfo?.isValid && (
|
||||||
<Badge variant="destructive">Expired</Badge>
|
<Badge variant="destructive">Expired</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -426,7 +433,7 @@ export default function ProfilePage() {
|
|||||||
Can Delegate
|
Can Delegate
|
||||||
</Label>
|
</Label>
|
||||||
<div className="mt-1 text-sm">
|
<div className="mt-1 text-sm">
|
||||||
{delegationInfo.hasDelegation ? (
|
{delegationInfo?.hasDelegation ? (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="text-green-600 border-green-600"
|
className="text-green-600 border-green-600"
|
||||||
@ -446,14 +453,14 @@ export default function ProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Delegation Actions */}
|
{/* Delegation Actions */}
|
||||||
{delegationInfo.hasDelegation && (
|
{delegationInfo?.hasDelegation && (
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setWalletWizardOpen(true)}
|
onClick={() => setWalletWizardOpen(true)}
|
||||||
>
|
>
|
||||||
{delegationInfo.isValid
|
{delegationInfo?.isValid
|
||||||
? 'Renew Delegation'
|
? 'Renew Delegation'
|
||||||
: 'Delegate Key'}
|
: 'Delegate Key'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user