feat: multi accounts

This commit is contained in:
codytseng
2024-12-23 23:22:49 +08:00
parent ee0c702135
commit 33ac5e60b6
17 changed files with 426 additions and 137 deletions

View File

@@ -0,0 +1,56 @@
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { useNostr } from '@/providers/NostrProvider'
import { Loader } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
export default function BunkerLogin({
back,
onLoginSuccess
}: {
back: () => void
onLoginSuccess: () => void
}) {
const { t } = useTranslation()
const { bunkerLogin } = useNostr()
const [pending, setPending] = useState(false)
const [bunkerInput, setBunkerInput] = useState('')
const [errMsg, setErrMsg] = useState<string | null>(null)
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setBunkerInput(e.target.value)
setErrMsg(null)
}
const handleLogin = () => {
if (bunkerInput === '') return
setPending(true)
bunkerLogin(bunkerInput)
.then(() => onLoginSuccess())
.catch((err) => setErrMsg(err.message))
.finally(() => setPending(false))
}
return (
<>
<div className="space-y-1">
<Input
placeholder="bunker://..."
value={bunkerInput}
onChange={handleInputChange}
className={errMsg ? 'border-destructive' : ''}
/>
{errMsg && <div className="text-xs text-destructive pl-3">{errMsg}</div>}
</div>
<Button onClick={handleLogin} disabled={pending}>
<Loader className={pending ? 'animate-spin' : 'hidden'} />
{t('Login')}
</Button>
<Button variant="secondary" onClick={back}>
{t('Back')}
</Button>
</>
)
}

View File

@@ -0,0 +1,57 @@
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { useNostr } from '@/providers/NostrProvider'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
export default function PrivateKeyLogin({
back,
onLoginSuccess
}: {
back: () => void
onLoginSuccess: () => void
}) {
const { t } = useTranslation()
const { nsecLogin } = useNostr()
const [nsec, setNsec] = useState('')
const [errMsg, setErrMsg] = useState<string | null>(null)
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNsec(e.target.value)
setErrMsg(null)
}
const handleLogin = () => {
if (nsec === '') return
nsecLogin(nsec)
.then(() => onLoginSuccess())
.catch((err) => {
setErrMsg(err.message)
})
}
return (
<>
<div className="text-orange-400">
{t(
'Using private key login is insecure. It is recommended to use a browser extension for login, such as alby, nostr-keyx or nos2x.'
)}
</div>
<div className="space-y-1">
<Input
type="password"
placeholder="nsec1.."
value={nsec}
onChange={handleInputChange}
className={errMsg ? 'border-destructive' : ''}
/>
{errMsg && <div className="text-xs text-destructive pl-3">{errMsg}</div>}
</div>
<Button onClick={handleLogin}>{t('Login')}</Button>
<Button variant="secondary" onClick={back}>
{t('Back')}
</Button>
</>
)
}

View File

@@ -0,0 +1,64 @@
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { useNostr } from '@/providers/NostrProvider'
import { TSignerType } from '@/types'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import AccountList from '../AccountList'
import BunkerLogin from './BunkerLogin'
import PrivateKeyLogin from './NsecLogin'
export default function AccountManager({ close }: { close: () => void }) {
const [loginMethod, setLoginMethod] = useState<TSignerType | null>(null)
return (
<>
{loginMethod === 'nsec' ? (
<PrivateKeyLogin back={() => setLoginMethod(null)} onLoginSuccess={() => close()} />
) : loginMethod === 'bunker' ? (
<BunkerLogin back={() => setLoginMethod(null)} onLoginSuccess={() => close()} />
) : (
<AccountManagerNav setLoginMethod={setLoginMethod} close={close} />
)}
</>
)
}
function AccountManagerNav({
setLoginMethod,
close
}: {
setLoginMethod: (method: TSignerType) => void
close: () => void
}) {
const { t } = useTranslation()
const { nip07Login, accounts } = useNostr()
return (
<>
<div className="text-center text-muted-foreground text-sm font-semibold">
{t('Add an Account')}
</div>
{!!window.nostr && (
<Button onClick={() => nip07Login().then(() => close())} className="w-full">
{t('Login with Browser Extension')}
</Button>
)}
<Button variant="secondary" onClick={() => setLoginMethod('bunker')} className="w-full">
{t('Login with Bunker')}
</Button>
<Button variant="secondary" onClick={() => setLoginMethod('nsec')} className="w-full">
{t('Login with Private Key')}
</Button>
{accounts.length > 0 && (
<>
<Separator />
<div className="text-center text-muted-foreground text-sm font-semibold">
{t('Logged in Accounts')}
</div>
<AccountList afterSwitch={() => close()} />
</>
)}
</>
)
}