diff options
| author | Adam Soltys <[email protected]> | 2024-04-16 10:45:13 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-04-16 10:45:13 -0700 |
| commit | 1acda0eb21d3832e2005603a73b1d2503fd684aa (patch) | |
| tree | b0ca5339d2fc810774d029b943c1f6070095d9e3 | |
| parent | de921f5bcc58e2ed5682340112c821c0a006b4d5 (diff) | |
| download | coinos-ui-separate-homepage.tar.gz coinos-ui-separate-homepage.zip | |
| -rw-r--r-- | src/components/AppHeader.svelte | 4 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+layout.svelte | 4 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+page.svelte | 163 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/home/+layout.server.js | 26 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/home/+layout.svelte | 276 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/home/+page.server.js | 11 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/home/+page.svelte | 201 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/home/event/[id]/+page.server.js | 12 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/home/event/[id]/+page.svelte | 7 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/home/followers/+page.svelte | 52 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/home/follows/+page.svelte | 51 |
11 files changed, 641 insertions, 166 deletions
diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte index 6f68c41a..18d024a1 100644 --- a/src/components/AppHeader.svelte +++ b/src/components/AppHeader.svelte @@ -51,10 +51,10 @@ > <nav class="flex justify-end items-center space-x-4 p-5"> {#if user} - <a href={`/${user.username}`}> + <a href={`/${user.username}/home`}> <button class="bg-white p-2 rounded-full w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl border border-black/10 {$page - .url.pathname === `/${user.username}` + .url.pathname === `/${user.username}/home` ? 'opacity-100' : 'opacity-70 hover:opacity-80'}" ><Icon icon="home" style="mx-auto w-6 sm:w-8" /> diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte index 0520f1b8..3d4f191b 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.svelte +++ b/src/routes/(app)/[username]/(profile)/+layout.svelte @@ -149,7 +149,7 @@ </a> {/if} - {#if !subject.anon && subject.username !== user?.username && !subject.hidepay} + {#if !subject.anon && subject.username !== user?.username} <a href={user ? `/pay/${subject.username}` @@ -168,7 +168,6 @@ </a> {/if} - {#if user?.username !== subject.username} <button type="button" on:click={toggleDetails} @@ -177,7 +176,6 @@ <Icon icon="profile" style="w-8 mr-2" /> <div class="mt-1">{$t("user.info")}</div> </button> - {/if} {#if user?.admin && user.username !== subject.username} <form class="w-full flex" on:submit|preventDefault={reset}> diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index 5878d252..c74f8e6d 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -1,22 +1,9 @@ <script> + import { btc, f, sat, post } from "$lib/utils"; import { browser } from "$app/environment"; import { goto, invalidate } from "$app/navigation"; - import { btc, f, sat, post } from "$lib/utils"; import Icon from "$comp/Icon.svelte"; - import Balance from "$comp/Balance.svelte"; - import DeleteItem from "$comp/DeleteItem.svelte"; import { t } from "$lib/translations"; - import { fade } from "svelte/transition"; - import { cubicIn } from "svelte/easing"; - import { flip } from "svelte/animate"; - import { installPrompt } from "$lib/store"; - - import { - dndzone, - SHADOW_ITEM_MARKER_PROPERTY_NAME, - SOURCES, - TRIGGERS, - } from "svelte-dnd-action"; export let data; @@ -28,8 +15,6 @@ $: rate = rates[currency]; $: refresh(data); let refresh = (d) => ({ items, subject, user, rates } = d); - let dragDisabled = - subject?.id !== user?.id || (browser && window.innerWidth < 768); let deleting; $: total = items.reduce((a, b) => a + b.price * b.quantity, 0); @@ -37,40 +22,6 @@ deleting = item; }; - function handleConsider(e) { - const { - items: newItems, - info: { source, trigger }, - } = e.detail; - items = newItems; - if (source === SOURCES.KEYBOARD && trigger === TRIGGERS.DRAG_STOPPED) - reset(); - } - - async function handleFinalize(e) { - const { - items: newItems, - info: { source }, - } = e.detail; - items = newItems; - if (source === SOURCES.POINTER) reset(); - - if (subject?.id === user?.id) { - await post("/api/items/sort", { items }); - invalidate("app:items"); - } - } - - function startDrag(e) { - e.preventDefault(); - dragDisabled = false; - } - - function handleKeyDown(e) { - if ((e.key === "Enter" || e.key === " ") && dragDisabled) - dragDisabled = false; - } - $: if (total > 0 && browser) { let payButton = document.querySelector("#payButton"); if (payButton) payButton.addEventListener("click", checkout); @@ -98,82 +49,12 @@ if (user) goto(`/send/invoice/${id}`, { invalidateAll: true }); else goto(`/${subject.username}/invoice/${id}`); }; - - let reset = () => - (dragDisabled = - user?.username !== subject?.username || window.innerWidth < 768); - - let install = async () => { - if (!$installPrompt) return; - await $installPrompt.prompt(); - $installPrompt = null; - }; </script> -<svelte:window on:resize={reset} /> - -{#if deleting} - <DeleteItem bind:item={deleting} /> -{/if} - <div class="space-y-5"> - {#if user?.id === subject.id} - <div class="flex justify-center lg:justify-start"> - <Balance {user} {rate} /> - </div> - - {#if !user.balance} - <div class="mb-8"> - <p class="text-secondary text-lg"> - {$t("user.welcome")} - </p> - </div> - {/if} - - <div class="flex flex-wrap gap-3 justify-center w-full text-xl"> - <a href={`/send`} class="contents grow"> - <button - type="button" - class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow" - > - <div class="mx-auto flex gap-2"> - <Icon icon="send" style="w-8" /> - <div class="my-auto">{$t("user.dashboard.send")}</div> - </div> - </button> - </a> - - <a href={`/${user.username}/receive`} class="contents"> - <button - class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow" - > - <div class="mx-auto flex gap-2"> - <Icon icon="numpad" style="w-8" /> - <div class="my-auto">{$t("user.dashboard.receive")}</div> - </div> - </button> - </a> - - {#if $installPrompt} - <button - class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white grow basis-full fixed bottom-16 xl:hidden" - on:click={install} - > - <div class="mx-auto flex gap-2"> - <Icon icon="save" style="w-8 mx-auto invert" /> - <div class="my-auto text-xl whitespace-nowrap">{$t("user.install")}</div> - </div> - </button> - {/if} - </div> - {/if} - <div class="grid sm:grid-cols-2 gap-4" class:pb-20={total > 0} - use:dndzone={{ items, flipDurationMs, dropTargetStyle, dragDisabled }} - on:consider={handleConsider} - on:finalize={handleFinalize} > {#each items as i (i.id)} <button @@ -182,12 +63,7 @@ on:click={() => i.quantity++} > <div class="relative w-full h-64 overflow-hidden rounded-xl"> - {#if i[SHADOW_ITEM_MARKER_PROPERTY_NAME]} - <div - in:fade={{ duration: 200, easing: cubicIn }} - class="custom-shadow-item" - /> - {:else if i.image} + {#if i.image} <img src={`/api/public/${i.image}.webp`} alt={i.name} @@ -199,41 +75,6 @@ alt="Banner" /> {/if} - {#if user?.username === subject.username} - <div class="flex gap-2 justify-center absolute top-2 right-2"> - <button type="button" on:click|stopPropagation={() => {}}> - <a href={`/${user.username}/items/${i.id}`}> - <button - class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100" - > - <Icon icon="edit" style="w-8 mx-auto invert" /> - </button> - </a> - </button> - <button - class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100" - on:click|stopPropagation={() => del(i)} - > - <Icon icon="trash" style="w-8 mx-auto invert" /> - </button> - <button - type="button" - on:click|stopPropagation={() => {}} - class="sm:hidden" - on:mousedown={startDrag} - on:touchstart={startDrag} - on:keydown={handleKeyDown} - > - <a href={`/${user.username}/items/${i.id}`}> - <button - class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100" - > - <Icon icon="move" style="w-8 mx-auto invert" /> - </button> - </a> - </button> - </div> - {/if} </div> <div class="bg-white rounded-xl py-2"> <div class="flex text-left gap-2"> diff --git a/src/routes/(app)/[username]/home/+layout.server.js b/src/routes/(app)/[username]/home/+layout.server.js new file mode 100644 index 00000000..a508de92 --- /dev/null +++ b/src/routes/(app)/[username]/home/+layout.server.js @@ -0,0 +1,26 @@ +import { get } from "$lib/utils"; + +export async function load({ parent }) { + let { subject, user } = await parent(); + + subject.follows = []; + subject.followers = []; + + try { + subject.follows = await get(`/${subject.pubkey}/follows`); + } catch (e) { + console.log("problem fetching follows", e); + } + + try { + subject.followers = await get(`/${subject.pubkey}/followers`); + } catch (e) { + console.log("problem fetching followers", e); + } + + if (user) { + user.follows = await get(`/${user.pubkey}/follows?tagsonly=true`); + } + + return { subject, user }; +} diff --git a/src/routes/(app)/[username]/home/+layout.svelte b/src/routes/(app)/[username]/home/+layout.svelte new file mode 100644 index 00000000..0520f1b8 --- /dev/null +++ b/src/routes/(app)/[username]/home/+layout.svelte @@ -0,0 +1,276 @@ +<script> + import { hexToUint8Array } from "uint8array-extras"; + import { copy, f, post, s, sats, success, fail } from "$lib/utils"; + import Icon from "$comp/Icon.svelte"; + import { t } from "$lib/translations"; + import { sign, send } from "$lib/nostr"; + import { bech32m } from "@scure/base"; + import { page } from "$app/stores"; + + export let data; + + let { encode, toWords } = bech32m; + let events, user, subject, src, text; + + $: ({ events, user, subject, src, rates, text } = data); + $: ({ currency, username: n, display } = subject); + + $: username = n.length > 60 ? n.substr(0, 6) : display || n; + $: npub = + subject.pubkey && + bech32m.encode("npub", toWords(hexToUint8Array(subject.pubkey)), 180); + $: lnaddr = `${n}@${$page.url.host}`; + $: profile = `${$page.url.host}/${n}`; + + let follow = async () => { + user.follows.push([ + "p", + subject.pubkey, + "wss://nostr.coinos.io", + subject.username, + ]); + update(); + }; + + 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, + }; + + await sign({ event, user }); + await send(event); + + user.follows = user.follows; + }; + + $: following = !!user?.follows.find((t) => t.includes(subject.pubkey)); + + let showBio; + let toggleBio = () => (showBio = !showBio); + + let showDetails; + let toggleDetails = () => (showDetails = !showDetails); + + let password; + + let reset = async () => { + try { + await post(`/${subject.username}/reset`, { password }); + success("Password reset"); + } catch (e) { + fail(e.message); + } + }; +</script> + +<div class="container mx-auto w-full px-4 flex flex-wrap lg:flex-nowrap"> + <div class="hidden lg:block lg:w-[280px] xl:w-[360px]" /> + <div + class="w-full lg:w-[280px] xl:w-[360px] lg:absolute space-y-5 left-20 mx-auto mb-5" + > + <div + class="flex text-3xl font-bold text-center mx-auto justify-center gap-2" + > + <div class="my-auto break-words w-full">{display || username}</div> + </div> + + {#if subject.address} + <div + class="text-secondary mx-auto text-center lg:mx-0" + class:line-clamp-2={!showBio} + on:click={toggleBio} + on:keydown={toggleBio} + > + {subject.address} + </div> + {/if} + + <div class="flex justify-center gap-2"> + <a href={`/${subject.pubkey}/follows`} + ><b>{subject.follows.length}</b> + <span class="text-secondary">{$t("user.following")}</span></a + > + <a href={`/${subject.pubkey}/followers`} + ><b>{subject.followers.length}</b> + <span class="text-secondary">{$t("user.followers")}</span></a + > + </div> + + <div class="flex flex-wrap gap-2 w-full text-xl"> + {#if user && user.username !== subject.username && subject.pubkey} + {#if following} + <button + class="mx-auto rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white grow" + on:click={unfollow} + > + <div class="mx-auto flex"> + <Icon icon={"profile"} style="my-auto w-8 mr-2 invert" /> + <div class="my-auto">{$t("user.following")}</div> + </div> + </button> + {:else} + <button + class="mx-auto rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex grow" + on:click={follow} + > + <div class="mx-auto flex"> + <Icon icon={"profile"} style="my-auto h-6 mr-2" /> + <div class="my-auto">{$t("user.follow")}</div> + </div> + </button> + {/if} + {/if} + + {#if user && user.username !== subject.username && subject.pubkey} + <a + href={`/${user.username}/messages/${subject.username}`} + class="contents" + > + <button + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-60 grow" + > + <div class="mx-auto flex"> + <Icon icon="message" style="w-8 mr-2 my-auto" /> + <div class="mt-1 my-auto">{$t("user.message")}</div> + </div> + </button> + </a> + {/if} + + {#if !subject.anon && subject.username !== user?.username && !subject.hidepay} + <a + href={user + ? `/pay/${subject.username}` + : `/${subject.username}/receive`} + class="contents" + id="payButton" + > + <button + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-60 grow" + > + <div class="mx-auto flex"> + <Icon icon="send" style="w-8 mr-2" /> + <div class="mt-1">{$t("user.pay")}</div> + </div> + </button> + </a> + {/if} + + {#if user?.username !== subject.username} + <button + type="button" + on:click={toggleDetails} + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex justify-center w-full" + > + <Icon icon="profile" style="w-8 mr-2" /> + <div class="mt-1">{$t("user.info")}</div> + </button> + {/if} + + {#if user?.admin && user.username !== subject.username} + <form class="w-full flex" on:submit|preventDefault={reset}> + <input placeholder="Password reset" bind:value={password} /> + <button + type="submit" + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-60" + > + <div class="mx-auto flex"> + <Icon icon="clock" style="mr-2 my-auto w-8" /> + <div class="mt-1 my-auto">Reset</div> + </div> + </button> + </form> + {/if} + </div> + {#if showDetails} + <div class="text-xl space-y-5"> + <div> + <div class="font-bold">{$t("user.profilePage")}</div> + <div class="flex gap-4"> + <div class="break-all grow text-secondary"> + {profile} + </div> + <div class="flex mb-auto gap-1"> + <button class="my-auto" on:click={() => copy(profile)} + ><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button + > + <a href={`/qr/${encodeURIComponent(profile)}`} class="my-auto"> + <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> + </a> + </div> + </div> + </div> + <div> + <div class="font-bold">{$t("user.lightningAddress")}</div> + <div class="flex gap-4"> + <div class="break-all grow text-secondary"> + {lnaddr} + </div> + <div class="flex mb-auto gap-1"> + <button class="my-auto" on:click={() => copy(lnaddr)} + ><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button + > + <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto"> + <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> + </a> + </div> + </div> + </div> + <div> + <div class="font-bold">{$t("user.nostrPubkey")}</div> + <div class="flex gap-4"> + <div class="break-all grow text-secondary"> + {npub} + </div> + <div class="flex my-auto gap-1"> + <button class="my-auto" on:click={() => copy(npub)} + ><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button + > + <a href={`/qr/${encodeURIComponent(npub)}`} class="my-auto"> + <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> + </a> + </div> + </div> + </div> + </div> + {/if} + </div> + + <div class="w-full"> + <div class="mx-auto space-y-5 lg:max-w-xl lg:pl-10 xl:max-w-2xl lg:pl-10"> + <slot /> + </div> + </div> +</div> + +{#if currency} + <div + class="flex fixed w-full px-4 bg-white py-2 pb-3 bottom-0 bg-opacity-90 tabular-nums" + > + <div class="text-secondary flex mr-auto"> + <div class="flex mr-1"> + <div class="my-auto mr-1">1</div> + <img src="/images/bitcoin.svg" class="w-5 my-auto" alt="Bitcoin" /> + </div> + <div>= {f(rates[currency], currency)}</div> + </div> + <div class="text-secondary flex ml-auto"> + <div class="flex"> + <div class="mr-1">⚡️{s((1 * sats) / rates[currency])} =</div> + <div>{f(1, currency)}</div> + </div> + </div> + </div> +{/if} diff --git a/src/routes/(app)/[username]/home/+page.server.js b/src/routes/(app)/[username]/home/+page.server.js new file mode 100644 index 00000000..59eb44d5 --- /dev/null +++ b/src/routes/(app)/[username]/home/+page.server.js @@ -0,0 +1,11 @@ +import { auth, get } from "$lib/utils"; + +export async function load({ cookies, depends, parent }) { + depends("app:items"); + + let { subject } = await parent(); + let items = await get(`/${subject.id}/items`, auth(cookies)); + items.map((i) => (i.quantity = 0)); + + return { items }; +} diff --git a/src/routes/(app)/[username]/home/+page.svelte b/src/routes/(app)/[username]/home/+page.svelte new file mode 100644 index 00000000..4e6365ff --- /dev/null +++ b/src/routes/(app)/[username]/home/+page.svelte @@ -0,0 +1,201 @@ +<script> + import { browser } from "$app/environment"; + import { goto, invalidate } from "$app/navigation"; + import { btc, f, sat, post } from "$lib/utils"; + import Icon from "$comp/Icon.svelte"; + import Balance from "$comp/Balance.svelte"; + import DeleteItem from "$comp/DeleteItem.svelte"; + import { t } from "$lib/translations"; + import { fade } from "svelte/transition"; + import { cubicIn } from "svelte/easing"; + import { flip } from "svelte/animate"; + import { installPrompt } from "$lib/store"; + + import { + dndzone, + SHADOW_ITEM_MARKER_PROPERTY_NAME, + SOURCES, + TRIGGERS, + } from "svelte-dnd-action"; + + export let data; + + let flipDurationMs = 200; + let dropTargetStyle = {}; + + let { subject, user, items, rates } = data; + $: ({ currency } = subject); + $: rate = rates[currency]; + $: refresh(data); + let refresh = (d) => ({ items, subject, user, rates } = d); + let dragDisabled = + subject?.id !== user?.id || (browser && window.innerWidth < 768); + + let deleting; + $: total = items.reduce((a, b) => a + b.price * b.quantity, 0); + let del = (item) => { + deleting = item; + }; + + function handleConsider(e) { + const { + items: newItems, + info: { source, trigger }, + } = e.detail; + items = newItems; + if (source === SOURCES.KEYBOARD && trigger === TRIGGERS.DRAG_STOPPED) + reset(); + } + + async function handleFinalize(e) { + const { + items: newItems, + info: { source }, + } = e.detail; + items = newItems; + if (source === SOURCES.POINTER) reset(); + + if (subject?.id === user?.id) { + await post("/api/items/sort", { items }); + invalidate("app:items"); + } + } + + function startDrag(e) { + e.preventDefault(); + dragDisabled = false; + } + + function handleKeyDown(e) { + if ((e.key === "Enter" || e.key === " ") && dragDisabled) + dragDisabled = false; + } + + $: if (total > 0 && browser) { + let payButton = document.querySelector("#payButton"); + if (payButton) payButton.addEventListener("click", checkout); + } + + let checkout = async () => { + let invoice = { + type: "lightning", + amount: btc(total, rate), + currency, + items: items.filter((i) => i.quantity > 0), + prompt: subject.prompt, + memoPrompt: subject.memoPrompt, + rate, + }; + + let { id } = await post(`/${subject.username}/invoice`, { + invoice, + user: { username: subject.username }, + }); + + if (invoice.memoPrompt) { + return goto(`/${subject.username}/invoice/${id}/memo`); + } + if (user) goto(`/send/invoice/${id}`, { invalidateAll: true }); + else goto(`/${subject.username}/invoice/${id}`); + }; + + let reset = () => + (dragDisabled = + user?.username !== subject?.username || window.innerWidth < 768); + + let install = async () => { + if (!$installPrompt) return; + await $installPrompt.prompt(); + $installPrompt = null; + }; +</script> + +<svelte:window on:resize={reset} /> + +{#if deleting} + <DeleteItem bind:item={deleting} /> +{/if} + +<div class="space-y-5"> + {#if user?.id === subject.id} + <div class="flex justify-center lg:justify-start"> + <Balance {user} {rate} /> + </div> + + {#if !user.balance} + <div class="mb-8"> + <p class="text-secondary text-lg"> + {$t("user.welcome")} + </p> + </div> + {/if} + + <div class="flex flex-wrap gap-3 justify-center w-full text-xl"> + <a href={`/send`} class="contents grow"> + <button + type="button" + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow" + > + <div class="mx-auto flex gap-2"> + <Icon icon="send" style="w-8" /> + <div class="my-auto">{$t("user.dashboard.send")}</div> + </div> + </button> + </a> + + <a href={`/${user.username}/receive`} class="contents"> + <button + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow" + > + <div class="mx-auto flex gap-2"> + <Icon icon="numpad" style="w-8" /> + <div class="my-auto">{$t("user.dashboard.receive")}</div> + </div> + </button> + </a> + + {#if $installPrompt} + <button + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white grow basis-full fixed bottom-16 xl:hidden" + on:click={install} + > + <div class="mx-auto flex gap-2"> + <Icon icon="save" style="w-8 mx-auto invert" /> + <div class="my-auto text-xl whitespace-nowrap">{$t("user.install")}</div> + </div> + </button> + {/if} + </div> + {/if} + + {#if total > 0} + <button + class="rounded-2xl py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white fixed bottom-16 right-4" + on:click={checkout} + > + <div class="mx-auto flex"> + <div class="my-auto text-2xl"> + {$t("user.dashboard.checkout")} + {f(total, currency)} + <span class="text-base"> + {sat(btc(total, rate), currency)} + </span> + </div> + </div> + </button> + {/if} +</div> + +<style> + .custom-shadow-item { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + visibility: visible; + background: #333; + opacity: 0.5; + margin: 0; + } +</style> diff --git a/src/routes/(app)/[username]/home/event/[id]/+page.server.js b/src/routes/(app)/[username]/home/event/[id]/+page.server.js new file mode 100644 index 00000000..24f2b611 --- /dev/null +++ b/src/routes/(app)/[username]/home/event/[id]/+page.server.js @@ -0,0 +1,12 @@ +import { get } from "$lib/utils"; +import { error } from "@sveltejs/kit"; + +export async function load({ params: { id } }) { + try { + let event = await get(`/event/${id}`); + if (!event) error(500, "Event not found"); + return { event }; + } catch (e) { + error(500, e.message); + } +} diff --git a/src/routes/(app)/[username]/home/event/[id]/+page.svelte b/src/routes/(app)/[username]/home/event/[id]/+page.svelte new file mode 100644 index 00000000..74ce1067 --- /dev/null +++ b/src/routes/(app)/[username]/home/event/[id]/+page.svelte @@ -0,0 +1,7 @@ +<script> + import Event from "$comp/Event.svelte"; + export let data; + let { event } = data; +</script> + +<Event {event} /> diff --git a/src/routes/(app)/[username]/home/followers/+page.svelte b/src/routes/(app)/[username]/home/followers/+page.svelte new file mode 100644 index 00000000..e087ad42 --- /dev/null +++ b/src/routes/(app)/[username]/home/followers/+page.svelte @@ -0,0 +1,52 @@ +<script> + import { t } from "$lib/translations"; + import Avatar from "$comp/Avatar.svelte"; + import { browser } from "$app/environment"; + import VirtualScroll from "svelte-virtual-scroll-list"; + + export let data; + let { + subject: { followers }, + } = data; + + let w; +</script> + +<svelte:window bind:innerWidth={w} /> + +<div class="px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> + {$t("user.followers")} + </h1> + + {#if browser} + {#if followers.length} + <VirtualScroll data={followers} key="pubkey" let:data pageMode={true}> + <a href={`/${data.pubkey}`}> + <div + class="flex border-b last:border-b-0 py-4 text-sm lg:text-lg text-secondary" + :key={data.pubkey} + > + <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]/home/follows/+page.svelte b/src/routes/(app)/[username]/home/follows/+page.svelte new file mode 100644 index 00000000..e4363796 --- /dev/null +++ b/src/routes/(app)/[username]/home/follows/+page.svelte @@ -0,0 +1,51 @@ +<script> + import { t } from "$lib/translations"; + import Avatar from "$comp/Avatar.svelte"; + import { browser } from "$app/environment"; + import VirtualScroll from "svelte-virtual-scroll-list"; + + export let data; + let { + subject: { follows }, + } = data; + let w; +</script> + +<svelte:window bind:innerWidth={w} /> + +<div class="px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> + {$t("user.following")} + </h1> + + {#if browser} + {#if follows.length} + <VirtualScroll data={follows} key="pubkey" let:data pageMode={true}> + <a href={`/${data.pubkey}`}> + <div + class="flex py-4 text-sm lg:text-lg text-secondary" + :key={data.pubkey} + > + <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> |
