mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
feat(settings): introduce dynamic-route shell + redirect /settings → /settings/account (#139)
* feat(settings): introduce dynamic-route shell + redirect /settings → /settings/account
Splits the 3218-LOC /settings monolith into a routing surface only — no
section content moves in this PR. Each of the eight slugs (account,
integrations, notifications, dashboard, ai, api, advanced, about) now lives
at its own /settings/[section] route and renders inside a new
<SettingsShell> sidebar+main layout. Every section currently shows a
placeholder ("Moving here with the next release update") so reviewers can
validate the URL structure in isolation. The historic monolith is preserved
verbatim as page.legacy.tsx for section-by-section extraction by the
follow-up PRs (A2-account, A2-about, A2-ai, A2-integrations,
A2-notifications, A2-rest).
Behaviour change: existing entry points (sidebar nav, topbar dropdown,
onboarding hand-off, dashboard checklist, Withings callback, Codex
callback) now point at /settings/<slug> instead of /settings#anchor. The
/settings root is a 308 (permanentRedirect) to /settings/account so old
bookmarks and external links keep working.
Co-Authored-By: Marc-André Bombeck <mbombeck@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(settings): route every Codex OAuth callback redirect to /settings/integrations
Review-pass H1: only the late success/failure paths in the Codex OAuth
callback were updated to point at the new section route. The four early
branches — rate-limited, provider-supplied `?error=...`, missing params,
invalid state — still redirected to `/settings?codex_error=...`.
After the section split that path 308-redirects to `/settings/account`,
where the Codex error toast/banner doesn't live. Users hitting any of
those edge cases would land on the wrong screen with the error code
silently lost.
All six branches now go to `/settings/integrations?codex_error=...`,
matching the success and token-exchange-failure paths and the existing
Withings callback shape.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ddab92e42b
commit
3221f86eea
18 files changed
+3879
-3253
No files matched your search
@@ -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`,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -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!,
|
||||
),
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user