@genesislcap/blank-app-seed 5.20.0 → 5.21.1
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.
- package/.genx/package.json +1 -1
- package/.genx/versions.json +1 -1
- package/.github/workflows/build.yml +1 -1
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/upgrade.yml +1 -1
- package/CHANGELOG.md +21 -0
- package/bdd-tests/gradle.properties +1 -1
- package/client-tmp/react/package.json +1 -0
- package/client-tmp/react/src/components/routes/AppRoutes.tsx +24 -34
- package/client-tmp/react/src/components/routes/ProtectedRoute.tsx +16 -16
- package/client-tmp/react/src/components/single-component/SingleComponent.tsx +55 -0
- package/client-tmp/react/src/components/single-component/initialParams.ts +16 -0
- package/client-tmp/react/src/components/single-component/registry.ts +16 -0
- package/client-tmp/react/src/share/foundation-login.ts +8 -9
- package/client-tmp/react/src/store/RoutesContext.tsx +20 -38
- package/package.json +1 -1
package/.genx/package.json
CHANGED
package/.genx/versions.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [5.21.1](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.21.0...v5.21.1) (2026-07-27)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* Bump BDD framework (bddVersion) from 3.5.30 to 3.5.73 GENC-0 (#610) 46bf5e4
|
|
9
|
+
|
|
10
|
+
## [5.21.0](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.20.0...v5.21.0) (2026-07-27)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **react:** migrate template routing to foundation-react-utils/router primitives [PTC-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 28bb768
|
|
16
|
+
* **react:** migrate template routing to foundation-react-utils/router primitives [PTC-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#607) 1d8f13e
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* **react:** add foundation-react-utils dep + avoid inline style double-brace [PTC-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) 7e5d676
|
|
22
|
+
* **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
|
|
23
|
+
|
|
3
24
|
## [5.20.0](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.19.2...v5.20.0) (2026-07-23)
|
|
4
25
|
|
|
5
26
|
|
|
@@ -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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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<
|
|
46
|
+
const RoutesContext = createContext<AppRouteConfig[]>([]);
|
|
54
47
|
|
|
55
48
|
export const RoutesProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
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={
|
|
53
|
+
return <RoutesContext.Provider value={routes}>{children}</RoutesContext.Provider>;
|
|
72
54
|
};
|
|
73
55
|
|
|
74
56
|
export const useRoutesContext = () => {
|