From 7a097d88112aec273af8b40a93a57ba27143bd39 Mon Sep 17 00:00:00 2001 From: Chad Welch Date: Wed, 22 Mar 2023 12:48:02 -0700 Subject: [PATCH 1/8] receiving profiles, but no storage --- .gitignore | 2 + components/LandingLayout.tsx | 17 +++++-- hooks/useProfile.ts | 32 ++++++++++++++ lib/nostr.ts | 86 ++++++++++++++++++++++++++++++++++++ pages/_app.tsx | 4 +- pages/test.tsx | 5 +++ 6 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 hooks/useProfile.ts create mode 100644 lib/nostr.ts create mode 100644 pages/test.tsx diff --git a/.gitignore b/.gitignore index c87c9b3..7a16b18 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +temp diff --git a/components/LandingLayout.tsx b/components/LandingLayout.tsx index 38ed62a..f1710ce 100644 --- a/components/LandingLayout.tsx +++ b/components/LandingLayout.tsx @@ -3,7 +3,8 @@ import Link from "next/link"; import React from "react"; import { Comfortaa, Source_Sans_Pro } from "next/font/google"; import { Bars3Icon } from "@heroicons/react/24/solid"; -import { useProfile } from "nostr-react"; +// import { useProfile } from "nostr-react"; +import useProfile from "@/hooks/useProfile"; import { usePubkey } from "@/context/pubkey"; import { nip19 } from "nostr-tools"; @@ -14,9 +15,17 @@ interface LandingLayoutProps { } const PubkeyNavMenu = ({ pubkey }: { pubkey: string }) => { - const { data: userData, isLoading } = useProfile({ - pubkey, - }); + // const { data: userData, isLoading } = useProfile({ + // pubkey, + // }); + + const profile = useProfile(pubkey) + + const isLoading = true + const userData = { + name: 'chad', + picture: 'chad', + } return ( <> diff --git a/hooks/useProfile.ts b/hooks/useProfile.ts new file mode 100644 index 0000000..8cead8d --- /dev/null +++ b/hooks/useProfile.ts @@ -0,0 +1,32 @@ +import { useEffect } from "react" +import { nostrClient } from "@/lib/nostr" + +export interface UserMetadata { + name?: string + pubkey?: string + npub?: string + display_name?: string + picture?: string + about?: string + website?: string + banner?: string + lud06?: string + lud16?: string + nip05?: string +} + +// TODO: need global store of queue, profiles + +export default function useProfile(pubkey: string | null) { + useEffect(() => { + if (!pubkey) return + + nostrClient.addProfileToFetch(pubkey) + + return () => { + nostrClient.removeProfileToFetch(pubkey) + } + }, [pubkey]) + + return null +} \ No newline at end of file diff --git a/lib/nostr.ts b/lib/nostr.ts new file mode 100644 index 0000000..c7710e7 --- /dev/null +++ b/lib/nostr.ts @@ -0,0 +1,86 @@ +import { SimplePool, Filter, nip19, Event } from "nostr-tools"; + +export interface UserMetadata { + name?: string + display_name?: string + picture?: string + about?: string + website?: string + banner?: string + lud06?: string + lud16?: string + nip05?: string +} + +export type UserMetadataStore = UserMetadata & { + pubkey?: string + npub?: string +} + +const defaultProfileRelays = [ + "wss://nostr.terminus.money", + "wss://brb.io", + "wss://nostr.wine", + "wss://relay.snort.social", + "wss://gratten.duckdns.org/nostrrelay/relay2", +] + +class NostrClient { + pool = new SimplePool() + profileQueue: Set = new Set() // set of hex public keys to query + paused: boolean = false + + addProfileToFetch(pubkey: string) { + this.profileQueue.add(pubkey) + this._fetchPubkeys() + } + + removeProfileToFetch(pubkey: string) { + this.profileQueue.delete(pubkey) + } + _fetchPubkeys() { + if (this.paused || this.profileQueue.size === 0) return + + const filters: Filter[] = [ + { + kinds: [0], + authors: Array.from(this.profileQueue), + }, + ] + + console.debug('subscribing for pubkeys: ', Array.from(this.profileQueue)) + + // TODO: subscribe w/ pool + const sub = this.pool.sub(defaultProfileRelays, filters) + + // How to handle this? Want the latest created_at profile event to be used. + // Each relay can return a different/outdated event + sub.on('event', event => { + console.debug('event', event) + + const metadataToStore: UserMetadataStore = { + ...JSON.parse(event.content), + pubkey: event.pubkey, + npub: nip19.npubEncode(event.pubkey), + } + + // TODO: insert into store or db + console.debug('storing profile metadata: ', metadataToStore) + }) + + + this.profileQueue.forEach(pubkey => this.profileQueue.delete(pubkey)) + + // limit amount of subs to one per 500ms + this.paused = true + setTimeout(() => { + this.paused = false + + // call it again in case new keys came in... + this._fetchPubkeys() + }, 500) + } + +} + +export const nostrClient = new NostrClient() diff --git a/pages/_app.tsx b/pages/_app.tsx index 755a984..70ff76e 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -16,10 +16,10 @@ const relays = [ export default function App({ Component, pageProps }: AppProps) { return ( - + // - + // ); } diff --git a/pages/test.tsx b/pages/test.tsx new file mode 100644 index 0000000..80501bd --- /dev/null +++ b/pages/test.tsx @@ -0,0 +1,5 @@ +export default function Test() { + return ( +
+ ) +} \ No newline at end of file From 39a9c803f43627ae2da213e17c2adae269f166af Mon Sep 17 00:00:00 2001 From: Chad Welch Date: Wed, 22 Mar 2023 13:06:23 -0700 Subject: [PATCH 2/8] intial add local storage DB dexie --- lib/nostr.ts | 26 ++++++++++++++++++++++---- package.json | 1 + store/dexieDb.ts | 37 +++++++++++++++++++++++++++++++++++++ yarn.lock | 5 +++++ 4 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 store/dexieDb.ts diff --git a/lib/nostr.ts b/lib/nostr.ts index c7710e7..37852e9 100644 --- a/lib/nostr.ts +++ b/lib/nostr.ts @@ -1,4 +1,5 @@ import { SimplePool, Filter, nip19, Event } from "nostr-tools"; +import db from '@/store/dexieDb' export interface UserMetadata { name?: string @@ -13,8 +14,9 @@ export interface UserMetadata { } export type UserMetadataStore = UserMetadata & { - pubkey?: string - npub?: string + pubkey: string + npub: string + created_at: number } const defaultProfileRelays = [ @@ -55,17 +57,33 @@ class NostrClient { // How to handle this? Want the latest created_at profile event to be used. // Each relay can return a different/outdated event - sub.on('event', event => { + sub.on('event', async (event) => { console.debug('event', event) const metadataToStore: UserMetadataStore = { ...JSON.parse(event.content), pubkey: event.pubkey, npub: nip19.npubEncode(event.pubkey), + created_at: event.created_at } - // TODO: insert into store or db + // TODO: insert into store or db. + // Really only want to save if the created_at is more recent than + // what is existing console.debug('storing profile metadata: ', metadataToStore) + // if metadataToStore.created_at > whatever is stored... + // store it + const existingProfile = await db.users.get(metadataToStore.pubkey) + if (!existingProfile) { + await db.users.put(metadataToStore) + return + } + + if (existingProfile.created_at > metadataToStore.created_at) { + return + } + + await db.users.put(metadataToStore) }) diff --git a/package.json b/package.json index 001e1d7..364d7ff 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@types/node": "18.15.3", "@types/react": "18.0.28", "@types/react-dom": "18.0.11", + "dexie": "^3.2.3", "eslint": "8.36.0", "eslint-config-next": "13.2.4", "next": "13.2.4", diff --git a/store/dexieDb.ts b/store/dexieDb.ts new file mode 100644 index 0000000..9368575 --- /dev/null +++ b/store/dexieDb.ts @@ -0,0 +1,37 @@ +import Dexie, { Table } from 'dexie' + +interface UserMetadata { + name?: string + display_name?: string + picture?: string + about?: string + website?: string + banner?: string + lud06?: string + lud16?: string + nip05?: string +} + +type UserMetadataStore = UserMetadata & { + pubkey?: string + npub?: string + created_at: number +} + +class DexieDB extends Dexie { + users!: Table + + constructor() { + super('DexieDB') + //Writing this because there have been some issues on github where people index images or movies + // without really understanding the purpose of indexing fields. + // A rule of thumb: Are you going to put your property in a where(‘…’) clause? + // If yes, index it, if not, dont. Large indexes will affect database performance and in + // extreme cases make it unstable. + this.version(1).stores({ + users: '++pubkey, name, npub, nip05', // Primary key and indexed props + }) + } +} + +export default new DexieDB() \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 9544624..7b1a990 100644 --- a/yarn.lock +++ b/yarn.lock @@ -913,6 +913,11 @@ detective@^5.2.1: defined "^1.0.0" minimist "^1.2.6" +dexie@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.3.tgz#f35c91ca797599df8e771b998e9ae9669c877f8c" + integrity sha512-iHayBd4UYryDCVUNa3PMsJMEnd8yjyh5p7a+RFeC8i8n476BC9wMhVvqiImq5zJZJf5Tuer+s4SSj+AA3x+ZbQ== + didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz" From a5ebdd1ec2650b0ba21adda830aa9a4b3501da04 Mon Sep 17 00:00:00 2001 From: Chad Welch Date: Wed, 22 Mar 2023 13:09:43 -0700 Subject: [PATCH 3/8] clean existing profile check --- lib/nostr.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/lib/nostr.ts b/lib/nostr.ts index 37852e9..c0dbef0 100644 --- a/lib/nostr.ts +++ b/lib/nostr.ts @@ -67,23 +67,12 @@ class NostrClient { created_at: event.created_at } - // TODO: insert into store or db. - // Really only want to save if the created_at is more recent than - // what is existing - console.debug('storing profile metadata: ', metadataToStore) - // if metadataToStore.created_at > whatever is stored... - // store it const existingProfile = await db.users.get(metadataToStore.pubkey) - if (!existingProfile) { - await db.users.put(metadataToStore) - return - } - if (existingProfile.created_at > metadataToStore.created_at) { - return + if (!existingProfile || (metadataToStore.created_at > existingProfile.created_at)) { + console.debug('storing profile metadata: ', metadataToStore) + await db.users.put(metadataToStore) } - - await db.users.put(metadataToStore) }) From e43d348fd4e365ac21ed1bc0785af536ffb2129d Mon Sep 17 00:00:00 2001 From: Chad Welch Date: Wed, 22 Mar 2023 13:19:03 -0700 Subject: [PATCH 4/8] add dexie live query to useProfile hook --- hooks/useProfile.ts | 13 ++++++++++++- lib/nostr.ts | 10 +++++----- package.json | 1 + yarn.lock | 5 +++++ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/hooks/useProfile.ts b/hooks/useProfile.ts index 8cead8d..474a3c1 100644 --- a/hooks/useProfile.ts +++ b/hooks/useProfile.ts @@ -1,5 +1,7 @@ import { useEffect } from "react" import { nostrClient } from "@/lib/nostr" +import dexieDb from "@/store/dexieDb" +import { useLiveQuery } from 'dexie-react-hooks' export interface UserMetadata { name?: string @@ -18,6 +20,15 @@ export interface UserMetadata { // TODO: need global store of queue, profiles export default function useProfile(pubkey: string | null) { + const profile = useLiveQuery(async () => { + if (!pubkey) return undefined + + console.debug('useLiveQuery: ', pubkey) + const ret = await dexieDb.users.get(pubkey) + console.debug('live query res: ', ret) + return ret + }, [pubkey]) + useEffect(() => { if (!pubkey) return @@ -28,5 +39,5 @@ export default function useProfile(pubkey: string | null) { } }, [pubkey]) - return null + return profile } \ No newline at end of file diff --git a/lib/nostr.ts b/lib/nostr.ts index c0dbef0..37134cf 100644 --- a/lib/nostr.ts +++ b/lib/nostr.ts @@ -1,5 +1,5 @@ import { SimplePool, Filter, nip19, Event } from "nostr-tools"; -import db from '@/store/dexieDb' +import dexieDb from '@/store/dexieDb' export interface UserMetadata { name?: string @@ -57,8 +57,8 @@ class NostrClient { // How to handle this? Want the latest created_at profile event to be used. // Each relay can return a different/outdated event - sub.on('event', async (event) => { - console.debug('event', event) + sub.on('event', async (event: Event) => { + console.debug('got event', event) const metadataToStore: UserMetadataStore = { ...JSON.parse(event.content), @@ -67,11 +67,11 @@ class NostrClient { created_at: event.created_at } - const existingProfile = await db.users.get(metadataToStore.pubkey) + const existingProfile = await dexieDb.users.get(metadataToStore.pubkey) if (!existingProfile || (metadataToStore.created_at > existingProfile.created_at)) { console.debug('storing profile metadata: ', metadataToStore) - await db.users.put(metadataToStore) + await dexieDb.users.put(metadataToStore) } }) diff --git a/package.json b/package.json index 364d7ff..cc17f0b 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@types/react": "18.0.28", "@types/react-dom": "18.0.11", "dexie": "^3.2.3", + "dexie-react-hooks": "^1.1.3", "eslint": "8.36.0", "eslint-config-next": "13.2.4", "next": "13.2.4", diff --git a/yarn.lock b/yarn.lock index 7b1a990..652b130 100644 --- a/yarn.lock +++ b/yarn.lock @@ -913,6 +913,11 @@ detective@^5.2.1: defined "^1.0.0" minimist "^1.2.6" +dexie-react-hooks@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dexie-react-hooks/-/dexie-react-hooks-1.1.3.tgz#dfcd723d533172605f06335823b205adf7442cc6" + integrity sha512-bXXE1gfYtfuVYTNiOlyam+YVaO8KaqacgRuxFuP37YtpS6o/jxT6KOl5h+hhqY36s0UavlHWbL+HWJFMcQumIg== + dexie@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.3.tgz#f35c91ca797599df8e771b998e9ae9669c877f8c" From 7584156edb782f7444693eb43a8722bf5b82697d Mon Sep 17 00:00:00 2001 From: Chad Welch Date: Wed, 22 Mar 2023 13:24:31 -0700 Subject: [PATCH 5/8] useProfile way faster now --- components/LandingLayout.tsx | 21 +++++---------------- hooks/useProfile.ts | 17 ++++++++++------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/components/LandingLayout.tsx b/components/LandingLayout.tsx index f1710ce..f109bea 100644 --- a/components/LandingLayout.tsx +++ b/components/LandingLayout.tsx @@ -3,7 +3,6 @@ import Link from "next/link"; import React from "react"; import { Comfortaa, Source_Sans_Pro } from "next/font/google"; import { Bars3Icon } from "@heroicons/react/24/solid"; -// import { useProfile } from "nostr-react"; import useProfile from "@/hooks/useProfile"; import { usePubkey } from "@/context/pubkey"; import { nip19 } from "nostr-tools"; @@ -15,29 +14,19 @@ interface LandingLayoutProps { } const PubkeyNavMenu = ({ pubkey }: { pubkey: string }) => { - // const { data: userData, isLoading } = useProfile({ - // pubkey, - // }); - - const profile = useProfile(pubkey) - - const isLoading = true - const userData = { - name: 'chad', - picture: 'chad', - } + const [profile, isLoading] = useProfile(pubkey) return ( <> {!isLoading && (
  • - {userData?.name - ? userData.name + {profile?.name + ? profile.name : nip19.npubEncode(pubkey).slice(0, 12)}
  • )} - {userData?.picture ? ( - + {profile?.picture ? ( + ) : (
  • diff --git a/hooks/useProfile.ts b/hooks/useProfile.ts index 474a3c1..54c5069 100644 --- a/hooks/useProfile.ts +++ b/hooks/useProfile.ts @@ -19,15 +19,18 @@ export interface UserMetadata { // TODO: need global store of queue, profiles -export default function useProfile(pubkey: string | null) { - const profile = useLiveQuery(async () => { - if (!pubkey) return undefined +export default function useProfile(pubkey: string | null): [UserMetadata | undefined, boolean] { + const [profile, isLoading] = useLiveQuery(async () => { + if (!pubkey) return [undefined, false] - console.debug('useLiveQuery: ', pubkey) const ret = await dexieDb.users.get(pubkey) console.debug('live query res: ', ret) - return ret - }, [pubkey]) + + return [ret, false] + }, + [pubkey], + [undefined, true] // default result returned on initial render. + ) useEffect(() => { if (!pubkey) return @@ -39,5 +42,5 @@ export default function useProfile(pubkey: string | null) { } }, [pubkey]) - return profile + return [profile, isLoading] } \ No newline at end of file From 691d6931f5dcc1fc555c6c7d5ca7ca9bcc7e7b14 Mon Sep 17 00:00:00 2001 From: Chad Welch Date: Wed, 22 Mar 2023 13:25:38 -0700 Subject: [PATCH 6/8] remove nostr-react --- package.json | 1 - pages/_app.tsx | 14 -------------- yarn.lock | 15 +-------------- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/package.json b/package.json index cc17f0b..0f26260 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "eslint-config-next": "13.2.4", "next": "13.2.4", "nodemon": "^2.0.21", - "nostr-react": "^0.6.4", "passport-lnurl-auth": "^1.5.1", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/pages/_app.tsx b/pages/_app.tsx index 70ff76e..65fe032 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,25 +1,11 @@ -import { NostrProvider } from "nostr-react"; import { PubkeyProvider } from "@/context/pubkey"; import "@/styles/globals.css"; import type { AppProps } from "next/app"; -// TODO: Save default relays in store, allow user to set and remove, retrieve user -// relays in a smart way based off who they are interacting with -// ...or just leave as fixed for hackathon (we have custom relay anyways) -const relays = [ - "wss://nostr.terminus.money", - "wss://brb.io", - "wss://nostr.wine", - "wss://relay.snort.social", - "wss://gratten.duckdns.org/nostrrelay/relay2", -]; - export default function App({ Component, pageProps }: AppProps) { return ( - // - // ); } diff --git a/yarn.lock b/yarn.lock index 652b130..467481e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1977,11 +1977,6 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -jotai@^1.12.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.13.1.tgz#20cc46454cbb39096b12fddfa635b873b3668236" - integrity sha512-RUmH1S4vLsG3V6fbGlKzGJnLrDcC/HNb5gH2AeA9DzuJknoVxSGvvg8OBB7lke+gDc4oXmdVsaKn/xDUhWZ0vw== - js-sdsl@^4.1.4: version "4.3.0" resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz" @@ -2303,15 +2298,7 @@ normalize-range@^0.1.2: resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== -nostr-react@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/nostr-react/-/nostr-react-0.6.4.tgz#03c15f6ac4807efdb3ad3c181457353bedf30d28" - integrity sha512-esRgmhTP5kPQ8ufs8cFAQxxJtMmzuba/k2QfXevG/ejHP3IMa41pb82qi8V0aPzY3KJ0Nr54x0OSa39d2InKzA== - dependencies: - jotai "^1.12.1" - nostr-tools "^1.1.0" - -nostr-tools@^1.1.0, nostr-tools@^1.7.5: +nostr-tools@^1.7.5: version "1.7.5" resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.7.5.tgz#349f469ff2877deb99d71c63d4883af93ec9f9a5" integrity sha512-FFaYOAn9lFyISClbBzPe2eQ2ZiKx8xFviwHmdgTAmuue+eLrtPEI3tCqPtP624HghX/X4VnaixoiMvDB8g2+tQ== From 146d0bca3cdf3ef2530896fd4d8420098639edf4 Mon Sep 17 00:00:00 2001 From: Chad Welch Date: Wed, 22 Mar 2023 13:28:36 -0700 Subject: [PATCH 7/8] remove old comments --- hooks/useProfile.ts | 2 -- lib/nostr.ts | 3 --- 2 files changed, 5 deletions(-) diff --git a/hooks/useProfile.ts b/hooks/useProfile.ts index 54c5069..1710511 100644 --- a/hooks/useProfile.ts +++ b/hooks/useProfile.ts @@ -17,8 +17,6 @@ export interface UserMetadata { nip05?: string } -// TODO: need global store of queue, profiles - export default function useProfile(pubkey: string | null): [UserMetadata | undefined, boolean] { const [profile, isLoading] = useLiveQuery(async () => { if (!pubkey) return [undefined, false] diff --git a/lib/nostr.ts b/lib/nostr.ts index 37134cf..c34b34e 100644 --- a/lib/nostr.ts +++ b/lib/nostr.ts @@ -52,11 +52,8 @@ class NostrClient { console.debug('subscribing for pubkeys: ', Array.from(this.profileQueue)) - // TODO: subscribe w/ pool const sub = this.pool.sub(defaultProfileRelays, filters) - // How to handle this? Want the latest created_at profile event to be used. - // Each relay can return a different/outdated event sub.on('event', async (event: Event) => { console.debug('got event', event) From 8d07e7246dde6afab49e333fd7b9c108c51f5acd Mon Sep 17 00:00:00 2001 From: Chad Welch Date: Wed, 22 Mar 2023 13:48:40 -0700 Subject: [PATCH 8/8] add temp useProfiles demo page --- pages/test.tsx | 5 ---- pages/testProfiles.tsx | 63 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) delete mode 100644 pages/test.tsx create mode 100644 pages/testProfiles.tsx diff --git a/pages/test.tsx b/pages/test.tsx deleted file mode 100644 index 80501bd..0000000 --- a/pages/test.tsx +++ /dev/null @@ -1,5 +0,0 @@ -export default function Test() { - return ( -
    - ) -} \ No newline at end of file diff --git a/pages/testProfiles.tsx b/pages/testProfiles.tsx new file mode 100644 index 0000000..360c036 --- /dev/null +++ b/pages/testProfiles.tsx @@ -0,0 +1,63 @@ +import useProfile from "@/hooks/useProfile" +import { nip19 } from "nostr-tools" + +// profiles from nostr.directory +const pubkeys = [ + "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", + "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2", + "00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700", + "04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9", + "6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93", + "1577e4599dd10c863498fe3c20bd82aafaf829a595ce83c5cf8ac3463531b09b", + "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", + "3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24", + "7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194", + "84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240", + "f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9", + "85080d3bad70ccdcd7f74c29a44f55bb85cbcd3dd0cbb957da1d215bdb931204", + "5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e", + "c48e29f04b482cc01ca1f9ef8c86ef8318c059e0e9353235162f080f26e14c11", + "bf2376e17ba4ec269d10fcc996a4746b451152be9031fa48e74553dde5526bce", + "c43bbb58e2e6bc2f9455758257f6ba5329107bd4e8274068c2936c69d9980b7d", + "d307643547703537dfdef811c3dea96f1f9e84c8249e200353425924a9908cf8", + "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c", + "803a613997a26e8714116f99aa1f98e8589cb6116e1aaa1fc9c389984fcd9bb8", + "c49d52a573366792b9a6e4851587c28042fb24fa5625c6d67b8c95c8751aca15", + "92de68b21302fa2137b1cbba7259b8ba967b535a05c6d2b0847d9f35ff3cf56a", + "eab0e756d32b80bcd464f3d844b8040303075a13eabc3599a762c9ac7ab91f4f", + "c4eabae1be3cf657bc1855ee05e69de9f059cb7a059227168b80b89761cbc4e0", + "f728d9e6e7048358e70930f5ca64b097770d989ccd86854fe618eda9c8a38106", +] + +const Profile = ({pubkey}:{pubkey: string}) => { + const [profile, isLoading] = useProfile(pubkey) + return ( +
    + {/* Image */} + {profile?.picture ? ( + + ) : ( +
    + )} + + {/* Name */} + {!isLoading && ( +

    + {profile?.name + ? profile.name + : nip19.npubEncode(pubkey).slice(0, 12)} +

    + )} +
    + ) +} + +export default function Test() { + return ( +
    + {pubkeys.map(pk => { + return + })} +
    + ) +} \ No newline at end of file