diff --git a/src/components/Tabs/KeystoreTab/KeystoreManagement.tsx b/src/components/Tabs/KeystoreTab/KeystoreManagement.tsx index ee50193..67fee56 100644 --- a/src/components/Tabs/KeystoreTab/KeystoreManagement.tsx +++ b/src/components/Tabs/KeystoreTab/KeystoreManagement.tsx @@ -7,13 +7,14 @@ import { readKeystoreFromFile, saveKeystoreCredentialToFile } from '../../../uti import { DecryptedCredentials, MembershipInfo, MembershipState } from '@waku/rln'; import { TerminalWindow } from '../../ui/terminal-window'; import { Button } from '../../ui/button'; -import { Copy, Eye, Download, Trash2, ArrowDownToLine } from 'lucide-react'; +import { Copy, Eye, Download, Trash2, ArrowDownToLine, Pencil, Check, X } 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'; +import { Input } from "@/components/ui/input"; interface ExtendedMembershipInfo extends Omit { address: string; @@ -40,6 +41,8 @@ export function KeystoreManagement({ tabId: _tabId }: KeystoreManagementProps) { const { hasStoredCredentials, storedCredentialsHashes, + credentialAliases, + setCredentialAlias, error, exportCredential, importKeystore, @@ -59,6 +62,8 @@ export function KeystoreManagement({ tabId: _tabId }: KeystoreManagementProps) { const [isDecrypting, setIsDecrypting] = useState(false); const [copiedHash, setCopiedHash] = useState(null); const [membershipInfo, setMembershipInfo] = useState(null); + const [editingAliasHash, setEditingAliasHash] = useState(null); + const [currentAliasInput, setCurrentAliasInput] = useState(''); React.useEffect(() => { if (error) { @@ -239,146 +244,189 @@ export function KeystoreManagement({ tabId: _tabId }: KeystoreManagementProps) { {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 && ( -
- { + const currentAlias = credentialAliases[hash] || ''; + const displayValue = currentAlias || `${hash.substring(0, 6)}...${hash.substring(hash.length - 4)}`; + const isEditing = editingAliasHash === hash; + + return ( +
+
+
+ {/* Display Alias/Hash or Edit Input */} + {isEditing ? ( +
+ setCurrentAliasInput(e.target.value)} + placeholder="Enter alias..." + className="h-8 text-sm flex-grow" /> - - {membershipInfo && ( -
- -
- )} + size="icon" + className="h-7 w-7 text-primary hover:text-primary" + onClick={() => { + setCredentialAlias(hash, currentAliasInput); + setEditingAliasHash(null); + setCurrentAliasInput(''); + }} + > + + + +
+ ) : ( +
+ + {displayValue} + + + +
+ )} + + {/* Action Buttons (View, Export, Remove) - only show if not editing */} + {!isEditing && ( +
+ + +
)}
- )} - - {/* 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" - /> - -
- )} + + {/* Password input for View Credential */} + {viewingCredential === hash && !decryptedInfo && ( +
+ setViewPassword(e.target.value)} + placeholder="Enter password to view details" + className="flex-grow h-8 text-sm" + /> + + +
+ )} + + {/* Password input for Export Credential */} + {selectedCredential === hash && ( +
+ setExportPassword(e.target.value)} + placeholder="Enter password to export" + className="flex-grow h-8 text-sm" + /> + + +
+ )} + + {/* Decrypted Credential Details */} + {viewingCredential === hash && decryptedInfo && ( + + )} + {/* Membership Details */} + {viewingCredential === hash && membershipInfo && ( + + )} +
-
- ))} + ); + })}
) : (
diff --git a/src/contexts/keystore/KeystoreContext.tsx b/src/contexts/keystore/KeystoreContext.tsx index e05e11e..1e34601 100644 --- a/src/contexts/keystore/KeystoreContext.tsx +++ b/src/contexts/keystore/KeystoreContext.tsx @@ -4,6 +4,7 @@ import { createContext, useContext, useState, useEffect, ReactNode } from 'react import { Keystore, KeystoreEntity } from '@waku/rln'; export const LOCAL_STORAGE_KEYSTORE_KEY = 'waku-rln-keystore'; +export const LOCAL_STORAGE_ALIASES_KEY = 'waku-rln-keystore-aliases'; interface KeystoreContextType { keystore: Keystore | null; @@ -11,6 +12,7 @@ interface KeystoreContextType { error: string | null; hasStoredCredentials: boolean; storedCredentialsHashes: string[]; + credentialAliases: { [hash: string]: string }; decryptedCredentials: KeystoreEntity | null; hideCredentials: () => void; saveCredentials: (credentials: KeystoreEntity, password: string) => Promise; @@ -19,6 +21,7 @@ interface KeystoreContextType { importKeystore: (keystore: Keystore) => boolean; removeCredential: (hash: string) => void; getDecryptedCredential: (hash: string, password: string) => Promise; + setCredentialAlias: (hash: string, alias: string) => void; } const KeystoreContext = createContext(undefined); @@ -28,12 +31,14 @@ export function KeystoreProvider({ children }: { children: ReactNode }) { const [isInitialized, setIsInitialized] = useState(false); const [error, setError] = useState(null); const [storedCredentialsHashes, setStoredCredentialsHashes] = useState([]); + const [credentialAliases, setCredentialAliases] = useState<{ [hash: string]: string }>({}); const [decryptedCredentials, setDecryptedCredentials] = useState(null); - // Initialize keystore - useEffect(() => { + // Initialize keystore and aliases + useEffect(() => { try { const storedKeystore = localStorage.getItem(LOCAL_STORAGE_KEYSTORE_KEY); + const storedAliases = localStorage.getItem(LOCAL_STORAGE_ALIASES_KEY); let keystoreInstance: Keystore; if (storedKeystore) { @@ -49,6 +54,18 @@ export function KeystoreProvider({ children }: { children: ReactNode }) { setKeystore(keystoreInstance); setStoredCredentialsHashes(keystoreInstance.keys()); + + if (storedAliases) { + try { + setCredentialAliases(JSON.parse(storedAliases)); + } catch (aliasErr) { + console.error("Error parsing aliases from localStorage:", aliasErr); + setCredentialAliases({}); + } + } else { + setCredentialAliases({}); + } + setIsInitialized(true); } catch (err) { console.error("Error initializing keystore:", err); @@ -67,6 +84,17 @@ export function KeystoreProvider({ children }: { children: ReactNode }) { } }, [keystore, isInitialized]); + // Save aliases to localStorage whenever they change + useEffect(() => { + if (isInitialized) { + try { + localStorage.setItem(LOCAL_STORAGE_ALIASES_KEY, JSON.stringify(credentialAliases)); + } catch (err) { + console.warn("Could not save aliases to localStorage:", err); + } + } + }, [credentialAliases, isInitialized]); + const saveCredentials = async (credentials: KeystoreEntity, password: string): Promise => { if (!keystore) { throw new Error("Keystore not initialized"); @@ -163,11 +191,16 @@ export function KeystoreProvider({ children }: { children: ReactNode }) { } }; - const importKeystore = (keystore: Keystore): boolean => { + const importKeystore = (importedKeystore: Keystore): boolean => { try { - setKeystore(keystore); - setStoredCredentialsHashes(keystore.keys()); - localStorage.setItem(LOCAL_STORAGE_KEYSTORE_KEY, keystore.toString()); + const newAliases = {}; + setCredentialAliases(newAliases); + localStorage.setItem(LOCAL_STORAGE_ALIASES_KEY, JSON.stringify(newAliases)); + + setKeystore(importedKeystore); + setStoredCredentialsHashes(importedKeystore.keys()); + localStorage.setItem(LOCAL_STORAGE_KEYSTORE_KEY, importedKeystore.toString()); + return true; } catch (err) { console.error("Error importing keystore:", err); @@ -182,8 +215,24 @@ export function KeystoreProvider({ children }: { children: ReactNode }) { } keystore.removeCredential(hash); - setStoredCredentialsHashes(keystore.keys()); + const remainingHashes = keystore.keys(); + setStoredCredentialsHashes(remainingHashes); localStorage.setItem(LOCAL_STORAGE_KEYSTORE_KEY, keystore.toString()); + + setCredentialAliases(prev => { + const newAliases = { ...prev }; + delete newAliases[hash]; + localStorage.setItem(LOCAL_STORAGE_ALIASES_KEY, JSON.stringify(newAliases)); + return newAliases; + }); + }; + + const setCredentialAlias = (hash: string, alias: string) => { + setCredentialAliases(prev => { + const newAliases = { ...prev, [hash]: alias }; + localStorage.setItem(LOCAL_STORAGE_ALIASES_KEY, JSON.stringify(newAliases)); + return newAliases; + }); }; const contextValue: KeystoreContextType = { @@ -192,6 +241,7 @@ export function KeystoreProvider({ children }: { children: ReactNode }) { error, hasStoredCredentials: storedCredentialsHashes.length > 0, storedCredentialsHashes, + credentialAliases, saveCredentials, exportCredential, exportEntireKeystore, @@ -199,6 +249,7 @@ export function KeystoreProvider({ children }: { children: ReactNode }) { removeCredential, getDecryptedCredential, decryptedCredentials, + setCredentialAlias, hideCredentials: () => {}, };