@nestjs-ssr/react 0.2.6 → 0.3.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/dist/cli/init.js +35 -72
- package/dist/cli/init.mjs +35 -72
- package/dist/client.d.mts +55 -304
- package/dist/client.d.ts +55 -304
- package/dist/client.js +360 -13
- package/dist/client.mjs +336 -5
- package/dist/{index-BMdluY1g.d.mts → index-BzOLOiIZ.d.ts} +207 -44
- package/dist/{index-rl0S5kqW.d.ts → index-DdE--mA2.d.mts} +207 -44
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +660 -326
- package/dist/index.mjs +654 -328
- package/dist/render/index.d.mts +2 -2
- package/dist/render/index.d.ts +2 -2
- package/dist/render/index.js +624 -324
- package/dist/render/index.mjs +624 -324
- package/dist/{render-response.interface-Dc-Kwb09.d.mts → render-response.interface-CxbuKGnV.d.mts} +57 -1
- package/dist/{render-response.interface-Dc-Kwb09.d.ts → render-response.interface-CxbuKGnV.d.ts} +57 -1
- package/dist/templates/entry-client.tsx +21 -5
- package/dist/templates/entry-server.tsx +48 -6
- package/dist/use-page-context-05ODF4zW.d.ts +281 -0
- package/dist/use-page-context-CGT9woWe.d.mts +281 -0
- package/package.json +1 -1
- package/src/global.d.ts +11 -0
- package/src/templates/entry-client.tsx +21 -5
- package/src/templates/entry-server.tsx +48 -6
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { H as HeadData, R as RenderContext } from './render-response.interface-CxbuKGnV.mjs';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generic type for React page component props.
|
|
7
|
+
* Spreads controller data directly as props (React-standard pattern).
|
|
8
|
+
*
|
|
9
|
+
* Request context is available via typed hooks created with createSSRHooks().
|
|
10
|
+
*
|
|
11
|
+
* @template TProps - The shape of props returned by the controller
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // src/lib/ssr-hooks.ts
|
|
16
|
+
* import { createSSRHooks, RenderContext } from '@nestjs-ssr/react';
|
|
17
|
+
*
|
|
18
|
+
* interface AppRenderContext extends RenderContext {
|
|
19
|
+
* user?: User;
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* export const { usePageContext } = createSSRHooks<AppRenderContext>();
|
|
23
|
+
*
|
|
24
|
+
* // src/views/product.tsx
|
|
25
|
+
* import { usePageContext } from '@/lib/ssr-hooks';
|
|
26
|
+
*
|
|
27
|
+
* interface ProductPageProps {
|
|
28
|
+
* product: Product;
|
|
29
|
+
* relatedProducts: Product[];
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* export default function ProductDetail(props: PageProps<ProductPageProps>) {
|
|
33
|
+
* const { product, relatedProducts, head } = props;
|
|
34
|
+
* const context = usePageContext(); // Fully typed!
|
|
35
|
+
*
|
|
36
|
+
* return (
|
|
37
|
+
* <html>
|
|
38
|
+
* <head>
|
|
39
|
+
* <title>{head?.title || product.name}</title>
|
|
40
|
+
* </head>
|
|
41
|
+
* <body>
|
|
42
|
+
* <h1>{product.name}</h1>
|
|
43
|
+
* <p>Current path: {context.path}</p>
|
|
44
|
+
* </body>
|
|
45
|
+
* </html>
|
|
46
|
+
* );
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
type PageProps<TProps = {}> = TProps & {
|
|
51
|
+
/**
|
|
52
|
+
* Optional head metadata for SEO (title, description, og tags, etc.)
|
|
53
|
+
* Pass from controller to populate meta tags, Open Graph, etc.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* // In controller:
|
|
58
|
+
* return {
|
|
59
|
+
* product,
|
|
60
|
+
* head: {
|
|
61
|
+
* title: product.name,
|
|
62
|
+
* description: product.description,
|
|
63
|
+
* }
|
|
64
|
+
* };
|
|
65
|
+
*
|
|
66
|
+
* // In component:
|
|
67
|
+
* <head>
|
|
68
|
+
* <title>{props.head?.title}</title>
|
|
69
|
+
* <meta name="description" content={props.head?.description} />
|
|
70
|
+
* </head>
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
head?: HeadData;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Update the page context during client-side navigation.
|
|
78
|
+
* Called by navigate() after successful navigation.
|
|
79
|
+
*/
|
|
80
|
+
declare function updatePageContext(context: RenderContext): void;
|
|
81
|
+
/**
|
|
82
|
+
* Provider component that makes page context available to all child components.
|
|
83
|
+
* Should wrap the entire app in entry-server and entry-client.
|
|
84
|
+
* On the client, this provider is stateful and updates during navigation.
|
|
85
|
+
*
|
|
86
|
+
* @param isSegment - If true, this is a segment provider (for hydrated segments)
|
|
87
|
+
* and won't register its setter to avoid overwriting the root provider's.
|
|
88
|
+
*/
|
|
89
|
+
declare function PageContextProvider({ context: initialContext, children, isSegment, }: {
|
|
90
|
+
context: RenderContext;
|
|
91
|
+
children: React.ReactNode;
|
|
92
|
+
isSegment?: boolean;
|
|
93
|
+
}): react_jsx_runtime.JSX.Element;
|
|
94
|
+
/**
|
|
95
|
+
* Factory function to create typed SSR hooks bound to your app's context type.
|
|
96
|
+
* Use this once in your app to create hooks with full type safety.
|
|
97
|
+
*
|
|
98
|
+
* This eliminates the need to pass generic types to every hook call,
|
|
99
|
+
* providing excellent DX with full IntelliSense support.
|
|
100
|
+
*
|
|
101
|
+
* @template T - Your extended RenderContext type with app-specific properties
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* // src/lib/ssr-hooks.ts - Define once
|
|
106
|
+
* import { createSSRHooks, RenderContext } from '@nestjs-ssr/react';
|
|
107
|
+
*
|
|
108
|
+
* interface AppRenderContext extends RenderContext {
|
|
109
|
+
* user?: {
|
|
110
|
+
* id: string;
|
|
111
|
+
* name: string;
|
|
112
|
+
* email: string;
|
|
113
|
+
* };
|
|
114
|
+
* tenant?: { id: string; name: string };
|
|
115
|
+
* featureFlags?: Record<string, boolean>;
|
|
116
|
+
* theme?: string; // From cookie
|
|
117
|
+
* }
|
|
118
|
+
*
|
|
119
|
+
* export const {
|
|
120
|
+
* usePageContext,
|
|
121
|
+
* useParams,
|
|
122
|
+
* useQuery,
|
|
123
|
+
* useRequest,
|
|
124
|
+
* useHeaders,
|
|
125
|
+
* useHeader,
|
|
126
|
+
* useCookies,
|
|
127
|
+
* useCookie,
|
|
128
|
+
* } = createSSRHooks<AppRenderContext>();
|
|
129
|
+
*
|
|
130
|
+
* // Create custom helper hooks
|
|
131
|
+
* export const useUser = () => usePageContext().user;
|
|
132
|
+
* export const useTheme = () => useCookie('theme');
|
|
133
|
+
* export const useUserAgent = () => useHeader('user-agent');
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* // src/views/home.tsx - Use everywhere with full types
|
|
139
|
+
* import { usePageContext, useUser, useTheme, useCookie, useHeader } from '@/lib/ssr-hooks';
|
|
140
|
+
*
|
|
141
|
+
* export default function Home() {
|
|
142
|
+
* const { user, featureFlags } = usePageContext(); // ✅ Fully typed!
|
|
143
|
+
* const user = useUser(); // ✅ Also typed!
|
|
144
|
+
* const theme = useTheme(); // ✅ From cookie
|
|
145
|
+
* const locale = useCookie('locale'); // ✅ Access specific cookie
|
|
146
|
+
* const tenantId = useHeader('x-tenant-id'); // ✅ Access specific header
|
|
147
|
+
*
|
|
148
|
+
* return (
|
|
149
|
+
* <div>
|
|
150
|
+
* <h1>Welcome {user?.name}</h1>
|
|
151
|
+
* <p>Theme: {theme}</p>
|
|
152
|
+
* <p>Locale: {locale}</p>
|
|
153
|
+
* <p>Tenant: {tenantId}</p>
|
|
154
|
+
* </div>
|
|
155
|
+
* );
|
|
156
|
+
* }
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
declare function createSSRHooks<T extends RenderContext = RenderContext>(): {
|
|
160
|
+
/**
|
|
161
|
+
* Hook to access the full page context with your app's type.
|
|
162
|
+
* Contains URL metadata, headers, and any custom properties you've added.
|
|
163
|
+
*/
|
|
164
|
+
usePageContext: () => T;
|
|
165
|
+
/**
|
|
166
|
+
* Hook to access route parameters.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```tsx
|
|
170
|
+
* // Route: /users/:id
|
|
171
|
+
* const params = useParams();
|
|
172
|
+
* console.log(params.id); // '123'
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
useParams: () => Record<string, string>;
|
|
176
|
+
/**
|
|
177
|
+
* Hook to access query string parameters.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```tsx
|
|
181
|
+
* // URL: /search?q=react&sort=date
|
|
182
|
+
* const query = useQuery();
|
|
183
|
+
* console.log(query.q); // 'react'
|
|
184
|
+
* console.log(query.sort); // 'date'
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
useQuery: () => Record<string, string | string[]>;
|
|
188
|
+
/**
|
|
189
|
+
* Alias for usePageContext() with a more intuitive name.
|
|
190
|
+
* Returns the full request context with your app's type.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```tsx
|
|
194
|
+
* const request = useRequest();
|
|
195
|
+
* console.log(request.path); // '/users/123'
|
|
196
|
+
* console.log(request.method); // 'GET'
|
|
197
|
+
* console.log(request.params); // { id: '123' }
|
|
198
|
+
* console.log(request.query); // { search: 'foo' }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
useRequest: () => T;
|
|
202
|
+
/**
|
|
203
|
+
* Hook to access headers configured via allowedHeaders.
|
|
204
|
+
* Returns all headers as a Record.
|
|
205
|
+
*
|
|
206
|
+
* Configure in module registration:
|
|
207
|
+
* ```typescript
|
|
208
|
+
* RenderModule.forRoot({
|
|
209
|
+
* allowedHeaders: ['user-agent', 'x-tenant-id', 'x-api-version']
|
|
210
|
+
* })
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```tsx
|
|
215
|
+
* const headers = useHeaders();
|
|
216
|
+
* console.log(headers['user-agent']); // 'Mozilla/5.0...'
|
|
217
|
+
* console.log(headers['x-tenant-id']); // 'tenant-123'
|
|
218
|
+
* console.log(headers['x-api-version']); // 'v2'
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
useHeaders: () => Record<string, string>;
|
|
222
|
+
/**
|
|
223
|
+
* Hook to access a specific custom header by name.
|
|
224
|
+
* Returns undefined if the header is not configured or not present.
|
|
225
|
+
*
|
|
226
|
+
* @param name - The header name (as configured in allowedHeaders)
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```tsx
|
|
230
|
+
* const tenantId = useHeader('x-tenant-id');
|
|
231
|
+
* if (tenantId) {
|
|
232
|
+
* console.log(`Tenant: ${tenantId}`);
|
|
233
|
+
* }
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
useHeader: (name: string) => string | undefined;
|
|
237
|
+
/**
|
|
238
|
+
* Hook to access cookies configured via allowedCookies.
|
|
239
|
+
* Returns all allowed cookies as a Record.
|
|
240
|
+
*
|
|
241
|
+
* Configure in module registration:
|
|
242
|
+
* ```typescript
|
|
243
|
+
* RenderModule.forRoot({
|
|
244
|
+
* allowedCookies: ['theme', 'locale', 'consent']
|
|
245
|
+
* })
|
|
246
|
+
* ```
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```tsx
|
|
250
|
+
* const cookies = useCookies();
|
|
251
|
+
* console.log(cookies.theme); // 'dark'
|
|
252
|
+
* console.log(cookies.locale); // 'en-US'
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
useCookies: () => Record<string, string>;
|
|
256
|
+
/**
|
|
257
|
+
* Hook to access a specific cookie by name.
|
|
258
|
+
* Returns undefined if the cookie is not configured or not present.
|
|
259
|
+
*
|
|
260
|
+
* @param name - The cookie name (as configured in allowedCookies)
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```tsx
|
|
264
|
+
* const theme = useCookie('theme');
|
|
265
|
+
* if (theme === 'dark') {
|
|
266
|
+
* console.log('Dark mode enabled');
|
|
267
|
+
* }
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
useCookie: (name: string) => string | undefined;
|
|
271
|
+
};
|
|
272
|
+
declare const usePageContext: () => RenderContext;
|
|
273
|
+
declare const useParams: () => Record<string, string>;
|
|
274
|
+
declare const useQuery: () => Record<string, string | string[]>;
|
|
275
|
+
declare const useRequest: () => RenderContext;
|
|
276
|
+
declare const useHeaders: () => Record<string, string>;
|
|
277
|
+
declare const useHeader: (name: string) => string | undefined;
|
|
278
|
+
declare const useCookies: () => Record<string, string>;
|
|
279
|
+
declare const useCookie: (name: string) => string | undefined;
|
|
280
|
+
|
|
281
|
+
export { type PageProps as P, PageContextProvider as a, useParams as b, createSSRHooks as c, useQuery as d, useRequest as e, useHeaders as f, useHeader as g, useCookies as h, useCookie as i, updatePageContext as j, usePageContext as u };
|
package/package.json
CHANGED
package/src/global.d.ts
CHANGED
|
@@ -18,6 +18,17 @@ declare global {
|
|
|
18
18
|
* Component name for the current page
|
|
19
19
|
*/
|
|
20
20
|
__COMPONENT_NAME__: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Layout metadata from the server for navigation
|
|
24
|
+
*/
|
|
25
|
+
__LAYOUTS__: Array<{ name: string; props?: any }>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Module registry for segment hydration after client-side navigation.
|
|
29
|
+
* Set by entry-client.tsx using Vite's import.meta.glob.
|
|
30
|
+
*/
|
|
31
|
+
__MODULES__: Record<string, { default: React.ComponentType<any> }>;
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
interface ImportMeta {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/// <reference types="@nestjs-ssr/react/global" />
|
|
2
2
|
import React, { StrictMode } from 'react';
|
|
3
3
|
import { hydrateRoot } from 'react-dom/client';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
PageContextProvider,
|
|
6
|
+
NavigationProvider,
|
|
7
|
+
} from '@nestjs-ssr/react/client';
|
|
5
8
|
|
|
6
9
|
const componentName = window.__COMPONENT_NAME__;
|
|
7
10
|
const initialProps = window.__INITIAL_STATE__ || {};
|
|
@@ -15,6 +18,9 @@ const modules: Record<string, { default: React.ComponentType<any> }> =
|
|
|
15
18
|
eager: true,
|
|
16
19
|
});
|
|
17
20
|
|
|
21
|
+
// Export modules globally for segment hydration after client-side navigation
|
|
22
|
+
window.__MODULES__ = modules;
|
|
23
|
+
|
|
18
24
|
// Build a map of components with their metadata
|
|
19
25
|
// Filter out entry files and modules without default exports
|
|
20
26
|
const componentMap = Object.entries(modules)
|
|
@@ -140,14 +146,24 @@ function composeWithLayout(
|
|
|
140
146
|
// Compose the component with its layout (if any)
|
|
141
147
|
const composedElement = composeWithLayout(ViewComponent, initialProps);
|
|
142
148
|
|
|
143
|
-
// Wrap with
|
|
149
|
+
// Wrap with providers to make context and navigation state available via hooks
|
|
144
150
|
const wrappedElement = (
|
|
145
|
-
<
|
|
146
|
-
{
|
|
147
|
-
|
|
151
|
+
<NavigationProvider>
|
|
152
|
+
<PageContextProvider context={renderContext}>
|
|
153
|
+
{composedElement}
|
|
154
|
+
</PageContextProvider>
|
|
155
|
+
</NavigationProvider>
|
|
148
156
|
);
|
|
149
157
|
|
|
150
158
|
hydrateRoot(
|
|
151
159
|
document.getElementById('root')!,
|
|
152
160
|
<StrictMode>{wrappedElement}</StrictMode>,
|
|
153
161
|
);
|
|
162
|
+
|
|
163
|
+
// Handle browser back/forward navigation
|
|
164
|
+
window.addEventListener('popstate', async () => {
|
|
165
|
+
// Dynamically import navigate to avoid circular dependency with hydrate-segment
|
|
166
|
+
const { navigate } = await import('@nestjs-ssr/react/client');
|
|
167
|
+
// Re-navigate to the current URL (browser already updated location)
|
|
168
|
+
navigate(location.href, { replace: true, scroll: false });
|
|
169
|
+
});
|
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { renderToString, renderToPipeableStream } from 'react-dom/server';
|
|
3
|
-
import { PageContextProvider } from '@nestjs-ssr/react';
|
|
3
|
+
import { PageContextProvider } from '@nestjs-ssr/react/client';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Compose a component with its layouts from the interceptor
|
|
7
|
-
* Layouts are passed from the RenderInterceptor based on decorators
|
|
6
|
+
* Compose a component with its layouts from the interceptor.
|
|
7
|
+
* Layouts are passed from the RenderInterceptor based on decorators.
|
|
8
|
+
* Each layout is wrapped with data-layout and data-outlet attributes
|
|
9
|
+
* for client-side navigation segment swapping.
|
|
8
10
|
*/
|
|
9
11
|
function composeWithLayouts(
|
|
10
12
|
ViewComponent: React.ComponentType<any>,
|
|
11
13
|
props: any,
|
|
12
14
|
layouts: Array<{ layout: React.ComponentType<any>; props?: any }> = [],
|
|
15
|
+
context?: any,
|
|
13
16
|
): React.ReactElement {
|
|
14
17
|
// Start with the page component
|
|
15
18
|
let result = <ViewComponent {...props} />;
|
|
16
19
|
|
|
17
20
|
// Wrap with each layout in the chain (outermost to innermost in array)
|
|
18
21
|
// We iterate normally because layouts are already in correct order from interceptor
|
|
22
|
+
// Pass context to layouts so they can access path, params, etc. for navigation
|
|
23
|
+
// Each layout gets data-layout attribute and children are wrapped in data-outlet
|
|
19
24
|
for (const { layout: Layout, props: layoutProps } of layouts) {
|
|
20
|
-
|
|
25
|
+
const layoutName = Layout.displayName || Layout.name || 'Layout';
|
|
26
|
+
result = (
|
|
27
|
+
<div data-layout={layoutName}>
|
|
28
|
+
<Layout context={context} layoutProps={layoutProps}>
|
|
29
|
+
<div data-outlet={layoutName}>{result}</div>
|
|
30
|
+
</Layout>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
return result;
|
|
@@ -32,7 +44,12 @@ export function renderComponent(
|
|
|
32
44
|
data: any,
|
|
33
45
|
) {
|
|
34
46
|
const { data: pageData, __context: context, __layouts: layouts } = data;
|
|
35
|
-
const composedElement = composeWithLayouts(
|
|
47
|
+
const composedElement = composeWithLayouts(
|
|
48
|
+
ViewComponent,
|
|
49
|
+
pageData,
|
|
50
|
+
layouts,
|
|
51
|
+
context,
|
|
52
|
+
);
|
|
36
53
|
|
|
37
54
|
// Wrap with PageContextProvider to make context available via hooks
|
|
38
55
|
const wrappedElement = (
|
|
@@ -44,6 +61,26 @@ export function renderComponent(
|
|
|
44
61
|
return renderToString(wrappedElement);
|
|
45
62
|
}
|
|
46
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Render just the page component for segment navigation.
|
|
66
|
+
* No layout wrappers - the layout already exists on the client.
|
|
67
|
+
*/
|
|
68
|
+
export function renderSegment(
|
|
69
|
+
ViewComponent: React.ComponentType<any>,
|
|
70
|
+
data: any,
|
|
71
|
+
) {
|
|
72
|
+
const { data: pageData, __context: context } = data;
|
|
73
|
+
|
|
74
|
+
// Render just the page component, no layout wrappers
|
|
75
|
+
const element = (
|
|
76
|
+
<PageContextProvider context={context}>
|
|
77
|
+
<ViewComponent {...pageData} />
|
|
78
|
+
</PageContextProvider>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return renderToString(element);
|
|
82
|
+
}
|
|
83
|
+
|
|
47
84
|
/**
|
|
48
85
|
* Streaming SSR (mode: 'stream' - default)
|
|
49
86
|
* Modern approach with progressive rendering and Suspense support
|
|
@@ -59,7 +96,12 @@ export function renderComponentStream(
|
|
|
59
96
|
},
|
|
60
97
|
) {
|
|
61
98
|
const { data: pageData, __context: context, __layouts: layouts } = data;
|
|
62
|
-
const composedElement = composeWithLayouts(
|
|
99
|
+
const composedElement = composeWithLayouts(
|
|
100
|
+
ViewComponent,
|
|
101
|
+
pageData,
|
|
102
|
+
layouts,
|
|
103
|
+
context,
|
|
104
|
+
);
|
|
63
105
|
|
|
64
106
|
// Wrap with PageContextProvider to make context available via hooks
|
|
65
107
|
const wrappedElement = (
|