@genesislcap/blank-app-seed 5.20.0 → 5.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/blank-app-seed-config",
3
3
  "description": "Genesis Blank App Seed Configuration",
4
- "version": "5.20.0",
4
+ "version": "5.21.0",
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {
7
7
  "lint": "eslint .",
@@ -1,5 +1,5 @@
1
1
  {
2
- "UI": "14.496.1",
2
+ "UI": "14.497.0",
3
3
  "GSF": "8.15.14",
4
4
  "Auth": "8.15.3"
5
5
  }
@@ -23,7 +23,7 @@ jobs:
23
23
  - name: Set up Node
24
24
  uses: actions/setup-node@v4
25
25
  with:
26
- node-version: 20
26
+ node-version: 22
27
27
 
28
28
  - name: Set up JDK
29
29
  uses: actions/setup-java@v3
@@ -17,7 +17,7 @@ jobs:
17
17
  - name: Set up Node
18
18
  uses: actions/setup-node@v4
19
19
  with:
20
- node-version: 20
20
+ node-version: 22
21
21
 
22
22
  - name: Release
23
23
  env:
@@ -40,7 +40,7 @@ jobs:
40
40
  - name: Set up Node
41
41
  uses: actions/setup-node@v4
42
42
  with:
43
- node-version: 20
43
+ node-version: 22
44
44
 
45
45
  - name: Set up Jfrog CLI
46
46
  uses: jfrog/setup-jfrog-cli@v4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.21.0](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.20.0...v5.21.0) (2026-07-27)
