@websolutespa/bom-next 3.0.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.
- package/CHANGELOG.md +7 -0
- package/README.md +11 -0
- package/dist/config.client-B8As5DY0.js +2 -0
- package/dist/config.edge-B8As5DY0.js +2 -0
- package/dist/config.server-B8As5DY0.js +2 -0
- package/dist/exports/client-xcFJNcG7.d.ts +979 -0
- package/dist/exports/client-xcFJNcG7.d.ts.map +1 -0
- package/dist/exports/client.js +5735 -0
- package/dist/exports/client.js.map +1 -0
- package/dist/exports/instrumentation-DBO6QH4d.d.ts +13 -0
- package/dist/exports/instrumentation-DBO6QH4d.d.ts.map +1 -0
- package/dist/exports/instrumentation.js +25 -0
- package/dist/exports/instrumentation.js.map +1 -0
- package/dist/exports/proxy-Cv7QFVci.d.ts +71 -0
- package/dist/exports/proxy-Cv7QFVci.d.ts.map +1 -0
- package/dist/exports/proxy.js +254 -0
- package/dist/exports/proxy.js.map +1 -0
- package/dist/exports/server-BHObJHJ6.d.ts +402 -0
- package/dist/exports/server-BHObJHJ6.d.ts.map +1 -0
- package/dist/exports/server.js +1974 -0
- package/dist/exports/server.js.map +1 -0
- package/dist/http-_2M-UFRx.js +61 -0
- package/dist/http-_2M-UFRx.js.map +1 -0
- package/dist/index-CdYcvudT.d.ts +4 -0
- package/dist/index-CtfAJ0y7.d.ts +499 -0
- package/dist/index-CtfAJ0y7.d.ts.map +1 -0
- package/dist/index.js +201 -0
- package/dist/index.js.map +1 -0
- package/dist/route-DzFwERQP.js +65 -0
- package/dist/route-DzFwERQP.js.map +1 -0
- package/dist/store-CJzqrHsl.js +73 -0
- package/dist/store-CJzqrHsl.js.map +1 -0
- package/dist/types-BatUTdbZ.d.ts +15 -0
- package/dist/types-BatUTdbZ.d.ts.map +1 -0
- package/dist/types-D29_snYY.d.ts +191 -0
- package/dist/types-D29_snYY.d.ts.map +1 -0
- package/dist/utils-Ca7xh5mm.js +78 -0
- package/dist/utils-Ca7xh5mm.js.map +1 -0
- package/dist/utils-YwteTbk7.js +23 -0
- package/dist/utils-YwteTbk7.js.map +1 -0
- package/package.json +156 -0
- package/src/client/client.tsx +18 -0
- package/src/client/extra.tsx +21 -0
- package/src/client/label.tsx +24 -0
- package/src/client/layout.tsx +22 -0
- package/src/client/local.collection.ts +33 -0
- package/src/client/metadata.tsx +217 -0
- package/src/client/page.tsx +22 -0
- package/src/client/useApi.test.tsx +203 -0
- package/src/client/useApi.ts +59 -0
- package/src/client/useCollectionApi.test.tsx +201 -0
- package/src/client/useCollectionApi.ts +66 -0
- package/src/client/useExtra.tsx +12 -0
- package/src/client/useHttp.ts +54 -0
- package/src/client/useLabel.test.tsx +76 -0
- package/src/client/useLabel.tsx +12 -0
- package/src/client/useLayout.tsx +13 -0
- package/src/client/useLink.tsx +13 -0
- package/src/client/useMetaData.tsx +12 -0
- package/src/client/usePage.tsx +12 -0
- package/src/client/useUser.tsx +12 -0
- package/src/client/user.tsx +43 -0
- package/src/exports/client.ts +20 -0
- package/src/exports/instrumentation.ts +1 -0
- package/src/exports/proxy.ts +5 -0
- package/src/exports/server.ts +19 -0
- package/src/global.d.ts +6 -0
- package/src/index.ts +17 -0
- package/src/instrumentation/client.ts +19 -0
- package/src/instrumentation/sentry/capture.requestError.ts +6 -0
- package/src/instrumentation/sentry/capture.routerTransitionStart.ts +4 -0
- package/src/instrumentation/sentry/config.client.ts +9 -0
- package/src/instrumentation/sentry/config.edge.ts +9 -0
- package/src/instrumentation/sentry/config.server.ts +9 -0
- package/src/instrumentation/server.ts +24 -0
- package/src/instrumentation/types.ts +12 -0
- package/src/lib/breadcrumb.ts +21 -0
- package/src/lib/const.ts +3 -0
- package/src/lib/http.test.ts +25 -0
- package/src/lib/http.ts +74 -0
- package/src/lib/label.ts +43 -0
- package/src/lib/layout.defaults.ts +167 -0
- package/src/lib/link.ts +19 -0
- package/src/lib/meta-data.ts +60 -0
- package/src/lib/open-graph.ts +165 -0
- package/src/lib/pagination.ts +78 -0
- package/src/lib/payload.config.ts +12 -0
- package/src/lib/route.ts +83 -0
- package/src/lib/store.ts +63 -0
- package/src/lib/structured-data.ts +24 -0
- package/src/lib/twitter-card.ts +49 -0
- package/src/lib/utils.ts +45 -0
- package/src/mock.ts +264 -0
- package/src/proxy/protected-route.test.ts +75 -0
- package/src/proxy/protected-route.ts +57 -0
- package/src/proxy/proxy.test.ts +48 -0
- package/src/proxy/proxy.ts +64 -0
- package/src/proxy/route-auto-detection.ts +126 -0
- package/src/proxy/route-proxy.ts +187 -0
- package/src/server/context.ts +147 -0
- package/src/server/handlers/auth.handler.ts +27 -0
- package/src/server/handlers/captions.handler.ts +32 -0
- package/src/server/handlers/collection.handler.test.ts +57 -0
- package/src/server/handlers/collection.handler.ts +212 -0
- package/src/server/handlers/error-response.ts +45 -0
- package/src/server/handlers/index.handler.ts +146 -0
- package/src/server/handlers/preview.handler.ts +63 -0
- package/src/server/handlers/registry.handler.test.ts +65 -0
- package/src/server/handlers/registry.handler.ts +259 -0
- package/src/server/handlers/route.handler.ts +161 -0
- package/src/server/handlers/sitemap.handler.ts +374 -0
- package/src/server/handlers/static-params.handler.ts +111 -0
- package/src/server/handlers/vars.handler.ts +167 -0
- package/src/server/layout.ts +120 -0
- package/src/server/lazy.ts +41 -0
- package/src/server/local-api.ts +63 -0
- package/src/server/metadata.ts +87 -0
- package/src/server/mixer-payload.ts +124 -0
- package/src/server/mixer-payload.type-test.ts +27 -0
- package/src/server/mixer.ts +439 -0
- package/src/server/mock/category.ts +34 -0
- package/src/server/mock/collection.ts +21 -0
- package/src/server/mock/page.ts +62 -0
- package/src/server/mock/route.ts +36 -0
- package/src/server/mock/store.ts +159 -0
- package/src/server/page.ts +86 -0
- package/src/server/session/session.ts +101 -0
- package/src/server/utils.ts +23 -0
- package/src/server/utmz.ts +11 -0
- package/src/types.ts +211 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { getHost, getOrigin, isDevelopment, isProduction } from '@websolutespa/bom-core';
|
|
2
|
+
import { headers as getHeaders } from 'next/headers';
|
|
3
|
+
import { NextRequest } from 'next/server';
|
|
4
|
+
import { SanitizedConfig } from 'payload';
|
|
5
|
+
import { getLayout } from '../layout';
|
|
6
|
+
import { getPayload, getSearchParams } from '../mixer';
|
|
7
|
+
import { getHandlerErrorResponse } from './error-response';
|
|
8
|
+
|
|
9
|
+
const DISALLOWED_KEYS = [
|
|
10
|
+
'__NEXT_PRIVATE_STANDALONE_CONFIG',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const getFilteredEnv = (env: Record<string, unknown>): Record<string, unknown> => {
|
|
14
|
+
const entries = Object.entries(env).filter(([k, v]) => (
|
|
15
|
+
!DISALLOWED_KEYS.includes(k) && typeof v === 'string'
|
|
16
|
+
));
|
|
17
|
+
entries.sort();
|
|
18
|
+
return Object.fromEntries(entries);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const sortKeys = (object?: Record<string, unknown>): Record<string, unknown> => {
|
|
22
|
+
const entries = Object.entries(object || {});
|
|
23
|
+
entries.sort();
|
|
24
|
+
return Object.fromEntries(entries);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function getVarsHandler(config: Promise<SanitizedConfig>) {
|
|
28
|
+
return async function (request: NextRequest) {
|
|
29
|
+
try {
|
|
30
|
+
const searchParams = request.nextUrl.searchParams;
|
|
31
|
+
// console.log('getVarsHandler', searchParams);
|
|
32
|
+
const { market, locale, defaultMarket, defaultLocale, params } = getSearchParams(request);
|
|
33
|
+
const headers = await getHeaders();
|
|
34
|
+
const { payload, user, getContext } = await getPayload({ config, headers });
|
|
35
|
+
if (!(user?.roles || []).includes('admin')) {
|
|
36
|
+
throw { status: 401, message: 'Unauthorized' };
|
|
37
|
+
}
|
|
38
|
+
const context = await getContext(market || defaultMarket, locale || defaultLocale);
|
|
39
|
+
const layout = await getLayout(market || defaultMarket, locale || defaultLocale, payload, user, context);
|
|
40
|
+
const info = {
|
|
41
|
+
production: isProduction,
|
|
42
|
+
development: isDevelopment,
|
|
43
|
+
origin: getOrigin(headers),
|
|
44
|
+
host: getHost(headers),
|
|
45
|
+
params: {},
|
|
46
|
+
searchParams: { ...sortKeys(Object.fromEntries(searchParams)) },
|
|
47
|
+
headers: { ...sortKeys(Object.fromEntries(headers)) },
|
|
48
|
+
env: { ...getFilteredEnv(process.env) },
|
|
49
|
+
user: { ...sortKeys(user as unknown as Record<string, unknown>) },
|
|
50
|
+
context: { ...sortKeys(context) },
|
|
51
|
+
layout: { ...sortKeys(layout) },
|
|
52
|
+
};
|
|
53
|
+
const accept = headers.get('accept');
|
|
54
|
+
if (!accept || !(accept.includes('text/html'))) {
|
|
55
|
+
return Response.json(info, {
|
|
56
|
+
status: 200,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return new Response(`<!DOCTYPE html>
|
|
60
|
+
<html>
|
|
61
|
+
<head>
|
|
62
|
+
<meta charSet="utf-8"/>
|
|
63
|
+
<style>
|
|
64
|
+
html, body {
|
|
65
|
+
background: #282828;
|
|
66
|
+
color: #7cacf8;
|
|
67
|
+
}
|
|
68
|
+
body {
|
|
69
|
+
padding: 0;
|
|
70
|
+
margin: 0;
|
|
71
|
+
font-family: monospace;
|
|
72
|
+
font-size: 14px;
|
|
73
|
+
line-height: 1.16;
|
|
74
|
+
font-weight: 100;
|
|
75
|
+
}
|
|
76
|
+
json-viewer {
|
|
77
|
+
padding: 20px;
|
|
78
|
+
min-height: 100vh;
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
line-height: 1.2;
|
|
81
|
+
--background-color: #282828;
|
|
82
|
+
--color: #e3e3e3;
|
|
83
|
+
--property-color: #7cacf8;
|
|
84
|
+
--string-color: #53d5fb;
|
|
85
|
+
--number-color: #7b80ff;
|
|
86
|
+
--boolean-color: #9980ff;
|
|
87
|
+
--null-color: #6f6f6f;
|
|
88
|
+
--preview-color: #8f8f8f;
|
|
89
|
+
--highlight-color: #7b0000;
|
|
90
|
+
--font-family: monospace;
|
|
91
|
+
--font-size: 14px;
|
|
92
|
+
--indent-size: 1em;
|
|
93
|
+
--indentguide-size: 1px;
|
|
94
|
+
--indentguide-style: solid;
|
|
95
|
+
--indentguide-color: #333;
|
|
96
|
+
--indentguide-color-active: #666;
|
|
97
|
+
--indentguide: var(--indentguide-size) var(--indentguide-style) var(--indentguide-color);
|
|
98
|
+
--indentguide-active: var(--indentguide-size) var(--indentguide-style) var(--indentguide-color-active);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
:root {
|
|
102
|
+
--scrollbar-size: 8px;
|
|
103
|
+
--scrollbar-border-radius: 2px;
|
|
104
|
+
--scrollbar-track-color: transparent;
|
|
105
|
+
--scrollbar-track-hover-color: transparent;
|
|
106
|
+
--scrollbar-thumb-color: #888888;
|
|
107
|
+
--scrollbar-thumb-hover-color: #aaaaaa;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
::-webkit-scrollbar {
|
|
111
|
+
width: var(--scrollbar-size);
|
|
112
|
+
height: var(--scrollbar-size);
|
|
113
|
+
border-radius: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
::-webkit-scrollbar-button {
|
|
117
|
+
display: none;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
::-webkit-scrollbar-button:hover {
|
|
121
|
+
display: none;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
::-webkit-scrollbar-thumb {
|
|
125
|
+
background-color: var(--scrollbar-thumb-color);
|
|
126
|
+
box-shadow: none;
|
|
127
|
+
border-radius: calc(var(--scrollbar-size) * 0.5);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
::-webkit-scrollbar-thumb:hover {
|
|
131
|
+
background-color: var(--scrollbar-thumb-hover-color);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
::-webkit-scrollbar-track {
|
|
135
|
+
background-color: var(--scrollbar-track-color);
|
|
136
|
+
border-radius: 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
::-webkit-scrollbar-track:hover {
|
|
140
|
+
background-color: var(--scrollbar-track-hover-color);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
</style>
|
|
144
|
+
<script src="https://unpkg.com/@alenaksu/json-viewer@2.0.0/dist/json-viewer.bundle.js"></script>
|
|
145
|
+
<script>
|
|
146
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
147
|
+
const viewer = document.querySelector('#json-viewer');
|
|
148
|
+
viewer.data = ${JSON.stringify(info)};
|
|
149
|
+
viewer.expand(/headers|env/);
|
|
150
|
+
});
|
|
151
|
+
</script>
|
|
152
|
+
</head>
|
|
153
|
+
<body>
|
|
154
|
+
<json-viewer id="json-viewer"></json-viewer>
|
|
155
|
+
</body>
|
|
156
|
+
</html>`, {
|
|
157
|
+
status: 200,
|
|
158
|
+
headers: {
|
|
159
|
+
'Content-Type': 'text/html',
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
} catch (error: unknown) {
|
|
163
|
+
console.log('getVarsHandler.error', error);
|
|
164
|
+
return getHandlerErrorResponse(error);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { IApp, IAppExtended, ICategory, ILabel, ILayout, ILocale, ILocalizedMenu, IMarket, IRoute, IRouteLink, getRootCategory } from '@websolutespa/bom-core';
|
|
4
|
+
import type { User } from '@websolutespa/payload-plugin-bowl';
|
|
5
|
+
import type { CollectionSlug, GlobalSlug } from 'payload';
|
|
6
|
+
import { getRouteLinkCategory } from '../lib/route';
|
|
7
|
+
import type { MixerContext } from '../types';
|
|
8
|
+
import { buildBaseOptions } from './mixer';
|
|
9
|
+
import type { MixerPayload } from './mixer-payload';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds the full ILayout object using Payload local API calls.
|
|
13
|
+
*
|
|
14
|
+
* All payload.find / payload.findGlobal calls pass `context: mixerContext` so
|
|
15
|
+
* payload-plugin-bowl can enrich results (hrefs, richText serialisation, etc.).
|
|
16
|
+
*
|
|
17
|
+
* Collection and global slugs use the payload-plugin-bowl defaults:
|
|
18
|
+
* market → 'market' (collection)
|
|
19
|
+
* locale → 'locale' (global)
|
|
20
|
+
* label → 'label' (collection)
|
|
21
|
+
* app → 'app' (global) ← defined in payload-plugin-bowl
|
|
22
|
+
* menu → 'menu' (collection)
|
|
23
|
+
*
|
|
24
|
+
* The route link tree is built from mixerContext.currentRoutes + mixerContext.categories
|
|
25
|
+
* using getRouteLinkCategory (pure fn, no store calls) from bom-mixer-models/server.
|
|
26
|
+
*/
|
|
27
|
+
export async function getLayout<T extends IAppExtended = IAppExtended>(
|
|
28
|
+
market: string,
|
|
29
|
+
locale: string,
|
|
30
|
+
payload: MixerPayload,
|
|
31
|
+
user: User,
|
|
32
|
+
mixerContext: MixerContext,
|
|
33
|
+
draft?: boolean
|
|
34
|
+
): Promise<ILayout<T>> {
|
|
35
|
+
const base = buildBaseOptions(user, locale, draft);
|
|
36
|
+
const findOpts = { ...base, pagination: false, limit: 10000 } as const;
|
|
37
|
+
|
|
38
|
+
// markets
|
|
39
|
+
const marketsResult = await payload.find({
|
|
40
|
+
...findOpts,
|
|
41
|
+
collection: 'market' as CollectionSlug,
|
|
42
|
+
context: mixerContext,
|
|
43
|
+
});
|
|
44
|
+
const markets = marketsResult.docs as unknown as IMarket[];
|
|
45
|
+
|
|
46
|
+
// locales (global with an `items` array)
|
|
47
|
+
const localeGlobal = await payload.findGlobal({
|
|
48
|
+
...base,
|
|
49
|
+
slug: 'locale' as GlobalSlug,
|
|
50
|
+
context: mixerContext,
|
|
51
|
+
}) as unknown as { items: ILocale[] };
|
|
52
|
+
const locales = (localeGlobal?.items ?? []).filter(x => x.isActive);
|
|
53
|
+
|
|
54
|
+
// labels
|
|
55
|
+
const labelsResult = await payload.find({
|
|
56
|
+
...findOpts,
|
|
57
|
+
collection: 'label' as CollectionSlug,
|
|
58
|
+
context: mixerContext,
|
|
59
|
+
});
|
|
60
|
+
const labels = labelsResult.docs as unknown as ILabel[];
|
|
61
|
+
|
|
62
|
+
// app global (slug 'app', registered by payload-plugin-bowl)
|
|
63
|
+
const app = await payload.findGlobal({
|
|
64
|
+
...base,
|
|
65
|
+
slug: 'app' as GlobalSlug,
|
|
66
|
+
context: mixerContext,
|
|
67
|
+
}) as unknown as IApp<T>;
|
|
68
|
+
|
|
69
|
+
// menus
|
|
70
|
+
const menusResult = await payload.find({
|
|
71
|
+
...findOpts,
|
|
72
|
+
collection: 'menu' as CollectionSlug,
|
|
73
|
+
context: mixerContext,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const menu: Record<string, ILocalizedMenu> = {};
|
|
77
|
+
(menusResult.docs as unknown as ILocalizedMenu[]).forEach(x => {
|
|
78
|
+
menu[String(x.id)] = x;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// route link tree — pure computation from mixer context data (no store calls)
|
|
82
|
+
const categories = (mixerContext.categories ?? []) as unknown as ICategory[];
|
|
83
|
+
const currentRoutes = (mixerContext.currentRoutes ?? []) as IRoute[];
|
|
84
|
+
const rootCategory = getRootCategory(categories);
|
|
85
|
+
const treeResult = rootCategory
|
|
86
|
+
? getRouteLinkCategory(locale, currentRoutes, categories, rootCategory, rootCategory)
|
|
87
|
+
: undefined;
|
|
88
|
+
const tree = Array.isArray(treeResult) ? treeResult[0] : treeResult;
|
|
89
|
+
|
|
90
|
+
// derive topLevelRoutes and topLevelHrefs from tree (mirrors layout.service.ts logic)
|
|
91
|
+
const firstLevelRoutes = tree?.items ?? [];
|
|
92
|
+
const flatTopLevelRoutes: IRouteLink[] = tree ? [tree, ...firstLevelRoutes] : [];
|
|
93
|
+
|
|
94
|
+
const topLevelRoutes = flatTopLevelRoutes.reduce((object, routeLink) => {
|
|
95
|
+
if (!object[routeLink.schema] || routeLink.schema === String(routeLink.category)) {
|
|
96
|
+
object[routeLink.schema] = routeLink;
|
|
97
|
+
}
|
|
98
|
+
return object;
|
|
99
|
+
}, {} as Record<string, IRouteLink>);
|
|
100
|
+
|
|
101
|
+
const topLevelHrefs = flatTopLevelRoutes.reduce((object, routeLink) => {
|
|
102
|
+
if (routeLink.href && (!object[routeLink.schema] || routeLink.schema === String(routeLink.category))) {
|
|
103
|
+
object[routeLink.schema] = routeLink.href;
|
|
104
|
+
}
|
|
105
|
+
return object;
|
|
106
|
+
}, {} as Record<string, string>);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
labels,
|
|
110
|
+
locale,
|
|
111
|
+
locales,
|
|
112
|
+
market,
|
|
113
|
+
markets,
|
|
114
|
+
app,
|
|
115
|
+
menu,
|
|
116
|
+
topLevelHrefs,
|
|
117
|
+
topLevelRoutes,
|
|
118
|
+
tree,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import type { IComponent } from '@websolutespa/bom-core';
|
|
4
|
+
import type { ILazyComponent, ILazyStaticProps, ILazyStaticPropsFunc } from '../types';
|
|
5
|
+
import { getPageContext } from './context';
|
|
6
|
+
import { getContext } from './mixer';
|
|
7
|
+
|
|
8
|
+
const LAZY_PROPS: ILazyStaticProps = {};
|
|
9
|
+
|
|
10
|
+
export async function withLazyProps<T>(key: string, getComponentProps: ILazyStaticPropsFunc<T>) {
|
|
11
|
+
LAZY_PROPS[key] = getComponentProps;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function decorateLazyComponents(extraComponents?: IComponent[]) {
|
|
15
|
+
const context = await getContext();
|
|
16
|
+
const pageContext = await getPageContext();
|
|
17
|
+
const page = pageContext.page;
|
|
18
|
+
if (!page) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const components = [
|
|
22
|
+
...(page.components || []),
|
|
23
|
+
...(extraComponents || []),
|
|
24
|
+
];
|
|
25
|
+
const decoratedComponents: ILazyComponent[] = [];
|
|
26
|
+
for (const component of components) {
|
|
27
|
+
const key = (component.blockType || component.schema || 'unkown_type') as keyof typeof LAZY_PROPS;
|
|
28
|
+
const getComponentProps = LAZY_PROPS[key];
|
|
29
|
+
if (typeof getComponentProps === 'function') {
|
|
30
|
+
const decoratedComponent = await getComponentProps({
|
|
31
|
+
...context,
|
|
32
|
+
...pageContext,
|
|
33
|
+
component,
|
|
34
|
+
});
|
|
35
|
+
decoratedComponents.push(decoratedComponent);
|
|
36
|
+
} else {
|
|
37
|
+
decoratedComponents.push(component);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
page.components = decoratedComponents;
|
|
41
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { CollectionSlug, getPayload, GlobalSlug, SanitizedConfig } from 'payload';
|
|
4
|
+
|
|
5
|
+
export async function getLocalApi(config: Promise<SanitizedConfig>, slug: string): Promise<any> {
|
|
6
|
+
const payload = await getPayload({ config });
|
|
7
|
+
const collectionSlugs = payload.config.collections.map(x => x.slug);
|
|
8
|
+
const globalSlugs = payload.config.globals.map(x => x.slug);
|
|
9
|
+
const keys = [
|
|
10
|
+
...collectionSlugs,
|
|
11
|
+
...globalSlugs,
|
|
12
|
+
];
|
|
13
|
+
keys.sort();
|
|
14
|
+
if (collectionSlugs.includes(slug as CollectionSlug)) {
|
|
15
|
+
const collectionSlug = slug as CollectionSlug;
|
|
16
|
+
return {
|
|
17
|
+
findMany: async (params: Parameters<typeof payload.find>) => {
|
|
18
|
+
const response = await payload.find({
|
|
19
|
+
...(params as any),
|
|
20
|
+
collection: collectionSlug,
|
|
21
|
+
pagination: false,
|
|
22
|
+
limit: 10000,
|
|
23
|
+
overrideAccess: false,
|
|
24
|
+
});
|
|
25
|
+
return response.docs;
|
|
26
|
+
},
|
|
27
|
+
findPaged: async (params: Parameters<typeof payload.find>) => {
|
|
28
|
+
const response = await payload.find({
|
|
29
|
+
...(params as any),
|
|
30
|
+
collection: collectionSlug,
|
|
31
|
+
overrideAccess: false,
|
|
32
|
+
});
|
|
33
|
+
return response;
|
|
34
|
+
},
|
|
35
|
+
findOne: async (params: Parameters<typeof payload.findByID>) => {
|
|
36
|
+
return await payload.findByID({
|
|
37
|
+
id: '',
|
|
38
|
+
...params,
|
|
39
|
+
collection: collectionSlug,
|
|
40
|
+
disableErrors: true,
|
|
41
|
+
overrideAccess: false,
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
} else if (globalSlugs.includes(slug as GlobalSlug)) {
|
|
46
|
+
const globalSlug = slug as GlobalSlug;
|
|
47
|
+
return {
|
|
48
|
+
findMany: async (params: Parameters<typeof payload.findGlobal>) => {
|
|
49
|
+
return await payload.findGlobal({
|
|
50
|
+
slug: globalSlug,
|
|
51
|
+
...params,
|
|
52
|
+
overrideAccess: false,
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
findPaged: async (params: Parameters<typeof payload.find>) => {
|
|
56
|
+
throw 'Not implemented';
|
|
57
|
+
},
|
|
58
|
+
findOne: async (params: Parameters<typeof payload.findByID>) => {
|
|
59
|
+
throw 'Not implemented';
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ResolvingMetadata } from 'next';
|
|
2
|
+
import type { CollectionSlug, SanitizedConfig } from 'payload';
|
|
3
|
+
import type { PageParams } from '../types';
|
|
4
|
+
import { createPageContext } from './context';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns a generateMetadata function for a given page collection slug.
|
|
8
|
+
* Uses bowl's createPageContext (Payload local API) instead of the store abstraction.
|
|
9
|
+
*/
|
|
10
|
+
export function getMetadata<TSlug extends CollectionSlug>(slug: TSlug, config: Promise<SanitizedConfig>) {
|
|
11
|
+
return async function generateMetadata(
|
|
12
|
+
props: Readonly<{ params: Promise<PageParams>; }>,
|
|
13
|
+
parent: ResolvingMetadata
|
|
14
|
+
) {
|
|
15
|
+
const { market, locale, id } = await props.params;
|
|
16
|
+
const { page } = await createPageContext(market, locale, slug, id, config);
|
|
17
|
+
return {
|
|
18
|
+
title: page!.meta?.title || page!.title,
|
|
19
|
+
description: page!.meta?.description || page!.abstract,
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
"viewport": null,
|
|
28
|
+
"themeColor": null,
|
|
29
|
+
"colorScheme": null,
|
|
30
|
+
"metadataBase": null,
|
|
31
|
+
"title": {
|
|
32
|
+
"absolute": "Mixer",
|
|
33
|
+
"template": null
|
|
34
|
+
},
|
|
35
|
+
"description": "Generated by bom create",
|
|
36
|
+
"applicationName": "Mixer",
|
|
37
|
+
"authors": [
|
|
38
|
+
{
|
|
39
|
+
"name": "Websolute Spa",
|
|
40
|
+
"url": "https://www.websolute.it"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"generator": "BOM",
|
|
44
|
+
"keywords": [
|
|
45
|
+
"Mixer",
|
|
46
|
+
"Next.js",
|
|
47
|
+
"React",
|
|
48
|
+
"JavaScript"
|
|
49
|
+
],
|
|
50
|
+
"referrer": "origin-when-cross-origin",
|
|
51
|
+
"creator": "Luca Zampetti",
|
|
52
|
+
"publisher": "Websolute Spa",
|
|
53
|
+
"robots": null,
|
|
54
|
+
"manifest": null,
|
|
55
|
+
"alternates": {
|
|
56
|
+
"canonical": null,
|
|
57
|
+
"languages": null,
|
|
58
|
+
"media": null,
|
|
59
|
+
"types": null
|
|
60
|
+
},
|
|
61
|
+
"icons": null,
|
|
62
|
+
"openGraph": null,
|
|
63
|
+
"twitter": null,
|
|
64
|
+
"verification": {},
|
|
65
|
+
"appleWebApp": null,
|
|
66
|
+
"formatDetection": {
|
|
67
|
+
"email": false,
|
|
68
|
+
"address": false,
|
|
69
|
+
"telephone": false
|
|
70
|
+
},
|
|
71
|
+
"itunes": null,
|
|
72
|
+
"facebook": null,
|
|
73
|
+
"abstract": null,
|
|
74
|
+
"appLinks": null,
|
|
75
|
+
"archives": null,
|
|
76
|
+
"assets": null,
|
|
77
|
+
"bookmarks": null,
|
|
78
|
+
"category": null,
|
|
79
|
+
"classification": null,
|
|
80
|
+
"pagination": {
|
|
81
|
+
"previous": null,
|
|
82
|
+
"next": null
|
|
83
|
+
},
|
|
84
|
+
"other": {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
*/
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { IMenu, IMenuItem } from '@websolutespa/bom-core';
|
|
2
|
+
import type { MixerReadContext } from '@websolutespa/payload-plugin-bowl';
|
|
3
|
+
import type {
|
|
4
|
+
BasePayload,
|
|
5
|
+
CollectionSlug,
|
|
6
|
+
DataFromCollectionSlug,
|
|
7
|
+
DataFromGlobalSlug,
|
|
8
|
+
FindOptions,
|
|
9
|
+
GlobalSlug,
|
|
10
|
+
PaginatedDocs,
|
|
11
|
+
RequestContext
|
|
12
|
+
} from 'payload';
|
|
13
|
+
|
|
14
|
+
/** Fields added to the root document by Bowl's market- and locale-aware reads. */
|
|
15
|
+
export type BowlDocumentDecoration = {
|
|
16
|
+
schema: string;
|
|
17
|
+
href?: string | null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type BowlDecoratedCategory<T> = T extends null
|
|
21
|
+
? null
|
|
22
|
+
: T extends string
|
|
23
|
+
? T
|
|
24
|
+
: T extends readonly (infer TItem)[]
|
|
25
|
+
? BowlDecoratedCategory<TItem>[]
|
|
26
|
+
: T extends object
|
|
27
|
+
? BowlDecoratedValue<T> & { href: string | null }
|
|
28
|
+
: T;
|
|
29
|
+
|
|
30
|
+
type BowlDecoratedNav<T> = Extract<T, null | undefined> | IMenuItem | IMenuItem[];
|
|
31
|
+
|
|
32
|
+
type BowlDecoratedNavs<T> = T extends null | undefined ? T : IMenuItem[];
|
|
33
|
+
|
|
34
|
+
type BowlDecoratedProperty<TKey, TValue> = TValue extends { root: unknown }
|
|
35
|
+
? string
|
|
36
|
+
: TKey extends 'category'
|
|
37
|
+
? BowlDecoratedCategory<TValue>
|
|
38
|
+
: TKey extends 'nav'
|
|
39
|
+
? BowlDecoratedNav<TValue>
|
|
40
|
+
: TKey extends 'navs'
|
|
41
|
+
? BowlDecoratedNavs<TValue>
|
|
42
|
+
: BowlDecoratedValue<TValue>;
|
|
43
|
+
|
|
44
|
+
type BowlDecoratedObject<T> = {
|
|
45
|
+
[TKey in keyof T]: BowlDecoratedProperty<TKey, T[TKey]>;
|
|
46
|
+
} & (T extends { mimeType?: unknown; filename?: unknown } ? { type: string } : unknown);
|
|
47
|
+
|
|
48
|
+
type BowlDecoratedValue<T> = T extends Date
|
|
49
|
+
? T
|
|
50
|
+
: T extends readonly (infer TItem)[]
|
|
51
|
+
? BowlDecoratedValue<TItem>[]
|
|
52
|
+
: T extends object
|
|
53
|
+
? BowlDecoratedObject<T>
|
|
54
|
+
: T;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Represents a document returned by a Bowl-decorated local Payload read.
|
|
58
|
+
*
|
|
59
|
+
* Localized values are selected by Payload's `locale` option. Bowl recursively
|
|
60
|
+
* serializes Lexical rich-text values to HTML, resolves populated category
|
|
61
|
+
* relationships, normalizes nav fields, and adds upload media types. Bowl menu
|
|
62
|
+
* collections return their `IMenu` projection rather than their stored shape.
|
|
63
|
+
*/
|
|
64
|
+
export type BowlDecorator<T, TSlug extends string = ''> = TSlug extends 'menu'
|
|
65
|
+
? IMenu
|
|
66
|
+
: BowlDecoratedValue<T> & BowlDocumentDecoration;
|
|
67
|
+
|
|
68
|
+
/** @deprecated Use `BowlDecorator<T>` for a decorated document result. */
|
|
69
|
+
export type BowlDecoration = BowlDocumentDecoration;
|
|
70
|
+
|
|
71
|
+
type DecorateForMixer<T, TSlug extends string, TContext> = TContext extends MixerReadContext
|
|
72
|
+
? BowlDecorator<T, TSlug>
|
|
73
|
+
: T;
|
|
74
|
+
|
|
75
|
+
type MixerFindOptions<
|
|
76
|
+
TSlug extends CollectionSlug,
|
|
77
|
+
TContext,
|
|
78
|
+
> = Omit<FindOptions<TSlug, any>, 'context'> & {
|
|
79
|
+
context?: TContext;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type MixerFindByIDOptions<
|
|
83
|
+
TSlug extends CollectionSlug,
|
|
84
|
+
TContext,
|
|
85
|
+
> = Omit<FindOptions<TSlug, any>, 'context'> & {
|
|
86
|
+
context?: TContext;
|
|
87
|
+
disableErrors?: boolean;
|
|
88
|
+
id: number | string;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
type MixerFindGlobalOptions<
|
|
92
|
+
TSlug extends GlobalSlug,
|
|
93
|
+
TContext,
|
|
94
|
+
> = Omit<FindOptions<CollectionSlug, any>, 'collection' | 'context'> & {
|
|
95
|
+
context?: TContext;
|
|
96
|
+
slug: TSlug;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Type-only view returned by BOM Next's getPayload loader.
|
|
101
|
+
*
|
|
102
|
+
* It is the same Payload instance at runtime. Standard local reads retain
|
|
103
|
+
* generated Payload types until their context includes market and locale.
|
|
104
|
+
*/
|
|
105
|
+
export type MixerPayload = Omit<BasePayload, 'find' | 'findByID' | 'findGlobal'> & {
|
|
106
|
+
find: <
|
|
107
|
+
TSlug extends CollectionSlug,
|
|
108
|
+
TContext extends RequestContext | undefined = undefined,
|
|
109
|
+
>(options: MixerFindOptions<TSlug, TContext>) => Promise<
|
|
110
|
+
PaginatedDocs<DecorateForMixer<DataFromCollectionSlug<TSlug>, TSlug, TContext>>
|
|
111
|
+
>;
|
|
112
|
+
findByID: <
|
|
113
|
+
TSlug extends CollectionSlug,
|
|
114
|
+
TContext extends RequestContext | undefined = undefined,
|
|
115
|
+
>(options: MixerFindByIDOptions<TSlug, TContext>) => Promise<
|
|
116
|
+
DecorateForMixer<DataFromCollectionSlug<TSlug>, TSlug, TContext> | null
|
|
117
|
+
>;
|
|
118
|
+
findGlobal: <
|
|
119
|
+
TSlug extends GlobalSlug,
|
|
120
|
+
TContext extends RequestContext | undefined = undefined,
|
|
121
|
+
>(options: MixerFindGlobalOptions<TSlug, TContext>) => Promise<
|
|
122
|
+
DecorateForMixer<DataFromGlobalSlug<TSlug>, TSlug, TContext>
|
|
123
|
+
>;
|
|
124
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { MixerPayload } from './mixer-payload';
|
|
2
|
+
|
|
3
|
+
declare const payload: MixerPayload;
|
|
4
|
+
|
|
5
|
+
const plainFind = payload.find({ collection: 'page' });
|
|
6
|
+
plainFind.then(result => {
|
|
7
|
+
result.docs[0].id;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const decoratedFind = payload.find({
|
|
11
|
+
collection: 'page',
|
|
12
|
+
context: { market: 'ww', locale: 'it' },
|
|
13
|
+
});
|
|
14
|
+
decoratedFind.then(result => result.docs[0].schema);
|
|
15
|
+
|
|
16
|
+
const decoratedByID = payload.findByID({
|
|
17
|
+
collection: 'page',
|
|
18
|
+
id: 'page-id',
|
|
19
|
+
context: { market: 'ww', locale: 'it' },
|
|
20
|
+
});
|
|
21
|
+
decoratedByID.then(result => result?.schema);
|
|
22
|
+
|
|
23
|
+
const decoratedGlobal = payload.findGlobal({
|
|
24
|
+
slug: 'app',
|
|
25
|
+
context: { market: 'ww', locale: 'it' },
|
|
26
|
+
});
|
|
27
|
+
decoratedGlobal.then(result => result.schema);
|