From 5480034273297b05c98d62ebf9e3c284f040c869 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Sat, 11 Jan 2025 11:55:40 +0100 Subject: [PATCH 1/4] Brought back APIs, moved route generation to proper v7 convention --- .../templates/react-app/src/catchall.tsx | 5 -- .../templates/react-app/src/root.tsx | 6 +- .../templates/react-app/src/router.tsx | 57 ------------------- .../templates/react-app/src/routes.ts | 30 +++++++++- .../templates/react-app/vite.config.ts | 4 +- .../Generator/templates/sdk/wasp/api/index.ts | 52 +++++++++-------- .../templates/sdk/wasp/core/storage.ts | 2 +- waspc/src/Wasp/Generator/WebAppGenerator.hs | 2 + 8 files changed, 65 insertions(+), 93 deletions(-) delete mode 100644 waspc/data/Generator/templates/react-app/src/catchall.tsx delete mode 100644 waspc/data/Generator/templates/react-app/src/router.tsx diff --git a/waspc/data/Generator/templates/react-app/src/catchall.tsx b/waspc/data/Generator/templates/react-app/src/catchall.tsx deleted file mode 100644 index 77b120de5a..0000000000 --- a/waspc/data/Generator/templates/react-app/src/catchall.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { router } from './router' - -export default function Component() { - return router; -} \ No newline at end of file diff --git a/waspc/data/Generator/templates/react-app/src/root.tsx b/waspc/data/Generator/templates/react-app/src/root.tsx index e486034913..0de566da8c 100644 --- a/waspc/data/Generator/templates/react-app/src/root.tsx +++ b/waspc/data/Generator/templates/react-app/src/root.tsx @@ -6,6 +6,7 @@ import { Scripts, ScrollRestoration, } from "react-router"; +import { DefaultRootErrorBoundary } from "./components/DefaultRootErrorBoundary"; export function Layout({ children, @@ -35,6 +36,9 @@ export function Layout({ ); } + export default function Root() { return ; -} \ No newline at end of file +} + +export const ErrorBoundary = DefaultRootErrorBoundary \ No newline at end of file diff --git a/waspc/data/Generator/templates/react-app/src/router.tsx b/waspc/data/Generator/templates/react-app/src/router.tsx deleted file mode 100644 index 95ff418147..0000000000 --- a/waspc/data/Generator/templates/react-app/src/router.tsx +++ /dev/null @@ -1,57 +0,0 @@ -{{={= =}=}} -import React from 'react' -import { createBrowserRouter, RouterProvider } from 'react-router' -{=# rootComponent.isDefined =} -{=& rootComponent.importStatement =} -{=/ rootComponent.isDefined =} - -{=# isAuthEnabled =} -import createAuthRequiredPage from "./auth/pages/createAuthRequiredPage" -{=/ isAuthEnabled =} - -{=# pagesToImport =} -{=& importStatement =} -{=/ pagesToImport =} - -{=# isExternalAuthEnabled =} -import { OAuthCallbackPage } from "./auth/pages/OAuthCallback" -{=/ isExternalAuthEnabled =} - -import { DefaultRootErrorBoundary } from './components/DefaultRootErrorBoundary' - -import { routes } from 'wasp/client/router' - -export const routeNameToRouteComponent = { - {=# routes =} - {= name =}: {= targetComponent =}, - {=/ routes =} -} as const; - -const waspDefinedRoutes = [ - {=# isExternalAuthEnabled =} - { - path: "{= oAuthCallbackPath =}", - Component: OAuthCallbackPage, - }, - {=/ isExternalAuthEnabled =} -] -const userDefinedRoutes = Object.entries(routes).map(([routeKey, route]) => { - return { - path: route.to, - Component: routeNameToRouteComponent[routeKey], - } -}) - -const browserRouter = createBrowserRouter([{ - path: '/', - {=# rootComponent.isDefined =} - element: <{= rootComponent.importIdentifier =} />, - {=/ rootComponent.isDefined =} - ErrorBoundary: DefaultRootErrorBoundary, - children: [ - ...waspDefinedRoutes, - ...userDefinedRoutes, - ], -}]) - -export const router = diff --git a/waspc/data/Generator/templates/react-app/src/routes.ts b/waspc/data/Generator/templates/react-app/src/routes.ts index 9b48bfbc21..5426dede75 100644 --- a/waspc/data/Generator/templates/react-app/src/routes.ts +++ b/waspc/data/Generator/templates/react-app/src/routes.ts @@ -2,8 +2,34 @@ import { type RouteConfig, route, } from "@react-router/dev/routes"; +import { layout, route } from "@react-router/dev/routes"; +import { routes } from 'wasp/client/router' + +export const routeNameToRouteComponent = { + {=# routes =} + {= name =}: {= targetComponent =}, + {=/ routes =} +} as const; + +const waspDefinedRoutes = [ + {=# isExternalAuthEnabled =} + route("{= oAuthCallbackPath =}", "./auth/pages/OAuthCallback"), + {=/ isExternalAuthEnabled =} +] +const userDefinedRoutes = Object.entries(routes).map(([routeKey, route]) => { + // TODO: This should be the path to the route module file (eg. /src/routes/home.tsx) + return route(routeKey, route) + +}) + export default [ - // * matches all URLs, the ? makes it optional so it will match / as well - route("*?", "catchall.tsx"), + layout( + // TODO: This should maybe always be defined, if not, + // TODO: the code should be (if defined => layout, otherwise => spread the routes normally in the array) + {=# rootComponent.isDefined =} + "{= rootComponent.importIdentifier =}" + {=/ rootComponent.isDefined =}, + [...waspDefinedRoutes, ...userDefinedRoutes] + ), ] satisfies RouteConfig; \ No newline at end of file diff --git a/waspc/data/Generator/templates/react-app/vite.config.ts b/waspc/data/Generator/templates/react-app/vite.config.ts index 686fd3cf06..4b6f9daf6d 100644 --- a/waspc/data/Generator/templates/react-app/vite.config.ts +++ b/waspc/data/Generator/templates/react-app/vite.config.ts @@ -3,7 +3,7 @@ import { mergeConfig } from "vite"; import { reactRouter } from "@react-router/dev/vite"; import { defaultExclude } from "vitest/config" - +import { reactRouterDevtools } from "react-router-devtools"; {=# customViteConfig.isDefined =} // Ignoring the TS error because we are importing a file outside of TS root dir. // @ts-ignore @@ -16,7 +16,7 @@ const _waspUserProvidedConfig = {}; const defaultViteConfig = { base: "{= baseDir =}", - plugins: [reactRouter()], + plugins: [reactRouterDevtools(), reactRouter()], optimizeDeps: { exclude: ['wasp'] }, diff --git a/waspc/data/Generator/templates/sdk/wasp/api/index.ts b/waspc/data/Generator/templates/sdk/wasp/api/index.ts index 69631e8f8b..4f7b51c33b 100644 --- a/waspc/data/Generator/templates/sdk/wasp/api/index.ts +++ b/waspc/data/Generator/templates/sdk/wasp/api/index.ts @@ -11,7 +11,7 @@ export const api: AxiosInstance = axios.create({ const WASP_APP_AUTH_SESSION_ID_NAME = 'sessionId' -let waspAppAuthSessionId: string | undefined = undefined // storage.get(WASP_APP_AUTH_SESSION_ID_NAME) as string | undefined +let waspAppAuthSessionId: string | undefined = typeof window !== "undefined" ? storage.get(WASP_APP_AUTH_SESSION_ID_NAME) : undefined // PRIVATE API (sdk) export function setSessionId(sessionId: string): void { @@ -39,37 +39,39 @@ export function removeLocalUserData(): void { apiEventsEmitter.emit('sessionId.clear') } -// api.interceptors.request.use((request) => { -// const sessionId = getSessionId() -// if (sessionId) { -// request.headers['Authorization'] = `Bearer ${sessionId}` -// } -// return request -// }) +api.interceptors.request.use((request) => { + const sessionId = getSessionId() + if (sessionId) { + request.headers['Authorization'] = `Bearer ${sessionId}` + } + return request +}) -// api.interceptors.response.use(undefined, (error) => { -// if (error.response?.status === 401) { -// clearSessionId() -// } -// return Promise.reject(error) -// }) +api.interceptors.response.use(undefined, (error) => { + if (error.response?.status === 401) { + clearSessionId() + } + return Promise.reject(error) +}) // This handler will run on other tabs (not the active one calling API functions), // and will ensure they know about auth session ID changes. // Ref: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event // "Note: This won't work on the same page that is making the changes — it is really a way // for other pages on the domain using the storage to sync any changes that are made." -// window.addEventListener('storage', (event) => { -// if (event.key === storage.getPrefixedKey(WASP_APP_AUTH_SESSION_ID_NAME)) { -// if (!!event.newValue) { -// waspAppAuthSessionId = event.newValue -// apiEventsEmitter.emit('sessionId.set') -// } else { -// waspAppAuthSessionId = undefined -// apiEventsEmitter.emit('sessionId.clear') -// } -// } -// }) +if (typeof window !== 'undefined') { + window.addEventListener('storage', (event) => { + if (event.key === storage.getPrefixedKey(WASP_APP_AUTH_SESSION_ID_NAME)) { + if (!!event.newValue) { + waspAppAuthSessionId = event.newValue + apiEventsEmitter.emit('sessionId.set') + } else { + waspAppAuthSessionId = undefined + apiEventsEmitter.emit('sessionId.clear') + } + } + }) +} // PRIVATE API (sdk) /** diff --git a/waspc/data/Generator/templates/sdk/wasp/core/storage.ts b/waspc/data/Generator/templates/sdk/wasp/core/storage.ts index 5c9e3b3121..ad4c9820ab 100644 --- a/waspc/data/Generator/templates/sdk/wasp/core/storage.ts +++ b/waspc/data/Generator/templates/sdk/wasp/core/storage.ts @@ -44,7 +44,7 @@ function createLocalStorageDataStore(prefix: string): DataStore { export const storage = createLocalStorageDataStore('wasp') function ensureLocalStorageIsAvailable(): void { - if (!window || !window.localStorage) { + if (typeof window === 'undefined' || !window.localStorage) { throw new Error('Local storage is not available.') } } diff --git a/waspc/src/Wasp/Generator/WebAppGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator.hs index 1fc25d89b1..fa8a9e7d13 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator.hs @@ -147,6 +147,8 @@ npmDepsForWasp _spec = -- NOTE: Make sure to bump the version of the tsconfig -- when updating Vite or React versions ("@tsconfig/vite-react", "^2.0.0"), + -- NOTE: You can remove this if you don't want to use the React Router DevTools + ("react-router-devtools", "^1.1.0"), ("@react-router/dev", "^7.1.1") ] } From b07b5be4de5d330ee2f4e14ce9d54881c730b8e8 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Sat, 11 Jan 2025 12:18:53 +0100 Subject: [PATCH 2/4] fixed up storage to be more generic --- .../Generator/templates/sdk/wasp/api/index.ts | 2 +- .../templates/sdk/wasp/core/storage.ts | 33 +++++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/waspc/data/Generator/templates/sdk/wasp/api/index.ts b/waspc/data/Generator/templates/sdk/wasp/api/index.ts index 4f7b51c33b..f99f179100 100644 --- a/waspc/data/Generator/templates/sdk/wasp/api/index.ts +++ b/waspc/data/Generator/templates/sdk/wasp/api/index.ts @@ -11,7 +11,7 @@ export const api: AxiosInstance = axios.create({ const WASP_APP_AUTH_SESSION_ID_NAME = 'sessionId' -let waspAppAuthSessionId: string | undefined = typeof window !== "undefined" ? storage.get(WASP_APP_AUTH_SESSION_ID_NAME) : undefined +let waspAppAuthSessionId: string | undefined = storage.get(WASP_APP_AUTH_SESSION_ID_NAME) // PRIVATE API (sdk) export function setSessionId(sessionId: string): void { diff --git a/waspc/data/Generator/templates/sdk/wasp/core/storage.ts b/waspc/data/Generator/templates/sdk/wasp/core/storage.ts index ad4c9820ab..d382e36983 100644 --- a/waspc/data/Generator/templates/sdk/wasp/core/storage.ts +++ b/waspc/data/Generator/templates/sdk/wasp/core/storage.ts @@ -6,7 +6,7 @@ export type DataStore = { clear(): void } -function createLocalStorageDataStore(prefix: string): DataStore { +function createStorageDataStore(prefix: string): DataStore { function getPrefixedKey(key: string): string { return `${prefix}:${key}` } @@ -14,12 +14,12 @@ function createLocalStorageDataStore(prefix: string): DataStore { return { getPrefixedKey, set(key, value) { - ensureLocalStorageIsAvailable() - localStorage.setItem(getPrefixedKey(key), JSON.stringify(value)) + const storage =getStorage() + storage?.setItem(getPrefixedKey(key), JSON.stringify(value)) }, get(key) { - ensureLocalStorageIsAvailable() - const value = localStorage.getItem(getPrefixedKey(key)) + const storage = getStorage() + const value = storage?.getItem(getPrefixedKey(key)) try { return value ? JSON.parse(value) : undefined } catch (e: any) { @@ -27,24 +27,29 @@ function createLocalStorageDataStore(prefix: string): DataStore { } }, remove(key) { - ensureLocalStorageIsAvailable() - localStorage.removeItem(getPrefixedKey(key)) + const storage = getStorage() + storage?.removeItem(getPrefixedKey(key)) }, clear() { - ensureLocalStorageIsAvailable() - Object.keys(localStorage).forEach((key) => { + const storage = getStorage() + if(!storage) { + return + } + Object.keys(storage).forEach((key) => { if (key.startsWith(prefix)) { - localStorage.removeItem(key) + storage.removeItem(key) } }) }, } } -export const storage = createLocalStorageDataStore('wasp') +export const storage = createStorageDataStore('wasp') -function ensureLocalStorageIsAvailable(): void { - if (typeof window === 'undefined' || !window.localStorage) { - throw new Error('Local storage is not available.') +// TODO: Make this function work in the server context as well. +function getStorage(): Storage | undefined { + if (typeof localStorage === 'undefined') { + return undefined } + return localStorage } From 05e3fdad18da8ca8a7bbe5ffc2722a5a40e1410c Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Sat, 11 Jan 2025 12:23:49 +0100 Subject: [PATCH 3/4] SSR decision --- .../react-app/react-router.config_20250111122335.ts | 12 ++++++++++++ .../templates/react-app/react-router.config.ts | 8 +++++++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 .history/waspc/data/Generator/templates/react-app/react-router.config_20250111122335.ts diff --git a/.history/waspc/data/Generator/templates/react-app/react-router.config_20250111122335.ts b/.history/waspc/data/Generator/templates/react-app/react-router.config_20250111122335.ts new file mode 100644 index 0000000000..ae08cbd646 --- /dev/null +++ b/.history/waspc/data/Generator/templates/react-app/react-router.config_20250111122335.ts @@ -0,0 +1,12 @@ +import type { Config } from "@react-router/dev/config"; + +let isSSR = false; + +{=# ssr.isDefined =} + isSSR = Boolean("{= ssr.value =}") +{=/ ssr.isDefined =}, + +export default { + appDirectory: "src", + ssr: isSSR, +} satisfies Config; diff --git a/waspc/data/Generator/templates/react-app/react-router.config.ts b/waspc/data/Generator/templates/react-app/react-router.config.ts index 3ff1b9fa81..ae08cbd646 100644 --- a/waspc/data/Generator/templates/react-app/react-router.config.ts +++ b/waspc/data/Generator/templates/react-app/react-router.config.ts @@ -1,6 +1,12 @@ import type { Config } from "@react-router/dev/config"; +let isSSR = false; + +{=# ssr.isDefined =} + isSSR = Boolean("{= ssr.value =}") +{=/ ssr.isDefined =}, + export default { appDirectory: "src", - ssr: false, + ssr: isSSR, } satisfies Config; From 999775e3a5732cddfb4759150a4bfa579e2e3253 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Sat, 11 Jan 2025 12:24:10 +0100 Subject: [PATCH 4/4] removed extra file --- .../react-app/react-router.config_20250111122335.ts | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .history/waspc/data/Generator/templates/react-app/react-router.config_20250111122335.ts diff --git a/.history/waspc/data/Generator/templates/react-app/react-router.config_20250111122335.ts b/.history/waspc/data/Generator/templates/react-app/react-router.config_20250111122335.ts deleted file mode 100644 index ae08cbd646..0000000000 --- a/.history/waspc/data/Generator/templates/react-app/react-router.config_20250111122335.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Config } from "@react-router/dev/config"; - -let isSSR = false; - -{=# ssr.isDefined =} - isSSR = Boolean("{= ssr.value =}") -{=/ ssr.isDefined =}, - -export default { - appDirectory: "src", - ssr: isSSR, -} satisfies Config;