index.tsx raw
1 import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'
2 import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer'
3 import { useScreenSize } from '@/providers/ScreenSizeProvider'
4 import { QrCodeIcon } from 'lucide-react'
5 import { nip19 } from 'nostr-tools'
6 import { useCallback, useMemo, useState } from 'react'
7 import { useTranslation } from 'react-i18next'
8 import { toast } from 'sonner'
9 import Nip05 from '../Nip05'
10 import QrCode from '../QrCode'
11 import UserAvatar from '../UserAvatar'
12 import Username from '../Username'
13
14 export default function NpubQrCode({ pubkey }: { pubkey: string }) {
15 const { t } = useTranslation()
16 const { isSmallScreen } = useScreenSize()
17 const [open, setOpen] = useState(false)
18 const npub = useMemo(() => {
19 // Validate pubkey is a 64-character hex string before encoding
20 if (!pubkey || !/^[0-9a-f]{64}$/i.test(pubkey)) return ''
21 try {
22 return nip19.npubEncode(pubkey)
23 } catch {
24 return ''
25 }
26 }, [pubkey])
27
28 const handleQrClick = useCallback(() => {
29 navigator.clipboard.writeText(npub)
30 toast.success(t('Copied npub to clipboard'))
31 setOpen(false)
32 }, [npub, t])
33
34 if (!npub) return null
35
36 const trigger = (
37 <button
38 className="bg-muted rounded-full h-5 w-5 flex flex-col items-center justify-center text-muted-foreground hover:text-foreground"
39 onClick={(e) => e.stopPropagation()}
40 >
41 <QrCodeIcon size={14} />
42 </button>
43 )
44
45 const content = (
46 <div className="w-full flex flex-col items-center gap-4 p-8">
47 <div className="flex items-center w-full gap-2 pointer-events-none px-1">
48 <UserAvatar size="big" userId={pubkey} />
49 <div className="flex-1 w-0">
50 <Username userId={pubkey} className="text-2xl font-semibold truncate" showQrCode={false} />
51 <Nip05 pubkey={pubkey} />
52 </div>
53 </div>
54 <button
55 onClick={handleQrClick}
56 className="cursor-pointer hover:opacity-90 transition-opacity"
57 title={t('Click to copy npub')}
58 >
59 <QrCode size={512} value={`nostr:${npub}`} />
60 </button>
61 <div className="text-sm text-muted-foreground">{t('Click QR code to copy npub')}</div>
62 </div>
63 )
64
65 if (isSmallScreen) {
66 return (
67 <Drawer open={open} onOpenChange={setOpen}>
68 <DrawerTrigger asChild>{trigger}</DrawerTrigger>
69 <DrawerContent>{content}</DrawerContent>
70 </Drawer>
71 )
72 }
73
74 return (
75 <Dialog open={open} onOpenChange={setOpen}>
76 <DialogTrigger asChild>{trigger}</DialogTrigger>
77 <DialogContent className="w-80 p-0 m-0" onOpenAutoFocus={(e) => e.preventDefault()}>
78 {content}
79 </DialogContent>
80 </Dialog>
81 )
82 }
83