@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,13 @@
1
+ 'use client';
2
+
3
+ import { IAppExtended, ILayout } from '@websolutespa/bom-core';
4
+ import { useContext } from 'react';
5
+ import { LayoutContext } from './layout';
6
+
7
+ export const useLayout = <T extends IAppExtended = IAppExtended>() => {
8
+ const context = useContext(LayoutContext);
9
+ if (!context) {
10
+ throw new Error('useLayout must be used within LayoutProvider');
11
+ }
12
+ return context as ILayout<T>;
13
+ };
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+
3
+ import { useCallback } from 'react';
4
+ import { getLink } from '../lib/link';
5
+ import { useLayout } from './useLayout';
6
+
7
+ export const useLink = () => {
8
+ const layout = useLayout();
9
+ const link = useCallback((category: string) => {
10
+ return getLink(category, layout);
11
+ }, [layout]);
12
+ return link;
13
+ };
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+
3
+ import { useContext } from 'react';
4
+ import { MetaDataContext } from './metadata';
5
+
6
+ export const useMetaData = () => {
7
+ const context = useContext(MetaDataContext);
8
+ if (!context) {
9
+ throw new Error('useMetaData must be used within MetaDataProvider');
10
+ }
11
+ return context;
12
+ };
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+
3
+ import { useContext } from 'react';
4
+ import { PageContext } from './page';
5
+
6
+ export const usePage = () => {
7
+ const context = useContext(PageContext);
8
+ if (!context) {
9
+ throw new Error('usePage must be used within PageProvider');
10
+ }
11
+ return context;
12
+ };
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+
3
+ import { useContext } from 'react';
4
+ import { UserContext } from './user';
5
+
6
+ export const useUser = () => {
7
+ const context = useContext(UserContext);
8
+ if (!context) {
9
+ throw new Error('useUser must be used within UserProvider');
10
+ }
11
+ return context;
12
+ };
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+ import { createContext, ReactNode, useEffect, useState } from 'react';
4
+ import { localApiGet } from '../lib/http';
5
+ import { IEndUser, IUser } from '../types';
6
+
7
+ export type UserContextType = {
8
+ user?: IEndUser | IUser | null;
9
+ setUser: (user?: IEndUser | IUser | null) => void;
10
+ };
11
+
12
+ export const UserContext = createContext<UserContextType>({
13
+ setUser: () => {
14
+ console.log('not implemented');
15
+ },
16
+ });
17
+
18
+ export function UserProvider({
19
+ children,
20
+ }: {
21
+ children: ReactNode;
22
+ }) {
23
+ const [user, setUser] = useState<IEndUser | IUser | null | undefined>();
24
+ useEffect(() => {
25
+ const getMe = async () => {
26
+ try {
27
+ const user: IEndUser | null = await localApiGet('/auth/me');
28
+ return user;
29
+ } catch (error) {
30
+ console.log('UserProvider.getMe.error', error);
31
+ return null;
32
+ }
33
+ };
34
+ getMe().then(user => {
35
+ setUser(user);
36
+ });
37
+ }, []);
38
+ return (
39
+ <UserContext.Provider value={{ user, setUser }}>
40
+ {children}
41
+ </UserContext.Provider>
42
+ );
43
+ }
@@ -0,0 +1,20 @@
1
+ export * from '../client/client';
2
+ export * from '../client/extra';
3
+ export * from '../client/label';
4
+ export * from '../client/layout';
5
+ export * from '../client/local.collection';
6
+ export * from '../client/metadata';
7
+ export * from '../client/page';
8
+ export * from '../client/useApi';
9
+ export * from '../client/useCollectionApi';
10
+ export * from '../client/useExtra';
11
+ export * from '../client/useHttp';
12
+ export * from '../client/useLabel';
13
+ export * from '../client/useLayout';
14
+ export * from '../client/useLink';
15
+ export * from '../client/useMetaData';
16
+ export * from '../client/usePage';
17
+ export * from '../client/user';
18
+ export * from '../client/useUser';
19
+ export * from '../instrumentation/client';
20
+
@@ -0,0 +1 @@
1
+ export * from '../instrumentation/server';
@@ -0,0 +1,5 @@
1
+ export * from '../proxy/protected-route';
2
+ export * from '../proxy/proxy';
3
+ export * from '../proxy/route-auto-detection';
4
+ export * from '../proxy/route-proxy';
5
+
@@ -0,0 +1,19 @@
1
+ export * from '../server/context';
2
+ export * from '../server/handlers/auth.handler';
3
+ export * from '../server/handlers/captions.handler';
4
+ export * from '../server/handlers/collection.handler';
5
+ export * from '../server/handlers/index.handler';
6
+ export * from '../server/handlers/preview.handler';
7
+ export * from '../server/handlers/registry.handler';
8
+ export * from '../server/handlers/route.handler';
9
+ export * from '../server/handlers/sitemap.handler';
10
+ export * from '../server/handlers/static-params.handler';
11
+ export * from '../server/handlers/vars.handler';
12
+ export * from '../server/lazy';
13
+ export * from '../server/local-api';
14
+ export * from '../server/metadata';
15
+ export * from '../server/mixer';
16
+ export * from '../server/page';
17
+ export * from '../server/utils';
18
+ export * from '../server/utmz';
19
+ // export * from '../server/mock/store';
@@ -0,0 +1,6 @@
1
+ import { Config } from '@websolutespa/payload-plugin-bowl';
2
+
3
+ declare module 'payload' {
4
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
5
+ export interface GeneratedTypes extends Config { }
6
+ }
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ export * from './lib/breadcrumb';
2
+ export * from './lib/const';
3
+ export * from './lib/http';
4
+ export * from './lib/label';
5
+ export * from './lib/layout.defaults';
6
+ export * from './lib/link';
7
+ export * from './lib/meta-data';
8
+ export * from './lib/open-graph';
9
+ export * from './lib/pagination';
10
+ export * from './lib/route';
11
+ export * from './lib/store';
12
+ export * from './lib/structured-data';
13
+ export * from './lib/twitter-card';
14
+ export * from './lib/utils';
15
+ export * from './mock';
16
+ export * from './types';
17
+
@@ -0,0 +1,19 @@
1
+ import { onRouterTransitionStart as sentryOnRouterTransitionStart } from './sentry/capture.routerTransitionStart';
2
+
3
+ export function registerClientIntrumentation() {
4
+ if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
5
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
6
+ import('./sentry/config.client');
7
+ }
8
+ }
9
+ }
10
+
11
+ // This export will instrument router navigations
12
+ export function captureRouterTransitionStart(href: string, navigationType: string): void {
13
+ if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
14
+ return sentryOnRouterTransitionStart(href, navigationType);
15
+ } else {
16
+ console.log('Instrumentation.onRouterTransitionStart', href, navigationType);
17
+ }
18
+ }
19
+
@@ -0,0 +1,6 @@
1
+ import * as Sentry from '@sentry/nextjs';
2
+
3
+ /**
4
+ * Reports errors passed to the the Next.js `onRequestError` instrumentation hook.
5
+ */
6
+ export const captureRequestError = Sentry.captureRequestError;
@@ -0,0 +1,4 @@
1
+ import * as Sentry from '@sentry/nextjs';
2
+
3
+ // This export will instrument router navigations
4
+ export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
@@ -0,0 +1,9 @@
1
+ import * as Sentry from '@sentry/nextjs';
2
+
3
+ Sentry.init({
4
+ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
5
+
6
+ // Capture 100% in dev, 10% in production
7
+ // Adjust based on your traffic volume
8
+ tracesSampleRate: process.env.NODE_ENV === 'development' ? 1.0 : 0.1,
9
+ });
@@ -0,0 +1,9 @@
1
+ import * as Sentry from '@sentry/nextjs';
2
+
3
+ Sentry.init({
4
+ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
5
+
6
+ // Capture 100% in dev, 10% in production
7
+ // Adjust based on your traffic volume
8
+ tracesSampleRate: process.env.NODE_ENV === 'development' ? 1.0 : 0.1,
9
+ });
@@ -0,0 +1,9 @@
1
+ import * as Sentry from '@sentry/nextjs';
2
+
3
+ Sentry.init({
4
+ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
5
+
6
+ // Capture 100% in dev, 10% in production
7
+ // Adjust based on your traffic volume
8
+ tracesSampleRate: process.env.NODE_ENV === 'development' ? 1.0 : 0.1,
9
+ });
@@ -0,0 +1,24 @@
1
+ import { captureRequestError as sentryCaptureRequestError } from './sentry/capture.requestError';
2
+ import { ErrorContext, RequestInfo } from './types';
3
+
4
+ export async function registerIntrumentation() {
5
+ if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
6
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
7
+ await import('./sentry/config.server');
8
+ }
9
+ if (process.env.NEXT_RUNTIME === 'edge') {
10
+ await import('./sentry/config.edge');
11
+ }
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Reports errors passed to the the Next.js `onRequestError` instrumentation hook.
17
+ */
18
+ export function captureRequestError(error: unknown, request: RequestInfo, errorContext: ErrorContext): void {
19
+ if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
20
+ return sentryCaptureRequestError(error, request, errorContext);
21
+ } else {
22
+ console.log('Instrumentation.captureRequestError', error, errorContext);
23
+ }
24
+ }
@@ -0,0 +1,12 @@
1
+
2
+ export type RequestInfo = {
3
+ path: string;
4
+ method: string;
5
+ headers: Record<string, string | string[] | undefined>;
6
+ };
7
+
8
+ export type ErrorContext = {
9
+ routerKind: string;
10
+ routePath: string;
11
+ routeType: string;
12
+ };
@@ -0,0 +1,21 @@
1
+ import { findRouteOfCategory, ICategory, IRoute, IRouteLink } from '@websolutespa/bom-core';
2
+ import { routeToLink } from './route';
3
+
4
+ /**
5
+ * Builds the breadcrumb trail from category segments + the current route.
6
+ * Uses currentRoutes (already filtered by market+locale) to resolve each segment's href.
7
+ * Replicates the logic of getBreadcrumbFromSegments without any store calls.
8
+ */
9
+ export function buildBreadcrumb(segments: ICategory[], currentRoute: IRoute, currentRoutes: IRoute[]): IRouteLink[] {
10
+ const tree: IRouteLink[] = segments
11
+ .map(segment => {
12
+ const route = findRouteOfCategory(currentRoutes, segment.id);
13
+ return route ? routeToLink(route) : null;
14
+ })
15
+ .filter((x): x is IRouteLink => x !== null);
16
+ // Append the leaf page only when it is not the default/index route of its category
17
+ if (!currentRoute.isDefault) {
18
+ tree.push(routeToLink(currentRoute));
19
+ }
20
+ return tree;
21
+ }
@@ -0,0 +1,3 @@
1
+ import type { Config, User } from '@websolutespa/payload-plugin-bowl';
2
+
3
+ export const guest = { collection: 'users', roles: ['guest'] } as (User & Config['user']);
@@ -0,0 +1,25 @@
1
+ // @vitest-environment node
2
+
3
+ import { afterEach, describe, expect, test, vi } from 'vitest';
4
+
5
+ const originalEnvironment = process.env;
6
+
7
+ afterEach(() => {
8
+ process.env = originalEnvironment;
9
+ vi.resetModules();
10
+ });
11
+
12
+ describe('getLocalApiUrl', () => {
13
+ test('uses the supplied request origin instead of the build-time URL', async () => {
14
+ process.env = {
15
+ ...originalEnvironment,
16
+ NEXT_PUBLIC_API: '/api',
17
+ NEXT_PUBLIC_URL: 'https://preview.example.vercel.app',
18
+ };
19
+ const { getLocalApiUrl } = await import('./http');
20
+
21
+ expect(getLocalApiUrl('/-route', 'https://www.example.com')).toBe(
22
+ 'https://www.example.com/api/-route'
23
+ );
24
+ });
25
+ });
@@ -0,0 +1,74 @@
1
+ import { FetchRequestOptions, httpFetch, isBrowser, merge } from '@websolutespa/bom-core';
2
+
3
+ const API_URL = process.env.NEXT_PUBLIC_URL || 'http://localhost:3000';
4
+ const API_BASE = process.env.NEXT_PUBLIC_API || '/api';
5
+
6
+ export type ApiRequestOptions = FetchRequestOptions;
7
+
8
+ export function getLocalApiRequestOptions(): ApiRequestOptions {
9
+ return {
10
+ headers: {
11
+ },
12
+ };
13
+ }
14
+
15
+ export function getRemoteApiRequestOptions(token?: string): ApiRequestOptions {
16
+ if (token) {
17
+ // authenticated end user JWT token bearer (end_users)
18
+ return {
19
+ // mode: 'cors',
20
+ headers: {
21
+ Authorization: `JWT ${token}`,
22
+ // Authorization: `Bearer ${token}`,
23
+ },
24
+ };
25
+ } else {
26
+ // guest user api key (users)
27
+ return {
28
+ // mode: 'cors',
29
+ headers: {
30
+ Authorization: `users API-Key ${process.env.STORE_API_KEY}`,
31
+ },
32
+ };
33
+ }
34
+ }
35
+
36
+ export function getLocalApiUrl(pathname: string, origin?: string): string {
37
+ const url = `${isBrowser ? window.location.origin : (origin || API_URL)}${API_BASE}${pathname}`;
38
+ // console.log('getLocalApiUrl', url);
39
+ return url;
40
+ }
41
+
42
+ export async function localApiFetch<T = unknown>(pathname: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }, origin?: string): Promise<T> {
43
+ // console.log('localApiFetch', pathname, options, serviceOptions);
44
+ const url = new URL(getLocalApiUrl(pathname, origin));
45
+ if (serviceOptions) {
46
+ url.searchParams.append('locale', serviceOptions.locale);
47
+ url.searchParams.append('market', serviceOptions.market);
48
+ }
49
+ const apiOptions = merge({ ...getLocalApiRequestOptions() }, options);
50
+ console.log('📡 localApiFetch', apiOptions.method, url.href);
51
+ const apiResponse = await httpFetch(url.href, apiOptions);
52
+ // console.log('localApiFetch', pathname, apiResponse);
53
+ return apiResponse;
54
+ }
55
+
56
+ export async function localApiGet<T = unknown>(url: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }, origin?: string): Promise<T> {
57
+ return await localApiFetch<T>(url, { ...options, method: 'GET' }, serviceOptions, origin);
58
+ }
59
+
60
+ export async function localApiPost<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {
61
+ return await localApiFetch<T>(url, { ...options, method: 'POST', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);
62
+ }
63
+
64
+ export async function localApiPut<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {
65
+ return await localApiFetch<T>(url, { ...options, method: 'PUT', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);
66
+ }
67
+
68
+ export async function localApiPatch<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {
69
+ return await localApiFetch<T>(url, { ...options, method: 'PATCH', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);
70
+ }
71
+
72
+ export async function localApiDelete<T = unknown>(url: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {
73
+ return await localApiFetch<T>(url, { ...options, method: 'DELETE' }, serviceOptions);
74
+ }
@@ -0,0 +1,43 @@
1
+ import type { ILabel, ILocalizable } from '@websolutespa/bom-core';
2
+
3
+ export function getLabelFromLabels(labels: ILabel[]) {
4
+ const labelService = new LabelService(labels);
5
+ return (key: string, params?: Record<string, any>): string => {
6
+ return labelService.getLabel(key, params);
7
+ };
8
+ }
9
+
10
+ function parseLabel(dictionary: Record<string, ILocalizable | undefined>, key: string, params?: Record<string, any>): string {
11
+ const label = (dictionary[key] != null ? dictionary[key] : key) as string;
12
+ if (params) {
13
+ return interpolateText(label, params);
14
+ }
15
+ return label;
16
+ }
17
+
18
+ export function interpolateText(text: string, dictionary: Record<string, any>): string {
19
+ const TEMPLATE_REGEXP: RegExp = /@(\w+)/g;
20
+ return text.replace(TEMPLATE_REGEXP, (text: string, key: string) => {
21
+ const replacer: unknown = dictionary[key];
22
+ return typeof replacer !== 'undefined' ? String(replacer) : text;
23
+ });
24
+ }
25
+
26
+ export class LabelService {
27
+ labels: ILabel[];
28
+ dictionary: Record<string, ILocalizable | undefined>;
29
+ constructor(labels: ILabel[]) {
30
+ this.labels = labels;
31
+ this.dictionary = Object.fromEntries(labels.map(l => [l.id, l.text]));
32
+ }
33
+ getLabel(key: string, params?: Record<string, any>): string {
34
+ return parseLabel(this.dictionary, key, params);
35
+ }
36
+ }
37
+
38
+ /*
39
+ export function resolveLabel(labels: ILabel[], id: string): string {
40
+ const label = labels.find(x => x.id === id);
41
+ return label && label.text ? label.text.toString() : id;
42
+ }
43
+ */
@@ -0,0 +1,167 @@
1
+ import { ILayout } from '@websolutespa/bom-core';
2
+
3
+ export const LayoutDefaults: ILayout = {
4
+ labels: [
5
+ {
6
+ id: 'error.required',
7
+ text: 'Required field',
8
+ },
9
+ {
10
+ id: 'items.found',
11
+ text: '@items items found',
12
+ },
13
+ ],
14
+ locale: 'en',
15
+ locales: [],
16
+ market: 'ww',
17
+ markets: [],
18
+ app: {
19
+ id: 'app',
20
+ brand: {
21
+ name: 'Mixer',
22
+ social: [],
23
+ },
24
+ },
25
+ menu: {
26
+ main: {
27
+ id: 'main',
28
+ items: [
29
+ {
30
+ id: 1,
31
+ type: 'category',
32
+ title: 'Collections',
33
+ category: 'product_index',
34
+ href: '#collections',
35
+ items: [
36
+ {
37
+ id: 'product_category_tile',
38
+ type: 'category',
39
+ title: 'Tiles',
40
+ category: 'product_category_tile',
41
+ href: '#collections/tiles',
42
+ items: [],
43
+ media: {
44
+ type: 'image',
45
+ src: 'https://placehold.co/960x600/e4e5eb/f4f5fb.webp?text=placehold',
46
+ },
47
+ },
48
+ ],
49
+ },
50
+ ],
51
+ },
52
+ },
53
+ tree: {
54
+ id: 'homepage',
55
+ category: 'homepage',
56
+ schema: 'homepage',
57
+ title: 'Homepage',
58
+ href: '/',
59
+ items: [
60
+ {
61
+ id: 'product_index',
62
+ category: 'product_index',
63
+ schema: 'product_index',
64
+ title: 'Collections',
65
+ href: '#collections',
66
+ items: [
67
+ {
68
+ id: 'product_category',
69
+ category: 'product_category',
70
+ schema: 'product_category',
71
+ title: 'Tiles',
72
+ href: '#product_category',
73
+ media: {
74
+ type: 'image',
75
+ src: 'https://bom-mixer.wslabs.it/assets/header/6366_n_tiles.jpg',
76
+ },
77
+ },
78
+ {
79
+ id: 'product_category',
80
+ category: 'product_category',
81
+ schema: 'product_category',
82
+ title: '3D Elements',
83
+ href: '#product_category',
84
+ media: {
85
+ type: 'image',
86
+ src: 'https://bom-mixer.wslabs.it/assets/header/6369_n_3d_elements_menu_cover.webp',
87
+ },
88
+ },
89
+ {
90
+ id: 'product_category',
91
+ category: 'product_category',
92
+ schema: 'product_category',
93
+ title: 'Paints',
94
+ href: '#product_category',
95
+ media: {
96
+ type: 'image',
97
+ src: 'https://bom-mixer.wslabs.it/assets/header/6367_n_paints.jpg',
98
+ },
99
+ },
100
+ {
101
+ id: 'product_category',
102
+ category: 'product_category',
103
+ schema: 'product_category',
104
+ title: 'Wood & Metal',
105
+ href: '#product_category',
106
+ media: {
107
+ type: 'image',
108
+ src: 'https://bom-mixer.wslabs.it/assets/header/6368_n_menu_wood_metal.webp',
109
+ },
110
+ },
111
+ {
112
+ id: 'product_category',
113
+ category: 'product_category',
114
+ schema: 'product_category',
115
+ title: 'Accessories',
116
+ href: '#product_category',
117
+ media: {
118
+ type: 'image',
119
+ src: 'https://bom-mixer.wslabs.it/assets/header/6371_n_accessories.jpg',
120
+ },
121
+ },
122
+ {
123
+ id: 'product_category',
124
+ category: 'product_category',
125
+ schema: 'product_category',
126
+ title: 'Editions',
127
+ href: '#product_category',
128
+ media: {
129
+ type: 'image',
130
+ src: 'https://bom-mixer.wslabs.it/assets/header/7858_n_Menu.webp',
131
+ },
132
+ },
133
+ ],
134
+ },
135
+ {
136
+ id: 'magazine_index',
137
+ category: 'magazine_index',
138
+ schema: 'magazine_index',
139
+ title: 'Magazine',
140
+ href: '#magazine_index',
141
+ },
142
+ {
143
+ id: 'store_index',
144
+ category: 'store_index',
145
+ schema: 'store_index',
146
+ title: 'Stores',
147
+ href: '#store_index',
148
+ },
149
+ {
150
+ id: 'contact',
151
+ category: 'contact',
152
+ schema: 'contact',
153
+ title: 'Contacts',
154
+ href: '#contact',
155
+ },
156
+ {
157
+ id: 'shop_index',
158
+ category: 'shop_index',
159
+ schema: 'shop_index',
160
+ title: 'Shop',
161
+ href: '#shop_index',
162
+ },
163
+ ],
164
+ },
165
+ topLevelHrefs: {},
166
+ topLevelRoutes: {},
167
+ };
@@ -0,0 +1,19 @@
1
+ import type { ILayout, IRouteLink } from '@websolutespa/bom-core';
2
+
3
+ function getLink_(category: string, routeLink: IRouteLink | undefined): string | undefined {
4
+ if (routeLink) {
5
+ if (routeLink.category === category) {
6
+ return routeLink.href;
7
+ }
8
+ if (routeLink.items) {
9
+ return routeLink.items.reduce((p: string | undefined, c) => {
10
+ return getLink_(category, c) || p;
11
+ }, undefined);
12
+ }
13
+ }
14
+ return undefined;
15
+ }
16
+
17
+ export function getLink(category: string, layout: ILayout) {
18
+ return getLink_(category, layout.tree);
19
+ }