PrivateKeyLogin.tsx raw
1 import QrScannerModal from '@/components/QrScannerModal'
2 import { Button } from '@/components/ui/button'
3 import { Input } from '@/components/ui/input'
4 import { Label } from '@/components/ui/label'
5 import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
6 import { useNostr } from '@/providers/NostrProvider'
7 import { useState } from 'react'
8 import { useTranslation } from 'react-i18next'
9 import { ScanLine } from 'lucide-react'
10
11 export default function PrivateKeyLogin({
12 back,
13 onLoginSuccess
14 }: {
15 back: () => void
16 onLoginSuccess: () => void
17 }) {
18 return (
19 <Tabs defaultValue="nsec">
20 <TabsList>
21 <TabsTrigger value="nsec">nsec</TabsTrigger>
22 <TabsTrigger value="ncryptsec">ncryptsec</TabsTrigger>
23 </TabsList>
24 <TabsContent value="nsec">
25 <NsecLogin back={back} onLoginSuccess={onLoginSuccess} />
26 </TabsContent>
27 <TabsContent value="ncryptsec">
28 <NcryptsecLogin back={back} onLoginSuccess={onLoginSuccess} />
29 </TabsContent>
30 </Tabs>
31 )
32 }
33
34 function NsecLogin({ back, onLoginSuccess }: { back: () => void; onLoginSuccess: () => void }) {
35 const { t } = useTranslation()
36 const { nsecLogin } = useNostr()
37 const [nsecOrHex, setNsecOrHex] = useState('')
38 const [errMsg, setErrMsg] = useState<string | null>(null)
39 const [password, setPassword] = useState('')
40 const [showScanner, setShowScanner] = useState(false)
41
42 const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
43 setNsecOrHex(e.target.value)
44 setErrMsg(null)
45 }
46
47 const handleScan = (result: string) => {
48 setNsecOrHex(result)
49 setErrMsg(null)
50 }
51
52 const handleLogin = () => {
53 if (nsecOrHex === '') return
54
55 nsecLogin(nsecOrHex, password)
56 .then(() => onLoginSuccess())
57 .catch((err) => {
58 setErrMsg(err.message)
59 })
60 }
61
62 return (
63 <>
64 {showScanner && (
65 <QrScannerModal onScan={handleScan} onClose={() => setShowScanner(false)} />
66 )}
67 <form
68 className="space-y-4"
69 onSubmit={(e) => {
70 e.preventDefault()
71 handleLogin()
72 }}
73 >
74 <div className="text-orange-400">
75 {t(
76 'Using private key login is insecure. It is recommended to use a browser extension for login, such as alby, nostr-keyx or nos2x. If you must use a private key, please set a password for encryption at minimum.'
77 )}
78 </div>
79 <div className="grid gap-2">
80 <Label htmlFor="nsec-input">nsec or hex</Label>
81 <div className="flex gap-2">
82 <Input
83 id="nsec-input"
84 type="password"
85 placeholder="nsec1.. or hex"
86 value={nsecOrHex}
87 onChange={handleInputChange}
88 className={errMsg ? 'border-destructive' : ''}
89 />
90 <Button
91 type="button"
92 variant="outline"
93 size="icon"
94 onClick={() => setShowScanner(true)}
95 title={t('Scan QR code')}
96 >
97 <ScanLine className="h-4 w-4" />
98 </Button>
99 </div>
100 {errMsg && <div className="text-xs text-destructive">{errMsg}</div>}
101 </div>
102 <div className="grid gap-2">
103 <Label htmlFor="password-input">{t('password')}</Label>
104 <Input
105 id="password-input"
106 type="password"
107 placeholder={t('optional: encrypt nsec')}
108 value={password}
109 onChange={(e) => setPassword(e.target.value)}
110 />
111 </div>
112 <div className="flex gap-2">
113 <Button className="w-fit px-8" variant="secondary" type="button" onClick={back}>
114 {t('Back')}
115 </Button>
116 <Button className="flex-1" type="submit">
117 {t('Login')}
118 </Button>
119 </div>
120 </form>
121 </>
122 )
123 }
124
125 function NcryptsecLogin({
126 back,
127 onLoginSuccess
128 }: {
129 back: () => void
130 onLoginSuccess: () => void
131 }) {
132 const { t } = useTranslation()
133 const { ncryptsecLogin } = useNostr()
134 const [ncryptsec, setNcryptsec] = useState('')
135 const [errMsg, setErrMsg] = useState<string | null>(null)
136 const [showScanner, setShowScanner] = useState(false)
137
138 const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
139 setNcryptsec(e.target.value)
140 setErrMsg(null)
141 }
142
143 const handleScan = (result: string) => {
144 setNcryptsec(result)
145 setErrMsg(null)
146 }
147
148 const handleLogin = () => {
149 if (ncryptsec === '') return
150
151 ncryptsecLogin(ncryptsec)
152 .then(() => onLoginSuccess())
153 .catch((err) => {
154 setErrMsg(err.message)
155 })
156 }
157
158 return (
159 <>
160 {showScanner && (
161 <QrScannerModal onScan={handleScan} onClose={() => setShowScanner(false)} />
162 )}
163 <form
164 className="space-y-4"
165 onSubmit={(e) => {
166 e.preventDefault()
167 handleLogin()
168 }}
169 >
170 <div className="grid gap-2">
171 <Label htmlFor="ncryptsec-input">ncryptsec</Label>
172 <div className="flex gap-2">
173 <Input
174 id="ncryptsec-input"
175 type="password"
176 placeholder="ncryptsec1.."
177 value={ncryptsec}
178 onChange={handleInputChange}
179 className={errMsg ? 'border-destructive' : ''}
180 />
181 <Button
182 type="button"
183 variant="outline"
184 size="icon"
185 onClick={() => setShowScanner(true)}
186 title={t('Scan QR code')}
187 >
188 <ScanLine className="h-4 w-4" />
189 </Button>
190 </div>
191 {errMsg && <div className="text-xs text-destructive">{errMsg}</div>}
192 </div>
193 <div className="flex gap-2">
194 <Button className="w-fit px-8" variant="secondary" type="button" onClick={back}>
195 {t('Back')}
196 </Button>
197 <Button className="flex-1" type="submit">
198 {t('Login')}
199 </Button>
200 </div>
201 </form>
202 </>
203 )
204 }
205