@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.
Files changed (130) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +11 -0
  3. package/dist/config.client-B8As5DY0.js +2 -0
  4. package/dist/config.edge-B8As5DY0.js +2 -0
  5. package/dist/config.server-B8As5DY0.js +2 -0
  6. package/dist/exports/client-xcFJNcG7.d.ts +979 -0
  7. package/dist/exports/client-xcFJNcG7.d.ts.map +1 -0
  8. package/dist/exports/client.js +5735 -0
  9. package/dist/exports/client.js.map +1 -0
  10. package/dist/exports/instrumentation-DBO6QH4d.d.ts +13 -0
  11. package/dist/exports/instrumentation-DBO6QH4d.d.ts.map +1 -0
  12. package/dist/exports/instrumentation.js +25 -0
  13. package/dist/exports/instrumentation.js.map +1 -0
  14. package/dist/exports/proxy-Cv7QFVci.d.ts +71 -0
  15. package/dist/exports/proxy-Cv7QFVci.d.ts.map +1 -0
  16. package/dist/exports/proxy.js +254 -0
  17. package/dist/exports/proxy.js.map +1 -0
  18. package/dist/exports/server-BHObJHJ6.d.ts +402 -0
  19. package/dist/exports/server-BHObJHJ6.d.ts.map +1 -0
  20. package/dist/exports/server.js +1974 -0
  21. package/dist/exports/server.js.map +1 -0
  22. package/dist/http-_2M-UFRx.js +61 -0
  23. package/dist/http-_2M-UFRx.js.map +1 -0
  24. package/dist/index-CdYcvudT.d.ts +4 -0
  25. package/dist/index-CtfAJ0y7.d.ts +499 -0
  26. package/dist/index-CtfAJ0y7.d.ts.map +1 -0
  27. package/dist/index.js +201 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/route-DzFwERQP.js +65 -0
  30. package/dist/route-DzFwERQP.js.map +1 -0
  31. package/dist/store-CJzqrHsl.js +73 -0
  32. package/dist/store-CJzqrHsl.js.map +1 -0
  33. package/dist/types-BatUTdbZ.d.ts +15 -0
  34. package/dist/types-BatUTdbZ.d.ts.map +1 -0
  35. package/dist/types-D29_snYY.d.ts +191 -0
  36. package/dist/types-D29_snYY.d.ts.map +1 -0
  37. package/dist/utils-Ca7xh5mm.js +78 -0
  38. package/dist/utils-Ca7xh5mm.js.map +1 -0
  39. package/dist/utils-YwteTbk7.js +23 -0
  40. package/dist/utils-YwteTbk7.js.map +1 -0
  41. package/package.json +156 -0
  42. package/src/client/client.tsx +18 -0
  43. package/src/client/extra.tsx +21 -0
  44. package/src/client/label.tsx +24 -0
  45. package/src/client/layout.tsx +22 -0
  46. package/src/client/local.collection.ts +33 -0
  47. package/src/client/metadata.tsx +217 -0
  48. package/src/client/page.tsx +22 -0
  49. package/src/client/useApi.test.tsx +203 -0
  50. package/src/client/useApi.ts +59 -0
  51. package/src/client/useCollectionApi.test.tsx +201 -0
  52. package/src/client/useCollectionApi.ts +66 -0
  53. package/src/client/useExtra.tsx +12 -0
  54. package/src/client/useHttp.ts +54 -0
  55. package/src/client/useLabel.test.tsx +76 -0
  56. package/src/client/useLabel.tsx +12 -0
  57. package/src/client/useLayout.tsx +13 -0
  58. package/src/client/useLink.tsx +13 -0
  59. package/src/client/useMetaData.tsx +12 -0
  60. package/src/client/usePage.tsx +12 -0
  61. package/src/client/useUser.tsx +12 -0
  62. package/src/client/user.tsx +43 -0
  63. package/src/exports/client.ts +20 -0
  64. package/src/exports/instrumentation.ts +1 -0
  65. package/src/exports/proxy.ts +5 -0
  66. package/src/exports/server.ts +19 -0
  67. package/src/global.d.ts +6 -0
  68. package/src/index.ts +17 -0
  69. package/src/instrumentation/client.ts +19 -0
  70. package/src/instrumentation/sentry/capture.requestError.ts +6 -0
  71. package/src/instrumentation/sentry/capture.routerTransitionStart.ts +4 -0
  72. package/src/instrumentation/sentry/config.client.ts +9 -0
  73. package/src/instrumentation/sentry/config.edge.ts +9 -0
  74. package/src/instrumentation/sentry/config.server.ts +9 -0
  75. package/src/instrumentation/server.ts +24 -0
  76. package/src/instrumentation/types.ts +12 -0
  77. package/src/lib/breadcrumb.ts +21 -0
  78. package/src/lib/const.ts +3 -0
  79. package/src/lib/http.test.ts +25 -0
  80. package/src/lib/http.ts +74 -0
  81. package/src/lib/label.ts +43 -0
  82. package/src/lib/layout.defaults.ts +167 -0
  83. package/src/lib/link.ts +19 -0
  84. package/src/lib/meta-data.ts +60 -0
  85. package/src/lib/open-graph.ts +165 -0
  86. package/src/lib/pagination.ts +78 -0
  87. package/src/lib/payload.config.ts +12 -0
  88. package/src/lib/route.ts +83 -0
  89. package/src/lib/store.ts +63 -0
  90. package/src/lib/structured-data.ts +24 -0
  91. package/src/lib/twitter-card.ts +49 -0
  92. package/src/lib/utils.ts +45 -0
  93. package/src/mock.ts +264 -0
  94. package/src/proxy/protected-route.test.ts +75 -0
  95. package/src/proxy/protected-route.ts +57 -0
  96. package/src/proxy/proxy.test.ts +48 -0
  97. package/src/proxy/proxy.ts +64 -0
  98. package/src/proxy/route-auto-detection.ts +126 -0
  99. package/src/proxy/route-proxy.ts +187 -0
  100. package/src/server/context.ts +147 -0
  101. package/src/server/handlers/auth.handler.ts +27 -0
  102. package/src/server/handlers/captions.handler.ts +32 -0
  103. package/src/server/handlers/collection.handler.test.ts +57 -0
  104. package/src/server/handlers/collection.handler.ts +212 -0
  105. package/src/server/handlers/error-response.ts +45 -0
  106. package/src/server/handlers/index.handler.ts +146 -0
  107. package/src/server/handlers/preview.handler.ts +63 -0
  108. package/src/server/handlers/registry.handler.test.ts +65 -0
  109. package/src/server/handlers/registry.handler.ts +259 -0
  110. package/src/server/handlers/route.handler.ts +161 -0
  111. package/src/server/handlers/sitemap.handler.ts +374 -0
  112. package/src/server/handlers/static-params.handler.ts +111 -0
  113. package/src/server/handlers/vars.handler.ts +167 -0
  114. package/src/server/layout.ts +120 -0
  115. package/src/server/lazy.ts +41 -0
  116. package/src/server/local-api.ts +63 -0
  117. package/src/server/metadata.ts +87 -0
  118. package/src/server/mixer-payload.ts +124 -0
  119. package/src/server/mixer-payload.type-test.ts +27 -0
  120. package/src/server/mixer.ts +439 -0
  121. package/src/server/mock/category.ts +34 -0
  122. package/src/server/mock/collection.ts +21 -0
  123. package/src/server/mock/page.ts +62 -0
  124. package/src/server/mock/route.ts +36 -0
  125. package/src/server/mock/store.ts +159 -0
  126. package/src/server/page.ts +86 -0
  127. package/src/server/session/session.ts +101 -0
  128. package/src/server/utils.ts +23 -0
  129. package/src/server/utmz.ts +11 -0
  130. package/src/types.ts +211 -0
