Skip to content

Commit 34cb3e0

Browse files
authored
Merge branch 'main' into hoda-heureka-vul-counts
2 parents 0257290 + 3589b0c commit 34cb3e0

33 files changed

Lines changed: 990 additions & 316 deletions

.changeset/good-moles-sleep.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@cloudoperators/juno-app-greenhouse": minor
3+
"@cloudoperators/juno-app-supernova": minor
4+
"@cloudoperators/juno-app-doop": minor
5+
---
6+
7+
All apps support client side routing using tanstack router.

.github/workflows/deploy-pr-preview.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ jobs:
108108
PACKAGE_PATH: apps/supernova
109109
TARGET_FOLDER: supernova
110110
APP_PROPS_BASE64: ${{ secrets.SUPERNOVA_APP_PROPS_BASE64 }}
111+
PREVIEW_URL: ${{ env.PREVIEW_URL }}
111112
continue-on-error: true
112113

113114
- name: Build HEUREKA if changes detected
@@ -129,6 +130,7 @@ jobs:
129130
PACKAGE_PATH: apps/doop
130131
TARGET_FOLDER: doop
131132
APP_PROPS_BASE64: ${{ secrets.DOOP_APP_PROPS_BASE64 }}
133+
PREVIEW_URL: ${{ env.PREVIEW_URL }}
132134
continue-on-error: true
133135

134136
- name: Build CARBON if changes detected
@@ -148,6 +150,7 @@ jobs:
148150
PACKAGE_PATH: apps/greenhouse
149151
TARGET_FOLDER: greenhouse
150152
APP_PROPS_BASE64: ${{ secrets.GREENHOUSE_APP_PROPS_BASE64 }}
153+
PREVIEW_URL: ${{ env.PREVIEW_URL }}
151154
continue-on-error: true
152155

153156
- name: Build EXAMPLE if changes detected

apps/doop/appProps.template.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@
44
"displayName": "MyApp",
55
"apiEndpoint": "https://api.example.com/v1",
66
"embedded": false,
7-
"showDebugSeverities": false
7+
"showDebugSeverities": false,
8+
"enableHashedRouting": false,
9+
"basePath": "/"
810
}

apps/doop/index.html

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,18 @@
3333

