feat: improve user npub QR code card
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { Loader, Copy, Check } from 'lucide-react'
|
||||
import { createNostrConnectURI, NostrConnectParams } from '@/providers/NostrProvider/nip46'
|
||||
import { DEFAULT_NOSTRCONNECT_RELAY } from '@/constants'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { createNostrConnectURI, NostrConnectParams } from '@/providers/NostrProvider/nip46'
|
||||
import { Check, Copy, Loader } from 'lucide-react'
|
||||
import { generateSecretKey, getPublicKey } from 'nostr-tools'
|
||||
import { QRCodeSVG } from 'qrcode.react'
|
||||
import { useState, useEffect, useRef, useLayoutEffect } from 'react'
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import QrCode from '../QrCode'
|
||||
|
||||
export default function NostrConnectLogin({
|
||||
back,
|
||||
@@ -107,12 +107,7 @@ export default function NostrConnectLogin({
|
||||
<>
|
||||
<div ref={qrContainerRef} className="flex flex-col items-center w-full space-y-3 mb-3">
|
||||
<a href={loginDetails.connectionString} aria-label="Open with Nostr signer app">
|
||||
<QRCodeSVG
|
||||
size={qrCodeSize}
|
||||
value={loginDetails.connectionString}
|
||||
bgColor="hsl(var(--background))"
|
||||
fgColor="hsl(var(--foreground))"
|
||||
/>
|
||||
<QrCode size={qrCodeSize} value={loginDetails.connectionString} />
|
||||
</a>
|
||||
{nostrConnectionErrMsg && (
|
||||
<div className="text-xs text-destructive text-center pt-1">{nostrConnectionErrMsg}</div>
|
||||
|
||||
57
src/components/NpubQrCode/index.tsx
Normal file
57
src/components/NpubQrCode/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { QrCodeIcon } from 'lucide-react'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import Nip05 from '../Nip05'
|
||||
import PubkeyCopy from '../PubkeyCopy'
|
||||
import QrCode from '../QrCode'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
|
||||
export default function NpubQrCode({ pubkey }: { pubkey: string }) {
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const npub = useMemo(() => (pubkey ? nip19.npubEncode(pubkey) : ''), [pubkey])
|
||||
if (!npub) return null
|
||||
|
||||
const trigger = (
|
||||
<div className="bg-muted rounded-full h-5 w-5 flex flex-col items-center justify-center text-muted-foreground hover:text-foreground">
|
||||
<QrCodeIcon size={14} />
|
||||
</div>
|
||||
)
|
||||
|
||||
const content = (
|
||||
<div className="w-full flex flex-col items-center gap-4 p-8">
|
||||
<div className="flex items-center w-full gap-2 pointer-events-none px-1">
|
||||
<UserAvatar size="semiBig" userId={pubkey} />
|
||||
<div className="flex-1 w-0">
|
||||
<Username userId={pubkey} className="text-xl font-semibold truncate" />
|
||||
<Nip05 pubkey={pubkey} />
|
||||
</div>
|
||||
</div>
|
||||
<QrCode size={512} value={`nostr:${npub}`} />
|
||||
<div className="flex flex-col items-center">
|
||||
<PubkeyCopy pubkey={pubkey} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<Drawer>
|
||||
<DrawerTrigger>{trigger}</DrawerTrigger>
|
||||
<DrawerContent>{content}</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger>{trigger}</DialogTrigger>
|
||||
<DialogContent className="w-80 p-0 m-0" onOpenAutoFocus={(e) => e.preventDefault()}>
|
||||
{content}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
70
src/components/QrCode/index.tsx
Normal file
70
src/components/QrCode/index.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { useTheme } from '@/providers/ThemeProvider'
|
||||
import QRCodeStyling from 'qr-code-styling'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
export default function QrCode({ value, size = 180 }: { value: string; size?: number }) {
|
||||
const { theme } = useTheme()
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [foregroundColor, setForegroundColor] = useState<string | undefined>()
|
||||
const [backgroundColor, setBackgroundColor] = useState<string | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
const fgColor = `hsl(${getColor('foreground')})`
|
||||
const bgColor = `hsl(${getColor('background')})`
|
||||
setForegroundColor(fgColor)
|
||||
setBackgroundColor(bgColor)
|
||||
}, 0)
|
||||
}, [theme])
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
const pixelRatio = window.devicePixelRatio || 2
|
||||
|
||||
const qrCode = new QRCodeStyling({
|
||||
width: size * pixelRatio,
|
||||
height: size * pixelRatio,
|
||||
data: value,
|
||||
dotsOptions: {
|
||||
type: 'dots',
|
||||
color: foregroundColor
|
||||
},
|
||||
cornersDotOptions: {
|
||||
type: 'extra-rounded',
|
||||
color: foregroundColor
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: 'extra-rounded',
|
||||
color: foregroundColor
|
||||
},
|
||||
backgroundOptions: {
|
||||
color: backgroundColor
|
||||
}
|
||||
})
|
||||
|
||||
if (ref.current) {
|
||||
ref.current.innerHTML = ''
|
||||
qrCode.append(ref.current)
|
||||
const canvas = ref.current.querySelector('canvas')
|
||||
if (canvas) {
|
||||
canvas.style.width = `${size}px`
|
||||
canvas.style.height = `${size}px`
|
||||
canvas.style.maxWidth = '100%'
|
||||
canvas.style.height = 'auto'
|
||||
}
|
||||
}
|
||||
}, 0)
|
||||
|
||||
return () => {
|
||||
if (ref.current) ref.current.innerHTML = ''
|
||||
}
|
||||
}, [value, size, foregroundColor, backgroundColor])
|
||||
|
||||
return <div ref={ref} />
|
||||
}
|
||||
|
||||
function getColor(name: string) {
|
||||
if (typeof window !== 'undefined') {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue(`--${name}`).trim()
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { QrCode } from 'lucide-react'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { QRCodeSVG } from 'qrcode.react'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function QrCodePopover({ pubkey }: { pubkey: string }) {
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const npub = useMemo(() => (pubkey ? nip19.npubEncode(pubkey) : ''), [pubkey])
|
||||
if (!npub) return null
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<Drawer>
|
||||
<DrawerTrigger>
|
||||
<div className="bg-muted rounded-full h-5 w-5 flex flex-col items-center justify-center text-muted-foreground hover:text-foreground">
|
||||
<QrCode size={14} />
|
||||
</div>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="h-1/2">
|
||||
<div className="flex justify-center items-center h-full">
|
||||
<QRCodeSVG
|
||||
size={300}
|
||||
value={`nostr:${npub}`}
|
||||
bgColor="hsl(var(--background))"
|
||||
fgColor="hsl(var(--foreground))"
|
||||
/>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<div className="bg-muted rounded-full h-5 w-5 flex flex-col items-center justify-center text-muted-foreground hover:text-foreground">
|
||||
<QrCode size={14} />
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-fit h-fit">
|
||||
<QRCodeSVG
|
||||
value={`nostr:${npub}`}
|
||||
bgColor="hsl(var(--background))"
|
||||
fgColor="hsl(var(--foreground))"
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { useMemo } from 'react'
|
||||
const UserAvatarSizeCnMap = {
|
||||
large: 'w-24 h-24',
|
||||
big: 'w-16 h-16',
|
||||
semiBig: 'w-12 h-12',
|
||||
normal: 'w-10 h-10',
|
||||
medium: 'w-8 h-8',
|
||||
small: 'w-7 h-7',
|
||||
@@ -26,7 +27,7 @@ export default function UserAvatar({
|
||||
}: {
|
||||
userId: string
|
||||
className?: string
|
||||
size?: 'large' | 'big' | 'normal' | 'medium' | 'small' | 'xSmall' | 'tiny'
|
||||
size?: 'large' | 'big' | 'semiBig' | 'normal' | 'medium' | 'small' | 'xSmall' | 'tiny'
|
||||
}) {
|
||||
const { profile } = useFetchProfile(userId)
|
||||
const defaultAvatar = useMemo(
|
||||
|
||||
@@ -2,7 +2,7 @@ import AccountManager from '@/components/AccountManager'
|
||||
import LoginDialog from '@/components/LoginDialog'
|
||||
import LogoutDialog from '@/components/LogoutDialog'
|
||||
import PubkeyCopy from '@/components/PubkeyCopy'
|
||||
import QrCodePopover from '@/components/QrCodePopover'
|
||||
import NpubQrCode from '@/components/NpubQrCode'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { SimpleUserAvatar } from '@/components/UserAvatar'
|
||||
@@ -53,7 +53,7 @@ const MePage = forwardRef((_, ref) => {
|
||||
/>
|
||||
<div className="flex gap-1 mt-1">
|
||||
<PubkeyCopy pubkey={pubkey} />
|
||||
<QrCodePopover pubkey={pubkey} />
|
||||
<NpubQrCode pubkey={pubkey} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import ProfileBanner from '@/components/ProfileBanner'
|
||||
import ProfileOptions from '@/components/ProfileOptions'
|
||||
import ProfileZapButton from '@/components/ProfileZapButton'
|
||||
import PubkeyCopy from '@/components/PubkeyCopy'
|
||||
import QrCodePopover from '@/components/QrCodePopover'
|
||||
import NpubQrCode from '@/components/NpubQrCode'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
@@ -157,7 +157,7 @@ const ProfilePage = forwardRef(({ id, index }: { id?: string; index?: number },
|
||||
)}
|
||||
<div className="flex gap-1 mt-1">
|
||||
<PubkeyCopy pubkey={pubkey} />
|
||||
<QrCodePopover pubkey={pubkey} />
|
||||
<NpubQrCode pubkey={pubkey} />
|
||||
</div>
|
||||
<Collapsible>
|
||||
<ProfileAbout
|
||||
|
||||
@@ -9,6 +9,7 @@ type ThemeProviderProps = {
|
||||
|
||||
type ThemeProviderState = {
|
||||
themeSetting: TThemeSetting
|
||||
theme: TTheme
|
||||
setThemeSetting: (themeSetting: TThemeSetting) => Promise<void>
|
||||
}
|
||||
|
||||
@@ -62,21 +63,23 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
updateTheme()
|
||||
}, [theme])
|
||||
|
||||
const value = {
|
||||
themeSetting: themeSetting,
|
||||
setThemeSetting: async (themeSetting: TThemeSetting) => {
|
||||
storage.setThemeSetting(themeSetting)
|
||||
setThemeSetting(themeSetting)
|
||||
if (themeSetting === 'system') {
|
||||
setTheme(getSystemTheme())
|
||||
return
|
||||
}
|
||||
setTheme(themeSetting)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProviderContext.Provider {...props} value={value}>
|
||||
<ThemeProviderContext.Provider
|
||||
{...props}
|
||||
value={{
|
||||
themeSetting: themeSetting,
|
||||
theme: theme,
|
||||
setThemeSetting: async (themeSetting: TThemeSetting) => {
|
||||
storage.setThemeSetting(themeSetting)
|
||||
setThemeSetting(themeSetting)
|
||||
if (themeSetting === 'system') {
|
||||
setTheme(getSystemTheme())
|
||||
return
|
||||
}
|
||||
setTheme(themeSetting)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ThemeProviderContext.Provider>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user