From 6f64aafdae6fa4f2c061d7c514c11419d792ccc6 Mon Sep 17 00:00:00 2001 From: codytseng Date: Fri, 26 Dec 2025 20:25:28 +0800 Subject: [PATCH] refactor: password login dialog --- src/components/PasswordInputDialog/index.tsx | 81 ++++++++++++++++++++ src/i18n/locales/ar.ts | 5 +- src/i18n/locales/de.ts | 5 +- src/i18n/locales/en.ts | 5 +- src/i18n/locales/es.ts | 5 +- src/i18n/locales/fa.ts | 5 +- src/i18n/locales/fr.ts | 5 +- src/i18n/locales/hi.ts | 5 +- src/i18n/locales/hu.ts | 5 +- src/i18n/locales/it.ts | 5 +- src/i18n/locales/ja.ts | 5 +- src/i18n/locales/ko.ts | 5 +- src/i18n/locales/pl.ts | 5 +- src/i18n/locales/pt-BR.ts | 5 +- src/i18n/locales/pt-PT.ts | 5 +- src/i18n/locales/ru.ts | 5 +- src/i18n/locales/th.ts | 5 +- src/i18n/locales/zh-TW.ts | 5 +- src/i18n/locales/zh.ts | 5 +- src/providers/NostrProvider/index.tsx | 52 ++++++++++--- 20 files changed, 194 insertions(+), 29 deletions(-) create mode 100644 src/components/PasswordInputDialog/index.tsx diff --git a/src/components/PasswordInputDialog/index.tsx b/src/components/PasswordInputDialog/index.tsx new file mode 100644 index 0000000..6313658 --- /dev/null +++ b/src/components/PasswordInputDialog/index.tsx @@ -0,0 +1,81 @@ +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog' +import { Input } from '@/components/ui/input' +import { useEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' + +type PasswordInputDialogProps = { + open: boolean + title?: string + description?: string + onConfirm: (password: string) => void + onCancel: () => void +} + +export default function PasswordInputDialog({ + open, + title, + description, + onConfirm, + onCancel +}: PasswordInputDialogProps) { + const { t } = useTranslation() + const [password, setPassword] = useState('') + const inputRef = useRef(null) + + useEffect(() => { + if (open) { + setPassword('') + // Focus input after dialog opens + setTimeout(() => { + inputRef.current?.focus() + }, 100) + } + }, [open]) + + const handleConfirm = () => { + if (password) { + onConfirm(password) + } + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && password) { + handleConfirm() + } + } + + return ( + !isOpen && onCancel()}> + + + {title || t('Enter Password')} + {description && {description}} + + setPassword(e.target.value)} + onKeyDown={handleKeyDown} + placeholder={t('Password')} + /> + + + + + + + ) +} diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index 57cd72a..42a86df 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -634,6 +634,9 @@ export default { 'Create a password (or skip)': 'أنشئ كلمة مرور (أو تخطى)', 'Enter your password again': 'أدخل كلمة المرور مرة أخرى', 'Complete Signup': 'إكمال التسجيل', - Recommended: 'موصى به' + Recommended: 'موصى به', + 'Enter Password': 'أدخل كلمة المرور', + Password: 'كلمة المرور', + Confirm: 'تأكيد' } } diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index 0fc4830..1eebbf9 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -655,6 +655,9 @@ export default { 'Create a password (or skip)': 'Erstellen Sie ein Passwort (oder überspringen)', 'Enter your password again': 'Geben Sie Ihr Passwort erneut ein', 'Complete Signup': 'Registrierung abschließen', - Recommended: 'Empfohlen' + Recommended: 'Empfohlen', + 'Enter Password': 'Passwort eingeben', + Password: 'Passwort', + Confirm: 'Bestätigen' } } diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 83abca1..04f7159 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -639,6 +639,9 @@ export default { 'Create a password (or skip)': 'Create a password (or skip)', 'Enter your password again': 'Enter your password again', 'Complete Signup': 'Complete Signup', - Recommended: 'Recommended' + Recommended: 'Recommended', + 'Enter Password': 'Enter Password', + Password: 'Password', + Confirm: 'Confirm' } } diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index 4efcb29..5404d26 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -649,6 +649,9 @@ export default { 'Create a password (or skip)': 'Crear una contraseña (o saltar)', 'Enter your password again': 'Ingresa tu contraseña nuevamente', 'Complete Signup': 'Completar registro', - Recommended: 'Recomendado' + Recommended: 'Recomendado', + 'Enter Password': 'Ingresar contraseña', + Password: 'Contraseña', + Confirm: 'Confirmar' } } diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts index 83e35b2..1e13de5 100644 --- a/src/i18n/locales/fa.ts +++ b/src/i18n/locales/fa.ts @@ -644,6 +644,9 @@ export default { 'Create a password (or skip)': 'یک رمز عبور ایجاد کنید (یا رد کنید)', 'Enter your password again': 'رمز عبور خود را دوباره وارد کنید', 'Complete Signup': 'تکمیل ثبت‌نام', - Recommended: 'توصیه شده' + Recommended: 'توصیه شده', + 'Enter Password': 'رمز عبور را وارد کنید', + Password: 'رمز عبور', + Confirm: 'تأیید' } } diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index e2585a6..b3497f6 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -652,6 +652,9 @@ export default { 'Create a password (or skip)': 'Créez un mot de passe (ou ignorez)', 'Enter your password again': 'Entrez à nouveau votre mot de passe', 'Complete Signup': "Terminer l'inscription", - Recommended: 'Recommandé' + Recommended: 'Recommandé', + 'Enter Password': 'Entrer le mot de passe', + Password: 'Mot de passe', + Confirm: 'Confirmer' } } diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index 669a1d7..8f8af33 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -645,6 +645,9 @@ export default { 'Create a password (or skip)': 'एक पासवर्ड बनाएं (या छोड़ें)', 'Enter your password again': 'अपना पासवर्ड फिर से दर्ज करें', 'Complete Signup': 'साइनअप पूर्ण करें', - Recommended: 'अनुशंसित' + Recommended: 'अनुशंसित', + 'Enter Password': 'पासवर्ड दर्ज करें', + Password: 'पासवर्ड', + Confirm: 'पुष्टि करें' } } diff --git a/src/i18n/locales/hu.ts b/src/i18n/locales/hu.ts index 4f6fdc2..b27e0c4 100644 --- a/src/i18n/locales/hu.ts +++ b/src/i18n/locales/hu.ts @@ -637,6 +637,9 @@ export default { 'Create a password (or skip)': 'Hozz létre jelszót (vagy hagyd ki)', 'Enter your password again': 'Add meg újra a jelszavad', 'Complete Signup': 'Regisztráció befejezése', - Recommended: 'Ajánlott' + Recommended: 'Ajánlott', + 'Enter Password': 'Jelszó megadása', + Password: 'Jelszó', + Confirm: 'Megerősítés' } } diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts index a439124..fe5e5c8 100644 --- a/src/i18n/locales/it.ts +++ b/src/i18n/locales/it.ts @@ -649,6 +649,9 @@ export default { 'Create a password (or skip)': 'Crea una password (o salta)', 'Enter your password again': 'Inserisci di nuovo la tua password', 'Complete Signup': 'Completa registrazione', - Recommended: 'Consigliato' + Recommended: 'Consigliato', + 'Enter Password': 'Inserisci password', + Password: 'Password', + Confirm: 'Conferma' } } diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index eb68621..f722d74 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -643,6 +643,9 @@ export default { 'Create a password (or skip)': 'パスワードを作成(またはスキップ)', 'Enter your password again': 'パスワードをもう一度入力', 'Complete Signup': '登録を完了', - Recommended: 'おすすめ' + Recommended: 'おすすめ', + 'Enter Password': 'パスワードを入力', + Password: 'パスワード', + Confirm: '確認' } } diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index 6a8ce38..03c3f9a 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -640,6 +640,9 @@ export default { 'Create a password (or skip)': '비밀번호 생성(또는 건너뛰기)', 'Enter your password again': '비밀번호를 다시 입력하세요', 'Complete Signup': '가입 완료', - Recommended: '추천' + Recommended: '추천', + 'Enter Password': '비밀번호 입력', + Password: '비밀번호', + Confirm: '확인' } } diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index bd6d924..bf0574d 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -650,6 +650,9 @@ export default { 'Create a password (or skip)': 'Utwórz hasło (lub pomiń)', 'Enter your password again': 'Wprowadź hasło ponownie', 'Complete Signup': 'Zakończ rejestrację', - Recommended: 'Polecane' + Recommended: 'Polecane', + 'Enter Password': 'Wprowadź hasło', + Password: 'Hasło', + Confirm: 'Potwierdź' } } diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts index 66726be..3426407 100644 --- a/src/i18n/locales/pt-BR.ts +++ b/src/i18n/locales/pt-BR.ts @@ -645,6 +645,9 @@ export default { 'Create a password (or skip)': 'Crie uma senha (opcional)', 'Enter your password again': 'Digite sua senha novamente', 'Complete Signup': 'Concluir cadastro', - Recommended: 'Recomendado' + Recommended: 'Recomendado', + 'Enter Password': 'Digite a senha', + Password: 'Senha', + Confirm: 'Confirmar' } } diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts index 7fe7c57..d197ce7 100644 --- a/src/i18n/locales/pt-PT.ts +++ b/src/i18n/locales/pt-PT.ts @@ -648,6 +648,9 @@ export default { 'Create a password (or skip)': 'Crie uma palavra-passe (ou ignore)', 'Enter your password again': 'Introduza novamente a sua palavra-passe', 'Complete Signup': 'Concluir registo', - Recommended: 'Recomendado' + Recommended: 'Recomendado', + 'Enter Password': 'Introduza a palavra-passe', + Password: 'Palavra-passe', + Confirm: 'Confirmar' } } diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index 1cba1d7..385b980 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -649,6 +649,9 @@ export default { 'Create a password (or skip)': 'Создайте пароль (или пропустите)', 'Enter your password again': 'Введите пароль еще раз', 'Complete Signup': 'Завершить регистрацию', - Recommended: 'Рекомендуемые' + Recommended: 'Рекомендуемые', + 'Enter Password': 'Введите пароль', + Password: 'Пароль', + Confirm: 'Подтвердить' } } diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts index 2824be3..057815c 100644 --- a/src/i18n/locales/th.ts +++ b/src/i18n/locales/th.ts @@ -634,6 +634,9 @@ export default { 'Create a password (or skip)': 'สร้างรหัสผ่าน (หรือข้าม)', 'Enter your password again': 'ป้อนรหัสผ่านของคุณอีกครั้ง', 'Complete Signup': 'เสร็จสิ้นการลงทะเบียน', - Recommended: 'แนะนำ' + Recommended: 'แนะนำ', + 'Enter Password': 'ป้อนรหัสผ่าน', + Password: 'รหัสผ่าน', + Confirm: 'ยืนยัน' } } diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index a54ecf0..ba05eec 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -620,6 +620,9 @@ export default { 'Create a password (or skip)': '建立密碼(或跳過)', 'Enter your password again': '再次輸入你的密碼', 'Complete Signup': '完成註冊', - Recommended: '推薦' + Recommended: '推薦', + 'Enter Password': '輸入密碼', + Password: '密碼', + Confirm: '確認' } } diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts index 25b8aa5..f00c6c7 100644 --- a/src/i18n/locales/zh.ts +++ b/src/i18n/locales/zh.ts @@ -625,6 +625,9 @@ export default { 'Create a password (or skip)': '创建密码(或跳过)', 'Enter your password again': '再次输入你的密码', 'Complete Signup': '完成注册', - Recommended: '推荐' + Recommended: '推荐', + 'Enter Password': '输入密码', + Password: '密码', + Confirm: '确认' } } diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index 2fc200c..a6ac683 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -1,4 +1,5 @@ import LoginDialog from '@/components/LoginDialog' +import PasswordInputDialog from '@/components/PasswordInputDialog' import { ApplicationDataKey, BIG_RELAY_URLS, ExtendedKind } from '@/constants' import { createDeletionRequestDraftEvent, @@ -34,7 +35,7 @@ import dayjs from 'dayjs' import { Event, kinds, VerifiedEvent } from 'nostr-tools' import * as nip19 from 'nostr-tools/nip19' import * as nip49 from 'nostr-tools/nip49' -import { createContext, useContext, useEffect, useState } from 'react' +import { createContext, useContext, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { useDeletedEvent } from '../DeletedEventProvider' @@ -128,6 +129,11 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { const [pinListEvent, setPinListEvent] = useState(null) const [notificationsSeenAt, setNotificationsSeenAt] = useState(-1) const [isInitialized, setIsInitialized] = useState(false) + const [passwordDialogOpen, setPasswordDialogOpen] = useState(false) + const passwordPromiseRef = useRef<{ + resolve: (password: string) => void + reject: () => void + } | null>(null) useEffect(() => { const init = async () => { @@ -411,6 +417,25 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { customEmojiService.init(userEmojiListEvent) }, [userEmojiListEvent]) + const requestPassword = (): Promise => { + return new Promise((resolve, reject) => { + passwordPromiseRef.current = { resolve, reject } + setPasswordDialogOpen(true) + }) + } + + const handlePasswordConfirm = (password: string) => { + passwordPromiseRef.current?.resolve(password) + passwordPromiseRef.current = null + setPasswordDialogOpen(false) + } + + const handlePasswordCancel = () => { + passwordPromiseRef.current?.reject() + passwordPromiseRef.current = null + setPasswordDialogOpen(false) + } + const hasNostrLoginHash = () => { return window.location.hash && window.location.hash.startsWith('#nostr-login') } @@ -485,10 +510,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { } const ncryptsecLogin = async (ncryptsec: string) => { - const password = prompt(t('Enter the password to decrypt your ncryptsec')) - if (!password) { - throw new Error('Password is required') - } + const password = await requestPassword() const privkey = nip49.decrypt(ncryptsec, password) const browserNsecSigner = new NsecSigner() const pubkey = browserNsecSigner.login(privkey) @@ -567,14 +589,15 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { } } else if (account.signerType === 'ncryptsec') { if (account.ncryptsec) { - const password = prompt(t('Enter the password to decrypt your ncryptsec')) - if (!password) { + try { + const password = await requestPassword() + const privkey = nip49.decrypt(account.ncryptsec, password) + const browserNsecSigner = new NsecSigner() + browserNsecSigner.login(privkey) + return login(browserNsecSigner, account) + } catch { return null } - const privkey = nip49.decrypt(account.ncryptsec, password) - const browserNsecSigner = new NsecSigner() - browserNsecSigner.login(privkey) - return login(browserNsecSigner, account) } } else if (account.signerType === 'nip-07') { const nip07Signer = new Nip07Signer() @@ -857,6 +880,13 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { > {children} + ) }