Files
freno-dev/src/routes/nessa/deletion.tsx
agent-cto 71bb9ec5df FRE-1670: publish /terms and the support address on the Nessa site
nessa.freno.me/terms was a live 404 although AppSettings.termsOfServiceURL
links it from Settings > Legal and the paywall. The document existed only on
an unmerged branch and had no host-dispatch route, so it could not serve on
the subdomain. Carry the Terms of Use page, add the /terms dispatcher, and
publish nessa-support@freno.me on terms, privacy, contact and deletion.

- src/routes/terms.tsx: host-aware dispatch for nessa; other sites 404.
- src/routes/nessa/terms.tsx: the Terms page; its contact section names the
  support address.
- privacy: add the controller clause and the support address.
- contact/deletion: show the support address; deletion also accepts a
  request by email.
- subdomain-dispatch.test.ts: list directories before filtering to .tsx
  files. The old predicate only ever received file names, so it asserted
  over an empty list and missed the missing /terms dispatch.
- support-contact.test.ts: guard that every contact route renders the one
  address the app shows.

App side unchanged: termsOfServiceURL stays https://nessa.freno.me/terms.
2026-11-09 16:14:14 +00:00

79 lines
3.2 KiB
TypeScript

/**
* Nessa per-subdomain account-deletion page — `nessa.freno.me/deletion`
* (see `./deletion-content.ts`).
*
* Served at the public browser path `/deletion` (vercel.json host rewrites
* `nessa.freno.me/*` → the internal `/nessa/*` route prefix, leaving the
* browser URL clean.
*
* Nessa deletion assessment (see `./deletion-content.ts` for the full
* rationale): Nessa stores user data (`users`, `workouts`, `workoutPlans`,
* `exerciseLibrary`, community memberships) in a per-user Turso DB +
* shared community tables, authenticated via Clerk. → A deletion flow IS
* needed; this page provides it via the same email-request pattern Lineage
* uses, reusing the shared `DeletionForm` with `product="nessa"` so the
* generalized `misc.sendDeletionRequestEmail` mutation sends Nessa-branded
* email + writes a Nessa-specific cooldown cookie.
*
* Auth: NO freno.me web-auth — Nessa authenticates via Clerk; the deletion
* request is email-based (the requester may be locked out of their Clerk
* session), NOT an authenticated self-delete. The nav-config does NOT list
* a Nessa deletion link by default, so this page is reachable by direct URL
* + from the Nessa privacy policy.
*
* Acceptance: `nessa.localhost:3000/deletion` renders the deletion form.
*/
import { PageHead } from "~/components/PageHead";
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
import SubdomainHeader from "~/components/SubdomainHeader";
import DeletionForm from "~/components/DeletionForm";
import {
DELETION_PRODUCT_KEY,
DELETION_COOKIE_NAME,
DELETION_GRACE_PERIOD_LABEL,
PAGE_META
} from "~/routes/nessa/deletion-content";
import { SUPPORT_EMAIL } from "./content";
export default function NessaDeletionPage() {
return (
<>
<EdgeCacheHeaders maxAge={300} />
<PageHead title={PAGE_META.title} description={PAGE_META.description} />
<SubdomainHeader />
<div class="pt-20">
<div class="mx-auto p-4 md:p-6 lg:p-12">
<div class="text-text w-full justify-center">
<div class="text-xl">
<em>What will happen</em>:
</div>
Once you send, if a match to the email provided is found in our
system, a {DELETION_GRACE_PERIOD_LABEL} grace period is started
where you can request a cancellation of the account deletion. Once
the grace period ends, your Nessa account entry, your workout and
plan data, and your community memberships will be completely
removed. No data related to the account is retained in any way.
</div>
<div class="text-text mt-4 w-full justify-center">
Prefer email? Send your deletion request from the address on your
Nessa account to{" "}
<a
href={`mailto:${SUPPORT_EMAIL}?subject=Account%20Deletion%20Request`}
class="text-blue hover-underline-animation"
>
{SUPPORT_EMAIL}
</a>
; the same {DELETION_GRACE_PERIOD_LABEL} grace period applies.
</div>
<DeletionForm
product={DELETION_PRODUCT_KEY}
cookieName={DELETION_COOKIE_NAME}
/>
</div>
</div>
</>
);
}