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