added sharing and subscriptions
This commit is contained in:
@@ -27,6 +27,55 @@
|
||||
let targetPrice = $state('');
|
||||
let busy = $state(false);
|
||||
let formError = $state('');
|
||||
let showDetails = $state(false);
|
||||
|
||||
// sharing
|
||||
let sharing = $state(false);
|
||||
let copied = $state(false);
|
||||
const shareUrl = $derived(
|
||||
list?.share_token ? `${page.url.origin}/shared/${list.share_token}` : ''
|
||||
);
|
||||
|
||||
async function share() {
|
||||
if (!list) return;
|
||||
sharing = true;
|
||||
formError = '';
|
||||
try {
|
||||
const updated = await listsApi.share(list.id);
|
||||
list = updated;
|
||||
lists.replace(updated);
|
||||
} catch (err) {
|
||||
formError = err instanceof ApiError ? err.message : 'failed to share';
|
||||
} finally {
|
||||
sharing = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function unshare() {
|
||||
if (!list || !confirm('revoke the share link? anyone holding it loses access.')) return;
|
||||
sharing = true;
|
||||
formError = '';
|
||||
try {
|
||||
const updated = await listsApi.unshare(list.id);
|
||||
list = updated;
|
||||
lists.replace(updated);
|
||||
} catch (err) {
|
||||
formError = err instanceof ApiError ? err.message : 'failed to unshare';
|
||||
} finally {
|
||||
sharing = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function copyShare() {
|
||||
if (!shareUrl) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(shareUrl);
|
||||
copied = true;
|
||||
setTimeout(() => (copied = false), 1500);
|
||||
} catch {
|
||||
/* clipboard blocked — the field is selectable as a fallback */
|
||||
}
|
||||
}
|
||||
|
||||
// inline edit
|
||||
let editingId = $state<string | null>(null);
|
||||
@@ -242,24 +291,65 @@
|
||||
<a href="/lists" class="label hover:text-iris">← all lists</a>
|
||||
<div class="mt-2 flex items-start gap-3">
|
||||
<span class="text-4xl leading-none">{list?.emoji ?? '✦'}</span>
|
||||
<div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<h1 class="font-display text-4xl font-bold">{list?.name ?? '…'}</h1>
|
||||
{#if list?.description}<p class="gospel mt-1 text-lg">{list.description}</p>{/if}
|
||||
</div>
|
||||
{#if list}
|
||||
{#if list.share_token}
|
||||
<button
|
||||
class="tag shrink-0 border-mint text-mint"
|
||||
title="this list is shared — manage below"
|
||||
onclick={() => document.getElementById('share-box')?.scrollIntoView({ behavior: 'smooth' })}
|
||||
>
|
||||
◈ shared
|
||||
</button>
|
||||
{:else}
|
||||
<button class="tag shrink-0 border-smoke text-mute hover:text-iris" disabled={sharing} onclick={share}>
|
||||
{sharing ? '…' : '◈ share'}
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if list?.share_token}
|
||||
<div id="share-box" class="panel mt-4 flex flex-wrap items-center gap-2 p-3 text-sm">
|
||||
<span class="label shrink-0">public link</span>
|
||||
<input class="field flex-1 text-xs" readonly value={shareUrl} onclick={(e) => e.currentTarget.select()} />
|
||||
<button class="rounded border border-smoke px-3 py-1.5 text-xs text-mute transition hover:border-iris hover:text-iris" onclick={copyShare}>
|
||||
{copied ? '✓ copied' : 'copy'}
|
||||
</button>
|
||||
<a href={shareUrl} target="_blank" rel="noopener noreferrer" class="rounded border border-smoke px-3 py-1.5 text-xs text-mute transition hover:border-iris hover:text-iris">open ↗</a>
|
||||
<button class="rounded border border-smoke px-3 py-1.5 text-xs text-mute transition hover:border-rose hover:text-rose" disabled={sharing} onclick={unshare}>
|
||||
{sharing ? '…' : 'unshare'}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Add item -->
|
||||
<form class="panel panel-acid space-y-4 p-6" onsubmit={addItem}>
|
||||
<p class="label">add an item</p>
|
||||
<input class="field" bind:value={title} maxlength="200" placeholder="what do you want?" />
|
||||
<div class="grid gap-4 sm:grid-cols-[1fr_8rem]">
|
||||
<input class="field" bind:value={url} placeholder="product link (we'll track the price)" />
|
||||
<input class="field" bind:value={targetPrice} inputmode="decimal" placeholder="target price" />
|
||||
<!-- Add item — compact quick-add; tracking details on demand -->
|
||||
<form class="panel space-y-3 p-4" onsubmit={addItem}>
|
||||
<div class="flex gap-2">
|
||||
<input class="field" bind:value={title} maxlength="200" placeholder="add an item — what do you want?" />
|
||||
<button class="btn btn-acid shrink-0" disabled={busy || !title.trim()}>{busy ? '…' : 'add +'}</button>
|
||||
</div>
|
||||
<input class="field" bind:value={note} maxlength="1000" placeholder="note to self (optional)" />
|
||||
<button
|
||||
type="button"
|
||||
class="label transition hover:text-iris"
|
||||
onclick={() => (showDetails = !showDetails)}
|
||||
>
|
||||
{showDetails ? '− fewer' : '+ link, target price & note'}
|
||||
</button>
|
||||
{#if showDetails}
|
||||
<div class="space-y-3 border-t border-smoke pt-3">
|
||||
<div class="grid gap-3 sm:grid-cols-[1fr_8rem]">
|
||||
<input class="field" bind:value={url} placeholder="product link (we'll track the price)" />
|
||||
<input class="field" bind:value={targetPrice} inputmode="decimal" placeholder="target price" />
|
||||
</div>
|
||||
<input class="field" bind:value={note} maxlength="1000" placeholder="note to self (optional)" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if formError}<p class="border-2 border-rose bg-rose/10 px-3 py-2 text-sm text-rose">{formError}</p>{/if}
|
||||
<button class="btn btn-acid" disabled={busy}>{busy ? 'adding…' : 'add item +'}</button>
|
||||
</form>
|
||||
|
||||
{#if !loaded}
|
||||
@@ -298,8 +388,8 @@
|
||||
</div>
|
||||
<div class="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-mute">
|
||||
{#if money(item.current_price, item.currency)}
|
||||
<span class:text-mint={onSale(item)} class:text-ink={!onSale(item)}>
|
||||
now {money(item.current_price, item.currency)}
|
||||
<span class="text-sm font-bold" class:text-mint={onSale(item)} class:text-ink={!onSale(item)}>
|
||||
{money(item.current_price, item.currency)}
|
||||
</span>
|
||||
{/if}
|
||||
{#if item.target_price != null}
|
||||
|
||||
Reference in New Issue
Block a user