Add legal pages, ToS acceptance, and alpha banner

- Journal: Impressum (§5 TMG), Terms of Service, GDPR privacy page
- Registration flow: required ToS checkbox, termsAcceptedAt persisted
- Alpha banner on Journal (always visible); alpha badge on Planner hero
- Footer with legal links + GitHub on both apps
- Reduce PII: sendDefaultPii=false, Sentry init only after login (Journal)
- Drop Sentry from Planner (no login, no consent possible)
- Drop localStorage caching from i18n client detection
- Sync SSR i18n resources each request so locale edits HMR without restart

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-18 00:08:48 +02:00
parent 6c1cd23358
commit cc0f44258a
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
24 changed files with 773 additions and 206 deletions

View file

@ -76,6 +76,7 @@ export async function finishRegistration(
email,
username,
domain,
termsAcceptedAt: new Date(),
});
await db.insert(credentials).values({
@ -158,7 +159,7 @@ export async function registerWithMagicLink(email: string, username: string): Pr
const userId = randomUUID();
const domain = process.env.DOMAIN ?? "localhost";
await db.insert(users).values({ id: userId, email, username, domain });
await db.insert(users).values({ id: userId, email, username, domain, termsAcceptedAt: new Date() });
// Create magic token for verification
const token = randomBytes(32).toString("base64url");

View file

@ -0,0 +1,20 @@
/**
* Operator details for the Impressum (§5 TMG / §18 MStV).
*
* Required by German law. Address must be a reachable physical address.
* Email must be a direct contact (no forms-only policy).
*/
export const operator = {
name: "Ullrich Schäfer",
address: {
street: "Mehringdamm 87",
postalCode: "10965",
city: "Berlin",
country: "Germany",
},
email: "legal@trails.cool",
// Responsible for content per §18 Abs. 2 MStV
responsiblePerson: "Ullrich Schäfer",
} as const;
export type Operator = typeof operator;

View file

@ -0,0 +1,30 @@
import * as Sentry from "@sentry/react";
import { useEffect } from "react";
import { useLocation, useNavigationType, createRoutesFromChildren, matchRoutes } from "react-router";
let initialized = false;
export function initSentryClient() {
if (initialized) return;
initialized = true;
const sentryEnvironment = import.meta.env.VITE_SENTRY_ENVIRONMENT ??
(import.meta.env.PROD ? "production" : "development");
Sentry.init({
dsn: "https://a32ffcc575d34be072e91b20f247eeee@o4509530546634752.ingest.de.sentry.io/4509530555547728",
integrations: [
Sentry.reactRouterV7BrowserTracingIntegration({
useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes,
}),
],
environment: sentryEnvironment,
tracesSampleRate: sentryEnvironment === "ci" ? 0 : 1.0,
enabled: import.meta.env.PROD && sentryEnvironment !== "ci",
sendDefaultPii: false,
});
}