pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

custom passphrase support

hold shift to enter a custom phrase

Pas 1e635c36 3f727364

+121 -19
+7 -2
src/assets/locales/en.json
··· 140 140 }, 141 141 "actions": { 142 142 "copied": "Copied", 143 - "copy": "Copy" 143 + "copy": "Copy", 144 + "cancel": "Cancel" 144 145 }, 145 146 "auth": { 146 147 "createAccount": "Don't have an account yet 😬 <0>Create an account.</0>", ··· 150 151 "description": "Your passphrase acts as your username and password. Make sure to keep it safe as you will need to enter it to login to your account. <bold>Do NOT lose your passphrase!</bold>", 151 152 "next": "I have saved my passphrase", 152 153 "passphraseFrameLabel": "Passphrase", 153 - "title": "Your passphrase" 154 + "title": "Your passphrase", 155 + "custom": "Custom", 156 + "customPassphraseLabel": "Custom Passphrase", 157 + "customPassphrasePlaceholder": "Enter your custom passphrase", 158 + "useCustomPassphrase": "Use Custom Passphrase" 154 159 }, 155 160 "hasAccount": "Already have an account? <0>Login here.</0>", 156 161 "login": {
+104 -14
src/components/form/PassphraseDisplay.tsx
··· 1 - import { useRef, useState } from "react"; 1 + import { useEffect, useRef, useState } from "react"; 2 2 import { useTranslation } from "react-i18next"; 3 3 import { useCopyToClipboard, useMountedState } from "react-use"; 4 4 5 5 import { Icon, Icons } from "../Icon"; 6 + import { AuthInputBox } from "../text-inputs/AuthInputBox"; 6 7 7 - export function PassphraseDisplay(props: { mnemonic: string }) { 8 + export function PassphraseDisplay(props: { 9 + mnemonic: string; 10 + onCustomPassphrase?: (passphrase: string) => void; 11 + }) { 8 12 const { t } = useTranslation(); 9 13 const individualWords = props.mnemonic.split(" "); 10 14 11 15 const [, copy] = useCopyToClipboard(); 12 16 13 17 const [hasCopied, setHasCopied] = useState(false); 18 + const [showCustomInput, setShowCustomInput] = useState(false); 19 + const [customPassphrase, setCustomPassphrase] = useState(""); 20 + const [isShiftHeld, setIsShiftHeld] = useState(false); 14 21 const isMounted = useMountedState(); 15 22 16 23 const timeout = useRef<ReturnType<typeof setTimeout>>(); ··· 22 29 timeout.current = setTimeout(() => isMounted() && setHasCopied(false), 500); 23 30 } 24 31 32 + function handleKeyDown(e: KeyboardEvent) { 33 + if (e.key === "Shift") { 34 + setIsShiftHeld(true); 35 + } 36 + } 37 + 38 + function handleKeyUp(e: KeyboardEvent) { 39 + if (e.key === "Shift") { 40 + setIsShiftHeld(false); 41 + } 42 + } 43 + 44 + function handleCustomPassphraseSubmit() { 45 + if (customPassphrase.trim()) { 46 + props.onCustomPassphrase?.(customPassphrase.trim()); 47 + setShowCustomInput(false); 48 + setCustomPassphrase(""); 49 + } 50 + } 51 + 52 + useEffect(() => { 53 + document.addEventListener("keydown", handleKeyDown); 54 + document.addEventListener("keyup", handleKeyUp); 55 + 56 + return () => { 57 + document.removeEventListener("keydown", handleKeyDown); 58 + document.removeEventListener("keyup", handleKeyUp); 59 + }; 60 + }, []); 61 + 62 + if (showCustomInput) { 63 + return ( 64 + <div className="rounded-lg border border-authentication-border/50"> 65 + <div className="px-4 py-2 flex justify-between border-b border-authentication-border/50"> 66 + <p className="font-bold text-sm text-white"> 67 + {t("auth.generate.customPassphraseLabel")} 68 + </p> 69 + <button 70 + type="button" 71 + className="text-authentication-copyText hover:text-authentication-copyTextHover transition-colors flex gap-2 items-center cursor-pointer" 72 + onClick={() => setShowCustomInput(false)} 73 + > 74 + <Icon icon={Icons.X} className="text-xs" /> 75 + <span className="text-sm">{t("actions.cancel")}</span> 76 + </button> 77 + </div> 78 + <div className="px-4 py-4"> 79 + <AuthInputBox 80 + value={customPassphrase} 81 + onChange={setCustomPassphrase} 82 + placeholder={t("auth.generate.customPassphrasePlaceholder")} 83 + passwordToggleable 84 + className="mb-4" 85 + /> 86 + <button 87 + type="button" 88 + className="w-full bg-authentication-inputBg hover:bg-authentication-inputBg/80 text-white font-medium py-2 px-4 rounded-lg transition-colors" 89 + onClick={handleCustomPassphraseSubmit} 90 + disabled={!customPassphrase.trim()} 91 + > 92 + {t("auth.generate.useCustomPassphrase")} 93 + </button> 94 + </div> 95 + </div> 96 + ); 97 + } 98 + 25 99 return ( 26 - <div className="rounded-lg border border-authentication-border/50 "> 100 + <div className="rounded-lg border border-authentication-border/50 relative"> 27 101 <div className="px-4 py-2 flex justify-between border-b border-authentication-border/50"> 28 102 <p className="font-bold text-sm text-white"> 29 103 {t("auth.generate.passphraseFrameLabel")} 30 104 </p> 31 - <button 32 - type="button" 33 - className="text-authentication-copyText hover:text-authentication-copyTextHover transition-colors flex gap-2 items-center cursor-pointer" 34 - onClick={() => copyMnemonic()} 35 - > 36 - <Icon 37 - icon={hasCopied ? Icons.CHECKMARK : Icons.COPY} 38 - className={hasCopied ? "text-xs" : ""} 39 - /> 40 - <span className="text-sm">{t("actions.copy")}</span> 41 - </button> 105 + <div className="flex gap-2 items-center"> 106 + {/* Hidden custom passphrase button */} 107 + <button 108 + type="button" 109 + className={`text-authentication-copyText hover:text-authentication-copyTextHover transition-all duration-200 flex gap-2 pr-2 items-center cursor-pointer ${ 110 + isShiftHeld 111 + ? "opacity-100 scale-100" 112 + : "opacity-0 scale-95 pointer-events-none" 113 + }`} 114 + onClick={() => setShowCustomInput(true)} 115 + title={t("auth.generate.useCustomPassphrase")} 116 + > 117 + <Icon icon={Icons.EDIT} className="text-xs" /> 118 + <span className="text-sm">{t("auth.generate.custom")}</span> 119 + </button> 120 + <button 121 + type="button" 122 + className="text-authentication-copyText hover:text-authentication-copyTextHover transition-colors flex gap-2 items-center cursor-pointer" 123 + onClick={() => copyMnemonic()} 124 + > 125 + <Icon 126 + icon={hasCopied ? Icons.CHECKMARK : Icons.COPY} 127 + className={hasCopied ? "text-xs" : ""} 128 + /> 129 + <span className="text-sm">{t("actions.copy")}</span> 130 + </button> 131 + </div> 42 132 </div> 43 133 <div className="px-4 py-4 grid grid-cols-3 text-sm sm:text-base sm:grid-cols-4 gap-2"> 44 134 {individualWords.map((word, i) => (
+10 -3
src/pages/parts/auth/PassphraseGeneratePart.tsx
··· 1 - import { useMemo } from "react"; 1 + import { useCallback, useState } from "react"; 2 2 import { Trans, useTranslation } from "react-i18next"; 3 3 4 4 import { genMnemonic } from "@/backend/accounts/crypto"; ··· 16 16 } 17 17 18 18 export function PassphraseGeneratePart(props: PassphraseGeneratePartProps) { 19 - const mnemonic = useMemo(() => genMnemonic(), []); 19 + const [mnemonic, setMnemonic] = useState(() => genMnemonic()); 20 20 const { t } = useTranslation(); 21 21 22 + const handleCustomPassphrase = useCallback((customPassphrase: string) => { 23 + setMnemonic(customPassphrase); 24 + }, []); 25 + 22 26 return ( 23 27 <LargeCard> 24 28 <LargeCardText ··· 32 36 }} 33 37 /> 34 38 </LargeCardText> 35 - <PassphraseDisplay mnemonic={mnemonic} /> 39 + <PassphraseDisplay 40 + mnemonic={mnemonic} 41 + onCustomPassphrase={handleCustomPassphrase} 42 + /> 36 43 37 44 <LargeCardButtons> 38 45 <Button theme="purple" onClick={() => props.onNext?.(mnemonic)}>