diff --git a/messages/de.json b/messages/de.json
index 621dd6bfc..0d3c2cedd 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -873,6 +873,46 @@
"settings": {
"title": "Einstellungen",
"subtitle": "Profil, Sicherheit & Verbindungen",
+ "shell": {
+ "sectionsNav": "Einstellungs-Bereiche"
+ },
+ "sections": {
+ "account": {
+ "title": "Konto",
+ "description": "Profil, Anmeldemethoden und Konto-Löschung."
+ },
+ "integrations": {
+ "title": "Integrationen",
+ "description": "Withings, moodLog und weitere verbundene Dienste."
+ },
+ "notifications": {
+ "title": "Benachrichtigungen",
+ "description": "Telegram, ntfy, Web Push."
+ },
+ "dashboard": {
+ "title": "Übersicht",
+ "description": "Kachel-Layout, Schwellwerte."
+ },
+ "ai": {
+ "title": "KI-Auswertungen",
+ "description": "Provider, Modell, API-Key."
+ },
+ "api": {
+ "title": "API & Tokens",
+ "description": "Bearer-Tokens für Drittanbieter-Clients."
+ },
+ "advanced": {
+ "title": "Erweitert",
+ "description": "Export, Import, Daten löschen."
+ },
+ "about": {
+ "title": "Über",
+ "description": "Version, Lizenz, Links."
+ },
+ "placeholder": {
+ "coming_soon": "Wird mit dem nächsten Release-Update hierher umziehen."
+ }
+ },
"sectionsTitle": "Settings",
"categoryGeneral": "Allgemein",
"categoryNotifications": "Benachrichtigungen",
diff --git a/messages/en.json b/messages/en.json
index 871bdf261..cffd9e743 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -873,6 +873,46 @@
"settings": {
"title": "Settings",
"subtitle": "Profile, Security & Connections",
+ "shell": {
+ "sectionsNav": "Settings sections"
+ },
+ "sections": {
+ "account": {
+ "title": "Account",
+ "description": "Profile, sign-in methods, and account deletion."
+ },
+ "integrations": {
+ "title": "Integrations",
+ "description": "Withings, moodLog, and other connected services."
+ },
+ "notifications": {
+ "title": "Notifications",
+ "description": "Telegram, ntfy, Web Push."
+ },
+ "dashboard": {
+ "title": "Dashboard",
+ "description": "Tile layout, thresholds."
+ },
+ "ai": {
+ "title": "AI Insights",
+ "description": "Provider, model, key."
+ },
+ "api": {
+ "title": "API & Tokens",
+ "description": "Bearer tokens for headless clients."
+ },
+ "advanced": {
+ "title": "Advanced",
+ "description": "Export, import, danger zone."
+ },
+ "about": {
+ "title": "About",
+ "description": "Version, license, links."
+ },
+ "placeholder": {
+ "coming_soon": "Moving here with the next release update."
+ }
+ },
"sectionsTitle": "Settings",
"categoryGeneral": "General",
"categoryNotifications": "Notifications",
diff --git a/src/app/api/auth/codex/callback/route.ts b/src/app/api/auth/codex/callback/route.ts
index c4c58bacb..2bd6301f4 100644
--- a/src/app/api/auth/codex/callback/route.ts
+++ b/src/app/api/auth/codex/callback/route.ts
@@ -14,7 +14,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
const rl = await checkRateLimit(`codex-callback:${user.id}`, 10, 60_000);
if (!rl.allowed) {
const appUrl = process.env.APP_URL || process.env.NEXT_PUBLIC_APP_URL;
- return NextResponse.redirect(`${appUrl}/settings?codex_error=rate_limited`);
+ return NextResponse.redirect(`${appUrl}/settings/integrations?codex_error=rate_limited`);
}
const url = new URL(request.url);
@@ -29,13 +29,13 @@ export const GET = apiHandler(async (request: NextRequest) => {
if (error) {
annotate({ meta: { oauth_error: error } });
return NextResponse.redirect(
- `${appUrl}/settings?codex_error=${encodeURIComponent(error)}`,
+ `${appUrl}/settings/integrations?codex_error=${encodeURIComponent(error)}`,
);
}
if (!code || !state) {
return NextResponse.redirect(
- `${appUrl}/settings?codex_error=missing_params`,
+ `${appUrl}/settings/integrations?codex_error=missing_params`,
);
}
@@ -48,7 +48,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
if (!storedState || !storedVerifier || state !== storedState) {
return NextResponse.redirect(
- `${appUrl}/settings?codex_error=invalid_state`,
+ `${appUrl}/settings/integrations?codex_error=invalid_state`,
);
}
@@ -70,9 +70,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
data: {
codexAccessTokenEncrypted: encrypted.accessEncrypted,
codexRefreshTokenEncrypted: encrypted.refreshEncrypted,
- codexTokenExpiresAt: new Date(
- Date.now() + tokens.expires_in * 1000,
- ),
+ codexTokenExpiresAt: new Date(Date.now() + tokens.expires_in * 1000),
codexConnectedAt: new Date(),
codexConnectionStatus: "connected",
insightsCachedAt: null,
@@ -86,12 +84,14 @@ export const GET = apiHandler(async (request: NextRequest) => {
});
annotate({ action: { name: "codex.oauth.callback.success" } });
- return NextResponse.redirect(`${appUrl}/settings?codex_connected=true`);
+ return NextResponse.redirect(
+ `${appUrl}/settings/integrations?codex_connected=true`,
+ );
} catch (err) {
const message = err instanceof Error ? err.message : "unknown";
annotate({ meta: { codex_token_error: message } });
return NextResponse.redirect(
- `${appUrl}/settings?codex_error=token_exchange_failed`,
+ `${appUrl}/settings/integrations?codex_error=token_exchange_failed`,
);
}
});
diff --git a/src/app/api/withings/callback/route.ts b/src/app/api/withings/callback/route.ts
index 18d437ae8..55a9ba214 100644
--- a/src/app/api/withings/callback/route.ts
+++ b/src/app/api/withings/callback/route.ts
@@ -30,7 +30,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
) {
return NextResponse.redirect(
new URL(
- "/settings?withings=error&reason=state",
+ "/settings/integrations?withings=error&reason=state",
process.env.NEXT_PUBLIC_APP_URL!,
),
);
@@ -41,7 +41,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
if (stateUserId !== user.id) {
return NextResponse.redirect(
new URL(
- "/settings?withings=error&reason=user",
+ "/settings/integrations?withings=error&reason=user",
process.env.NEXT_PUBLIC_APP_URL!,
),
);
@@ -50,7 +50,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
if (!code) {
return NextResponse.redirect(
new URL(
- "/settings?withings=error&reason=nocode",
+ "/settings/integrations?withings=error&reason=nocode",
process.env.NEXT_PUBLIC_APP_URL!,
),
);
@@ -61,7 +61,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
if (!creds) {
return NextResponse.redirect(
new URL(
- "/settings?withings=error&reason=nocreds",
+ "/settings/integrations?withings=error&reason=nocreds",
process.env.NEXT_PUBLIC_APP_URL!,
),
);
@@ -90,7 +90,9 @@ export const GET = apiHandler(async (request: NextRequest) => {
});
// Subscribe to webhooks in background
- setupWebhook(user.id).catch((err) => getEvent()?.addWarning("Webhook setup failed: " + err));
+ setupWebhook(user.id).catch((err) =>
+ getEvent()?.addWarning("Webhook setup failed: " + err),
+ );
await auditLog("withings.connect", {
userId: user.id,
@@ -98,7 +100,10 @@ export const GET = apiHandler(async (request: NextRequest) => {
});
const response = NextResponse.redirect(
- new URL("/settings?withings=connected", process.env.NEXT_PUBLIC_APP_URL!),
+ new URL(
+ "/settings/integrations?withings=connected",
+ process.env.NEXT_PUBLIC_APP_URL!,
+ ),
);
response.cookies.delete("withings_state");
return response;
@@ -106,7 +111,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
getEvent()?.setError(err);
return NextResponse.redirect(
new URL(
- "/settings?withings=error&reason=token",
+ "/settings/integrations?withings=error&reason=token",
process.env.NEXT_PUBLIC_APP_URL!,
),
);
diff --git a/src/app/notifications/page.tsx b/src/app/notifications/page.tsx
index f9127b01d..affa7140f 100644
--- a/src/app/notifications/page.tsx
+++ b/src/app/notifications/page.tsx
@@ -163,7 +163,7 @@ export default function NotificationsPage() {
{t("notifications.noChannels")}