diff options
| author | Adam Soltys <[email protected]> | 2022-12-27 22:49:25 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2022-12-27 22:49:25 -0800 |
| commit | cf9ccd673cbbf29d9c9d00c2d379382f0a9c06c0 (patch) | |
| tree | 053265f969281e4cd7ae0cd1cb81ac686eb9956f | |
| parent | 4b6883362463c3ddfb8c7a574b9beba5220d120b (diff) | |
| download | coinos-ui-cf9ccd673cbbf29d9c9d00c2d379382f0a9c06c0.tar.gz coinos-ui-cf9ccd673cbbf29d9c9d00c2d379382f0a9c06c0.zip | |
show follows and followers
| -rw-r--r-- | src/components/Feed.svelte | 21 | ||||
| -rw-r--r-- | src/lib/nostr.js | 31 | ||||
| -rw-r--r-- | src/lib/utils.js | 1 | ||||
| -rw-r--r-- | src/routes/(app)/+layout.svelte | 8 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+layout.server.js | 6 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+layout.svelte (renamed from src/routes/(app)/[username]/+page.svelte) | 92 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+page.server.js (renamed from src/routes/(app)/[username]/+page.server.js) | 10 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+page.svelte | 18 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/followers/+page.server.js | 10 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/followers/+page.svelte | 40 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/follows/+page.server.js | 11 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/follows/+page.svelte | 40 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/settings/[...tab]/+page.svelte | 25 |
13 files changed, 238 insertions, 75 deletions
diff --git a/src/components/Feed.svelte b/src/components/Feed.svelte index eef12a91..43d02361 100644 --- a/src/components/Feed.svelte +++ b/src/components/Feed.svelte @@ -5,27 +5,16 @@ import VirtualScroll from 'svelte-virtual-scroll-list'; import { enhance } from '$app/forms'; import { post, punk, failure } from '$lib/utils'; - import { Password } from '$comp'; - import { password, passwordPrompt } from '$lib/store'; import { sign, send } from '$lib/nostr'; export let events; export let user; - let message, submitting; - $: submitting && $password && submit(); + let message; let submit = async () => { - submitting = true; try { let mnemonic, key, seed, entropy, child, privkey; - if (!$password) return ($passwordPrompt = true); - - try { - await post('/password', { password: $password }); - } catch (e) { - return ($passwordPrompt = true); - } let event = { pubkey: user.pubkey, @@ -35,7 +24,7 @@ tags: [] }; - await sign(event, user, $password); + await sign({ event, user }); await send(event); event.user = user; @@ -69,16 +58,12 @@ let w; $: sorted = Object.values(events) - .filter((ev) => ev?.pubkey) + .filter((ev) => ev?.pubkey && ev?.kind === 1) .sort((a, b) => b.seen - a.seen); </script> <svelte:window bind:innerWidth={w} /> -{#if $passwordPrompt} - <Password {user} /> -{/if} - <form on:submit|preventDefault={submit} class="flex justify-center gap-4 max-w-2xl mx-auto"> <div class="grow"> <input diff --git a/src/lib/nostr.js b/src/lib/nostr.js index 385c7ac6..7523e97f 100644 --- a/src/lib/nostr.js +++ b/src/lib/nostr.js @@ -1,12 +1,39 @@ +import { get } from 'svelte/store'; import { entropyToMnemonic, mnemonicToSeedSync } from 'bip39'; import { Buffer } from 'buffer'; import { bech32m } from 'bech32'; import { bip32, post, stretch } from '$lib/utils'; import { calculateId, signId } from 'nostr'; +import { password as pw, passwordPrompt } from '$lib/store'; const { decode, fromWords } = bech32m; -export let sign = async (event, { cipher, username, salt }, password) => { +let signing; + +pw.subscribe((nv, ov) => { + if (!signing) return; + sign(signing); +}); + +export let sign = async ({ event, user }) => { + signing = { event, user }; + let { cipher, username, salt } = user; + + let password = get(pw); + + if (!password) { + passwordPrompt.set(true); + return; + } + + try { + await post('/password', { password }); + } catch (e) { + console.log(e); + passwordPrompt.set(true); + return; + } + let mnemonic, key, seed, entropy, child, privkey; entropy = Buffer.from( await crypto.subtle.decrypt( @@ -25,6 +52,8 @@ export let sign = async (event, { cipher, username, salt }, password) => { event.id = await calculateId(event); event.sig = await signId(privkey, event.id); + + signing = false; }; export let send = (event) => { diff --git a/src/lib/utils.js b/src/lib/utils.js index 43e77022..8b3e8070 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -120,6 +120,7 @@ export const info = (m) => { export const login = async (user, cookies) => { let maxAge = 30 * 24 * 60 * 60; + let res = await fetch(base + '/login', { method: 'POST', body: JSON.stringify(user), diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 4cd08ab4..7178917a 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -2,11 +2,11 @@ import { SvelteToast } from '@zerodevx/svelte-toast'; import { onDestroy, onMount } from 'svelte'; import { close, connect, send } from '$lib/socket'; - import { last, selectedRate, invoice, rate, user, request } from '$lib/store'; + import { last, selectedRate, invoice, rate, user, request, passwordPrompt } from '$lib/store'; import { page } from '$app/stores'; import { browser } from '$app/environment'; import { goto } from '$app/navigation'; - import { LoadingSplash, Invoice, Request } from '$comp'; + import { LoadingSplash, Invoice, Password, Request } from '$comp'; import { warning, protectedRoutes } from '$lib/utils'; import { t, locale } from '$lib/translations'; @@ -58,6 +58,10 @@ onDestroy(() => browser && clearTimeout(timer)); </script> +{#if $passwordPrompt} + <Password {user} /> +{/if} + <svelte:head> {#if subject} <title>coinos - {subject.username}</title> diff --git a/src/routes/(app)/[username]/(profile)/+layout.server.js b/src/routes/(app)/[username]/(profile)/+layout.server.js new file mode 100644 index 00000000..c97c7bdb --- /dev/null +++ b/src/routes/(app)/[username]/(profile)/+layout.server.js @@ -0,0 +1,6 @@ +export async function load({ depends, parent }) { + depends('app:user'); + let { subject } = await parent(); + console.log('SUBJ', subject); + return { subject }; +} diff --git a/src/routes/(app)/[username]/+page.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte index f4598e6f..3fc01721 100644 --- a/src/routes/(app)/[username]/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+layout.svelte @@ -1,15 +1,44 @@ <script> - import { Feed, Icon, PageNotFound } from '$comp'; + import { Icon } from '$comp'; import { t } from '$lib/translations'; + import { sign, send } from '$lib/nostr'; export let data; + let events, user, subject, src, text; + $: ({ events, user, subject, src, text } = data); + $: ({ username: n } = subject); - let { events, user, subject, src, text } = data; - let followed = false; + $: username = n.length > 60 ? n.substr(0, 6) : n; - let { username: n } = subject; + let follow = async () => { + user.follows.push(['p', subject.pubkey, 'wss://nostr.coinos.io', subject.username]); + update(); + }; - $: username = n.length > 60 ? n.substr(0, 6) : n; + let unfollow = async () => { + user.follows.splice( + user.follows.findIndex((t) => t[1] === subject.pubkey), + 1 + ); + update(); + }; + + let update = async () => { + let event = { + pubkey: user.pubkey, + created_at: Math.floor(Date.now() / 1000), + kind: 3, + content: '', + tags: user.follows + }; + + user.follows = user.follows; + + await sign({ event, user }); + await send(event); + }; + + $: following = user?.follows.find((t) => t.includes(subject.pubkey)); </script> <div class="container mx-auto w-full px-4 mb-4 flex flex-wrap md:flex-nowrap"> @@ -21,7 +50,39 @@ {subject.address && subject.address !== 'null' ? subject.address : ''} </div> + <div class="flex justify-around"> + <a href={`/${subject.username}/follows`}><b>{subject.follows.length}</b> Following</a> + <a href={`/${subject.username}/followers`}><b>{subject.followers.length}</b> Followers</a> + </div> + <div class="flex flex-wrap gap-2 w-full"> + {#if user && user.username !== subject.username} + {#if following} + <div class="w-full flex"> + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60" + on:click={unfollow} + > + <div class="mx-auto flex"> + <Icon icon={'profile'} style="my-auto" /> + <div>{$t('user.unfollow')}</div> + </div> + </button> + </div> + {:else} + <div class="w-full flex"> + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60" + on:click={follow} + > + <div class="mx-auto flex"> + <Icon icon={'profile'} style="my-auto" /> + <div>{$t('user.follow')}</div> + </div> + </button> + </div> + {/if} + {/if} {#if !subject.anon} <div class="w-full flex"> <a href={`/${subject.username}/receive`} class="mx-auto"> @@ -58,31 +119,12 @@ </div> {/if} {/if} - <!-- <div class="w-full flex"> --> - <!-- <a href={`/${subject.username}/message`} class="mx-auto"> --> - <!-- <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60"> --> - <!-- <div class="mx-auto flex"> --> - <!-- <Icon icon="support" style="mr-1" /> --> - <!-- <div>{$t('transactions.sendMessage')}</div> --> - <!-- </div> --> - <!-- </button> --> - <!-- </a> --> - <!-- </div> --> </div> </div> <div class="w-full md:pt-12 lg:pt-0"> <div class="mx-auto space-y-5 mt-5 lg:max-w-lg xl:max-w-2xl"> - {#if events.length} - <Feed {events} {user} /> - {:else} - <div class="text-center text-2xl mt-20"> - Nothing here yet. Post your first <a - href="https://github.com/nostr-protocol/nostr#nostr---notes-and-other-stuff-transmitted-by-relays" - class="underline">nostr</a - > note! - </div> - {/if} + <slot /> </div> </div> </div> diff --git a/src/routes/(app)/[username]/+page.server.js b/src/routes/(app)/[username]/(profile)/+page.server.js index e406253f..9c91efae 100644 --- a/src/routes/(app)/[username]/+page.server.js +++ b/src/routes/(app)/[username]/(profile)/+page.server.js @@ -1,25 +1,21 @@ import { get } from '$lib/utils'; -import Qr from 'qrcode-base64'; export async function load({ params, parent, url }) { let { subject } = await parent(); let { pubkey } = subject; if (!pubkey) pubkey = params.username; - let { username } = params; - let text = `${encodeURI(username)}@${url.hostname}`; - let src = Qr.drawImg(text, { size: 300 }); let events = []; try { - events = await get(`/${pubkey}/events`); + events = await get(`/nostr/${pubkey}`); } catch (e) { - console.log(e); + console.log(`failed to fetch nostr events for ${pubkey}`, e); } events.map((e) => { e.seen = e.created_at; }); - return { events, src, text }; + return { events }; } diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte new file mode 100644 index 00000000..19687ed0 --- /dev/null +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -0,0 +1,18 @@ +<script> + import { Feed } from '$comp'; + import { t } from '$lib/translations'; + + export let data; + let { events, user } = data; +</script> + +{#if events.length} + <Feed {events} {user} /> +{:else} + <div class="text-center text-2xl mt-20"> + Nothing here yet. Post your first <a + href="https://github.com/nostr-protocol/nostr#nostr---notes-and-other-stuff-transmitted-by-relays" + class="underline">nostr</a + > note! + </div> +{/if} diff --git a/src/routes/(app)/[username]/(profile)/followers/+page.server.js b/src/routes/(app)/[username]/(profile)/followers/+page.server.js new file mode 100644 index 00000000..a05d324d --- /dev/null +++ b/src/routes/(app)/[username]/(profile)/followers/+page.server.js @@ -0,0 +1,10 @@ +import { get } from '$lib/utils'; + +export async function load({ params, parent, url }) { + let { subject } = await parent(); + let { pubkey } = subject; + + let followers = await get(`/${pubkey}/followers`); + + return { followers }; +} diff --git a/src/routes/(app)/[username]/(profile)/followers/+page.svelte b/src/routes/(app)/[username]/(profile)/followers/+page.svelte new file mode 100644 index 00000000..a718ff15 --- /dev/null +++ b/src/routes/(app)/[username]/(profile)/followers/+page.svelte @@ -0,0 +1,40 @@ +<script> + import { Avatar } from '$comp'; + import { browser } from '$app/environment'; + import VirtualScroll from 'svelte-virtual-scroll-list'; + + export let data; + let { followers } = data; + let w; +</script> + +<svelte:window bind:innerWidth={w} /> + +<div class="mt-24 mb-20 px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> + {#if browser} + {#if followers.length} + <VirtualScroll data={followers} key="username" let:data pageMode={true}> + <a href={`/${data.username}`}> + <div class="flex border-b py-4 text-sm lg:text-lg text-secondary" :key={data.username}> + <div class="mb-auto mr-2"> + <div class="md:hidden"> + <Avatar size={12} user={data} disabled={true} /> + </div> + <div class="hidden md:block"> + <Avatar size={20} user={data} disabled={true} /> + </div> + </div> + + <div class="w-full flex pb-1 text-black my-auto"> + <div> + {data.display_name || ''} <span class="text-secondary">@{data.username}</span> + </div> + </div> + </div> + </a> + </VirtualScroll> + {:else} + <div>No followers</div> + {/if} + {/if} +</div> diff --git a/src/routes/(app)/[username]/(profile)/follows/+page.server.js b/src/routes/(app)/[username]/(profile)/follows/+page.server.js new file mode 100644 index 00000000..60b95636 --- /dev/null +++ b/src/routes/(app)/[username]/(profile)/follows/+page.server.js @@ -0,0 +1,11 @@ +import { get } from '$lib/utils'; + +export async function load({ params, parent, url }) { + let { subject } = await parent(); + let { pubkey } = subject; + + let follows = await get(`/${pubkey}/follows`); + + console.log('GOT', follows); + return { follows }; +} diff --git a/src/routes/(app)/[username]/(profile)/follows/+page.svelte b/src/routes/(app)/[username]/(profile)/follows/+page.svelte new file mode 100644 index 00000000..87e34501 --- /dev/null +++ b/src/routes/(app)/[username]/(profile)/follows/+page.svelte @@ -0,0 +1,40 @@ +<script> + import { Avatar } from '$comp'; + import { browser } from '$app/environment'; + import VirtualScroll from 'svelte-virtual-scroll-list'; + + export let data; + let { follows } = data; + let w; +</script> + +<svelte:window bind:innerWidth={w} /> + +<div class="mt-24 mb-20 px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> + {#if browser} + {#if follows.length} + <VirtualScroll data={follows} key="username" let:data pageMode={true}> + <a href={`/${data.username}`}> + <div class="flex border-b py-4 text-sm lg:text-lg text-secondary" :key={data.username}> + <div class="mb-auto mr-2"> + <div class="md:hidden"> + <Avatar size={12} user={data} disabled={true} /> + </div> + <div class="hidden md:block"> + <Avatar size={20} user={data} disabled={true} /> + </div> + </div> + + <div class="w-full flex pb-1 text-black my-auto"> + <div> + {data.display_name || ''} <span class="text-secondary">@{data.username}</span> + </div> + </div> + </div> + </a> + </VirtualScroll> + {:else} + <div>No follows</div> + {/if} + {/if} +</div> diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte index 55963105..b02ddca4 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte @@ -3,10 +3,10 @@ import { fly } from 'svelte/transition'; import { applyAction, deserialize } from '$app/forms'; - import { Icon, Spinner, Pin, Password } from '$comp'; + import { Icon, Spinner, Pin } from '$comp'; import { t } from '$lib/translations'; import { failure, post, success } from '$lib/utils'; - import { avatarUpload, bannerUpload, passwordPrompt, password, pin } from '$lib/store'; + import { avatarUpload, bannerUpload, pin } from '$lib/store'; import { upload } from '$lib/upload'; import { page } from '$app/stores'; import { sign, send } from '$lib/nostr'; @@ -66,19 +66,6 @@ failure('Something went wrong'); } - if (!$password) { - $passwordPrompt = true; - return; - } - - try { - await post('/password', { password: $password }); - } catch (e) { - console.log(e); - $passwordPrompt = true; - return; - } - let event = { pubkey: user.pubkey, created_at: Math.floor(Date.now() / 1000), @@ -91,7 +78,7 @@ tags: [] }; - await sign(event, user, $password); + await sign({ event, user }); await send(event); const response = await fetch(formElement.action, { @@ -111,18 +98,12 @@ let loaded; onMount(() => setTimeout(() => (loaded = true), 50)); - - $: loading && $password && handleSubmit(); </script> {#if loaded && user.haspin && $pin?.length !== 6} <Pin /> {/if} -{#if $passwordPrompt} - <Password {user} /> -{/if} - <form method="POST" class="mb-[154px]" |
