feat: qrcode
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -31,6 +31,7 @@
|
|||||||
"lru-cache": "^11.0.1",
|
"lru-cache": "^11.0.1",
|
||||||
"lucide-react": "^0.453.0",
|
"lucide-react": "^0.453.0",
|
||||||
"nostr-tools": "^2.9.1",
|
"nostr-tools": "^2.9.1",
|
||||||
|
"qrcode.react": "^4.1.0",
|
||||||
"react-resizable-panels": "^2.1.5",
|
"react-resizable-panels": "^2.1.5",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
@@ -7885,6 +7886,14 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/qrcode.react": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-uqXVIIVD/IPgWLYxbOczCNAQw80XCM/LulYDADF+g2xDsPj5OoRwSWtIS4jGyp295wyjKstfG1qIv/I2/rNWpQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
"lru-cache": "^11.0.1",
|
"lru-cache": "^11.0.1",
|
||||||
"lucide-react": "^0.453.0",
|
"lucide-react": "^0.453.0",
|
||||||
"nostr-tools": "^2.9.1",
|
"nostr-tools": "^2.9.1",
|
||||||
|
"qrcode.react": "^4.1.0",
|
||||||
"react-resizable-panels": "^2.1.5",
|
"react-resizable-panels": "^2.1.5",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default function ShortTextNoteCard({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
className={`hover:bg-muted/50 text-left cursor-pointer ${size === 'normal' ? 'p-4' : 'p-2'}`}
|
className={`hover:bg-muted/50 text-left cursor-pointer ${size === 'normal' ? 'p-4' : 'p-3'}`}
|
||||||
>
|
>
|
||||||
<Note
|
<Note
|
||||||
size={size}
|
size={size}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
import { embeddedNpubRenderer } from '@renderer/embedded/EmbeddedNpub'
|
import { embeddedNpubRenderer } from '@renderer/embedded/EmbeddedNpub'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
export default function ProfileAbout({ about }: { about?: string }) {
|
export default function ProfileAbout({ about, className }: { about?: string; className?: string }) {
|
||||||
const nodes = useMemo(() => {
|
const nodes = useMemo(() => {
|
||||||
return about
|
return about
|
||||||
? embedded(about, [
|
? embedded(about, [
|
||||||
@@ -19,5 +19,5 @@ export default function ProfileAbout({ about }: { about?: string }) {
|
|||||||
: null
|
: null
|
||||||
}, [about])
|
}, [about])
|
||||||
|
|
||||||
return <>{nodes}</>
|
return <div className={className}>{nodes}</div>
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/renderer/src/pages/secondary/ProfilePage/PubkeyCopy.tsx
Normal file
27
src/renderer/src/pages/secondary/ProfilePage/PubkeyCopy.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { formatNpub } from '@renderer/lib/pubkey'
|
||||||
|
import { Check, Copy } from 'lucide-react'
|
||||||
|
import { nip19 } from 'nostr-tools'
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
export default function PubkeyCopy({ pubkey }: { pubkey: string }) {
|
||||||
|
const npub = useMemo(() => (pubkey ? nip19.npubEncode(pubkey) : ''), [pubkey])
|
||||||
|
const [copied, setCopied] = useState(false)
|
||||||
|
|
||||||
|
const copyNpub = () => {
|
||||||
|
if (!npub) return
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(npub)
|
||||||
|
setCopied(true)
|
||||||
|
setTimeout(() => setCopied(false), 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex gap-2 text-sm text-muted-foreground items-center bg-muted w-fit px-2 rounded-full hover:text-foreground cursor-pointer"
|
||||||
|
onClick={() => copyNpub()}
|
||||||
|
>
|
||||||
|
<div>{formatNpub(npub, 24)}</div>
|
||||||
|
{copied ? <Check size={14} /> : <Copy size={14} />}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover'
|
||||||
|
import { QrCode } from 'lucide-react'
|
||||||
|
import { nip19 } from 'nostr-tools'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { QRCodeSVG } from 'qrcode.react'
|
||||||
|
|
||||||
|
export default function QrCodePopover({ pubkey }: { pubkey: string }) {
|
||||||
|
const npub = useMemo(() => (pubkey ? nip19.npubEncode(pubkey) : ''), [pubkey])
|
||||||
|
if (!npub) return null
|
||||||
|
|
||||||
|
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}`} />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -9,33 +9,25 @@ import { useFetchFollowings, useFetchProfile } from '@renderer/hooks'
|
|||||||
import { useFetchRelayList } from '@renderer/hooks/useFetchRelayList'
|
import { useFetchRelayList } from '@renderer/hooks/useFetchRelayList'
|
||||||
import SecondaryPageLayout from '@renderer/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@renderer/layouts/SecondaryPageLayout'
|
||||||
import { toFollowingList } from '@renderer/lib/link'
|
import { toFollowingList } from '@renderer/lib/link'
|
||||||
import { formatNpub, generateImageByPubkey } from '@renderer/lib/pubkey'
|
import { generateImageByPubkey } from '@renderer/lib/pubkey'
|
||||||
import { SecondaryPageLink } from '@renderer/PageManager'
|
import { SecondaryPageLink } from '@renderer/PageManager'
|
||||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||||
import { Copy } from 'lucide-react'
|
import { useMemo } from 'react'
|
||||||
import { nip19 } from 'nostr-tools'
|
import PubkeyCopy from './PubkeyCopy'
|
||||||
import { useMemo, useState } from 'react'
|
import QrCodePopover from './QrCodePopover'
|
||||||
|
|
||||||
export default function ProfilePage({ pubkey }: { pubkey?: string }) {
|
export default function ProfilePage({ pubkey }: { pubkey?: string }) {
|
||||||
const { banner, username, nip05, about, avatar } = useFetchProfile(pubkey)
|
const { banner, username, nip05, about, avatar } = useFetchProfile(pubkey)
|
||||||
const relayList = useFetchRelayList(pubkey)
|
const relayList = useFetchRelayList(pubkey)
|
||||||
const [copied, setCopied] = useState(false)
|
|
||||||
const { pubkey: accountPubkey } = useNostr()
|
const { pubkey: accountPubkey } = useNostr()
|
||||||
const { followings } = useFetchFollowings(pubkey)
|
const { followings } = useFetchFollowings(pubkey)
|
||||||
const isFollowingYou = useMemo(
|
const isFollowingYou = useMemo(
|
||||||
() => !!accountPubkey && accountPubkey !== pubkey && followings.includes(accountPubkey),
|
() => !!accountPubkey && accountPubkey !== pubkey && followings.includes(accountPubkey),
|
||||||
[followings, pubkey]
|
[followings, pubkey]
|
||||||
)
|
)
|
||||||
const npub = useMemo(() => (pubkey ? nip19.npubEncode(pubkey) : undefined), [pubkey])
|
|
||||||
const defaultImage = useMemo(() => (pubkey ? generateImageByPubkey(pubkey) : ''), [pubkey])
|
const defaultImage = useMemo(() => (pubkey ? generateImageByPubkey(pubkey) : ''), [pubkey])
|
||||||
|
|
||||||
if (!pubkey || !npub) return null
|
if (!pubkey) return null
|
||||||
|
|
||||||
const copyNpub = () => {
|
|
||||||
navigator.clipboard.writeText(npub)
|
|
||||||
setCopied(true)
|
|
||||||
setTimeout(() => setCopied(false), 2000)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout titlebarContent={username}>
|
<SecondaryPageLayout titlebarContent={username}>
|
||||||
@@ -63,22 +55,11 @@ export default function ProfilePage({ pubkey }: { pubkey?: string }) {
|
|||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<div className="text-xl font-semibold">{username}</div>
|
<div className="text-xl font-semibold">{username}</div>
|
||||||
{nip05 && <Nip05 nip05={nip05} pubkey={pubkey} />}
|
{nip05 && <Nip05 nip05={nip05} pubkey={pubkey} />}
|
||||||
<div
|
<div className="flex gap-1 mt-1">
|
||||||
className="mt-1 flex gap-2 text-sm text-muted-foreground items-center bg-muted w-fit px-2 rounded-full hover:text-foreground cursor-pointer"
|
<PubkeyCopy pubkey={pubkey} />
|
||||||
onClick={() => copyNpub()}
|
<QrCodePopover pubkey={pubkey} />
|
||||||
>
|
|
||||||
{copied ? (
|
|
||||||
<div>copied!</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div>{formatNpub(npub, 24)}</div>
|
|
||||||
<Copy size={14} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="text-wrap break-words whitespace-pre-wrap mt-2">
|
|
||||||
<ProfileAbout about={about} />
|
|
||||||
</div>
|
</div>
|
||||||
|
<ProfileAbout about={about} className="text-wrap break-words whitespace-pre-wrap mt-2" />
|
||||||
<SecondaryPageLink
|
<SecondaryPageLink
|
||||||
to={toFollowingList(pubkey)}
|
to={toFollowingList(pubkey)}
|
||||||
className="mt-2 flex gap-1 hover:underline text-sm"
|
className="mt-2 flex gap-1 hover:underline text-sm"
|
||||||
|
|||||||
Reference in New Issue
Block a user