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:
parent
6c1cd23358
commit
cc0f44258a
24 changed files with 773 additions and 206 deletions
|
|
@ -29,6 +29,7 @@ export const users = journalSchema.table("users", {
|
|||
displayName: text("display_name"),
|
||||
bio: text("bio"),
|
||||
domain: text("domain").notNull(),
|
||||
termsAcceptedAt: timestamp("terms_accepted_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -26,18 +26,34 @@ const commonOptions = {
|
|||
/**
|
||||
* Initialize i18next for server-side rendering.
|
||||
* No language detector — language is determined from the request.
|
||||
*
|
||||
* Re-adds resource bundles on every call so dev-mode HMR edits to the
|
||||
* locale files are reflected without a Node restart (the i18n singleton
|
||||
* otherwise caches the resources from the first init call).
|
||||
*/
|
||||
export function initI18nServer(lng: SupportedLng = "en") {
|
||||
if (i18n.isInitialized) {
|
||||
syncResources();
|
||||
i18n.changeLanguage(lng);
|
||||
return;
|
||||
}
|
||||
i18n.use(initReactI18next).init({ ...commonOptions, lng });
|
||||
}
|
||||
|
||||
function syncResources() {
|
||||
for (const [lang, namespaces] of Object.entries(resources)) {
|
||||
for (const [ns, bundle] of Object.entries(namespaces)) {
|
||||
i18n.addResourceBundle(lang, ns, bundle, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize i18next for the client.
|
||||
* Uses LanguageDetector with htmlTag priority so hydration matches the server.
|
||||
* No localStorage caching — we don't want to set non-essential storage
|
||||
* without user consent. Language is resolved per-request from the server
|
||||
* (Accept-Language → htmlTag) or the browser's navigator as a fallback.
|
||||
*/
|
||||
export function initI18nClient() {
|
||||
if (i18n.isInitialized) return;
|
||||
|
|
@ -47,8 +63,8 @@ export function initI18nClient() {
|
|||
.init({
|
||||
...commonOptions,
|
||||
detection: {
|
||||
order: ["htmlTag", "localStorage", "navigator"],
|
||||
caches: ["localStorage"],
|
||||
order: ["htmlTag", "navigator"],
|
||||
caches: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,9 +143,12 @@ export default {
|
|||
saveRoutesDesc: "Erstelle ein kostenloses Konto auf trails.cool, um Routen zu speichern, Aktivitäten zu verfolgen und mehr.",
|
||||
goToJournal: "Zu trails.cool",
|
||||
footer: {
|
||||
imprint: "Impressum",
|
||||
privacy: "Datenschutz",
|
||||
terms: "Nutzungsbedingungen",
|
||||
source: "Quellcode",
|
||||
builtWith: "Erstellt mit BRouter und OpenStreetMap",
|
||||
alpha: "Alpha-Version",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -250,6 +253,16 @@ export default {
|
|||
settings: "Einstellungen",
|
||||
logout: "Abmelden",
|
||||
},
|
||||
alpha: {
|
||||
message: "trails.cool befindet sich in früher Entwicklung — deine Daten können zurückgesetzt werden.",
|
||||
},
|
||||
footer: {
|
||||
imprint: "Impressum",
|
||||
privacy: "Datenschutz",
|
||||
terms: "Nutzungsbedingungen",
|
||||
source: "Quellcode",
|
||||
alpha: "Alpha-Version",
|
||||
},
|
||||
auth: {
|
||||
login: "Anmelden",
|
||||
register: "Registrieren",
|
||||
|
|
@ -267,7 +280,14 @@ export default {
|
|||
linkExpires: "Der Link ist 15 Minuten gültig.",
|
||||
codeHelp: "Oder gib den 6-stelligen Code aus der E-Mail ein:",
|
||||
verifyCode: "Code bestätigen",
|
||||
termsBefore: "Ich habe die ",
|
||||
termsLink: "Nutzungsbedingungen",
|
||||
termsAfter: " gelesen und akzeptiere sie.",
|
||||
termsRequired: "Du musst die Nutzungsbedingungen akzeptieren.",
|
||||
noAccount: "Noch kein Konto?",
|
||||
alreadyHaveAccount: "Bereits ein Konto?",
|
||||
handleWillBe: "Dein Handle wird {{handle}} sein",
|
||||
passkeyNotFound: "Für diese Seite wurde kein Passkey gefunden. Registriere ein neues Konto oder nutze stattdessen einen Magic Link.",
|
||||
registerDescription: "Registriere dich mit einem Passkey — kein Passwort nötig.",
|
||||
registerWithPasskey: "Mit Passkey registrieren",
|
||||
creatingPasskey: "Erstelle Passkey...",
|
||||
|
|
|
|||
|
|
@ -143,9 +143,12 @@ export default {
|
|||
saveRoutesDesc: "Create a free account on trails.cool to keep your routes, track activities, and more.",
|
||||
goToJournal: "Go to trails.cool",
|
||||
footer: {
|
||||
imprint: "Imprint",
|
||||
privacy: "Privacy",
|
||||
terms: "Terms",
|
||||
source: "Source Code",
|
||||
builtWith: "Built with BRouter and OpenStreetMap",
|
||||
alpha: "Alpha version",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -250,6 +253,16 @@ export default {
|
|||
settings: "Settings",
|
||||
logout: "Log Out",
|
||||
},
|
||||
alpha: {
|
||||
message: "trails.cool is in early development — your data may be reset.",
|
||||
},
|
||||
footer: {
|
||||
imprint: "Imprint",
|
||||
privacy: "Privacy",
|
||||
terms: "Terms",
|
||||
source: "Source Code",
|
||||
alpha: "Alpha version",
|
||||
},
|
||||
auth: {
|
||||
login: "Sign In",
|
||||
register: "Register",
|
||||
|
|
@ -267,7 +280,14 @@ export default {
|
|||
linkExpires: "The link expires in 15 minutes.",
|
||||
codeHelp: "Or enter the 6-digit code from the email:",
|
||||
verifyCode: "Verify Code",
|
||||
termsBefore: "I have read and accept the ",
|
||||
termsLink: "Terms of Service",
|
||||
termsAfter: ".",
|
||||
termsRequired: "You must accept the Terms of Service to register.",
|
||||
noAccount: "Don't have an account?",
|
||||
alreadyHaveAccount: "Already have an account?",
|
||||
handleWillBe: "Your handle will be {{handle}}",
|
||||
passkeyNotFound: "No passkey found for this site. Register a new account or use a magic link instead.",
|
||||
registerDescription: "Register with a passkey — no password needed.",
|
||||
registerWithPasskey: "Register with Passkey",
|
||||
creatingPasskey: "Creating passkey...",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue