Skip to content
13 changes: 12 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import "./styles/global.scss";
import {isSsr} from "./utilites/helpers.ts";
import {StartupChecks} from "./StartupChecks.tsx";
import {ThirdPartyScripts} from "./components/common/ThirdPartyScripts";
import { getBasePath } from "./utilites/basePath.ts";

declare global {
interface Window {
Expand All @@ -32,10 +33,20 @@ export const App: FC<
}>
> = (props) => {
const [isLoadedOnBrowser, setIsLoadedOnBrowser] = React.useState(false);
const basePath = getBasePath();

useEffect(() => {
setIsLoadedOnBrowser(!isSsr());
}, []);

// Ensure that the client is always accessing via the base path
// This is to ensure that the app is always served from the correct base path
if (!window.location.pathname.startsWith(basePath)) {
window.location.replace(basePath);
}
}, [] );




return (
<React.StrictMode>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {hydrateRoot} from "react-dom/client";
import {createBrowserRouter, matchRoutes, RouterProvider} from "react-router-dom";
import {hydrate} from "@tanstack/react-query";

import {router} from "./router";
import {options, routes} from "./router";
import {App} from "./App";
import {queryClient} from "./utilites/queryClient";
import {dynamicActivateLocale, getClientLocale, getSupportedLocale,} from "./locales.ts";
Expand All @@ -23,7 +23,7 @@ async function initClientApp() {
await dynamicActivateLocale(locale);

// Resolve lazy-loaded routes before hydration
const matches = matchRoutes(router, window.location)?.filter((m) => m.route.lazy);
const matches = matchRoutes(routes, window.location)?.filter((m) => m.route.lazy);
if (matches && matches.length > 0) {
await Promise.all(
matches.map(async (m) => {
Expand All @@ -33,7 +33,7 @@ async function initClientApp() {
);
}

const browserRouter = createBrowserRouter(router);
const browserRouter = createBrowserRouter(routes, options);

hydrateRoot(
document.getElementById("app") as HTMLElement,
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type * as express from "express";
import ReactDOMServer from "react-dom/server";
import {dehydrate} from "@tanstack/react-query";

import {router} from "./router";
import {routes} from "./router";
import {App} from "./App";
import {queryClient} from "./utilites/queryClient";
import {setAuthToken} from "./utilites/apiClient.ts";
Expand All @@ -26,7 +26,7 @@ export async function render(params: {
}) {
setAuthToken(params.req.cookies.token);

const {query, dataRoutes} = createStaticHandler(router);
const {query, dataRoutes} = createStaticHandler(routes);
const remixRequest = createFetchRequest(params.req, params.res);
const context = await query(remixRequest);

Expand Down
9 changes: 7 additions & 2 deletions frontend/src/router.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {Navigate, RouteObject} from "react-router";
import { createBrowserRouter, Navigate, RouteObject} from "react-router";
import ErrorPage from "./error-page.tsx";
import {eventsClientPublic} from "./api/event.client.ts";
import {promoCodeClientPublic} from "./api/promo-code.client.ts";
import {useEffect, useState} from "react";
import {useGetMe} from "./queries/useGetMe.ts";
import { getBasePath } from "./utilites/basePath.ts";

const Root = () => {
const [redirectPath, setRedirectPath] = useState<string | null>(null);
Expand All @@ -20,7 +21,11 @@ const Root = () => {
}
};

export const router: RouteObject[] = [
export const options: Parameters<typeof createBrowserRouter>[1] = {
basename: getBasePath(),
};

export const routes: RouteObject[] = [
{
path: "",
element: <Root/>,
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/utilites/basePath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getConfig } from "./config";

export function getBasePath() {
const frontendUrl: string = getConfig( "VITE_FRONTEND_URL" ) as string || import.meta.env.VITE_FRONTEND_URL as string;

try {
const url = new URL(frontendUrl);
let basePath: string = url.pathname;

// Make sure it always ends without trailing slash (except root)
if (basePath !== "/" && basePath.endsWith("/")) {
basePath = basePath.slice(0, -1);
}

return basePath || "/";
} catch ( e ) {
// If URL parsing fails, fallback to root
console.warn(
`Invalid frontend URL: ${frontendUrl}. This might be due to an incorrect environment variable 'VITE_FRONTEND_URL'.`, e
);

return "/";
}
}
82 changes: 43 additions & 39 deletions frontend/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
import {defineConfig} from "vite";
import {lingui} from "@lingui/vite-plugin";
import { lingui } from "@lingui/vite-plugin";
import react from "@vitejs/plugin-react";
import {copy} from "vite-plugin-copy";
import { defineConfig, loadEnv } from "vite";
import { copy } from "vite-plugin-copy";

export default defineConfig({
optimizeDeps: {
include: ["react-router"]
},
server: {
hmr: {
port: 24678,
protocol: "ws",
},
},
plugins: [
react({
babel: {
plugins: ["macros"],
},
}),
lingui(),
copy({
targets: [{src: "src/embed/widget.js", dest: "public"}],
hook: "writeBundle",
}),
],
define: {
"process.env": process.env,
},
ssr: {
noExternal: ["react-helmet-async"],
},
css: {
preprocessorOptions: {
scss: {
api: "modern-compiler",
}
}
}
});
export default defineConfig( ( { mode } ) => {
const env = loadEnv(mode, process.cwd(), "");
return {
base: new URL(env.VITE_FRONTEND_URL).pathname || "/",
optimizeDeps: {
include: ["react-router"],
},
server: {
hmr: {
port: 24678,
protocol: "ws",
},
},
plugins: [
react({
babel: {
plugins: ["macros"],
},
}),
lingui(),
copy({
targets: [{ src: "src/embed/widget.js", dest: "public" }],
hook: "writeBundle",
}),
],
define: {
"process.env": process.env,
},
ssr: {
noExternal: ["react-helmet-async"],
},
css: {
preprocessorOptions: {
scss: {
api: "modern-compiler",
},
},
},
};
})