aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2022-12-27 22:49:25 -0800
committerAdam Soltys <[email protected]>2022-12-27 22:49:25 -0800
commitcf9ccd673cbbf29d9c9d00c2d379382f0a9c06c0 (patch)
tree053265f969281e4cd7ae0cd1cb81ac686eb9956f /src
parent4b6883362463c3ddfb8c7a574b9beba5220d120b (diff)
downloadcoinos-ui-cf9ccd673cbbf29d9c9d00c2d379382f0a9c06c0.tar.gz
coinos-ui-cf9ccd673cbbf29d9c9d00c2d379382f0a9c06c0.zip
show follows and followers
Diffstat (limited to 'src')
-rw-r--r--src/components/Feed.svelte21
-rw-r--r--src/lib/nostr.js31
-rw-r--r--src/lib/utils.js1
-rw-r--r--src/routes/(app)/+layout.svelte8
-rw-r--r--src/routes/(app)/[username]/(profile)/+layout.server.js6
-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.svelte18
-rw-r--r--src/routes/(app)/[username]/(profile)/followers/+page.server.js10
-rw-r--r--src/routes/(app)/[username]/(profile)/followers/+page.svelte40
-rw-r--r--src/routes/(app)/[username]/(profile)/follows/+page.server.js11
-rw-r--r--src/routes/(app)/[username]/(profile)/follows/+page.svelte40
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/+page.svelte25
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]"