4
+
5
+
6
+ ### Features
7
+
8
+ * **react:** migrate template routing to foundation-react-utils/router primitives [PTC-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 28bb768
9
+ * **react:** migrate template routing to foundation-react-utils/router primitives [PTC-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#607) 1d8f13e
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **react:** add foundation-react-utils dep + avoid inline style double-brace [PTC-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 7e5d676
15
+ * **react:** theme single-component via FUI modal-theme (active-theme) not legacy design-tokens [PTC-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 020d99e
16
+
3
17
  ## [5.20.0](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.19.2...v5.20.0) (2026-07-23)
4
18
 
5
19
 
@@ -58,6 +58,7 @@
58
58
  "@genesislcap/foundation-layout": "{{versions.UI}}",
59
59
  "@genesislcap/foundation-logger": "{{versions.UI}}",
60
60
  "@genesislcap/foundation-notifications": "{{versions.UI}}",
61
+ "@genesislcap/foundation-react-utils": "{{versions.UI}}",
61
62
  "@genesislcap/foundation-shell": "{{versions.UI}}",
62
63
  "@genesislcap/foundation-ui": "{{versions.UI}}",
63
64
  "@genesislcap/foundation-user": "{{versions.UI}}",
@@ -1,41 +1,31 @@
1
- import { Routes, Route{{#if routes.[0]}}, Navigate{{/if}} } from 'react-router-dom';
2
- import AuthPage from '../../pages/AuthPage/AuthPage';
3
- {{#if routes.[0]}}
4
- import {{pascalCase routes.[0].name}} from '../../pages/{{pascalCase routes.[0].name}}/{{pascalCase routes.[0].name}}';
5
- {{/if}}
1
+ import { renderAppRoutes } from '@genesislcap/foundation-react-utils/router';
6
2
  import DefaultLayout from '../../layouts/default/DefaultLayout';
7
- import ProtectedRoute from './ProtectedRoute';
8
- import { useRoutesContext } from '../../store/RoutesContext';
9
- import PBCContainer from '../../pbc/container';
10
3
  import NotFoundPage from '../../pages/NotFoundPage/NotFoundPage.tsx';
4
+ import { useRoutesContext } from '../../store/RoutesContext';
5
+ import ProtectedRoute from './ProtectedRoute';
6
+ {{#if routes.[0]}}
7
+ import SingleComponent, { initialComponentName } from '../single-component/SingleComponent';
8
+ {{/if}}
11
9
 
10
+ /**
11
+ * Builds the app's `<Routes>` from the declarative route table (see RoutesContext)
12
+ * via `renderAppRoutes`, which applies the auth/permission guard, wraps in-layout
13
+ * routes in `DefaultLayout`, and handles the `/` redirect, not-found, and the
14
+ * single-component (`?component=<name>`) short-circuit.
15
+ */
12
16
  const AppRoutes = () => {
13
17
  const routes = useRoutesContext();
14
- return (
15
- <Routes>
16
- <Route path="/login" element={<AuthPage />} />
17
- {{#if routes.[0]}}
18
- <Route path="/" element={<Navigate to="/{{kebabCase routes.[0].name}}" replace />} />
19
- {{/if}}
20
- <Route element={<DefaultLayout />}>
21
- {{#if routes.[0]}}
22
- <Route path="/" element={<{{pascalCase routes.[0].name}} />} />
23
- {{/if}}
24
- {routes.map(route => (<Route
25
- key={route.path}
26
- path={route.path}
27
- element={
28
- <ProtectedRoute>
29
- {route.data?.pbcElement ? <PBCContainer /> : route.element}
30
- </ProtectedRoute>
31
- }
32
- />))}
33
- </Route>
34
-
35
- <Route path="*" element={<NotFoundPage />} />
36
- </Routes>
37
- );
38
- };
39
18
 
40
- export default AppRoutes;
19
+ return renderAppRoutes({
20
+ routes,
21
+ ProtectedRoute,
22
+ layout: <DefaultLayout />,
23
+ {{#if routes.[0]}}
24
+ redirects: [{ from: '/', to: '/{{kebabCase routes.[0].name}}' }],
25
+ singleComponent: { name: initialComponentName, element: <SingleComponent /> },
26
+ {{/if}}
27
+ notFound: <NotFoundPage />,
28
+ });
29
+ };
41
30
 
31
+ export default AppRoutes;
@@ -1,19 +1,19 @@
1
- import { Navigate, useLocation } from 'react-router-dom';
1
+ import { createProtectedRoute } from '@genesislcap/foundation-react-utils/router';
2
2
  import { getUser } from '@genesislcap/foundation-user';
3
+ import { NOT_PERMITTED_PATH } from '../../config';
4
+ import hasPermissionHelper from '../../helpers/hasPermissionHelper';
3
5
 
4
- interface ProtectedRouteProps {
5
- children: React.ReactNode;
6
- }
6
+ /**
7
+ * App auth + permission guard. Redirects to `/login` (stashing the full origin
8
+ * location, incl. query + hash) when unauthenticated, or to `/not-permitted`
9
+ * when authenticated but the route's `permissionCode` is denied. The reusable
10
+ * behaviour lives in `@genesislcap/foundation-react-utils/router`; this binds it
11
+ * to this app's user.
12
+ */
13
+ const ProtectedRoute = createProtectedRoute({
14
+ getIsAuthenticated: () => getUser().isAuthenticated,
15
+ hasPermission: hasPermissionHelper,
16
+ notPermittedPath: `/${NOT_PERMITTED_PATH}`,
17
+ });
7
18
 
8
- function ProtectedRoute({ children }: ProtectedRouteProps) {
9
- const location = useLocation();
10
-
11
- if (!getUser().isAuthenticated) {
12
- const loginState = { from: location };
13
- return <Navigate to="/login" state={loginState} replace />;
14
- }
15
-
16
- return <>{children}</>;
17
- }
18
-
19
- export default ProtectedRoute;
19
+ export default ProtectedRoute;
@@ -0,0 +1,55 @@
1
+ import { SingleComponentOutlet } from '@genesislcap/foundation-react-utils/router';
2
+ import { applyMode, injectThemeStyles, resolveInitialMode } from '@genesislcap/rapid-design-system';
3
+ import { useEffect, useRef } from 'react';
4
+ import { registerStylesTarget } from '../../pbc/utils';
5
+ import { activeTheme } from '../../styles/active-theme';
6
+ import ErrorMessage from '../ErrorMessage/ErrorMessage';
7
+ import { initialComponentName } from './initialParams';
8
+ import { registry } from './registry';
9
+
10
+ export { SINGLE_COMPONENT_PARAM, initialComponentName } from './initialParams';
11
+
12
+ // Defined outside JSX so the template stays free of inline `style={{ … }}`,
13
+ // whose double braces collide with the seed's handlebars interpolation.
14
+ const providerStyle = { display: 'block', height: '100%', width: '100%' } as const;
15
+
16
+ /**
17
+ * Renders a single registered component full-screen based on the
18
+ * `?component=<name>` URL parameter captured at app load. The component is
19
+ * wrapped in the same design-system provider / theme that DefaultLayout sets
20
+ * up (the FUI modal-theme runtime via `activeTheme`), so it is themed
21
+ * identically to how it appears in-app — just without the header and
22
+ * navigation chrome (and without a mode toggle).
23
+ *
24
+ * Resolution + fallback are handled by `SingleComponentOutlet` from
25
+ * `@genesislcap/foundation-react-utils/router`; this file only supplies the
26
+ * app-specific provider/theming and the themed unknown-component message.
27
+ */
28
+ const SingleComponent = () => {
29
+ const providerRef = useRef<HTMLElement>(null);
30
+
31
+ useEffect(() => {
32
+ if (providerRef.current) {
33
+ injectThemeStyles(providerRef.current, activeTheme);
34
+ applyMode(providerRef.current, activeTheme, resolveInitialMode(activeTheme));
35
+ registerStylesTarget(document.body, 'content');
36
+ }
37
+ }, []);
38
+
39
+ return (
40
+ <rapid-design-system-provider ref={providerRef} style={providerStyle}>
41
+ <SingleComponentOutlet
42
+ name={initialComponentName}
43
+ registry={registry}
44
+ renderUnknown={(name, available) => (
45
+ <ErrorMessage
46
+ elementType="h3"
47
+ message={`Unknown component "${name ?? ''}". Available: ${available.join(', ')}`}
48
+ />
49
+ )}
50
+ />
51
+ </rapid-design-system-provider>
52
+ );
53
+ };
54
+
55
+ export default SingleComponent;
@@ -0,0 +1,16 @@
1
+ import { readInitialParam } from '@genesislcap/foundation-react-utils/router';
2
+
3
+ /**
4
+ * The app shell performs async bootstrap (component registration, session
5
+ * restore) before React Router mounts, and during that window the platform
6
+ * rewrites the URL and drops the query string. We therefore capture the
7
+ * requested component name synchronously at module-evaluation time — which runs
8
+ * during the initial import phase, before any of that async work starts — so
9
+ * the `?component=<name>` request survives.
10
+ */
11
+
12
+ /** Query-string key used to request a single standalone component. */
13
+ export const SINGLE_COMPONENT_PARAM = 'component';
14
+
15
+ /** The requested component name from the initial URL, or null if absent. */
16
+ export const initialComponentName = readInitialParam(SINGLE_COMPONENT_PARAM);
@@ -0,0 +1,16 @@
1
+ import { createComponentRegistry } from '@genesislcap/foundation-react-utils/router';
2
+ {{#each routes}}
3
+ import {{pascalCase this.name}} from '../../pages/{{pascalCase this.name}}/{{pascalCase this.name}}';
4
+ {{/each}}
5
+
6
+ /**
7
+ * Registry of components that can be rendered standalone via the
8
+ * `?component=<name>` URL parameter. Generated from the app's routes; add or
9
+ * remove entries here to control what is exposed. Lookup is case- and
10
+ * separator-insensitive (see `createComponentRegistry`).
11
+ */
12
+ export const registry = createComponentRegistry({
13
+ {{#each routes}}
14
+ {{pascalCase this.name}},
15
+ {{/each}}
16
+ });
@@ -1,15 +1,13 @@
1
1
  import { configure, defaultAuthConfig } from '@genesislcap/foundation-auth/config';
2
+ import {
3
+ buildPostLoginRedirect,
4
+ type RedirectableLocationState,
5
+ } from '@genesislcap/foundation-react-utils/router';
2
6
  import { AUTH_PATH } from '../config';
3
7
  import { Connect } from '@genesislcap/foundation-comms';
4
8
  import { DI } from '@genesislcap/web-core';
5
9
  import type { NavigateFunction, Location as RouterLocation } from 'react-router-dom';
6
10
 
7
- interface LocationState {
8
- from?: {
9
- pathname: string;
10
- };
11
- }
12
-
13
11
  /**
14
12
  * Configure the micro frontend
15
13
  */
@@ -18,7 +16,7 @@ export const configureFoundationLogin = ({
18
16
  location,
19
17
  }: {
20
18
  navigate: NavigateFunction;
21
- location: RouterLocation<LocationState>;
19
+ location: RouterLocation<RedirectableLocationState>;
22
20
  }) => {
23
21
  const baseElement = document.querySelector('base');
24
22
  const basePath = baseElement?.getAttribute('href') || '';
@@ -36,8 +34,9 @@ export const configureFoundationLogin = ({
36
34
  hostPath: basePath + AUTH_PATH,
37
35
  postLoginRedirect: async () => {
38
36
  await connect.connect();
39
- const from = location.state?.from?.pathname || '/';
40
- navigate(from, { replace: true });
37
+ // Preserve the full original location (query string + hash) so deep-link
38
+ // params such as `?component=<name>` survive the login bounce.
39
+ navigate(buildPostLoginRedirect(location), { replace: true });
41
40
  },
42
41
  });
43
42
  };
@@ -1,40 +1,33 @@
1
+ import {
2
+ mergePbcRoutes,
3
+ type AppRouteConfig,
4
+ type PbcRouteInput,
5
+ } from '@genesislcap/foundation-react-utils/router';
1
6
  import React, { createContext, useContext, ReactNode } from 'react';
2
- import { Navigate } from 'react-router-dom';
3
- import type { AppRoute } from '../types/AppRoute';
4
7
  import { getApp } from '@genesislcap/foundation-shell/app';
5
8
  import AuthPage from '../pages/AuthPage/AuthPage';
6
- import NotFoundPage from '../pages/NotFoundPage/NotFoundPage';
7
9
  import NotPermittedPage from '../pages/NotPermittedPage/NotPermittedPage';
8
10
  {{#each routes}}
9
11
  import {{pascalCase this.name}} from '../pages/{{pascalCase this.name}}/{{pascalCase this.name}}';
10
12
  {{/each}}
11
13
  import PBCContainer from '../pbc/container';
12
14
  import { AUTH_PATH, NOT_PERMITTED_PATH } from '../config';
13
- import * as changeCase from 'change-case';
14
15
 
15
- const routes = [
16
- {
17
- path: '',
18
- element: <Navigate to={AUTH_PATH} replace />,
19
- },
20
- {
21
- path: '/not-found',
22
- element: <NotFoundPage />,
23
- },
24
- {
25
- path: `/${AUTH_PATH}`,
26
- element: <AuthPage />,
27
- },
28
- {
29
- path: `/${NOT_PERMITTED_PATH}`,
30
- element: <NotPermittedPage />,
31
- },
16
+ /**
17
+ * Static application routes as a declarative table consumed by `renderAppRoutes`
18
+ * (routing), `DefaultLayout` (nav items), and `PBCContainer` (pbc lookup).
19
+ * `public` routes render outside the layout without the auth guard;
20
+ * `permissionCode` gates a route via the shared guard.
21
+ */
22
+ const staticRoutes: AppRouteConfig[] = [
23
+ { path: `/${AUTH_PATH}`, element: <AuthPage />, public: true },
24
+ { path: `/${NOT_PERMITTED_PATH}`, element: <NotPermittedPage />, public: true },
32
25
  {{#each routes}}
33
26
  {
34
27
  path: '/{{kebabCase this.name}}',
35
28
  element: <{{pascalCase this.name}} />,
29
+ permissionCode: '{{this.permissions.viewRight}}',
36
30
  data: {
37
- permissionCode: '{{this.permissions.viewRight}}',
38
31
  navItems: [
39
32
  {
40
33
  navId: 'header',
@@ -50,25 +43,14 @@ const routes = [
50
43
  {{/each}}
51
44
  ];
52
45
 
53
- const RoutesContext = createContext<AppRoute[]>([]);
46
+ const RoutesContext = createContext<AppRouteConfig[]>([]);
54
47
 
55
48
  export const RoutesProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
56
- const pbcRoutes = getApp().routes.map((route) => ({
57
- title: changeCase.capitalCase(route.path),
58
- path: `/${route.path}`,
59
- element: <PBCContainer />,
60
- data: {
61
- ...route.settings,
62
- pbcElement: route.element,
63
- // @ts-expect-error - getApp() is not typed to return the elementTag
64
- pbcElementTag: route.elementTag,
65
- navItems: route.navItems,
66
- },
67
- }));
68
-
69
- const allRoutes = [...routes, ...pbcRoutes];
49
+ const routes = mergePbcRoutes(staticRoutes, getApp().routes as unknown as PbcRouteInput[], {
50
+ renderPbc: () => <PBCContainer />,
51
+ });
70
52
 
71
- return <RoutesContext.Provider value={allRoutes}>{children}</RoutesContext.Provider>;
53
+ return <RoutesContext.Provider value={routes}>{children}</RoutesContext.Provider>;
72
54
  };
73
55
 
74
56
  export const useRoutesContext = () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/blank-app-seed",
3
3
  "description": "Genesis Blank App Seed",
4
- "version": "5.20.0",
4
+ "version": "5.21.0",
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {
7
7
  "release": "semantic-release"