aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-02-20 19:53:49 -0800
committerAdam Soltys <[email protected]>2024-04-25 12:43:02 -0700
commit2ef0d75f69051e267f534d07c535a053f064778d (patch)
treecacac0c117679941e536539aebecee729edad6f7 /src
parent3e3f89bdb199dd18b47f6e1c7883bbbc9bcecefc (diff)
downloadcoinos-ui-2ef0d75f69051e267f534d07c535a053f064778d.tar.gz
coinos-ui-2ef0d75f69051e267f534d07c535a053f064778d.zip
items
Diffstat (limited to 'src')
-rw-r--r--src/components/DeleteItem.svelte47
-rw-r--r--src/components/ItemForm.svelte86
-rw-r--r--src/lib/upload.js3
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.server.js36
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte263
-rw-r--r--src/routes/(app)/[username]/items/[id]/+page.server.js24
-rw-r--r--src/routes/(app)/[username]/items/[id]/+page.svelte9
-rw-r--r--src/routes/(app)/[username]/items/create/+page.server.js17
-rw-r--r--src/routes/(app)/[username]/items/create/+page.svelte4
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} &mdash; {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>