Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 112 additions & 8 deletions apps/web/src/components/AccountView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,59 @@
}
}

type ShareEntry = { user_id: string; email?: string };
let shareExpanded = $state("");
let shares = $state<ShareEntry[]>([]);
let shareEmail = $state("");
let shareError = $state("");
let sharing = $state(false);

async function openShares(addr: string) {
if (shareExpanded === addr) {
shareExpanded = "";
shares = [];
return;
}
shareExpanded = addr;
shareEmail = "";
shareError = "";
if (!user.apiKey) return;
try {
const res = await api.listInboxShares(addr, user.apiKey);
shares = res.shares || [];
} catch {
shares = [];
}
}

async function addShare() {
if (!user.apiKey || !shareExpanded || !shareEmail.trim()) return;
shareError = "";
sharing = true;
try {
const entry = await api.addInboxShare(shareExpanded, shareEmail.trim(), user.apiKey);
if (!shares.find((s) => s.user_id === entry.user_id)) {
shares = [...shares, entry];
}
shareEmail = "";
} catch (e: any) {
const msg = e?.message || "";
if (msg.includes("404")) shareError = t("shareUserNotFound");
else if (msg.includes("400")) shareError = t("shareSelf");
else shareError = t("genericError");
} finally {
sharing = false;
}
}

async function removeShare(uid: string) {
if (!user.apiKey || !shareExpanded) return;
try {
await api.removeInboxShare(shareExpanded, uid, user.apiKey);
shares = shares.filter((s) => s.user_id !== uid);
} catch { /* silent */ }
}

async function loadPlanData() {
if (!user.info?.user_id) return;
try {
Expand Down Expand Up @@ -506,14 +559,65 @@
</h3>
<div class="space-y-2">
{#each activeInboxes as addr}
<a
href="/inbox/{addr}"
class="flex items-center gap-2 p-2 bg-slab/40 border border-ghost/10 rounded-sm
hover:border-neon/30 hover:bg-slab/60 transition-all duration-200"
>
<div class="w-1.5 h-1.5 rounded-full bg-neon"></div>
<span class="text-xs font-mono text-white truncate">{addr}</span>
</a>
<div class="bg-slab/40 border border-ghost/10 rounded-sm hover:border-neon/30 transition-colors">
<div class="flex items-center gap-2 p-2">
<a
href="/inbox/{addr}"
class="flex items-center gap-2 flex-1 min-w-0"
>
<div class="w-1.5 h-1.5 rounded-full bg-neon flex-shrink-0"></div>
<span class="text-xs font-mono text-white truncate">{addr}</span>
</a>
<button
onclick={() => openShares(addr)}
class="text-[10px] font-mono text-muted hover:text-neon transition-colors px-2 py-1"
>
{t("shareInbox")}
</button>
</div>

{#if shareExpanded === addr}
<div class="border-t border-ghost/15 p-3 space-y-2">
<p class="text-[10px] font-mono text-muted">{t("shareInboxDescription")}</p>
<div class="flex gap-2">
<input
type="email"
bind:value={shareEmail}
placeholder={t("shareEmailPlaceholder")}
class="flex-1 bg-slab/50 border border-ghost/20 text-white text-[11px] font-mono px-2 py-1.5 rounded-sm
focus:border-neon/40 focus:outline-none placeholder:text-ghost/40"
/>
<button
onclick={addShare}
disabled={sharing || !shareEmail.trim()}
class="px-2 py-1.5 text-[10px] font-mono border border-neon/40 text-neon
hover:bg-neon/10 transition-all disabled:opacity-40"
>
{t("shareAdd")}
</button>
</div>
{#if shareError}
<div class="text-ember text-[11px] font-mono">{shareError}</div>
{/if}
{#if shares.length > 0}
<div class="space-y-1">
<div class="text-[10px] font-mono tracking-widest text-muted">{t("sharedWith")}</div>
{#each shares as s}
<div class="flex items-center justify-between gap-2 text-[11px] font-mono">
<span class="text-white/80 truncate">{s.email || s.user_id}</span>
<button
onclick={() => removeShare(s.user_id)}
class="text-ember/70 hover:text-ember text-[10px]"
>
{t("shareRemove")}
</button>
</div>
{/each}
</div>
{/if}
</div>
{/if}
</div>
{/each}
</div>
</div>
Expand Down
Loading
Loading