feat: Swiss-compliant launch — Impressum/AGB/Contact, support panel, DSG exports, cookie banner
Legal (Swiss minimum, no individual named):
- Impressum page (UWG Art. 3 lit. s) — provider, contact via support panel,
no email required, jurisdiction = Switzerland
- AGB page — subscription terms, payment, cancellation, suspension on payment
fail, 14-day money-back, AI-processing-per-tier disclosure, Swiss law +
Swiss venue, modeled after typical Schweizer SaaS terms
- Privacy: Stripe added as subprocessor with full data-flow disclosure
Support panel replaces email contact entirely:
- @bmm/db: support_status enum + support_tickets + support_messages tables,
migration applied to prod DB
- @bmm/api: support routes (user create/list/view/reply, admin list/view/reply
/set-status), public /v1/contact for logged-out visitors with per-IP rate
limit of 3 submissions/day to prevent spam-flood
- Web: /settings/support (list + new), /settings/support/[id] (conversation),
/admin/support, /admin/support/[id]
- Public /contact form with email collection for guest tickets
Data rights (DSG Art. 25 / GDPR Art. 15+20):
- /v1/account/export returns user-scoped JSON of profile, org, servers,
builds, audit, support tickets and messages — excludes hashes, encrypted
secrets, other-user data
- /settings/account: download button + deletion-via-ticket workflow
Production-readiness gaps closed:
- org.suspended now blocks /v1/servers POST and /v1/servers/preview (402);
webhook flagged this state but enforcement was missing
- Cookie banner: minimal, essential-cookies-only disclosure (Swiss DSG +
GDPR compliant without dark-pattern consent UI), mounts on both layouts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:12:06 +02:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
2026-05-31 19:23:41 +02:00
|
|
|
import { apiFetch, apiUrl } from '@/lib/api';
|
feat: Swiss-compliant launch — Impressum/AGB/Contact, support panel, DSG exports, cookie banner
Legal (Swiss minimum, no individual named):
- Impressum page (UWG Art. 3 lit. s) — provider, contact via support panel,
no email required, jurisdiction = Switzerland
- AGB page — subscription terms, payment, cancellation, suspension on payment
fail, 14-day money-back, AI-processing-per-tier disclosure, Swiss law +
Swiss venue, modeled after typical Schweizer SaaS terms
- Privacy: Stripe added as subprocessor with full data-flow disclosure
Support panel replaces email contact entirely:
- @bmm/db: support_status enum + support_tickets + support_messages tables,
migration applied to prod DB
- @bmm/api: support routes (user create/list/view/reply, admin list/view/reply
/set-status), public /v1/contact for logged-out visitors with per-IP rate
limit of 3 submissions/day to prevent spam-flood
- Web: /settings/support (list + new), /settings/support/[id] (conversation),
/admin/support, /admin/support/[id]
- Public /contact form with email collection for guest tickets
Data rights (DSG Art. 25 / GDPR Art. 15+20):
- /v1/account/export returns user-scoped JSON of profile, org, servers,
builds, audit, support tickets and messages — excludes hashes, encrypted
secrets, other-user data
- /settings/account: download button + deletion-via-ticket workflow
Production-readiness gaps closed:
- org.suspended now blocks /v1/servers POST and /v1/servers/preview (402);
webhook flagged this state but enforcement was missing
- Cookie banner: minimal, essential-cookies-only disclosure (Swiss DSG +
GDPR compliant without dark-pattern consent UI), mounts on both layouts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:12:06 +02:00
|
|
|
import Link from 'next/link';
|
|
|
|
|
import { useState } from 'react';
|
|
|
|
|
|
|
|
|
|
export default function AccountPage() {
|
|
|
|
|
const [downloading, setDownloading] = useState(false);
|
2026-05-31 19:23:41 +02:00
|
|
|
const [confirmText, setConfirmText] = useState('');
|
|
|
|
|
const [deleting, setDeleting] = useState(false);
|
|
|
|
|
const [delError, setDelError] = useState<string | null>(null);
|
|
|
|
|
|
|
|
|
|
async function deleteAccount() {
|
|
|
|
|
if (!confirmText.trim()) return;
|
|
|
|
|
if (!confirm('Permanently delete your account and all its data? This cannot be undone.')) return;
|
|
|
|
|
setDeleting(true);
|
|
|
|
|
setDelError(null);
|
|
|
|
|
try {
|
|
|
|
|
await apiFetch('/v1/account', {
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
body: JSON.stringify({ confirm: confirmText.trim() }),
|
|
|
|
|
});
|
|
|
|
|
window.location.href = '/';
|
|
|
|
|
} catch (e) {
|
|
|
|
|
const detail = (e as { detail?: { detail?: string; error?: string } }).detail;
|
|
|
|
|
setDelError(detail?.detail ?? detail?.error ?? (e as Error).message);
|
|
|
|
|
setDeleting(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
feat: Swiss-compliant launch — Impressum/AGB/Contact, support panel, DSG exports, cookie banner
Legal (Swiss minimum, no individual named):
- Impressum page (UWG Art. 3 lit. s) — provider, contact via support panel,
no email required, jurisdiction = Switzerland
- AGB page — subscription terms, payment, cancellation, suspension on payment
fail, 14-day money-back, AI-processing-per-tier disclosure, Swiss law +
Swiss venue, modeled after typical Schweizer SaaS terms
- Privacy: Stripe added as subprocessor with full data-flow disclosure
Support panel replaces email contact entirely:
- @bmm/db: support_status enum + support_tickets + support_messages tables,
migration applied to prod DB
- @bmm/api: support routes (user create/list/view/reply, admin list/view/reply
/set-status), public /v1/contact for logged-out visitors with per-IP rate
limit of 3 submissions/day to prevent spam-flood
- Web: /settings/support (list + new), /settings/support/[id] (conversation),
/admin/support, /admin/support/[id]
- Public /contact form with email collection for guest tickets
Data rights (DSG Art. 25 / GDPR Art. 15+20):
- /v1/account/export returns user-scoped JSON of profile, org, servers,
builds, audit, support tickets and messages — excludes hashes, encrypted
secrets, other-user data
- /settings/account: download button + deletion-via-ticket workflow
Production-readiness gaps closed:
- org.suspended now blocks /v1/servers POST and /v1/servers/preview (402);
webhook flagged this state but enforcement was missing
- Cookie banner: minimal, essential-cookies-only disclosure (Swiss DSG +
GDPR compliant without dark-pattern consent UI), mounts on both layouts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:12:06 +02:00
|
|
|
|
|
|
|
|
async function downloadExport() {
|
|
|
|
|
setDownloading(true);
|
|
|
|
|
try {
|
|
|
|
|
// Trigger a same-origin attachment download. The cookie ships with the
|
|
|
|
|
// request because we're same-credentials with the API origin via CORS.
|
|
|
|
|
window.location.href = apiUrl('/v1/account/export');
|
|
|
|
|
} finally {
|
|
|
|
|
setTimeout(() => setDownloading(false), 1500);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="mx-auto max-w-3xl px-6 py-10">
|
|
|
|
|
<h1 className="text-[22px] font-semibold tracking-tight">Account</h1>
|
|
|
|
|
<p className="mt-1 text-[13px] text-[--color-fg-muted]">
|
|
|
|
|
Your data, your rights. Swiss DSG Art. 25 / GDPR Art. 15 + 20.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div className="mt-8 space-y-4">
|
|
|
|
|
<section className="panel p-5">
|
|
|
|
|
<h2 className="text-[14px] font-semibold tracking-tight">Download your data</h2>
|
|
|
|
|
<p className="mt-2 text-[12.5px] leading-relaxed text-[--color-fg-muted]">
|
|
|
|
|
One JSON file with everything we hold for your account: profile, organization, MCP
|
|
|
|
|
servers, build history (last 1000 entries), audit log (last 1000 events) and your
|
|
|
|
|
support-ticket history. Excludes password hashes, encrypted secrets and other
|
|
|
|
|
users' data.
|
|
|
|
|
</p>
|
|
|
|
|
<div className="mt-4">
|
|
|
|
|
<Button variant="primary" size="md" onClick={downloadExport} disabled={downloading}>
|
|
|
|
|
{downloading ? 'Preparing…' : 'Download .json'}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
2026-05-31 19:23:41 +02:00
|
|
|
<section className="panel border-[--color-danger]/30 p-5">
|
|
|
|
|
<h2 className="text-[14px] font-semibold tracking-tight text-[--color-danger]">
|
|
|
|
|
Delete account
|
|
|
|
|
</h2>
|
feat: Swiss-compliant launch — Impressum/AGB/Contact, support panel, DSG exports, cookie banner
Legal (Swiss minimum, no individual named):
- Impressum page (UWG Art. 3 lit. s) — provider, contact via support panel,
no email required, jurisdiction = Switzerland
- AGB page — subscription terms, payment, cancellation, suspension on payment
fail, 14-day money-back, AI-processing-per-tier disclosure, Swiss law +
Swiss venue, modeled after typical Schweizer SaaS terms
- Privacy: Stripe added as subprocessor with full data-flow disclosure
Support panel replaces email contact entirely:
- @bmm/db: support_status enum + support_tickets + support_messages tables,
migration applied to prod DB
- @bmm/api: support routes (user create/list/view/reply, admin list/view/reply
/set-status), public /v1/contact for logged-out visitors with per-IP rate
limit of 3 submissions/day to prevent spam-flood
- Web: /settings/support (list + new), /settings/support/[id] (conversation),
/admin/support, /admin/support/[id]
- Public /contact form with email collection for guest tickets
Data rights (DSG Art. 25 / GDPR Art. 15+20):
- /v1/account/export returns user-scoped JSON of profile, org, servers,
builds, audit, support tickets and messages — excludes hashes, encrypted
secrets, other-user data
- /settings/account: download button + deletion-via-ticket workflow
Production-readiness gaps closed:
- org.suspended now blocks /v1/servers POST and /v1/servers/preview (402);
webhook flagged this state but enforcement was missing
- Cookie banner: minimal, essential-cookies-only disclosure (Swiss DSG +
GDPR compliant without dark-pattern consent UI), mounts on both layouts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:12:06 +02:00
|
|
|
<p className="mt-2 text-[12.5px] leading-relaxed text-[--color-fg-muted]">
|
2026-05-31 19:23:41 +02:00
|
|
|
Permanently erases your account and every organization where you are the only member —
|
|
|
|
|
servers, encrypted secrets, builds and history are wiped and running containers are
|
|
|
|
|
stopped. This cannot be undone. Swiss DSG Art. 32 / GDPR Art. 17.
|
feat: Swiss-compliant launch — Impressum/AGB/Contact, support panel, DSG exports, cookie banner
Legal (Swiss minimum, no individual named):
- Impressum page (UWG Art. 3 lit. s) — provider, contact via support panel,
no email required, jurisdiction = Switzerland
- AGB page — subscription terms, payment, cancellation, suspension on payment
fail, 14-day money-back, AI-processing-per-tier disclosure, Swiss law +
Swiss venue, modeled after typical Schweizer SaaS terms
- Privacy: Stripe added as subprocessor with full data-flow disclosure
Support panel replaces email contact entirely:
- @bmm/db: support_status enum + support_tickets + support_messages tables,
migration applied to prod DB
- @bmm/api: support routes (user create/list/view/reply, admin list/view/reply
/set-status), public /v1/contact for logged-out visitors with per-IP rate
limit of 3 submissions/day to prevent spam-flood
- Web: /settings/support (list + new), /settings/support/[id] (conversation),
/admin/support, /admin/support/[id]
- Public /contact form with email collection for guest tickets
Data rights (DSG Art. 25 / GDPR Art. 15+20):
- /v1/account/export returns user-scoped JSON of profile, org, servers,
builds, audit, support tickets and messages — excludes hashes, encrypted
secrets, other-user data
- /settings/account: download button + deletion-via-ticket workflow
Production-readiness gaps closed:
- org.suspended now blocks /v1/servers POST and /v1/servers/preview (402);
webhook flagged this state but enforcement was missing
- Cookie banner: minimal, essential-cookies-only disclosure (Swiss DSG +
GDPR compliant without dark-pattern consent UI), mounts on both layouts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:12:06 +02:00
|
|
|
</p>
|
2026-05-31 19:23:41 +02:00
|
|
|
<p className="mt-3 text-[12px] text-[--color-fg-subtle]">
|
|
|
|
|
Type your account email (or phone) to confirm:
|
|
|
|
|
</p>
|
|
|
|
|
<div className="mt-2 flex flex-wrap items-center gap-2">
|
|
|
|
|
<input
|
|
|
|
|
value={confirmText}
|
|
|
|
|
onChange={(e) => setConfirmText(e.target.value)}
|
|
|
|
|
placeholder="you@example.com"
|
|
|
|
|
className="h-9 w-64 rounded-md border border-[--color-border] bg-[--color-bg-subtle] px-3 text-[13px] outline-none transition-colors focus:border-[--color-border-strong]"
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={deleteAccount}
|
|
|
|
|
disabled={deleting || !confirmText.trim()}
|
|
|
|
|
className="inline-flex h-9 items-center rounded-md border border-[--color-danger]/50 bg-[--color-danger]/10 px-4 text-[13px] font-medium text-[--color-danger] transition-colors hover:bg-[--color-danger]/20 disabled:opacity-50"
|
|
|
|
|
>
|
|
|
|
|
{deleting ? 'Deleting…' : 'Delete my account'}
|
|
|
|
|
</button>
|
feat: Swiss-compliant launch — Impressum/AGB/Contact, support panel, DSG exports, cookie banner
Legal (Swiss minimum, no individual named):
- Impressum page (UWG Art. 3 lit. s) — provider, contact via support panel,
no email required, jurisdiction = Switzerland
- AGB page — subscription terms, payment, cancellation, suspension on payment
fail, 14-day money-back, AI-processing-per-tier disclosure, Swiss law +
Swiss venue, modeled after typical Schweizer SaaS terms
- Privacy: Stripe added as subprocessor with full data-flow disclosure
Support panel replaces email contact entirely:
- @bmm/db: support_status enum + support_tickets + support_messages tables,
migration applied to prod DB
- @bmm/api: support routes (user create/list/view/reply, admin list/view/reply
/set-status), public /v1/contact for logged-out visitors with per-IP rate
limit of 3 submissions/day to prevent spam-flood
- Web: /settings/support (list + new), /settings/support/[id] (conversation),
/admin/support, /admin/support/[id]
- Public /contact form with email collection for guest tickets
Data rights (DSG Art. 25 / GDPR Art. 15+20):
- /v1/account/export returns user-scoped JSON of profile, org, servers,
builds, audit, support tickets and messages — excludes hashes, encrypted
secrets, other-user data
- /settings/account: download button + deletion-via-ticket workflow
Production-readiness gaps closed:
- org.suspended now blocks /v1/servers POST and /v1/servers/preview (402);
webhook flagged this state but enforcement was missing
- Cookie banner: minimal, essential-cookies-only disclosure (Swiss DSG +
GDPR compliant without dark-pattern consent UI), mounts on both layouts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:12:06 +02:00
|
|
|
</div>
|
2026-05-31 19:23:41 +02:00
|
|
|
{delError && <p className="mt-2 text-[12px] text-[--color-danger]">{delError}</p>}
|
feat: Swiss-compliant launch — Impressum/AGB/Contact, support panel, DSG exports, cookie banner
Legal (Swiss minimum, no individual named):
- Impressum page (UWG Art. 3 lit. s) — provider, contact via support panel,
no email required, jurisdiction = Switzerland
- AGB page — subscription terms, payment, cancellation, suspension on payment
fail, 14-day money-back, AI-processing-per-tier disclosure, Swiss law +
Swiss venue, modeled after typical Schweizer SaaS terms
- Privacy: Stripe added as subprocessor with full data-flow disclosure
Support panel replaces email contact entirely:
- @bmm/db: support_status enum + support_tickets + support_messages tables,
migration applied to prod DB
- @bmm/api: support routes (user create/list/view/reply, admin list/view/reply
/set-status), public /v1/contact for logged-out visitors with per-IP rate
limit of 3 submissions/day to prevent spam-flood
- Web: /settings/support (list + new), /settings/support/[id] (conversation),
/admin/support, /admin/support/[id]
- Public /contact form with email collection for guest tickets
Data rights (DSG Art. 25 / GDPR Art. 15+20):
- /v1/account/export returns user-scoped JSON of profile, org, servers,
builds, audit, support tickets and messages — excludes hashes, encrypted
secrets, other-user data
- /settings/account: download button + deletion-via-ticket workflow
Production-readiness gaps closed:
- org.suspended now blocks /v1/servers POST and /v1/servers/preview (402);
webhook flagged this state but enforcement was missing
- Cookie banner: minimal, essential-cookies-only disclosure (Swiss DSG +
GDPR compliant without dark-pattern consent UI), mounts on both layouts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:12:06 +02:00
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section className="panel p-5">
|
|
|
|
|
<h2 className="text-[14px] font-semibold tracking-tight">Cookies on this site</h2>
|
|
|
|
|
<p className="mt-2 text-[12.5px] leading-relaxed text-[--color-fg-muted]">
|
|
|
|
|
We use only strictly-necessary cookies: a session cookie (
|
|
|
|
|
<span className="mono">bmm_session</span>, httpOnly, 30 days) and a short-lived
|
|
|
|
|
OAuth-CSRF state cookie (<span className="mono">bmm_oauth_state</span>, 10 minutes
|
|
|
|
|
during a third-party login flow). No analytics, no tracking, no third-party cookies on
|
|
|
|
|
this domain.
|
|
|
|
|
</p>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mt-10 text-[12px] text-[--color-fg-subtle]">
|
|
|
|
|
<Link href="/privacy" className="hover:text-[--color-fg]">
|
|
|
|
|
← Privacy policy
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|