feat(account): self-service GDPR Art.17 erasure; Enterprise price -> Custom
All checks were successful
Deploy to Production / deploy (push) Successful in 1m19s
All checks were successful
Deploy to Production / deploy (push) Successful in 1m19s
Account deletion (DELETE /v1/account): re-type email/phone to confirm, stops live containers and hard-deletes every org where the caller is sole member (FK cascade clears servers, builds, logs, encrypted secrets), deletes the user (cascade drops sessions), audits the action, clears the session cookie. Frontend danger-zone replaces the old open-a-ticket placeholder. Closes audit ACC-001. Enterprise price unified to Custom on landing + pricing, removing the 499/999 inconsistency. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,33 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { apiUrl } from '@/lib/api';
|
||||
import { apiFetch, apiUrl } from '@/lib/api';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function AccountPage() {
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadExport() {
|
||||
setDownloading(true);
|
||||
@@ -42,20 +63,35 @@ export default function AccountPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="panel p-5">
|
||||
<h2 className="text-[14px] font-semibold tracking-tight">Delete account</h2>
|
||||
<section className="panel border-[--color-danger]/30 p-5">
|
||||
<h2 className="text-[14px] font-semibold tracking-tight text-[--color-danger]">
|
||||
Delete account
|
||||
</h2>
|
||||
<p className="mt-2 text-[12.5px] leading-relaxed text-[--color-fg-muted]">
|
||||
We don't do one-click account deletion yet — too easy to fat-finger and lose
|
||||
paid-tier server configs. Open a ticket and we'll wipe everything within 30
|
||||
days (servers, secrets, audit, tickets) per Swiss DSG Art. 32 / GDPR Art. 17.
|
||||
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.
|
||||
</p>
|
||||
<div className="mt-4">
|
||||
<Link href="/settings/support">
|
||||
<Button variant="secondary" size="md">
|
||||
Open deletion ticket
|
||||
</Button>
|
||||
</Link>
|
||||
<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>
|
||||
</div>
|
||||
{delError && <p className="mt-2 text-[12px] text-[--color-danger]">{delError}</p>}
|
||||
</section>
|
||||
|
||||
<section className="panel p-5">
|
||||
|
||||
@@ -129,8 +129,8 @@ const TIERS = [
|
||||
},
|
||||
{
|
||||
name: 'Enterprise',
|
||||
price: '€499+',
|
||||
tag: '/ month',
|
||||
price: 'Custom',
|
||||
tag: 'talk to us',
|
||||
features: ['Unlimited', 'Custom infra · on request', 'SSO/SAML · on request', 'Dedicated hosting', 'Customer success'],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -65,8 +65,8 @@ const TIERS = [
|
||||
},
|
||||
{
|
||||
name: 'Enterprise',
|
||||
price: '€999+',
|
||||
tag: '/ month',
|
||||
price: 'Custom',
|
||||
tag: 'talk to us',
|
||||
description: 'For organizations with custom infrastructure, compliance and scale needs.',
|
||||
model: 'Claude AI',
|
||||
modelDetail: 'Top-tier Claude · EU data-residency option',
|
||||
|
||||
Reference in New Issue
Block a user