ea56985efd
Extract the AI provider/endpoints settings and the MCP server section out of the Workspace "General" settings page into their own "AI" settings page, reachable from a new sidebar entry. - add page apps/client/.../settings/workspace/ai-settings.tsx (AiProviderSettings admin-gated + McpSettings), with its own Helmet title - register the /settings/ai route in App.tsx and add SETTINGS.WORKSPACE.AI to app-route.ts - add an "AI" item (IconSparkles) to the Workspace group in settings-sidebar - trim workspace-settings.tsx back to the General section and drop the now-unused imports
63 lines
2.0 KiB
TypeScript
63 lines
2.0 KiB
TypeScript
const APP_ROUTE = {
|
|
HOME: "/home",
|
|
SPACES: "/spaces",
|
|
FAVORITES: "/favorites",
|
|
SEARCH: "/search",
|
|
AUTH: {
|
|
LOGIN: "/login",
|
|
SIGNUP: "/signup",
|
|
SETUP: "/setup/register",
|
|
FORGOT_PASSWORD: "/forgot-password",
|
|
PASSWORD_RESET: "/password-reset",
|
|
},
|
|
SETTINGS: {
|
|
ACCOUNT: {
|
|
PROFILE: "/settings/account/profile",
|
|
PREFERENCES: "/settings/account/preferences",
|
|
},
|
|
WORKSPACE: {
|
|
GENERAL: "/settings/workspace",
|
|
AI: "/settings/ai",
|
|
MEMBERS: "/settings/members",
|
|
GROUPS: "/settings/groups",
|
|
SPACES: "/settings/spaces",
|
|
},
|
|
},
|
|
};
|
|
|
|
export function safeRedirectPath(input: unknown): string | null {
|
|
if (typeof input !== "string") return null;
|
|
if (input.length === 0 || input.length > 2048) return null;
|
|
// Reject whitespace, backslash, and any Unicode "Other" category char
|
|
// (ASCII controls, zero-width space, BOM, bidi marks, etc).
|
|
if (/[\s\\]|\p{C}/u.test(input)) return null;
|
|
if (!input.startsWith("/") || input.startsWith("//")) return null;
|
|
if (input.toLowerCase().includes("://")) return null;
|
|
if (/^\/[a-z][a-z0-9+\-.]*:/i.test(input)) return null;
|
|
try {
|
|
const resolved = new URL(input, window.location.origin);
|
|
if (resolved.origin !== window.location.origin) return null;
|
|
return resolved.pathname + resolved.search + resolved.hash;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function getPostLoginRedirect(): string {
|
|
const params = new URLSearchParams(window.location.search);
|
|
return safeRedirectPath(params.get("redirect")) ?? APP_ROUTE.HOME;
|
|
}
|
|
|
|
/**
|
|
* Returns the `?redirect=` value from the current URL only when it is a safe
|
|
* same-origin path. Unlike {@link getPostLoginRedirect} this returns `null`
|
|
* (not `/home`) when no redirect is present, so callers can distinguish
|
|
* "user came here directly" from "user was bounced from a deep link".
|
|
*/
|
|
export function getRedirectParam(): string | null {
|
|
const params = new URLSearchParams(window.location.search);
|
|
return safeRedirectPath(params.get("redirect"));
|
|
}
|
|
|
|
export default APP_ROUTE;
|