diff --git a/apps/web/src/components/AccountView.svelte b/apps/web/src/components/AccountView.svelte index 2fe982f..e6eb69d 100644 --- a/apps/web/src/components/AccountView.svelte +++ b/apps/web/src/components/AccountView.svelte @@ -39,6 +39,56 @@ let apiKeyCopied = $state(false); let plans = $state(null); let selectedPlan = $state("annual"); + let webhookUrl = $state(""); + let webhookSecret = $state(""); + let webhookInput = $state(""); + let webhookSaving = $state(false); + let webhookError = $state(""); + let webhookSecretCopied = $state(false); + + async function loadWebhook() { + if (!user.apiKey || !user.isPremium) return; + try { + const cfg = await api.getWebhook(user.apiKey); + webhookUrl = cfg.url || ""; + webhookSecret = cfg.secret || ""; + webhookInput = webhookUrl; + } catch { + // silent + } + } + + async function saveWebhook() { + if (!user.apiKey) return; + if (!webhookInput.startsWith("https://")) { + webhookError = t("webhookInvalidUrl"); + return; + } + webhookSaving = true; + webhookError = ""; + try { + const cfg = await api.setWebhook(webhookInput.trim(), user.apiKey); + webhookUrl = cfg.url; + webhookSecret = cfg.secret; + } catch (e: any) { + webhookError = e?.message || t("genericError"); + } finally { + webhookSaving = false; + } + } + + async function removeWebhook() { + if (!user.apiKey) return; + webhookSaving = true; + try { + await api.deleteWebhook(user.apiKey); + webhookUrl = ""; + webhookSecret = ""; + webhookInput = ""; + } finally { + webhookSaving = false; + } + } async function loadPlanData() { if (!user.info?.user_id) return; @@ -137,6 +187,7 @@ initUser().then(() => { loadInboxes(); if (!user.isPremium) loadPlanData(); + if (user.isPremium) loadWebhook(); }); document.addEventListener("visibilitychange", onVisibilityChange); return () => document.removeEventListener("visibilitychange", onVisibilityChange); @@ -227,7 +278,7 @@ {#if !user.isPremium} {#if plans?.monthly && plans?.annual}
-
{t("chooseBilling")}
+
{t("chooseBilling")}
+ +
+
+

{t("webhook")}

+ {#if webhookUrl} + + {t("webhookActive")} + + {/if} +
+

{t("webhookDescription")}

+ + +
+ + + {#if webhookUrl} + + {/if} +
+ {#if webhookError} +
{webhookError}
+ {/if} + + {#if webhookSecret} + +
+ + {webhookSecret} + + +
+

{t("webhookSecretHelp")}

+ {/if} +
+ {#if activeInboxes.length > 0}
diff --git a/apps/web/src/components/InboxView.svelte b/apps/web/src/components/InboxView.svelte index 9008d43..0c20e6e 100644 --- a/apps/web/src/components/InboxView.svelte +++ b/apps/web/src/components/InboxView.svelte @@ -119,8 +119,34 @@ } await createNewInbox(); } else { - // Existing address — load saved token, real expiry comes from first poll - inboxToken = loadToken(inboxAddress); + // Existing address — prefer the saved token from creation. If we don't + // have one, fall back to the logged-in user's API key (the backend + // checks ownership). If neither exists, this inbox isn't ours and we + // bounce to home so visitors can create their own. + const saved = loadToken(inboxAddress); + let candidate = ""; + if (saved) { + candidate = saved; + } else if (user.apiKey) { + candidate = user.apiKey; + } else { + window.location.replace(localizePath("/")); + return; + } + + // Probe the API once with the candidate before rendering the inbox UI. + // A 401 means we don't actually own this address — redirect home so we + // never show the inbox shell to someone who can't read the messages. + try { + const probe = await api.getInbox(inboxAddress, candidate); + inboxToken = candidate; + messages = probe.messages || []; + messageCount = messages.length; + if (probe.expires_at) expiresAt = probe.expires_at; + } catch { + window.location.replace(localizePath("/")); + return; + } } } finally { isLoading = false; @@ -403,7 +429,11 @@
{#if selectedMessage}
- +
{:else}
diff --git a/apps/web/src/components/LandingPage.svelte b/apps/web/src/components/LandingPage.svelte index 22919d4..9756b02 100644 --- a/apps/web/src/components/LandingPage.svelte +++ b/apps/web/src/components/LandingPage.svelte @@ -204,8 +204,8 @@
$4{t("perMonth")}
-
- $40{t("perYear")} +
+ $40{t("perYear")} {t("saveAnnual", { percent: 17 })} diff --git a/apps/web/src/components/MessageDetail.svelte b/apps/web/src/components/MessageDetail.svelte index 42d9403..ad6e06b 100644 --- a/apps/web/src/components/MessageDetail.svelte +++ b/apps/web/src/components/MessageDetail.svelte @@ -1,14 +1,23 @@