3434
<body>
3535
<script type="module">
36-
// appProps are excluded from standalone build and should be generated from outside
37-
fetch("./appProps.json")
36+
/*
37+
* The `VITE_APP_PROPS_URL` can vary depending on the deployment environment.
38+
*
39+
* Examples:
40+
* - For a standard static build, this variable is typically undefined,
41+
* and the app expects `appProps.json` to be located at the server root "/".
42+
* - For deployments like GitHub Pages, it can be set to a subdirectory path
43+
* (e.g., a pull request-specific folder) where the static build is hosted.
44+
*/
45+
const { VITE_APP_PROPS_URL } = import.meta.env
46+
const appPropsUrl = VITE_APP_PROPS_URL || "/appProps.json"
47+
fetch(appPropsUrl)
3848
.then((res) => res.json())
3949
.catch((error) => {
4050
console.warn("No appProps found, using default props", error.message)

apps/doop/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
"@svgr/plugin-jsx": "^8.0.0",
2020
"@tanstack/react-query": "5.62.2",
2121
"@tailwindcss/vite": "^4.1.11",
22+
"@tanstack/router-plugin": "1.119.0",
2223
"@testing-library/jest-dom": "^6.6.3",
24+
"@testing-library/react": "^16.3.0",
2325
"@types/react": "19.1.8",
2426
"@types/react-dom": "19.1.6",
25-
"@testing-library/react": "^16.3.0",
2627
"@vitejs/plugin-react": "^4.6.0",
2728
"autoprefixer": "^10.4.2",
2829
"interweave": "^13.1.0",
@@ -54,6 +55,8 @@
5455
"@cloudoperators/juno-messages-provider": "workspace:*",
5556
"@cloudoperators/juno-ui-components": "workspace:*",
5657
"@cloudoperators/juno-url-state-provider": "workspace:*",
58+
"@tanstack/react-router": "1.119.0",
59+
"@tanstack/react-router-devtools": "1.119.1",
5760
"react": "19.1.0",
5861
"react-dom": "19.1.0"
5962
}

apps/doop/src/App.tsx

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,111 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import React, { useLayoutEffect } from "react"
6+
import React, { StrictMode, useLayoutEffect } from "react"
77

88
import { AppShellProvider, ContentHeading } from "@cloudoperators/juno-ui-components"
9-
import AppContent from "./components/AppContent"
9+
import { RouterProvider, createBrowserHistory, createHashHistory, createRouter } from "@tanstack/react-router"
10+
import { decodeV2, encodeV2 } from "@cloudoperators/juno-url-state-provider"
1011
import styles from "./styles.css?inline"
1112
import { MessagesProvider } from "@cloudoperators/juno-messages-provider"
1213
import StoreProvider from "./components/StoreProvider"
1314
import AsyncWorker from "./components/AsyncWorker"
1415
import { AppShell } from "@cloudoperators/juno-ui-components"
1516
import { QueryClientProvider, QueryClient } from "@tanstack/react-query"
1617
import { useGlobalsActions } from "./components/StoreProvider"
18+
import { routeTree } from "./routeTree.gen"
1719

18-
const App = (props = {}) => {
20+
// Create a new router instance
21+
const router = createRouter({
22+
routeTree,
23+
context: {
24+
appProps: undefined!,
25+
queryClient: undefined!,
26+
},
27+
})
28+
29+
// Register the router instance for type safety
30+
declare module "@tanstack/react-router" {
31+
interface Register {
32+
router: typeof router
33+
}
34+
}
35+
36+
export type AppProps = {
37+
id?: string
38+
theme?: "theme-dark" | "theme-light"
39+
apiEndpoint?: string
40+
embedded?: boolean
41+
basePath?: string
42+
enableHashedRouting?: boolean
43+
showDebugSeverities?: boolean
44+
}
45+
46+
const App = (props: AppProps = {}) => {
1947
// @ts-expect-error TS(2339) FIXME: Property 'setEndpoint' does not exist on type '{}'.
2048
const { setEndpoint } = useGlobalsActions()
2149

2250
const queryClient = new QueryClient({
2351
defaultOptions: {
2452
queries: {
2553
meta: {
26-
// @ts-expect-error TS(2339) FIXME: Property 'apiEndpoint' does not exist on type '{}'... Remove this comment to see the full error message
2754
endpoint: props.apiEndpoint,
2855
},
2956
},
3057
},
3158
})
3259

60+
/*
61+
* Dynamically change the type of history on the router
62+
* based on the enableHashedRouting prop. This ensures that
63+
* the correct history type is used when A Shell app does not
64+
* want the app to use browser history.
65+
*/
66+
router.update({
67+
routeTree,
68+
context: { appProps: props, queryClient },
69+
history: props.enableHashedRouting ? createHashHistory() : createBrowserHistory(),
70+
stringifySearch: encodeV2,
71+
parseSearch: (searchString) => {
72+
if (!props.enableHashedRouting) {
73+
return decodeV2(searchString)
74+
}
75+
76+
/*
77+
* In case of hashed routing Tanstack router returns URL search params of the entire URL rather than just from the hashed part.
78+
* We'll have to extract the query part from the hash because otherwise in embedded mode the app will be taking search params from the shell app as well.
79+
* Sanitize the search string by extracting the substring between the first '?' and the next '?' (if any), keeping the first '?'.
80+
* https://github.com/TanStack/router/issues/4370
81+
* http://localhost:3000/?preHashParam=prehashtest#/services?postHashParam1=test1?preHashParam=prehashtest
82+
* searchString = "?postHashParam1=test1?preHashParam=prehashtest"
83+
* searchStringFromHash = "?postHashParam1=test1"
84+
*/
85+
const postHashParams = searchString.indexOf("?")
86+
if (postHashParams === -1) return {} // If no query part is found, return an empty object
87+
const preHashParams = searchString.indexOf("?", postHashParams + 1)
88+
const searchStringFromHash = searchString.slice(postHashParams, preHashParams === -1 ? undefined : preHashParams)
89+
90+
return decodeV2(searchStringFromHash)
91+
},
92+
})
93+
3394
// on load application save the props to be used in oder components
3495
useLayoutEffect(() => {
3596
setEndpoint(window.location.origin)
3697
}, [])
3798

3899
return (
39100
<MessagesProvider>
40-
{/* @ts-expect-error TS(2339): Property 'embedded' does not exist on type '{}'. // @ts-expect-error TS(2339) FIXME: */}
41101
<AppShell pageHeader={`Doop`} embedded={props.embedded === true}>
42102
<ContentHeading
43103
// @ts-expect-error TS(2339) FIXME: Property 'displayName' does not exist on type '{}'... Remove this comment to see the full error message
44104
heading={`Decentralized Observer of Policies ${props.displayName ? ` - ${props.displayName}` : ""}`}
45105
/>
46-
{/* @ts-expect-error TS(2339): Property 'id' does not exist on type '{}'. // @ts-expect-error TS(2339) FIXME: */}
47106
<AsyncWorker consumerId={props.id || "doop"} />
48107
<QueryClientProvider client={queryClient}>
49-
{/* @ts-expect-error TS(2339): Property 'id' does not exist on type '{}'. // @ts-expect-error TS(2339) FIXME: */}
50-
<AppContent id={props?.id} showDebugSeverities={props.showDebugSeverities} />
108+
<StrictMode>
109+
<RouterProvider basepath={props.basePath || "/"} router={router} />
110+
</StrictMode>
51111
</QueryClientProvider>
52112
</AppShell>
53113
</MessagesProvider>

apps/doop/src/routeTree.gen.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/* eslint-disable */
2+
3+
// @ts-nocheck
4+
5+
// noinspection JSUnusedGlobalSymbols
6+
7+
// This file was automatically generated by TanStack Router.
8+
// You should NOT make any changes in this file as it will be overwritten.
9+
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10+
11+
// Import Routes
12+
13+
import { Route as rootRoute } from "./routes/__root"
14+
import { Route as IndexImport } from "./routes/index"
15+
16+
// Create/Update Routes
17+
18+
const IndexRoute = IndexImport.update({
19+
id: "/",
20+
path: "/",
21+
getParentRoute: () => rootRoute,
22+
} as any)
23+
24+
// Populate the FileRoutesByPath interface
25+
26+
declare module "@tanstack/react-router" {
27+
interface FileRoutesByPath {
28+
"/": {
29+
id: "/"
30+
path: "/"
31+
fullPath: "/"
32+
preLoaderRoute: typeof IndexImport
33+
parentRoute: typeof rootRoute
34+
}
35+
}
36+
}
37+
38+
// Create and export the route tree
39+
40+
export interface FileRoutesByFullPath {
41+
"/": typeof IndexRoute
42+
}
43+
44+
export interface FileRoutesByTo {
45+
"/": typeof IndexRoute
46+
}
47+
48+
export interface FileRoutesById {
49+
__root__: typeof rootRoute
50+
"/": typeof IndexRoute
51+
}
52+
53+
export interface FileRouteTypes {
54+
fileRoutesByFullPath: FileRoutesByFullPath
55+
fullPaths: "/"
56+
fileRoutesByTo: FileRoutesByTo
57+
to: "/"
58+
id: "__root__" | "/"
59+
fileRoutesById: FileRoutesById
60+
}
61+
62+
export interface RootRouteChildren {
63+
IndexRoute: typeof IndexRoute
64+
}
65+
66+
const rootRouteChildren: RootRouteChildren = {
67+
IndexRoute: IndexRoute,
68+
}
69+
70+
export const routeTree = rootRoute._addFileChildren(rootRouteChildren)._addFileTypes<FileRouteTypes>()
71+
72+
/* ROUTE_MANIFEST_START
73+
{
74+
"routes": {
75+
"__root__": {
76+
"filePath": "__root.tsx",
77+
"children": [
78+
"/"
79+
]
80+
},
81+
"/": {
82+
"filePath": "index.tsx"
83+
}
84+
}
85+
}
86+
ROUTE_MANIFEST_END */

apps/doop/src/routes/__root.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { createRootRouteWithContext, Outlet } from "@tanstack/react-router"
7+
import { QueryClient } from "@tanstack/react-query"
8+
import { AppProps } from "../App"
9+
10+
export type RouteContext = {
11+
appProps: AppProps
12+
queryClient: QueryClient
13+
}
14+
15+
export const Route = createRootRouteWithContext<RouteContext>()({
16+
component: Outlet,
17+
})

apps/doop/src/routes/index.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React from "react"
7+
import { createFileRoute } from "@tanstack/react-router"
8+
import AppContent from "../components/AppContent"
9+
10+
export const Route = createFileRoute("/")({
11+
component: RouteComponent,
12+
})
13+
14+
function RouteComponent() {
15+
const { appProps } = Route.useRouteContext()
16+
17+
return (
18+
<>
19+
<AppContent id={appProps?.id} showDebugSeverities={appProps.showDebugSeverities} />
20+
</>
21+
)
22+
}

apps/doop/vite.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import react from "@vitejs/plugin-react"
77
import tailwindcss from "@tailwindcss/vite"
8+
import { TanStackRouterVite } from "@tanstack/router-plugin/vite"
89

910
export default ({ mode }) => {
1011
const sharedConfig = {
@@ -13,7 +14,7 @@ export default ({ mode }) => {
1314
"process.env": {},
1415
},
1516

16-
plugins: [tailwindcss(), react()],
17+
plugins: [tailwindcss(), TanStackRouterVite({ target: "react", autoCodeSplitting: true }), react()],
1718

1819
server: {
1920
host: "0.0.0.0",

0 commit comments

Comments
 (0)