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