diff options
| author | Adam Soltys <[email protected]> | 2024-02-20 19:53:49 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-04-25 12:43:02 -0700 |
| commit | 2ef0d75f69051e267f534d07c535a053f064778d (patch) | |
| tree | cacac0c117679941e536539aebecee729edad6f7 /src | |
| parent | 3e3f89bdb199dd18b47f6e1c7883bbbc9bcecefc (diff) | |
| download | coinos-ui-2ef0d75f69051e267f534d07c535a053f064778d.tar.gz coinos-ui-2ef0d75f69051e267f534d07c535a053f064778d.zip | |
items
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/DeleteItem.svelte | 47 | ||||
| -rw-r--r-- | src/components/ItemForm.svelte | 86 | ||||
| -rw-r--r-- | src/lib/upload.js | 3 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+page.server.js | 36 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+page.svelte | 263 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/items/[id]/+page.server.js | 24 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/items/[id]/+page.svelte | 9 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/items/create/+page.server.js | 17 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/items/create/+page.svelte | 4 |
9 files changed, 244 insertions, 245 deletions
diff --git a/src/components/DeleteItem.svelte b/src/components/DeleteItem.svelte new file mode 100644 index 00000000..8f7f97ca --- /dev/null +++ b/src/components/DeleteItem.svelte @@ -0,0 +1,47 @@ +<script> + import Icon from "$comp/Icon.svelte"; + import { fail, post } from "$lib/utils"; + import { invalidate } from "$app/navigation"; + + export let item; + + let cancel = () => (item = null); + + let submit = async () => { + try { + await post("/api/items/delete", { item }); + invalidate("app:items"); + item = null; + } catch (e) { + fail("Failed to delete item"); + } + }; +</script> + +<div + class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-20" +> + <div + class="relative top-1/3 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white space-y-5" + > + <h1 class="text-center text-2xl font-semibold">Delete item</h1> + <form on:submit|preventDefault={submit}> + <div class="w-full flex"> + <button + type="button" + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto" + on:click={cancel} + on:keydown={cancel} + > + <div class="my-auto">No</div> + </button> + <button + type="submit" + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto bg-black text-white" + > + <div class="my-auto">Yes</div> + </button> + </div> + </form> + </div> +</div> diff --git a/src/components/ItemForm.svelte b/src/components/ItemForm.svelte index 044020b7..71114c29 100644 --- a/src/components/ItemForm.svelte +++ b/src/components/ItemForm.svelte @@ -1,9 +1,12 @@ <script> import { t } from "$lib/translations"; import { upload } from "$lib/upload"; - export let item, token; + import { fail } from "$lib/utils"; + import { applyAction, deserialize } from "$app/forms"; + import Spinner from "$comp/Spinner.svelte"; + export let item; - let fileInput, formElement, file; + let fileInput, formElement, file, submitting, progress; let select = () => fileInput.click(); let src; @@ -30,14 +33,12 @@ if (src) { try { - let { hash } = JSON.parse( - await upload(file, "item", progress, token) - ); + let { hash } = JSON.parse(await upload(file, "item", progress)); data.set("image", hash); await fetch(`/api/public/${hash}.webp`, { - cache: "reload", + cache: "reload", mode: "no-cors", }); } catch (e) { @@ -68,41 +69,66 @@ on:submit|preventDefault={handleSubmit} bind:this={formElement} > + <input type="hidden" name="id" bind:value={item.id} /> + <input type="hidden" name="image" bind:value={item.image} /> + <div> <label for="name" class="font-bold mb-1 block">{$t("items.name")}</label> <input name="name" bind:value={item.name} /> </div> + <div> + <label for="price" class="font-bold mb-1 block">{$t("items.price")}</label> + <input name="price" bind:value={item.price} /> + </div> + + <div class="w-full"> <label for="img" class="font-bold mb-1 block">{$t("items.image")}</label> - <div class="relative w-full h-64"> - {#if item.hash} - <img - src={`/api/public/${item.hash}.webp`} - alt={item.name} - class="mx-auto object-cover h-full w-full" - /> - {:else if src} - <img {src} alt={item.name} class="mx-auto object-cover h-full w-full" /> - {:else} - <div - class="bg-gradient-to-r from-primary to-gradient w-full h-48 mb-4 cursor-pointer hover:opacity-80" - on:click={select} - on:keydown={select} - alt="Banner" - /> - {/if} - <input - type="file" - class="hidden" - bind:this={fileInput} - on:change={(e) => handleFile(e, "item")} - /> + + <div class="grid grid-cols-2 gap-4"> + <div class="h-64 rounded-2xl overflow-hidden"> + {#if src} + <img + {src} + alt={item.name} + class="object-cover w-full h-full" + on:click={select} + on:keydown={select} + /> + {:else if item.image} + <img + src={`/api/public/${item.image}.webp`} + alt={item.name} + class="object-cover w-full h-full" + on:click={select} + on:keydown={select} + /> + {:else} + <div + class="bg-gradient-to-r from-primary to-gradient mb-4 cursor-pointer hover:opacity-80 w-full h-full" + on:click={select} + on:keydown={select} + alt="Banner" + /> + {/if} + </div> </div> + + <input + type="file" + class="hidden" + bind:this={fileInput} + on:change={(e) => handleFile(e, "item")} + /> </div> <button class="rounded-full py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white text-2xl" > - {$t("items.submit")} + {#if submitting} + <Spinner /> + {:else} + {$t("items.submit")} + {/if} </button> </form> diff --git a/src/lib/upload.js b/src/lib/upload.js index 552e4a7a..31c235c6 100644 --- a/src/lib/upload.js +++ b/src/lib/upload.js @@ -1,4 +1,4 @@ -export const upload = async (file, type, progress, token) => { +export const upload = async (file, type, progress) => { let url = `/api/upload/${type}`; let formData = new FormData(); formData.append("file", file); @@ -13,7 +13,6 @@ export const upload = async (file, type, progress, token) => { }); ajax.upload.addEventListener("progress", progress, false); ajax.open("POST", url); - // ajax.setRequestHeader('Authorization', `Bearer ${token}`); ajax.send(formData); }); }; diff --git a/src/routes/(app)/[username]/(profile)/+page.server.js b/src/routes/(app)/[username]/(profile)/+page.server.js index 3110ec40..59eb44d5 100644 --- a/src/routes/(app)/[username]/(profile)/+page.server.js +++ b/src/routes/(app)/[username]/(profile)/+page.server.js @@ -1,35 +1,11 @@ import { auth, get } from "$lib/utils"; -export async function load({ cookies, params, parent, url }) { - let { user, subject } = await parent(); - let { pubkey } = subject; - let { since = 0 } = params; +export async function load({ cookies, depends, parent }) { + depends("app:items"); - let messages, invoices, sent, received, notes; - messages = invoices = sent = received = notes = []; + let { subject } = await parent(); + let items = await get(`/${subject.id}/items`, auth(cookies)); + items.map((i) => (i.quantity = 0)); - if (pubkey) { - if (user) { - try { - messages = await get(`/${user.pubkey}/${since}/messages`); - messages = messages.sort((a, b) => b.created_at - a.created_at); - } catch (e) { - console.log(`failed to fetch nostr messages`, e); - } - - ({ invoices, sent, received } = await get("/requests", auth(cookies))); - } - - try { - notes = await get(`/${pubkey}/notes`); - } catch (e) { - console.log(`failed to fetch nostr notes for ${pubkey}`, e); - } - - notes.map((e) => { - e.seen = e.created_at; - }); - } - - return { invoices, messages, notes, sent, received }; + return { items }; } diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index 670c2a2d..4868ac86 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -4,6 +4,7 @@ import Avatar from "$comp/Avatar.svelte"; import Balance from "$comp/Balance.svelte"; import Feed from "$comp/Feed.svelte"; + import DeleteItem from "$comp/DeleteItem.svelte"; import { t } from "$lib/translations"; import { btc, ease, f, sat, sats } from "$lib/utils"; import { sign, send, encrypt, decrypt } from "$lib/nostr"; @@ -11,60 +12,27 @@ import { browser } from "$app/environment"; export let data; - let { - messages, - notes, - invoices, - sent, - received, - subject, - user, - rate, - rates, - } = data; + let { subject, user, items, rates } = data; $: refresh(data); - let refresh = (d) => ({ subject, user, rates } = d); - - let items = [ - { - id: 1, - name: "Full Pour", - price: 9, - quantity: 0, - hash: "fa3a9d22eb24b33ae2dee8d7566c1ce25d1c76e18b46d54437621d3e34a3fbc9", - }, - { - id: 2, - name: "Half Pour", - price: 6, - quantity: 0, - hash: "a79c7b921a0dde39076201ae5f24505d5faa5f12f0cfd3b9d86cbc9c77ab0bd1", - }, - { - id: 3, - name: "Taster", - price: 3, - quantity: 0, - hash: "9d8fe68d068f7271cf08d4cb9b033966984549c1bcd827f7516e66e5b537f502", - }, - { - id: 4, - name: "4-packs", - price: 18, - quantity: 0, - hash: "13cf773ae51607e670c2a7c640aa5a1dc8b288554026de08370ed3da44e59493", - }, - ]; + let refresh = (d) => ({ items, subject, user, rates } = d); + let deleting; $: total = items.reduce((a, b) => a + b.price * b.quantity, 0); - let del = () => {}; + let del = (item) => { + deleting = item; + }; </script> -<div class="space-y-8"> - <div class="flex justify-center lg:justify-start mb-8"> - <Balance {user} {rate} /> - </div> - <div class="space-y-8 mt-8"> +{#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={rates[user.currency]} /> + </div> + {#if !user.balance} <p class="text-secondary dark:text-gray-300 text-lg max-w-2xl text-center lg:text-start mx-auto" @@ -117,6 +85,22 @@ </a> </div> + {#if user?.username === subject.username} + <div> + <a href={`/${user.username}/items/create`} class="w-full"> + <button + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-full bg-black text-white" + > + <div class="mx-auto flex"> + <div class="my-auto text-2xl"> + {$t("items.add")} + </div> + </div> + </button> + </a> + </div> + {/if} + <div class="grid grid-cols-2 gap-4"> {#each items as i} <div @@ -124,11 +108,20 @@ on:click={() => i.quantity++} > <div class="relative w-full h-64 overflow-hidden rounded-xl"> - <img - src={`/api/public/${i.hash}.webp`} - alt={i.name} - class="mx-auto object-cover h-full w-full" - /> + {#if i.image} + <img + src={`/api/public/${i.image}.webp`} + alt={i.name} + class="mx-auto object-cover h-full w-full" + /> + {:else} + <div + class="bg-gradient-to-r from-primary to-gradient mb-4 cursor-pointer hover:opacity-80 w-full h-full" + on:click={select} + on:keydown={select} + alt="Banner" + /> + {/if} {#if user?.username === subject.username} <div class="flex gap-2 justify-center absolute top-2 right-2"> <div @@ -136,23 +129,37 @@ i.quantity > 0 && i.quantity--} > <a href={`/${user.username}/items/${i.id}`}> - <button class="bg-white rounded-xl"> - <Icon icon="edit" style="w-10" /> + <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> </div> - <button class="bg-white rounded-xl"> - <Icon icon="trash" style="w-10" on:click={() => del(i)} /> + <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> </div> {/if} </div> <div class="bg-white rounded-xl p-2"> - <div class="text-2xl"> - {i.name} — {f(i.price, subject.currency)} + <div class="flex"> + <div class="text-2xl mr-auto">{i.name}</div> + <div class="flex gap-2 my-auto"> + <div class="font-semibold"> + {f(i.price, subject.currency)} + </div> + <div class="text-secondary"> + {sat(btc(i.price, rates[subject.currency]))} + </div> + </div> </div> - <div class="flex gap-2 justify-center"> + <div class="flex gap-2"> <div + class="ml-auto" on:click|stopPropagation={() => i.quantity > 0 && i.quantity--} > <Icon icon="minus" style="w-10" /> @@ -164,119 +171,25 @@ </div> {/each} </div> + {/if} +</div> - {#if total > 0} - <div> - <a - href={`/send/${subject.username}/${total}/${subject.currency}`} - class="w-full" - > - <button - class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-full bg-black text-white" - > - <div class="mx-auto flex"> - <div class="my-auto text-2xl"> - {f(total, subject.currency)} - {$t("user.dashboard.checkout")} - </div> - </div> - </button> - </a> - </div> - {/if} - {#if user?.username === subject.username} - <div> - <a href={`/${user.username}/items/create`} class="w-full"> - <button - class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-full bg-black text-white" - > - <div class="mx-auto flex"> - <div class="my-auto text-2xl"> - {$t("items.add")} - </div> - </div> - </button> - </a> - </div> - {/if} - - {#if invoices.length} - <div> - <h1 class="text-secondary text-xl">Invoices</h1> - {#each invoices as { amount, currency, rate, request: { requester, recipient }, hash }} - {@const requested = user.username === requester?.username} - <a - href={`/${recipient.username}/invoice/${hash}` + - (requested ? "/tip" : "")} - > - <div class="border-b p-2 last:border-b-0 hover:bg-gray-100"> - <div class="flex gap-2"> - <div> - {#if requested} - <div class="flex"> - <Avatar user={recipient} size={20} /> - <div class="my-auto text-left"> - <p class="text-secondary"> - {$t("payments.invoiceFrom")} - </p> - <p class="ml-1 text-lg break-words"> - {recipient.username} - </p> - </div> - </div> - {:else} - <div class="flex"> - <Avatar user={requester} size={20} /> - <div class="my-auto text-left"> - <p class="text-secondary">{$t("payments.awaiting")}</p> - <p class="ml-1 text-lg break-words"> - {requester.username} - </p> - </div> - </div> - {/if} - </div> - <div class="whitespace-nowrap my-auto ml-auto flex gap-2"> - <div class="font-bold"> - {f(amount * (rate / sats), currency)} - </div> - - <div class="text-secondary">{sat(amount)}</div> - </div> - </div> - </div> - </a> - {/each} - </div> - {/if} - {#if received.length} - <div> - <h1 class="text-secondary text-xl">Requests</h1> - - {#each received as { id, invoice, memo, requester: r }} - <a href={`/${user.username}/receive/${id}`}> - <div class="border-b p-2 last:border-b-0 hover:bg-gray-100"> - <div class="flex"> - <div> - <div class="flex"> - <Avatar user={r} size={20} /> - <div class="my-auto text-left"> - <p class="ml-1 text-lg break-words">{r.username}</p> - <p class="ml-1 text-secondary">{memo}</p> - </div> - </div> - </div> - <div class="whitespace-nowrap my-auto ml-auto flex gap-2"> - <button - class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full" - >Invoice</button - > - </div> - </div> - </div> - </a> - {/each} - </div> - {/if} +{#if total > 0} + <div> + <a + href={`/send/${subject.username}/${total}/${subject.currency}`} + class="w-full" + > + <button + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-full bg-black text-white" + > + <div class="mx-auto flex"> + <div class="my-auto text-2xl"> + {f(total, subject.currency)} + {$t("user.dashboard.checkout")} + </div> + </div> + </button> + </a> </div> -</div> +{/if} diff --git a/src/routes/(app)/[username]/items/[id]/+page.server.js b/src/routes/(app)/[username]/items/[id]/+page.server.js index e1fe2efd..960f9af4 100644 --- a/src/routes/(app)/[username]/items/[id]/+page.server.js +++ b/src/routes/(app)/[username]/items/[id]/+page.server.js @@ -1,6 +1,22 @@ -import { get } from "$lib/utils"; -export let load = async ({ cookies, params: { id } }) => { - let item = await get(`/item/${id}`); - console.log("item", item) +import { fd, auth, get, post } from "$lib/utils"; +import { fail, redirect } from "@sveltejs/kit"; + + export let load = async ({ cookies, params: { id } }) => { + let item = await get(`/items/${id}`); return { item }; }; + +export const actions = { + default: async ({ cookies, request, url }) => { + let form = await fd(request); + try { + await post(`/items`, form, auth(cookies)); + } catch (e) { + console.log(e); + return fail(400, { message: e.message }); + } + + let success = `/${url.pathname.split("/")[1]}`; + redirect(307, success); + }, +}; diff --git a/src/routes/(app)/[username]/items/[id]/+page.svelte b/src/routes/(app)/[username]/items/[id]/+page.svelte index 5a19e892..ddf4cf1d 100644 --- a/src/routes/(app)/[username]/items/[id]/+page.svelte +++ b/src/routes/(app)/[username]/items/[id]/+page.svelte @@ -1,7 +1,14 @@ <script> + import { t } from "$lib/translations"; import ItemForm from "$comp/ItemForm.svelte"; export let data; let { item } = data; + console.log(item) </script> -<ItemForm {item} /> +<div class="container max-w-xl mx-auto"> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> + {$t("items.edit")} + </h1> + <ItemForm {item} /> +</div> diff --git a/src/routes/(app)/[username]/items/create/+page.server.js b/src/routes/(app)/[username]/items/create/+page.server.js new file mode 100644 index 00000000..ea0571e7 --- /dev/null +++ b/src/routes/(app)/[username]/items/create/+page.server.js @@ -0,0 +1,17 @@ +import { fail, redirect } from "@sveltejs/kit"; +import { fd, auth, get, post } from "$lib/utils"; + +export const actions = { + default: async ({ cookies, request, url }) => { + let form = await fd(request); + try { + await post("/items", form, auth(cookies)); + } catch (e) { + console.log(e); + return fail(400, { message: e.message }); + } + + let success = `/${url.pathname.split('/')[1]}` + redirect(307, success); + }, +}; diff --git a/src/routes/(app)/[username]/items/create/+page.svelte b/src/routes/(app)/[username]/items/create/+page.svelte index c9aeae1d..46ee8e0c 100644 --- a/src/routes/(app)/[username]/items/create/+page.svelte +++ b/src/routes/(app)/[username]/items/create/+page.svelte @@ -1,8 +1,6 @@ <script> import { t } from "$lib/translations"; import ItemForm from "$comp/ItemForm.svelte"; - export let data; - let { token } = data; let item = {}; </script> @@ -10,5 +8,5 @@ <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> {$t("items.add")} </h1> - <ItemForm {item} {token} /> + <ItemForm {item} /> </div> |
