"use client"; import React, { useState } from 'react'; import { useKeystore } from '../../../contexts/keystore/KeystoreContext'; import { useRLN } from '../../../contexts/rln/RLNContext'; import { readKeystoreFromFile, saveKeystoreCredentialToFile } from '../../../utils/keystore'; import { DecryptedCredentials, MembershipInfo, MembershipState } from '@waku/rln'; import { useAppState } from '../../../contexts/AppStateContext'; import { TerminalWindow } from '../../ui/terminal-window'; import { Button } from '../../ui/button'; import { Copy, Eye, Download, Trash2, ArrowDownToLine } from 'lucide-react'; import { KeystoreExporter } from '../../KeystoreExporter'; import { keystoreManagement, type ContentSegment } from '../../../content/index'; import { ethers } from 'ethers'; import { toast } from 'sonner'; import { CredentialDetails } from '@/components/CredentialDetails'; import { MembershipDetails } from '@/components/MembershipDetails'; interface ExtendedMembershipInfo extends Omit { address: string; chainId: string; treeIndex: number; rateLimit: number; idCommitment: string; startBlock: number; endBlock: number; state: MembershipState; depositAmount: ethers.BigNumber; activeDuration: number; gracePeriodDuration: number; holder: string; token: string; } export function KeystoreManagement() { const { hasStoredCredentials, storedCredentialsHashes, error, exportCredential, importKeystore, removeCredential, getDecryptedCredential } = useKeystore(); const { getMembershipInfo } = useRLN(); const [exportPassword, setExportPassword] = useState(''); const [selectedCredential, setSelectedCredential] = useState(null); const [viewPassword, setViewPassword] = useState(''); const [viewingCredential, setViewingCredential] = useState(null); const [decryptedInfo, setDecryptedInfo] = useState(null); const [isDecrypting, setIsDecrypting] = useState(false); const [copiedHash, setCopiedHash] = useState(null); const [membershipInfo, setMembershipInfo] = useState(null); React.useEffect(() => { if (error) { toast.error(error); } }, [error]); const handleExportKeystoreCredential = async (hash: string) => { try { if (!exportPassword) { toast.error('Please enter your keystore password to export'); return; } const keystore = await exportCredential(hash, exportPassword); saveKeystoreCredentialToFile(keystore); setExportPassword(''); setSelectedCredential(null); } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to export credential'); } }; const handleImportKeystore = async () => { try { const keystore = await readKeystoreFromFile(); const success = importKeystore(keystore); if (!success) { toast.error('Failed to import keystore'); } } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to import keystore'); } }; const handleRemoveCredential = (hash: string) => { try { removeCredential(hash); } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to remove credential'); } }; const handleViewCredential = async (hash: string) => { if (!viewPassword) { toast.error('Please enter your keystore password to view credential'); return; } setIsDecrypting(true); setMembershipInfo(null); try { const credential = await getDecryptedCredential(hash, viewPassword); setIsDecrypting(false); if (credential) { setDecryptedInfo(credential); const info = await getMembershipInfo(hash, viewPassword); setMembershipInfo(info as ExtendedMembershipInfo); } else { toast.error('Could not decrypt credential. Please check your password and try again.'); } } catch (err) { setIsDecrypting(false); toast.error(err instanceof Error ? err.message : 'Failed to decrypt credential'); } }; // Reset view state when changing credentials React.useEffect(() => { if (viewingCredential !== selectedCredential) { setDecryptedInfo(null); } }, [viewingCredential, selectedCredential]); // Add a function to copy text to clipboard with visual feedback const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text) .then(() => { setCopiedHash(text); setTimeout(() => setCopiedHash(null), 2000); }) .catch(err => { console.error('Failed to copy: ', err); }); }; return (

{keystoreManagement.title}

{/* Warning - RLN not initialized */} {!hasStoredCredentials && (

⚠️ {keystoreManagement.noCredentialsWarning}

)} {/* About Section */}
{">"}

{keystoreManagement.infoHeader}

{keystoreManagement.about.map((paragraph: ContentSegment[], i: number) => (

{paragraph.map((segment: ContentSegment, j: number) => ( segment.type === 'link' ? ( {segment.content} ) : ( {segment.content} ) ))}

))}
{/* Resources Section */}

{keystoreManagement.resources.title}

{keystoreManagement.resources.links.map((link: { name: string; url: string }, i: number) => ( {link.name} ))}
{/* Stored Credentials */}

{keystoreManagement.storedCredentialsTitle}

{hasStoredCredentials ? (
{storedCredentialsHashes.map((hash) => (
{hash.slice(0, 10)}...{hash.slice(-6)} {copiedHash === hash && ( Copied! )}
{/* View Credential Section */} {viewingCredential === hash && (
setViewPassword(e.target.value)} placeholder="Enter credential password" className="flex-1 px-3 py-2 border border-terminal-border rounded-md bg-terminal-background text-foreground font-mono focus:ring-1 focus:ring-accent focus:border-accent text-sm" disabled={isDecrypting} />
{/* Decrypted Information Display */} {decryptedInfo && (
{membershipInfo && (
)}
)}
)} {/* Export Credential Section */} {selectedCredential === hash && (
setExportPassword(e.target.value)} placeholder="Enter keystore password" className="w-full px-3 py-2 border border-terminal-border rounded-md bg-terminal-background text-foreground font-mono focus:ring-1 focus:ring-primary focus:border-primary text-sm" />
)}
))}
) : (
No credentials stored
)}
); }