@@ -0,0 +1,78 @@
1
+ //#region src/lib/label.ts
2
+ function getLabelFromLabels(labels) {
3
+ const labelService = new LabelService(labels);
4
+ return (key, params) => {
5
+ return labelService.getLabel(key, params);
6
+ };
7
+ }
8
+ function parseLabel(dictionary, key, params) {
9
+ const label = dictionary[key] != null ? dictionary[key] : key;
10
+ if (params) return interpolateText(label, params);
11
+ return label;
12
+ }
13
+ function interpolateText(text, dictionary) {
14
+ return text.replace(/@(\w+)/g, (text, key) => {
15
+ const replacer = dictionary[key];
16
+ return typeof replacer !== "undefined" ? String(replacer) : text;
17
+ });
18
+ }
19
+ var LabelService = class {
20
+ labels;
21
+ dictionary;
22
+ constructor(labels) {
23
+ this.labels = labels;
24
+ this.dictionary = Object.fromEntries(labels.map((l) => [l.id, l.text]));
25
+ }
26
+ getLabel(key, params) {
27
+ return parseLabel(this.dictionary, key, params);
28
+ }
29
+ };
30
+ //#endregion
31
+ //#region src/lib/link.ts
32
+ function getLink_(category, routeLink) {
33
+ if (routeLink) {
34
+ if (routeLink.category === category) return routeLink.href;
35
+ if (routeLink.items) return routeLink.items.reduce((p, c) => {
36
+ return getLink_(category, c) || p;
37
+ }, void 0);
38
+ }
39
+ }
40
+ function getLink(category, layout) {
41
+ return getLink_(category, layout.tree);
42
+ }
43
+ //#endregion
44
+ //#region src/lib/utils.ts
45
+ function getPublicUrl() {
46
+ return process.env && process.env.NEXT_PUBLIC_URL ? process.env.NEXT_PUBLIC_URL : "";
47
+ }
48
+ function resolveHref(href = "") {
49
+ return href.startsWith("http") ? href : `${getPublicUrl()}${href}`;
50
+ }
51
+ function trimTerminalSlash(path) {
52
+ return path.replace(/\/+$/, "");
53
+ }
54
+ function sanitizeHref(url, urlBeforeRedirect) {
55
+ if (urlBeforeRedirect) {
56
+ const sanitizedUrl = new URL(urlBeforeRedirect);
57
+ sanitizedUrl.protocol = url.protocol;
58
+ return trimTerminalSlash(sanitizedUrl.href);
59
+ } else return trimTerminalSlash(url.href);
60
+ }
61
+ function sanitizeRedirectFrom(path, url, urlBeforeRedirect) {
62
+ path = trimTerminalSlash(path);
63
+ if (urlBeforeRedirect) return path.replace(/^(\^?)((https?:\/\/)+)/, `$1${url.protocol}//`);
64
+ else return path.replace(/^(\^?)((https?:\/\/)+(\w|\.|:)+)/, `$1${url.origin}`);
65
+ }
66
+ function sanitizeRedirectTo(path) {
67
+ return path ? trimTerminalSlash(path) : void 0;
68
+ }
69
+ function sanitizeRedirectUrl(path, url) {
70
+ if (path != null) {
71
+ path = trimTerminalSlash(path);
72
+ return path.match(/^((https?:\/\/)+)/) ? path : `${url.origin}${path}`;
73
+ } else return path;
74
+ }
75
+ //#endregion
76
+ export { sanitizeRedirectTo as a, getLink as c, interpolateText as d, sanitizeRedirectFrom as i, LabelService as l, resolveHref as n, sanitizeRedirectUrl as o, sanitizeHref as r, trimTerminalSlash as s, getPublicUrl as t, getLabelFromLabels as u };
77
+
78
+ //# sourceMappingURL=utils-Ca7xh5mm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils-Ca7xh5mm.js","names":[],"sources":["../src/lib/label.ts","../src/lib/link.ts","../src/lib/utils.ts"],"sourcesContent":["import type { ILabel, ILocalizable } from '@websolutespa/bom-core';\r\n\r\nexport function getLabelFromLabels(labels: ILabel[]) {\r\n const labelService = new LabelService(labels);\r\n return (key: string, params?: Record<string, any>): string => {\r\n return labelService.getLabel(key, params);\r\n };\r\n}\r\n\r\nfunction parseLabel(dictionary: Record<string, ILocalizable | undefined>, key: string, params?: Record<string, any>): string {\r\n const label = (dictionary[key] != null ? dictionary[key] : key) as string;\r\n if (params) {\r\n return interpolateText(label, params);\r\n }\r\n return label;\r\n}\r\n\r\nexport function interpolateText(text: string, dictionary: Record<string, any>): string {\r\n const TEMPLATE_REGEXP: RegExp = /@(\\w+)/g;\r\n return text.replace(TEMPLATE_REGEXP, (text: string, key: string) => {\r\n const replacer: unknown = dictionary[key];\r\n return typeof replacer !== 'undefined' ? String(replacer) : text;\r\n });\r\n}\r\n\r\nexport class LabelService {\r\n labels: ILabel[];\r\n dictionary: Record<string, ILocalizable | undefined>;\r\n constructor(labels: ILabel[]) {\r\n this.labels = labels;\r\n this.dictionary = Object.fromEntries(labels.map(l => [l.id, l.text]));\r\n }\r\n getLabel(key: string, params?: Record<string, any>): string {\r\n return parseLabel(this.dictionary, key, params);\r\n }\r\n}\r\n\r\n/*\r\nexport function resolveLabel(labels: ILabel[], id: string): string {\r\n const label = labels.find(x => x.id === id);\r\n return label && label.text ? label.text.toString() : id;\r\n}\r\n*/\r\n","import type { ILayout, IRouteLink } from '@websolutespa/bom-core';\r\n\r\nfunction getLink_(category: string, routeLink: IRouteLink | undefined): string | undefined {\r\n if (routeLink) {\r\n if (routeLink.category === category) {\r\n return routeLink.href;\r\n }\r\n if (routeLink.items) {\r\n return routeLink.items.reduce((p: string | undefined, c) => {\r\n return getLink_(category, c) || p;\r\n }, undefined);\r\n }\r\n }\r\n return undefined;\r\n}\r\n\r\nexport function getLink(category: string, layout: ILayout) {\r\n return getLink_(category, layout.tree);\r\n}\r\n","\r\nexport function getPublicUrl() {\r\n const publicUrl = process.env && process.env.NEXT_PUBLIC_URL ? process.env.NEXT_PUBLIC_URL : '';\r\n return publicUrl;\r\n}\r\n\r\nexport function resolveHref(href: string = '') {\r\n return href.startsWith('http') ? href : `${getPublicUrl()}${href}`;\r\n}\r\n\r\nexport function trimTerminalSlash(path: string) {\r\n return path.replace(/\\/+$/, '');\r\n}\r\n\r\nexport function sanitizeHref(url: URL, urlBeforeRedirect: URL | null) {\r\n if (urlBeforeRedirect) {\r\n const sanitizedUrl = new URL(urlBeforeRedirect);\r\n sanitizedUrl.protocol = url.protocol;\r\n return trimTerminalSlash(sanitizedUrl.href);\r\n } else {\r\n return trimTerminalSlash(url.href);\r\n }\r\n}\r\n\r\nexport function sanitizeRedirectFrom(path: string, url: URL, urlBeforeRedirect: URL | null) {\r\n path = trimTerminalSlash(path);\r\n if (urlBeforeRedirect) {\r\n return path.replace(/^(\\^?)((https?:\\/\\/)+)/, `$1${url.protocol}//`);\r\n } else {\r\n return path.replace(/^(\\^?)((https?:\\/\\/)+(\\w|\\.|:)+)/, `$1${url.origin}`);\r\n }\r\n}\r\n\r\nexport function sanitizeRedirectTo(path?: string) {\r\n return path ? trimTerminalSlash(path) : undefined;\r\n}\r\n\r\nexport function sanitizeRedirectUrl(path: string | undefined, url: URL) {\r\n if (path != null) {\r\n path = trimTerminalSlash(path);\r\n return path.match(/^((https?:\\/\\/)+)/) ? path : `${url.origin}${path}`;\r\n } else {\r\n return path;\r\n }\r\n}\r\n"],"mappings":";AAEA,SAAgB,mBAAmB,QAAkB;CACnD,MAAM,eAAe,IAAI,aAAa,MAAM;CAC5C,QAAQ,KAAa,WAAyC;EAC5D,OAAO,aAAa,SAAS,KAAK,MAAM;CAC1C;AACF;AAEA,SAAS,WAAW,YAAsD,KAAa,QAAsC;CAC3H,MAAM,QAAS,WAAW,QAAQ,OAAO,WAAW,OAAO;CAC3D,IAAI,QACF,OAAO,gBAAgB,OAAO,MAAM;CAEtC,OAAO;AACT;AAEA,SAAgB,gBAAgB,MAAc,YAAyC;CAErF,OAAO,KAAK,QAAQ,YAAkB,MAAc,QAAgB;EAClE,MAAM,WAAoB,WAAW;EACrC,OAAO,OAAO,aAAa,cAAc,OAAO,QAAQ,IAAI;CAC9D,CAAC;AACH;AAEA,IAAa,eAAb,MAA0B;CACxB;CACA;CACA,YAAY,QAAkB;EAC5B,KAAK,SAAS;EACd,KAAK,aAAa,OAAO,YAAY,OAAO,KAAI,MAAK,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;CACtE;CACA,SAAS,KAAa,QAAsC;EAC1D,OAAO,WAAW,KAAK,YAAY,KAAK,MAAM;CAChD;AACF;;;ACjCA,SAAS,SAAS,UAAkB,WAAuD;CACzF,IAAI,WAAW;EACb,IAAI,UAAU,aAAa,UACzB,OAAO,UAAU;EAEnB,IAAI,UAAU,OACZ,OAAO,UAAU,MAAM,QAAQ,GAAuB,MAAM;GAC1D,OAAO,SAAS,UAAU,CAAC,KAAK;EAClC,GAAG,KAAA,CAAS;CAEhB;AAEF;AAEA,SAAgB,QAAQ,UAAkB,QAAiB;CACzD,OAAO,SAAS,UAAU,OAAO,IAAI;AACvC;;;ACjBA,SAAgB,eAAe;CAE7B,OADkB,QAAQ,OAAO,QAAQ,IAAI,kBAAkB,QAAQ,IAAI,kBAAkB;AAE/F;AAEA,SAAgB,YAAY,OAAe,IAAI;CAC7C,OAAO,KAAK,WAAW,MAAM,IAAI,OAAO,GAAG,aAAa,IAAI;AAC9D;AAEA,SAAgB,kBAAkB,MAAc;CAC9C,OAAO,KAAK,QAAQ,QAAQ,EAAE;AAChC;AAEA,SAAgB,aAAa,KAAU,mBAA+B;CACpE,IAAI,mBAAmB;EACrB,MAAM,eAAe,IAAI,IAAI,iBAAiB;EAC9C,aAAa,WAAW,IAAI;EAC5B,OAAO,kBAAkB,aAAa,IAAI;CAC5C,OACE,OAAO,kBAAkB,IAAI,IAAI;AAErC;AAEA,SAAgB,qBAAqB,MAAc,KAAU,mBAA+B;CAC1F,OAAO,kBAAkB,IAAI;CAC7B,IAAI,mBACF,OAAO,KAAK,QAAQ,0BAA0B,KAAK,IAAI,SAAS,GAAG;MAEnE,OAAO,KAAK,QAAQ,oCAAoC,KAAK,IAAI,QAAQ;AAE7E;AAEA,SAAgB,mBAAmB,MAAe;CAChD,OAAO,OAAO,kBAAkB,IAAI,IAAI,KAAA;AAC1C;AAEA,SAAgB,oBAAoB,MAA0B,KAAU;CACtE,IAAI,QAAQ,MAAM;EAChB,OAAO,kBAAkB,IAAI;EAC7B,OAAO,KAAK,MAAM,mBAAmB,IAAI,OAAO,GAAG,IAAI,SAAS;CAClE,OACE,OAAO;AAEX"}
@@ -0,0 +1,23 @@
1
+ import { r as resolveRoute } from "./route-DzFwERQP.js";
2
+ import { getHost } from "@websolutespa/bom-core";
3
+ //#region src/server/utils.ts
4
+ function getNextUrl(request, route) {
5
+ const nextUrl = request.nextUrl.clone();
6
+ nextUrl.pathname = resolveRoute(route);
7
+ return nextUrl;
8
+ }
9
+ function getHrefBeforeRedirect(request) {
10
+ let hrefBeforeRedirect = null;
11
+ const urlBeforeRedirect = request.nextUrl.clone();
12
+ const host = getHost(request.headers);
13
+ if (host) {
14
+ urlBeforeRedirect.host = host;
15
+ urlBeforeRedirect.port = host.split(":")[1] || "";
16
+ hrefBeforeRedirect = urlBeforeRedirect.href;
17
+ }
18
+ return hrefBeforeRedirect;
19
+ }
20
+ //#endregion
21
+ export { getNextUrl as n, getHrefBeforeRedirect as t };
22
+
23
+ //# sourceMappingURL=utils-YwteTbk7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils-YwteTbk7.js","names":[],"sources":["../src/server/utils.ts"],"sourcesContent":["import { getHost, IRoute } from '@websolutespa/bom-core';\r\nimport { NextRequest } from 'next/server';\r\nimport { resolveRoute } from '../lib/route';\r\n\r\nexport function getNextUrl(request: NextRequest, route: IRoute) {\r\n const nextUrl = request.nextUrl.clone();\r\n const resolvedPathname = resolveRoute(route);\r\n // console.log('getNextUrl.resolvedPathname', resolvedPathname);\r\n nextUrl.pathname = resolvedPathname;\r\n return nextUrl;\r\n}\r\n\r\nexport function getHrefBeforeRedirect(request: NextRequest): string | null {\r\n let hrefBeforeRedirect: string | null = null;\r\n const urlBeforeRedirect = request.nextUrl.clone();\r\n const host = getHost(request.headers);\r\n if (host) {\r\n urlBeforeRedirect.host = host;\r\n urlBeforeRedirect.port = host.split(':')[1] || '';\r\n hrefBeforeRedirect = urlBeforeRedirect.href;\r\n }\r\n return hrefBeforeRedirect;\r\n}\r\n"],"mappings":";;;AAIA,SAAgB,WAAW,SAAsB,OAAe;CAC9D,MAAM,UAAU,QAAQ,QAAQ,MAAM;CAGtC,QAAQ,WAFiB,aAAa,KAEJ;CAClC,OAAO;AACT;AAEA,SAAgB,sBAAsB,SAAqC;CACzE,IAAI,qBAAoC;CACxC,MAAM,oBAAoB,QAAQ,QAAQ,MAAM;CAChD,MAAM,OAAO,QAAQ,QAAQ,OAAO;CACpC,IAAI,MAAM;EACR,kBAAkB,OAAO;EACzB,kBAAkB,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM;EAC/C,qBAAqB,kBAAkB;CACzC;CACA,OAAO;AACT"}
package/package.json ADDED
@@ -0,0 +1,156 @@
1
+ {
2
+ "name": "@websolutespa/bom-next",
3
+ "version": "3.0.0",
4
+ "description": "Next module of the BOM Repository",
5
+ "keywords": [
6
+ "bom",
7
+ "next"
8
+ ],
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git://github.com/websolutespa/bom.git"
13
+ },
14
+ "scripts": {
15
+ "lint": "eslint src/**/*.ts*",
16
+ "test": "vitest run",
17
+ "test-watch": "vitest",
18
+ "coverage:integration": "npm run test -- --coverage.enabled --coverage.provider=istanbul --coverage.all",
19
+ "coverage": "run-s -c --silent coverage:*",
20
+ "compile": "tsdown",
21
+ "prepack": "bom prepack",
22
+ "postpack": "bom postpack",
23
+ "types": "tsc --noemit"
24
+ },
25
+ "dependencies": {
26
+ "transliteration": "latest"
27
+ },
28
+ "peerDependencies": {
29
+ "@sentry/nextjs": "10.75.0",
30
+ "@websolutespa/bom-core": "^3.0.1",
31
+ "@websolutespa/payload-db-memory": "^3.0.2",
32
+ "@websolutespa/payload-plugin-bowl": "^3.0.17",
33
+ "@websolutespa/payload-utils": "^3.0.2",
34
+ "next": "16.1.6",
35
+ "payload": "3.74.0",
36
+ "react-dom": "19.2.3",
37
+ "react": "19.2.3"
38
+ },
39
+ "devDependencies": {
40
+ "@sentry/nextjs": "10.75.0",
41
+ "@types/react-dom": "19.2.3",
42
+ "@types/react": "19.2.10",
43
+ "@websolutespa/bom-cli": "^3.0.1",
44
+ "@websolutespa/bom-core": "^3.0.1",
45
+ "@websolutespa/eslint-config": "^3.0.0",
46
+ "@websolutespa/payload-db-memory": "^3.0.2",
47
+ "@websolutespa/payload-plugin-bowl": "^3.0.17",
48
+ "@websolutespa/payload-utils": "^3.0.2",
49
+ "@websolutespa/test": "^3.0.2",
50
+ "@websolutespa/tsconfig": "^3.0.0",
51
+ "eslint": "9.31.0",
52
+ "npm-run-all": "4.1.5",
53
+ "react-dom": "19.2.3",
54
+ "react-error-boundary": "4.1.2",
55
+ "react": "19.2.3",
56
+ "typescript": "5.8.3"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ },
61
+ "sideEffects": false,
62
+ "type": "module",
63
+ "prepack": {
64
+ "types": "./dist/index.d.ts",
65
+ "main": "./dist/index.js",
66
+ "source": "./src/index.ts",
67
+ "exports": {
68
+ ".": {
69
+ "import": "./dist/index.js",
70
+ "types": "./dist/index.d.ts",
71
+ "default": "./dist/index.js"
72
+ },
73
+ "./client": {
74
+ "import": "./dist/exports/client.js",
75
+ "types": "./dist/exports/client.d.ts",
76
+ "default": "./dist/exports/client.js"
77
+ },
78
+ "./instrumentation": {
79
+ "import": "./dist/exports/instrumentation.js",
80
+ "types": "./dist/exports/instrumentation.d.ts",
81
+ "default": "./dist/exports/instrumentation.js"
82
+ },
83
+ "./proxy": {
84
+ "import": "./dist/exports/proxy.js",
85
+ "types": "./dist/exports/proxy.d.ts",
86
+ "default": "./dist/exports/proxy.js"
87
+ },
88
+ "./server": {
89
+ "import": "./dist/exports/server.js",
90
+ "types": "./dist/exports/server.d.ts",
91
+ "default": "./dist/exports/server.js"
92
+ }
93
+ }
94
+ },
95
+ "postpack": {
96
+ "types": "./src/index.ts",
97
+ "main": "./src/index.ts",
98
+ "exports": {
99
+ ".": {
100
+ "import": "./src/index.ts",
101
+ "types": "./src/index.ts",
102
+ "default": "./src/index.ts"
103
+ },
104
+ "./client": {
105
+ "import": "./src/exports/client.ts",
106
+ "types": "./src/exports/client.ts",
107
+ "default": "./src/exports/client.ts"
108
+ },
109
+ "./instrumentation": {
110
+ "import": "./src/exports/instrumentation.ts",
111
+ "types": "./src/exports/instrumentation.ts",
112
+ "default": "./src/exports/instrumentation.ts"
113
+ },
114
+ "./proxy": {
115
+ "import": "./src/exports/proxy.ts",
116
+ "types": "./src/exports/proxy.ts",
117
+ "default": "./src/exports/proxy.ts"
118
+ },
119
+ "./server": {
120
+ "import": "./src/exports/server.ts",
121
+ "types": "./src/exports/server.ts",
122
+ "default": "./src/exports/server.ts"
123
+ }
124
+ }
125
+ },
126
+ "types": "./dist/index.d.ts",
127
+ "main": "./dist/index.js",
128
+ "source": "./src/index.ts",
129
+ "exports": {
130
+ ".": {
131
+ "import": "./dist/index.js",
132
+ "types": "./dist/index.d.ts",
133
+ "default": "./dist/index.js"
134
+ },
135
+ "./client": {
136
+ "import": "./dist/exports/client.js",
137
+ "types": "./dist/exports/client.d.ts",
138
+ "default": "./dist/exports/client.js"
139
+ },
140
+ "./instrumentation": {
141
+ "import": "./dist/exports/instrumentation.js",
142
+ "types": "./dist/exports/instrumentation.d.ts",
143
+ "default": "./dist/exports/instrumentation.js"
144
+ },
145
+ "./proxy": {
146
+ "import": "./dist/exports/proxy.js",
147
+ "types": "./dist/exports/proxy.d.ts",
148
+ "default": "./dist/exports/proxy.js"
149
+ },
150
+ "./server": {
151
+ "import": "./dist/exports/server.js",
152
+ "types": "./dist/exports/server.d.ts",
153
+ "default": "./dist/exports/server.js"
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+
3
+ import { getClassNames } from '@websolutespa/bom-core';
4
+ import { useLabel } from './useLabel';
5
+ import { usePage } from './usePage';
6
+
7
+ export function Client() {
8
+ const label = useLabel();
9
+ const page = usePage();
10
+ // console.log('Client', layout, page, label('label'));
11
+ const classNames = getClassNames('client');
12
+ return (
13
+ <div className={classNames}>
14
+ <h2 className="display-2">{page.title} <sup>{page.id}</sup></h2>
15
+ <h2>{label('label')}</h2>
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import { createContext } from 'react';
4
+
5
+ export type IExtraContext = Record<string, any>;
6
+
7
+ export const ExtraContext = createContext<IExtraContext | undefined>(undefined);
8
+
9
+ export function ExtraProvider({
10
+ extra = {},
11
+ children,
12
+ }: {
13
+ extra?: Record<string, any>,
14
+ children?: React.ReactNode
15
+ }) {
16
+ return (
17
+ <ExtraContext.Provider value={extra}>
18
+ {children}
19
+ </ExtraContext.Provider>
20
+ );
21
+ }
@@ -0,0 +1,24 @@
1
+ 'use client';
2
+
3
+ import { createContext } from 'react';
4
+ import { LabelService } from '../lib/label';
5
+ import { useLayout } from './useLayout';
6
+
7
+ export type ILabelContext = (key: string, params?: Record<string, any>) => string;
8
+
9
+ export const LabelContext = createContext<ILabelContext | undefined>(undefined);
10
+
11
+ export function LabelProvider({ children }: { children?: React.ReactNode }) {
12
+ const { labels } = useLayout();
13
+ // console.log('LabelProvider', labels);
14
+ const labelService = new LabelService(labels);
15
+ const getLabel = (key: string, params?: Record<string, any>): string => {
16
+ return labelService.getLabel(key, params);
17
+ };
18
+ return (
19
+ <LabelContext.Provider value={getLabel}>
20
+ {children}
21
+ </LabelContext.Provider>
22
+ );
23
+ }
24
+
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import { ILayout } from '@websolutespa/bom-core';
4
+ import { createContext } from 'react';
5
+
6
+ export type ILayoutContext = ILayout;
7
+
8
+ export const LayoutContext = createContext<ILayoutContext | undefined>(undefined);
9
+
10
+ export function LayoutProvider({
11
+ layout,
12
+ children,
13
+ }: {
14
+ layout: ILayout,
15
+ children?: React.ReactNode
16
+ }) {
17
+ return (
18
+ <LayoutContext.Provider value={layout}>
19
+ {children}
20
+ </LayoutContext.Provider>
21
+ );
22
+ }
@@ -0,0 +1,33 @@
1
+ import { httpFetch, merge } from '@websolutespa/bom-core';
2
+ import { ApiRequestOptions, getLocalApiRequestOptions, getLocalApiUrl } from '../lib/http';
3
+
4
+ export async function localCollectionFetch<T = unknown>(pathname: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {
5
+ const url = new URL(getLocalApiUrl(pathname));
6
+ if (serviceOptions) {
7
+ url.searchParams.append('locale', serviceOptions.locale);
8
+ url.searchParams.append('market', serviceOptions.market);
9
+ }
10
+ const apiOptions = merge({ ...getLocalApiRequestOptions() }, options);
11
+ const apiResponse = await httpFetch(url.href, apiOptions);
12
+ return apiResponse;
13
+ }
14
+
15
+ export async function localCollectionGet<T = unknown>(url: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {
16
+ return await localCollectionFetch<T>(url, { ...options, method: 'GET' }, serviceOptions);
17
+ }
18
+
19
+ export async function localCollectionPost<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {
20
+ return await localCollectionFetch<T>(url, { ...options, method: 'POST', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);
21
+ }
22
+
23
+ export async function localCollectionPut<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {
24
+ return await localCollectionFetch<T>(url, { ...options, method: 'PUT', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);
25
+ }
26
+
27
+ export async function localCollectionPatch<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {
28
+ return await localCollectionFetch<T>(url, { ...options, method: 'PATCH', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);
29
+ }
30
+
31
+ export async function localCollectionDelete<T = unknown>(url: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {
32
+ return await localCollectionFetch<T>(url, { ...options, method: 'DELETE' }, serviceOptions);
33
+ }
@@ -0,0 +1,217 @@
1
+ 'use client';
2
+
3
+ import { defaultLocale, defaultMarket, IMedia, IRoute } from '@websolutespa/bom-core';
4
+ import { convert as htmlToText } from 'html-to-text';
5
+ import { useRouter } from 'next/compat/router';
6
+ import { createContext, ReactNode, useMemo } from 'react';
7
+ import { ContactPoint, Organization, PostalAddress } from 'schema-dts';
8
+ import { IAlternate, IBrand, IMeta, IMetaData } from '../lib/meta-data';
9
+ import { OpenGraphType } from '../lib/open-graph';
10
+ import { StructuredDataType } from '../lib/structured-data';
11
+ import { TwitterCardType } from '../lib/twitter-card';
12
+ import { getPublicUrl, resolveHref } from '../lib/utils';
13
+ import { useLayout } from './useLayout';
14
+ import { usePage } from './usePage';
15
+
16
+ export type IMetaDataOptions = {
17
+ brand: {
18
+ name: string;
19
+ logo: string;
20
+ businessName?: string;
21
+ appName?: string;
22
+ url?: string;
23
+ email?: string;
24
+ telephone?: string;
25
+ zipCode?: string;
26
+ address?: string;
27
+ city?: string;
28
+ provinceCode?: string;
29
+ countryName?: string;
30
+ countryCode?: string;
31
+ twitterName?: string;
32
+ social?: Record<string, string>;
33
+ },
34
+ structuredData?: StructuredDataType;
35
+ openGraph?: OpenGraphType;
36
+ twitterCard?: TwitterCardType;
37
+ };
38
+
39
+ export type IMetaDataContext = IMetaData;
40
+
41
+ export const MetaDataContext = createContext<IMetaDataContext | undefined>(undefined);
42
+
43
+ export function MetaDataProvider({
44
+ metaData,
45
+ children,
46
+ }: {
47
+ metaData: IMetaDataOptions,
48
+ children?: ReactNode,
49
+ }) {
50
+ const router = useRouter();
51
+ const layout = useLayout();
52
+ const page = usePage();
53
+ const brand: IBrand = {
54
+ ...metaData.brand,
55
+ logo: resolveHref(metaData.brand.logo),
56
+ url: getPublicUrl(), // layout.topLevelHrefs.homepage,
57
+ businessName: metaData.brand.businessName || metaData.brand.name,
58
+ appName: metaData.brand.appName || metaData.brand.name,
59
+ };
60
+ const title = htmlToText(page.meta?.title || page.title || '');
61
+ const description = htmlToText(page.meta?.description || page.abstract || '');
62
+ const media = ((page.meta as { image: IMedia } | undefined)?.image || page.media);
63
+ const image = media ? resolveHref(media.src || media.url) : brand.logo;
64
+ let canonical = resolveHref(page.href);
65
+ const asPath = router?.asPath || '';
66
+ const queryIndex = asPath.indexOf('?');
67
+ const query = queryIndex !== -1 ? asPath.substring(queryIndex) : '';
68
+ if (query.match(/^\?pagination\[page\]=(\d+)$/) && query !== '?pagination[page]=1') {
69
+ canonical = `${canonical}${query}`;
70
+ // title = `${title} - ${query.replace('?pagination[page]=','')}`;
71
+ }
72
+ const url = `${canonical}${query}`;
73
+ const alternates = useMemo(() => {
74
+ const alternates: IAlternate[] = [];
75
+ (page.alternates || []).forEach((alternate: IRoute) => {
76
+ const market = layout.markets.find(x => x.id === alternate.market);
77
+ if (market) {
78
+ if (market.countries && market.countries.length > 0) {
79
+ market.countries.forEach((country: any) => {
80
+ // country: string | { alpha2Code: string };
81
+ const alpha2Code = typeof country === 'string' ? country : country.alpha2Code;
82
+ alternates.push({
83
+ id: `${alternate.market}-${alternate.locale}-${alpha2Code}`,
84
+ href: resolveHref(alternate.id),
85
+ hrefLang: `${alternate.locale}-${alpha2Code}`,
86
+ });
87
+ });
88
+ } else {
89
+ alternates.push({
90
+ id: `${alternate.market}-${alternate.locale}`,
91
+ href: resolveHref(alternate.id),
92
+ hrefLang: alternate.locale,
93
+ });
94
+ }
95
+ }
96
+ });
97
+ return alternates;
98
+ }, [layout, page]);
99
+ const xDefault = useMemo(() => {
100
+ const xDefaultMarket = layout.markets.find(x => x.isDefault)?.id || defaultMarket;
101
+ const xDefaultLocale = layout.locales.find(x => x.isDefault)?.id || defaultLocale;
102
+ const alternate = page.alternates.find(x => x.market === xDefaultMarket && x.locale === xDefaultLocale);
103
+ if (alternate) {
104
+ return {
105
+ id: `${alternate.market}-${alternate.locale}`,
106
+ href: resolveHref(alternate.id),
107
+ hrefLang: alternate.locale,
108
+ };
109
+ }
110
+ return undefined;
111
+ }, [layout, page]);
112
+ const meta: IMeta = {
113
+ title: title,
114
+ description: description,
115
+ image: image,
116
+ keywords: page.meta?.keywords,
117
+ robots: page.meta?.robots,
118
+ slug: page.href,
119
+ url,
120
+ canonical,
121
+ alternates,
122
+ xDefault,
123
+ };
124
+ const contactPoint: ContactPoint = {
125
+ '@type': 'ContactPoint',
126
+ telephone: brand.telephone,
127
+ email: brand.email,
128
+ contactType: 'customer service',
129
+ areaServed: layout.locales.map(x => x.id),
130
+ availableLanguage: layout.locales.map(x => x.id),
131
+ };
132
+ const postalAddress: PostalAddress = {
133
+ '@type': 'PostalAddress',
134
+ streetAddress: brand.address,
135
+ addressLocality: brand.city,
136
+ postalCode: brand.zipCode,
137
+ addressRegion: brand.provinceCode,
138
+ addressCountry: brand.countryCode,
139
+ };
140
+ const organization: Organization = {
141
+ '@type': 'Organization',
142
+ name: brand.businessName,
143
+ logo: {
144
+ '@type': 'ImageObject',
145
+ url: brand.logo,
146
+ },
147
+ url: brand.url,
148
+ /*
149
+ description,
150
+ founder,
151
+ foundingDate,
152
+ numberOfEmployees,
153
+ */
154
+ /*
155
+ foundingDate: '2020-01-01',
156
+ numberOfEmployees: {
157
+ '@type': 'QuantitativeValue',
158
+ value: 150,
159
+ },
160
+ */
161
+ };
162
+ const hasContact = Boolean(brand.email || brand.telephone);
163
+ if (hasContact) {
164
+ organization.contactPoint = contactPoint;
165
+ }
166
+ const hasAddress = Boolean(brand.address || brand.city || brand.zipCode || brand.provinceCode || brand.countryCode);
167
+ if (hasAddress) {
168
+ organization.address = postalAddress;
169
+ }
170
+ if (brand.social) {
171
+ organization.sameAs = Object.values(brand.social);
172
+ }
173
+ const structuredData: StructuredDataType = metaData.structuredData || {
174
+ '@type': 'WebPage',
175
+ name: title,
176
+ url: url,
177
+ description: description,
178
+ publisher: organization,
179
+ };
180
+ const openGraph: OpenGraphType = metaData.openGraph || {
181
+ type: 'website',
182
+ title: title,
183
+ description: description,
184
+ url: url,
185
+ siteName: brand.name,
186
+ image: image,
187
+ locale: layout.locale,
188
+ };
189
+ const twitterCard: TwitterCardType | undefined = metaData.twitterCard || (
190
+ brand.twitterName ? {
191
+ card: 'summary_large_image',
192
+ title: title,
193
+ description: description,
194
+ image: image,
195
+ imageAlt: brand.businessName,
196
+ site: brand.twitterName,
197
+ creator: brand.twitterName,
198
+ } : undefined
199
+ );
200
+ const value: IMetaData = {
201
+ layout,
202
+ page,
203
+ brand,
204
+ contactPoint,
205
+ meta,
206
+ openGraph,
207
+ organization,
208
+ postalAddress,
209
+ structuredData,
210
+ twitterCard,
211
+ };
212
+ return (
213
+ <MetaDataContext.Provider value={value}>
214
+ {children}
215
+ </MetaDataContext.Provider>
216
+ );
217
+ }
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import { ILocalized, IPage } from '@websolutespa/bom-core';
4
+ import { createContext } from 'react';
5
+
6
+ export type IPageContext = ILocalized<IPage>;
7
+
8
+ export const PageContext = createContext<IPageContext | undefined>(undefined);
9
+
10
+ export function PageProvider({
11
+ page,
12
+ children,
13
+ }: {
14
+ page: ILocalized<IPage>,
15
+ children?: React.ReactNode
16
+ }) {
17
+ return (
18
+ <PageContext.Provider value={page}>
19
+ {children}
20
+ </PageContext.Provider>
21
+ );
22
+ }