Implement URL resolver functions for client and server, update navigation links to use resolved URLs, and remove deprecated nav component

This commit is contained in:
Raj Nandan Sharma
2026-02-06 11:41:02 +05:30
parent 6ed95ed8dc
commit 6aa43fa261
9 changed files with 104 additions and 138 deletions
+33
View File
@@ -0,0 +1,33 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ResolveFn = (...args: any[]) => string;
/**
* Wrapper for SvelteKit's resolve function
* @param resolve - The resolve function from $app/paths
* @param path - The route path or route ID (e.g., "/blog/[slug]") or absolute URL
* @param params - Optional parameters for dynamic route segments
* @returns The resolved URL with base path, or the original URL if it's absolute
*
* @example
* ```ts
* // Using a static path
* urlResolve(resolve, "/dashboard-apis/monitor-bar")
*
* // Using a dynamic route with params
* urlResolve(resolve, "/blog/[slug]", { slug: "hello-world" })
*
* // Using an absolute URL (returns as-is)
* urlResolve(resolve, "https://example.com/api")
* ```
*/
export default function urlResolve(resolve: ResolveFn, path: string, params?: Record<string, string>): string {
// If path is an absolute URL, return it as-is
if (path.startsWith("http://") || path.startsWith("https://")) {
return path;
}
if (params) {
return resolve(path, params);
}
return resolve(path);
}
+6 -4
View File
@@ -2,6 +2,8 @@
import { page } from "$app/state";
import * as NavigationMenu from "$lib/components/ui/navigation-menu/index.js";
import { navigationMenuTriggerStyle } from "$lib/components/ui/navigation-menu/navigation-menu-trigger.svelte";
import { resolve } from "$app/paths";
import urlResolve from "$lib/client/resolver.js";
let { data } = page;
const navItems: { name: string; url: string; iconURL: string }[] = data.navItems || [];
@@ -13,12 +15,12 @@
<div class="bg-background flex items-center justify-between rounded-3xl border p-1">
<!-- Brand -->
<a
href={siteUrl}
href={urlResolve(resolve, siteUrl)}
class="{navigationMenuTriggerStyle()} hover:border-border border border-transparent text-xs hover:bg-transparent"
style="border-radius: var(--radius-3xl)"
>
{#if logo}
<img src={logo} alt={siteName} class="mr-2 h-6 w-6 rounded-full object-cover" />
<img src={urlResolve(resolve, logo)} alt={siteName} class="mr-2 h-6 w-6 rounded-full object-cover" />
{/if}
{siteName}
</a>
@@ -31,14 +33,14 @@
<NavigationMenu.Link>
{#snippet child()}
<a
href={item.url}
href={urlResolve(resolve, item.url)}
class="{navigationMenuTriggerStyle()} hover:border-border border border-transparent text-xs hover:bg-transparent"
target={item.url.startsWith("http") ? "_blank" : undefined}
rel={item.url.startsWith("http") ? "noopener noreferrer" : undefined}
style="border-radius: var(--radius-3xl)"
>
{#if item.iconURL}
<img src={item.iconURL} alt={item.name} class="mr-2 h-4 w-4" />
<img src={urlResolve(resolve, item.iconURL)} alt={item.name} class="mr-2 h-4 w-4" />
{/if}
{item.name}
</a>
+2 -1
View File
@@ -9,6 +9,7 @@
import StatusBarCalendar from "$lib/components/StatusBarCalendar.svelte";
import { selectedTimezone } from "$lib/stores/timezone";
import type { MonitorBarResponse, BarData } from "$lib/server/api-server/monitor-bar/get.js";
import { resolve } from "$app/paths";
interface Props {
tag: string;
@@ -52,7 +53,7 @@
try {
const endOfDayTodayAtTz = getEndOfDayAtTz($selectedTimezone);
const response = await fetch(
`/dashboard-apis/monitor-bar?tag=${encodeURIComponent(tag)}&endOfDayTodayAtTz=${endOfDayTodayAtTz}`
`${resolve("/dashboard-apis/monitor-bar")}?tag=${encodeURIComponent(tag)}&endOfDayTodayAtTz=${endOfDayTodayAtTz}`
);
if (!response.ok) {
throw new Error("Failed to fetch monitor data");
-119
View File
@@ -1,119 +0,0 @@
<script>
import { Button } from "$lib/components/ui/button";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import Languages from "lucide-svelte/icons/languages";
import Menu from "lucide-svelte/icons/menu";
import { base } from "$app/paths";
import { analyticsEvent } from "$lib/boringOne";
import GMI from "$lib/components/gmi.svelte";
export let data;
let defaultPattern = data.site?.pattern || "squares";
let allPets = [
{
url: base + "/chicken.gif",
bottom: "-5"
},
{
url: base + "/dog.gif",
bottom: "-17"
},
{
url: base + "/cockatiel.gif",
bottom: "-10"
},
{
url: base + "/crab.gif",
bottom: "-20"
},
{
url: base + "/fox.gif",
bottom: "-9"
},
{
url: base + "/horse.gif",
bottom: "-11"
},
{
url: base + "/panda.gif",
bottom: "0"
},
{
url: base + "/totoro.gif",
bottom: "-27"
},
{
url: base + "/rabbit.gif",
bottom: "0"
},
{
url: base + "/duck.gif",
bottom: "-5"
},
{
url: base + "/snake.gif",
bottom: "0"
}
];
let randomPet = allPets[Math.floor(Math.random() * allPets.length)];
</script>
{#if defaultPattern == "pets" && !!randomPet}
<div class="pets-pattern" style="background-image: url({randomPet.url});bottom: {randomPet.bottom}px"></div>
{:else}
<div class="{defaultPattern}-pattern"></div>
{/if}
<header class="sticky top-0 z-50 mx-auto md:mt-2">
<div class="bg-card container flex h-14 max-w-[820px] items-center border px-3 md:rounded-md">
<a rel="external" href={data.site.home ? data.site.home : base} class="mr-6 flex items-center space-x-2">
{#if data.site.logo}
<GMI src={data.site.logo} classList="w-8" alt={data.site.title} srcset="" />
{/if}
{#if data.site.siteName}
<span class=" inline-block text-[15px] font-bold lg:text-base">
{data.site.siteName}
</span>
{/if}
</a>
<div class="flex w-full justify-end">
{#if data.site.nav}
<nav class=" hidden flex-wrap items-center text-sm font-medium md:flex">
{#each data.site.nav as navItem}
<a
rel="external"
href={navItem.url}
class="text-card-foreground hover:bg-background flex rounded-md px-3 py-2 transition-all ease-linear"
on:click={() =>
analyticsEvent("navigation", {
name: navItem.name
})}
>
{#if navItem.iconURL}
<GMI src={navItem.iconURL} classList="mr-1.5 mt-0.5 inline h-4" alt={navItem.name} />
{/if}
<span>{navItem.name}</span>
</a>
{/each}
</nav>
<div class="flex md:hidden">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="outline" size="sm">
<Menu size={14} />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{#each data.site.nav as navItem}
<DropdownMenu.Group>
<DropdownMenu.Item>
<a rel="external" href={navItem.url}> {navItem.name} </a>
</DropdownMenu.Item>
</DropdownMenu.Group>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
{/if}
</div>
</div>
</header>
+52
View File
@@ -0,0 +1,52 @@
/**
* Server-side URL resolver that uses KENER_BASE_PATH environment variable
* Works in both SvelteKit and Node scheduler contexts
*
* @param path - The route path (e.g., "/api/monitor") or absolute URL
* @param params - Optional parameters for dynamic route segments (e.g., { slug: "hello" })
* @returns The resolved URL with base path, or the original URL if it's absolute
*
* @example
* ```ts
* // Using a static path
* serverResolve("/dashboard-apis/monitor-bar")
* // Returns: "/status/dashboard-apis/monitor-bar" (if KENER_BASE_PATH=/status)
*
* // Using dynamic route with params
* serverResolve("/blog/[slug]", { slug: "hello-world" })
* // Returns: "/status/blog/hello-world"
*
* // Using an absolute URL (returns as-is)
* serverResolve("https://example.com/api")
* // Returns: "https://example.com/api"
* ```
*/
export function serverResolve(path: string, params?: Record<string, string>): string {
// If path is an absolute URL, return it as-is
if (path.startsWith("http://") || path.startsWith("https://")) {
return path;
}
// Get base path from environment variable
const basePath = process.env.KENER_BASE_PATH || "";
// Replace route parameters if provided
let resolvedPath = path;
if (params) {
for (const [key, value] of Object.entries(params)) {
resolvedPath = resolvedPath.replace(`[${key}]`, value);
}
}
// Ensure path starts with /
if (!resolvedPath.startsWith("/")) {
resolvedPath = "/" + resolvedPath;
}
// Combine base path with resolved path
// Ensure no double slashes
const fullPath = basePath + resolvedPath;
return fullPath.replace(/\/+/g, "/");
}
export default serverResolve;
@@ -1,21 +1,19 @@
import { redirect } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
import dotenv from "dotenv";
import { VerifyToken } from "$lib/server/controllers/controller.js";
import db from "$lib/server/db/db.js";
dotenv.config();
import serverResolve from "$lib/server/resolver.js";
export const load: PageServerLoad = async ({ cookies }) => {
const tokenData = cookies.get("kener-user");
if (tokenData) {
const tokenUser = await VerifyToken(tokenData);
if (!tokenUser) {
throw redirect(302, "/account/logout");
throw redirect(302, serverResolve("/account/logout"));
}
const userDB = await db.getUserByEmail(tokenUser.email);
if (userDB) {
throw redirect(302, "/manage/app/site-configurations");
throw redirect(302, serverResolve("/manage/app/site-configurations"));
}
}
+7 -4
View File
@@ -1,11 +1,14 @@
import { error } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
import { GetPageDashboardData } from "$lib/server/controllers/dashboardController.js";
import { resolve } from "$app/paths";
import { env } from "$env/dynamic/private";
export const load: PageServerLoad = async ({ url }) => {
const pagePath = url.pathname.substring(1); // Remove leading slash
const dashboardData = await GetPageDashboardData(pagePath);
export const load: PageServerLoad = async ({ url, params }) => {
const pagePath = url.pathname.replace(/\//g, ""); // Remove all slash if it exists
const base = !!env.KENER_BASE_PATH ? env.KENER_BASE_PATH.substring(1) : ""; // Remove leading slash from base path if it exists
const normalizedPagePath = base && pagePath.startsWith(base) ? pagePath.substring(base.length) : pagePath;
const dashboardData = await GetPageDashboardData(normalizedPagePath);
if (!dashboardData) {
throw error(404, "Page Not Found");
}
+1 -2
View File
@@ -1,9 +1,8 @@
<script lang="ts">
import Bell from "@lucide/svelte/icons/bell";
import { t } from "$lib/stores/i18n";
import * as Item from "$lib/components/ui/item/index.js";
import { resolve } from "$app/paths";
import EventsCard from "$lib/components/EventsCard.svelte";
import MonitorBar from "$lib/components/MonitorBar.svelte";
import ThemePlus from "$lib/components/ThemePlus.svelte";
-3
View File
@@ -18,8 +18,6 @@ function getAllowedHost(origin: string): string | undefined {
export default defineConfig(({ mode }) => {
const port = Number(process.env.PORT) || 3000;
const basePath = (process.env.KENER_BASE_PATH ?? "").trim();
const viteBase = basePath === "" ? undefined : basePath.endsWith("/") ? basePath : `${basePath}/`;
const buildEnv = process.env.VITE_BUILD_ENV || mode || "development";
const isProduction = buildEnv === "production";
@@ -28,7 +26,6 @@ export default defineConfig(({ mode }) => {
const allowedHost = getAllowedHost(origin);
return {
base: viteBase,
optimizeDeps: {
include: ["rrule"],
exclude: [