refactor: password login dialog
This commit is contained in:
parent
e60a460480
commit
6f64aafdae
20 changed files with 194 additions and 29 deletions
81
src/components/PasswordInputDialog/index.tsx
Normal file
81
src/components/PasswordInputDialog/index.tsx
Normal file
|
|
@ -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<HTMLInputElement>(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 (
|
||||
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onCancel()}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title || t('Enter Password')}</DialogTitle>
|
||||
{description && <DialogDescription>{description}</DialogDescription>}
|
||||
</DialogHeader>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={t('Password')}
|
||||
/>
|
||||
<DialogFooter className="w-full flex flex-wrap gap-2">
|
||||
<Button variant="outline" onClick={onCancel} className="flex-1">
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleConfirm} disabled={!password} className="flex-1">
|
||||
{t('Confirm')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
|
@ -634,6 +634,9 @@ export default {
|
|||
'Create a password (or skip)': 'أنشئ كلمة مرور (أو تخطى)',
|
||||
'Enter your password again': 'أدخل كلمة المرور مرة أخرى',
|
||||
'Complete Signup': 'إكمال التسجيل',
|
||||
Recommended: 'موصى به'
|
||||
Recommended: 'موصى به',
|
||||
'Enter Password': 'أدخل كلمة المرور',
|
||||
Password: 'كلمة المرور',
|
||||
Confirm: 'تأكيد'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -644,6 +644,9 @@ export default {
|
|||
'Create a password (or skip)': 'یک رمز عبور ایجاد کنید (یا رد کنید)',
|
||||
'Enter your password again': 'رمز عبور خود را دوباره وارد کنید',
|
||||
'Complete Signup': 'تکمیل ثبتنام',
|
||||
Recommended: 'توصیه شده'
|
||||
Recommended: 'توصیه شده',
|
||||
'Enter Password': 'رمز عبور را وارد کنید',
|
||||
Password: 'رمز عبور',
|
||||
Confirm: 'تأیید'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -645,6 +645,9 @@ export default {
|
|||
'Create a password (or skip)': 'एक पासवर्ड बनाएं (या छोड़ें)',
|
||||
'Enter your password again': 'अपना पासवर्ड फिर से दर्ज करें',
|
||||
'Complete Signup': 'साइनअप पूर्ण करें',
|
||||
Recommended: 'अनुशंसित'
|
||||
Recommended: 'अनुशंसित',
|
||||
'Enter Password': 'पासवर्ड दर्ज करें',
|
||||
Password: 'पासवर्ड',
|
||||
Confirm: 'पुष्टि करें'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -643,6 +643,9 @@ export default {
|
|||
'Create a password (or skip)': 'パスワードを作成(またはスキップ)',
|
||||
'Enter your password again': 'パスワードをもう一度入力',
|
||||
'Complete Signup': '登録を完了',
|
||||
Recommended: 'おすすめ'
|
||||
Recommended: 'おすすめ',
|
||||
'Enter Password': 'パスワードを入力',
|
||||
Password: 'パスワード',
|
||||
Confirm: '確認'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -640,6 +640,9 @@ export default {
|
|||
'Create a password (or skip)': '비밀번호 생성(또는 건너뛰기)',
|
||||
'Enter your password again': '비밀번호를 다시 입력하세요',
|
||||
'Complete Signup': '가입 완료',
|
||||
Recommended: '추천'
|
||||
Recommended: '추천',
|
||||
'Enter Password': '비밀번호 입력',
|
||||
Password: '비밀번호',
|
||||
Confirm: '확인'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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ź'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -649,6 +649,9 @@ export default {
|
|||
'Create a password (or skip)': 'Создайте пароль (или пропустите)',
|
||||
'Enter your password again': 'Введите пароль еще раз',
|
||||
'Complete Signup': 'Завершить регистрацию',
|
||||
Recommended: 'Рекомендуемые'
|
||||
Recommended: 'Рекомендуемые',
|
||||
'Enter Password': 'Введите пароль',
|
||||
Password: 'Пароль',
|
||||
Confirm: 'Подтвердить'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -634,6 +634,9 @@ export default {
|
|||
'Create a password (or skip)': 'สร้างรหัสผ่าน (หรือข้าม)',
|
||||
'Enter your password again': 'ป้อนรหัสผ่านของคุณอีกครั้ง',
|
||||
'Complete Signup': 'เสร็จสิ้นการลงทะเบียน',
|
||||
Recommended: 'แนะนำ'
|
||||
Recommended: 'แนะนำ',
|
||||
'Enter Password': 'ป้อนรหัสผ่าน',
|
||||
Password: 'รหัสผ่าน',
|
||||
Confirm: 'ยืนยัน'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -620,6 +620,9 @@ export default {
|
|||
'Create a password (or skip)': '建立密碼(或跳過)',
|
||||
'Enter your password again': '再次輸入你的密碼',
|
||||
'Complete Signup': '完成註冊',
|
||||
Recommended: '推薦'
|
||||
Recommended: '推薦',
|
||||
'Enter Password': '輸入密碼',
|
||||
Password: '密碼',
|
||||
Confirm: '確認'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -625,6 +625,9 @@ export default {
|
|||
'Create a password (or skip)': '创建密码(或跳过)',
|
||||
'Enter your password again': '再次输入你的密码',
|
||||
'Complete Signup': '完成注册',
|
||||
Recommended: '推荐'
|
||||
Recommended: '推荐',
|
||||
'Enter Password': '输入密码',
|
||||
Password: '密码',
|
||||
Confirm: '确认'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Event | null>(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<string> => {
|
||||
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) {
|
||||
return null
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
} else if (account.signerType === 'nip-07') {
|
||||
const nip07Signer = new Nip07Signer()
|
||||
|
|
@ -857,6 +880,13 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||
>
|
||||
{children}
|
||||
<LoginDialog open={openLoginDialog} setOpen={setOpenLoginDialog} />
|
||||
<PasswordInputDialog
|
||||
open={passwordDialogOpen}
|
||||
title={t('Enter Password')}
|
||||
description={t('Enter the password to decrypt your ncryptsec')}
|
||||
onConfirm={handlePasswordConfirm}
|
||||
onCancel={handlePasswordCancel}
|
||||
/>
|
||||
</NostrContext.Provider>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue