type SiteUrlKey = | "missionControl" | "ganttBoard" | "blogBackup" | "gitea" | "github" | "vercel" | "supabase" | "google" | "googleCalendar" | "googleCalendarSettings"; const DEFAULT_SITE_URLS: Record = { missionControl: "https://mission-control.twisteddevices.com", ganttBoard: "https://gantt-board.twisteddevices.com", blogBackup: "https://blog-backup-two.vercel.app", gitea: "http://192.168.1.128:3000", github: "https://github.com", vercel: "https://vercel.com", supabase: "https://supabase.com", google: "https://google.com", googleCalendar: "https://calendar.google.com", googleCalendarSettings: "https://calendar.google.com/calendar/u/0/r/settings", }; const ENV_SITE_URLS: Partial> = { missionControl: process.env.NEXT_PUBLIC_MISSION_CONTROL_URL, ganttBoard: process.env.NEXT_PUBLIC_GANTT_BOARD_URL, blogBackup: process.env.NEXT_PUBLIC_BLOG_BACKUP_URL, gitea: process.env.NEXT_PUBLIC_GITEA_URL, github: process.env.NEXT_PUBLIC_GITHUB_URL, vercel: process.env.NEXT_PUBLIC_VERCEL_URL, supabase: process.env.NEXT_PUBLIC_SUPABASE_SITE_URL, google: process.env.NEXT_PUBLIC_GOOGLE_URL, googleCalendar: process.env.NEXT_PUBLIC_GOOGLE_CALENDAR_URL, googleCalendarSettings: process.env.NEXT_PUBLIC_GOOGLE_CALENDAR_SETTINGS_URL, }; function normalizeBaseUrl(value: string, fallback: string): string { try { const normalized = new URL(value.trim()).toString(); return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized; } catch { return fallback; } } const configuredSiteUrls = Object.entries(DEFAULT_SITE_URLS).reduce( (acc, [key, fallback]) => { const envValue = ENV_SITE_URLS[key as SiteUrlKey]; acc[key as SiteUrlKey] = normalizeBaseUrl(envValue || fallback, fallback); return acc; }, {} as Record, ); export const siteUrls = Object.freeze(configuredSiteUrls); function toUrl(base: string, path = ""): string { if (!path) return base; const normalizedPath = path.startsWith("/") ? path : `/${path}`; return `${base}${normalizedPath}`; } function toUrlWithQuery( base: string, path: string, params: Record, ): string { const query = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { if (value) query.set(key, value); } const queryString = query.toString(); return queryString ? `${toUrl(base, path)}?${queryString}` : toUrl(base, path); } export function getGanttTaskUrl(taskId: string): string { return toUrl(siteUrls.ganttBoard, `/tasks/${encodeURIComponent(taskId)}`); } export function getGanttProjectUrl(projectId: string): string { return toUrl(siteUrls.ganttBoard, `/projects/${encodeURIComponent(projectId)}`); } export function getGanttSprintUrl(sprintId: string): string { return toUrl(siteUrls.ganttBoard, `/sprints/${encodeURIComponent(sprintId)}`); } export function getGanttTasksUrl(params?: Record): string { return params ? toUrlWithQuery(siteUrls.ganttBoard, "/tasks", params) : toUrl(siteUrls.ganttBoard, "/tasks"); } export function getMissionControlDocumentsUrl(): string { return toUrl(siteUrls.missionControl, "/documents"); }