@akinon/next 2.0.0-beta.2 → 2.0.0-beta.21

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 (133) hide show
  1. package/.eslintrc.js +12 -0
  2. package/CHANGELOG.md +400 -7
  3. package/__tests__/next-config.test.ts +83 -0
  4. package/__tests__/tsconfig.json +23 -0
  5. package/api/auth.ts +381 -60
  6. package/api/barcode-search.ts +59 -0
  7. package/api/cache.ts +41 -5
  8. package/api/client.ts +21 -4
  9. package/api/form.ts +85 -0
  10. package/api/image-proxy.ts +75 -0
  11. package/api/product-categories.ts +53 -0
  12. package/api/similar-product-list.ts +63 -0
  13. package/api/similar-products.ts +111 -0
  14. package/api/virtual-try-on.ts +382 -0
  15. package/assets/styles/index.scss +84 -0
  16. package/babel.config.js +6 -0
  17. package/bin/pz-generate-routes.js +115 -0
  18. package/bin/pz-install-plugins.js +1 -1
  19. package/bin/pz-prebuild.js +1 -0
  20. package/bin/pz-predev.js +1 -0
  21. package/bin/pz-run-tests.js +99 -0
  22. package/bin/run-prebuild-tests.js +46 -0
  23. package/components/accordion.tsx +20 -5
  24. package/components/button.tsx +51 -36
  25. package/components/client-root.tsx +138 -2
  26. package/components/file-input.tsx +65 -3
  27. package/components/index.ts +1 -0
  28. package/components/input.tsx +1 -1
  29. package/components/link.tsx +46 -16
  30. package/components/logger-popup.tsx +213 -0
  31. package/components/modal.tsx +32 -16
  32. package/components/plugin-module.tsx +62 -3
  33. package/components/price.tsx +2 -2
  34. package/components/select.tsx +1 -1
  35. package/components/selected-payment-option-view.tsx +21 -0
  36. package/data/client/account.ts +17 -2
  37. package/data/client/api.ts +2 -0
  38. package/data/client/basket.ts +66 -5
  39. package/data/client/checkout.ts +391 -99
  40. package/data/client/misc.ts +38 -2
  41. package/data/client/product.ts +19 -2
  42. package/data/client/user.ts +16 -8
  43. package/data/server/category.ts +11 -9
  44. package/data/server/flatpage.ts +11 -4
  45. package/data/server/form.ts +15 -4
  46. package/data/server/landingpage.ts +11 -4
  47. package/data/server/list.ts +5 -4
  48. package/data/server/menu.ts +11 -3
  49. package/data/server/product.ts +111 -55
  50. package/data/server/seo.ts +14 -4
  51. package/data/server/special-page.ts +5 -4
  52. package/data/server/widget.ts +90 -5
  53. package/data/urls.ts +16 -5
  54. package/hocs/client/with-segment-defaults.tsx +2 -2
  55. package/hocs/server/with-segment-defaults.tsx +65 -20
  56. package/hooks/index.ts +4 -0
  57. package/hooks/use-localization.ts +24 -10
  58. package/hooks/use-logger-context.tsx +114 -0
  59. package/hooks/use-logger.ts +92 -0
  60. package/hooks/use-loyalty-availability.ts +21 -0
  61. package/hooks/use-payment-options.ts +2 -1
  62. package/hooks/use-pz-params.ts +37 -0
  63. package/hooks/use-router.ts +51 -14
  64. package/hooks/use-sentry-uncaught-errors.ts +24 -0
  65. package/instrumentation/index.ts +10 -1
  66. package/instrumentation/node.ts +2 -20
  67. package/jest.config.js +25 -0
  68. package/lib/cache-handler.mjs +534 -16
  69. package/lib/cache.ts +272 -37
  70. package/localization/index.ts +2 -1
  71. package/localization/provider.tsx +2 -5
  72. package/middlewares/bfcache-headers.ts +18 -0
  73. package/middlewares/checkout-provider.ts +1 -1
  74. package/middlewares/complete-gpay.ts +32 -26
  75. package/middlewares/complete-masterpass.ts +33 -26
  76. package/middlewares/complete-wallet.ts +182 -0
  77. package/middlewares/default.ts +360 -215
  78. package/middlewares/index.ts +10 -2
  79. package/middlewares/locale.ts +34 -11
  80. package/middlewares/masterpass-rest-callback.ts +230 -0
  81. package/middlewares/oauth-login.ts +200 -57
  82. package/middlewares/pretty-url.ts +21 -8
  83. package/middlewares/redirection-payment.ts +32 -26
  84. package/middlewares/saved-card-redirection.ts +33 -26
  85. package/middlewares/three-d-redirection.ts +32 -26
  86. package/middlewares/url-redirection.ts +11 -1
  87. package/middlewares/wallet-complete-redirection.ts +206 -0
  88. package/package.json +24 -9
  89. package/plugins.d.ts +19 -4
  90. package/plugins.js +10 -1
  91. package/redux/actions.ts +47 -0
  92. package/redux/middlewares/checkout.ts +63 -138
  93. package/redux/middlewares/index.ts +14 -10
  94. package/redux/middlewares/pre-order/address.ts +7 -2
  95. package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
  96. package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
  97. package/redux/middlewares/pre-order/delivery-option.ts +7 -1
  98. package/redux/middlewares/pre-order/index.ts +16 -10
  99. package/redux/middlewares/pre-order/installment-option.ts +8 -1
  100. package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
  101. package/redux/middlewares/pre-order/payment-option.ts +7 -1
  102. package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
  103. package/redux/middlewares/pre-order/redirection.ts +8 -2
  104. package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
  105. package/redux/middlewares/pre-order/shipping-option.ts +7 -1
  106. package/redux/middlewares/pre-order/shipping-step.ts +5 -1
  107. package/redux/reducers/checkout.ts +23 -3
  108. package/redux/reducers/index.ts +11 -3
  109. package/redux/reducers/root.ts +7 -2
  110. package/redux/reducers/widget.ts +80 -0
  111. package/sentry/index.ts +69 -13
  112. package/tailwind/content.js +16 -0
  113. package/types/commerce/account.ts +5 -1
  114. package/types/commerce/checkout.ts +35 -1
  115. package/types/commerce/widget.ts +33 -0
  116. package/types/index.ts +114 -6
  117. package/types/next-auth.d.ts +2 -2
  118. package/types/widget.ts +80 -0
  119. package/utils/app-fetch.ts +7 -2
  120. package/utils/generate-commerce-search-params.ts +3 -2
  121. package/utils/get-checkout-path.ts +3 -0
  122. package/utils/get-root-hostname.ts +28 -0
  123. package/utils/index.ts +64 -10
  124. package/utils/localization.ts +4 -0
  125. package/utils/mobile-3d-iframe.ts +8 -2
  126. package/utils/override-middleware.ts +7 -12
  127. package/utils/pz-segments.ts +92 -0
  128. package/utils/redirect-ignore.ts +35 -0
  129. package/utils/redirect.ts +9 -3
  130. package/utils/redirection-iframe.ts +8 -2
  131. package/utils/widget-styles.ts +107 -0
  132. package/views/error-page.tsx +93 -0
  133. package/with-pz-config.js +21 -7
package/utils/redirect.ts CHANGED
@@ -3,21 +3,27 @@ import Settings from 'settings';
3
3
  import { headers } from 'next/headers';
4
4
  import { ServerVariables } from '@akinon/next/utils/server-variables';
5
5
  import { getUrlPathWithLocale } from '@akinon/next/utils/localization';
6
+ import { urlLocaleMatcherRegex } from '@akinon/next/utils';
6
7
 
7
8
  export const redirect = async (path: string, type?: RedirectType) => {
8
9
  const nextHeaders = await headers();
9
10
  const pageUrl = new URL(
10
- nextHeaders.get('pz-url') ?? process.env.NEXT_PUBLIC_URL
11
+ nextHeaders.get('pz-url') ?? process.env.NEXT_PUBLIC_URL ?? ''
11
12
  );
12
13
 
13
14
  const currentLocale = Settings.localization.locales.find(
14
15
  (locale) => locale.value === ServerVariables.locale
15
16
  );
16
17
 
17
- const callbackUrl = pageUrl.pathname;
18
+ const searchParams = new URLSearchParams(pageUrl.search);
19
+
20
+ const callbackUrl =
21
+ pageUrl.pathname.replace(urlLocaleMatcherRegex, '') +
22
+ (searchParams.toString() ? `?${searchParams.toString()}` : '');
23
+
18
24
  const redirectUrlWithLocale = getUrlPathWithLocale(
19
25
  path,
20
- currentLocale.localePath ?? currentLocale.value
26
+ currentLocale?.value
21
27
  );
22
28
 
23
29
  const redirectUrl = `${redirectUrlWithLocale}?callbackUrl=${callbackUrl}`;
@@ -1,8 +1,14 @@
1
1
  const iframeURLChange = (iframe, callback) => {
2
2
  iframe.addEventListener('load', () => {
3
3
  setTimeout(() => {
4
- if (iframe?.contentWindow?.location) {
5
- callback(iframe.contentWindow.location);
4
+ try {
5
+ if (iframe?.contentWindow?.location) {
6
+ const iframeLocation = iframe.contentWindow.location;
7
+
8
+ callback(iframeLocation);
9
+ }
10
+ } catch (error) {
11
+ // Expected: browser blocks cross-origin iframe access for security
6
12
  }
7
13
  }, 0);
8
14
  });
@@ -0,0 +1,107 @@
1
+ const kebabize = (str: string) => {
2
+ return str.replace(
3
+ /[A-Z]+(?![a-z])|[A-Z]/g,
4
+ ($, ofs) => (ofs ? '-' : '') + $.toLowerCase()
5
+ );
6
+ };
7
+
8
+ const formatCssValue = (value: string | number) => {
9
+ return /^\d+$/.test(value.toString()) ? `${value}px` : value;
10
+ };
11
+
12
+ interface NodeProperties {
13
+ style?: Record<string, any>;
14
+ parent_id?: string;
15
+ type?: string;
16
+ tag?: string;
17
+ breakpoints?: any[];
18
+ dataSources?: any[];
19
+ is_slider?: boolean;
20
+ iteratingNode?: string;
21
+ iteratorValue?: string;
22
+ selectedDataSourceId?: string;
23
+ order?: number;
24
+ }
25
+
26
+ interface Node {
27
+ key: string;
28
+ properties: NodeProperties;
29
+ label?: string;
30
+ data_type?: string;
31
+ }
32
+
33
+ const processNode = (node: Node, selector = '', styles: string[] = []) => {
34
+ const { key, properties } = node;
35
+ const { style } = properties;
36
+
37
+ if (key && style) {
38
+ const baseStyles: Record<string, any> = {};
39
+ const breakpointStyles: Record<string, Record<string, any>> = {};
40
+
41
+ Object.entries(style).forEach(([prop, val]) => {
42
+ if (typeof val === 'object') {
43
+ breakpointStyles[prop] = val;
44
+ } else {
45
+ baseStyles[prop] = val;
46
+ }
47
+ });
48
+
49
+ const baseCssRules = Object.entries(baseStyles)
50
+ .map(([prop, val]) => `${kebabize(prop)}: ${formatCssValue(val)};`)
51
+ .join(' ');
52
+
53
+ if (baseCssRules) {
54
+ styles.push(`${selector} { ${baseCssRules} }`);
55
+ }
56
+
57
+ Object.keys(breakpointStyles)
58
+ .map((bp) => parseInt(bp, 10))
59
+ .sort((a, b) => a - b)
60
+ .forEach((bp) => {
61
+ const bpCssRules = Object.entries(breakpointStyles[bp])
62
+ .map(([prop, val]) => `${kebabize(prop)}: ${formatCssValue(val)};`)
63
+ .join(' ');
64
+
65
+ if (bpCssRules) {
66
+ styles.push(
67
+ `@media only screen and (min-width: ${bp}px) { ${selector} { ${bpCssRules} } }`
68
+ );
69
+ }
70
+ });
71
+ }
72
+
73
+ return styles;
74
+ };
75
+
76
+ const componentToCss = (components: Record<string, Node>) => {
77
+ const styles: string[] = [];
78
+ const componentsById: Record<string, Node> = {};
79
+
80
+ Object.values(components).forEach((component) => {
81
+ componentsById[component.key] = component;
82
+ });
83
+
84
+ const addStylesForComponent = (component: Node) => {
85
+ const selector = `[data-id="${component.key}"]`;
86
+ processNode(component, selector, styles);
87
+
88
+ Object.values(componentsById)
89
+ .filter((child) => child.properties.parent_id === component.key)
90
+ .forEach(addStylesForComponent);
91
+ };
92
+
93
+ Object.values(componentsById)
94
+ .filter((component) => !component.properties?.parent_id)
95
+ .forEach(addStylesForComponent);
96
+
97
+ return styles.join(' ');
98
+ };
99
+
100
+ export const generateWidgetStyles = (widgetSchemas: Record<string, Node>[]) => {
101
+ return widgetSchemas
102
+ .filter((schema) => Object.keys(schema).length > 0)
103
+ .map((data) => componentToCss(data))
104
+ .join('\n');
105
+ };
106
+
107
+ export type { Node, NodeProperties };
@@ -0,0 +1,93 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useLocalization } from '../hooks';
3
+ import { Button, Link } from '../components';
4
+ import { ROUTES } from 'routes';
5
+
6
+ export default function PzErrorPage({
7
+ error,
8
+ reset
9
+ }: {
10
+ error: Error & { digest?: string; isServerError?: boolean };
11
+ reset: () => void;
12
+ }) {
13
+ const [isServerError, setIsServerError] = useState(false);
14
+
15
+ useEffect(() => {
16
+ if ('isServerError' in error) {
17
+ setIsServerError(true);
18
+ return;
19
+ }
20
+
21
+ setIsServerError(!!error.digest);
22
+ }, [error]);
23
+
24
+ return isServerError ? (
25
+ <ServerErrorUI />
26
+ ) : (
27
+ <ClientErrorUI error={error} reset={reset} />
28
+ );
29
+ }
30
+
31
+ function ClientErrorUI({
32
+ error,
33
+ reset
34
+ }: {
35
+ error: Error & { digest?: string };
36
+ reset: () => void;
37
+ }) {
38
+ const { t } = useLocalization();
39
+
40
+ const errorMessage = error?.message || 'Unknown error';
41
+
42
+ return (
43
+ <section className="text-center px-6 py-6 my-14 md:px-0 md:m-14">
44
+ <div className="text-4xl font-bold md:text-6xl text-red-500">500</div>
45
+ <h1 className="text-lg md:text-xl mt-4">
46
+ {t('common.client_error.title')}
47
+ </h1>
48
+ <p className="text-lg md:text-xl mt-2">
49
+ {t('common.client_error.description')}
50
+ </p>
51
+
52
+ <div className="mt-4 mx-auto max-w-lg">
53
+ <p className="text-xs text-gray-600 font-mono bg-gray-100 p-3 rounded overflow-auto text-left">
54
+ <span className="font-semibold">Error:</span> {errorMessage}
55
+ </p>
56
+ </div>
57
+
58
+ <div className="mt-6 flex flex-col gap-4 items-center justify-center">
59
+ <Link href={ROUTES.HOME} className="text-lg underline">
60
+ {t('common.client_error.link_text')}
61
+ </Link>
62
+ <Button onClick={reset} className="text-lg">
63
+ {t('common.try_again')}
64
+ </Button>
65
+ </div>
66
+ </section>
67
+ );
68
+ }
69
+
70
+ function ServerErrorUI() {
71
+ const { t } = useLocalization();
72
+
73
+ const reloadPage = () => {
74
+ window.location.reload();
75
+ };
76
+
77
+ return (
78
+ <section className="text-center px-6 my-14 md:px-0 md:m-14">
79
+ <div className="text-7xl font-bold md:text-8xl">500</div>
80
+ <h1 className="text-lg md:text-xl"> {t('common.page_500.title')} </h1>
81
+ <p className="text-lg md:text-xl"> {t('common.page_500.description')} </p>
82
+
83
+ <div className="mt-6 flex flex-col gap-4 items-center justify-center">
84
+ <Link href={ROUTES.HOME} className="text-lg underline">
85
+ {t('common.page_500.link_text')}
86
+ </Link>
87
+ <Button onClick={reloadPage} className="text-lg">
88
+ {t('common.try_again')}
89
+ </Button>
90
+ </div>
91
+ </section>
92
+ );
93
+ }
package/with-pz-config.js CHANGED
@@ -1,19 +1,22 @@
1
+ const path = require('path');
1
2
  const pzPlugins = require('./plugins');
2
3
  const deepMerge = require('./utils/deep-merge');
3
4
 
4
5
  /** @type {import('next').NextConfig} */
5
6
  const defaultConfig = {
6
- experimental: { instrumentationHook: true },
7
7
  reactStrictMode: true,
8
- transpilePackages: ['@akinon/next', ...pzPlugins.map((p) => `@akinon/${p}`)],
8
+ transpilePackages: ['@akinon/next', '@akinon/pz-theme', ...pzPlugins.map((p) => `@akinon/${p}`)],
9
9
  skipTrailingSlashRedirect: true,
10
10
  poweredByHeader: false,
11
11
  cacheMaxMemorySize: 0,
12
+ output: 'standalone',
13
+ compress: false,
12
14
  env: {
13
15
  NEXT_PUBLIC_SENTRY_DSN: process.env.SENTRY_DSN
14
16
  },
15
17
  images: {
16
18
  remotePatterns: [
19
+ //It has to be like this for security reasons
17
20
  {
18
21
  protocol: 'https',
19
22
  hostname: '**.akinoncloud.com'
@@ -44,14 +47,21 @@ const defaultConfig = {
44
47
  value: 'max-age=63072000; includeSubDomains; preload'
45
48
  },
46
49
  {
47
- key: 'X-Frame-Options',
48
- value: 'SAMEORIGIN'
50
+ key: 'Content-Security-Policy',
51
+ value:
52
+ "frame-ancestors 'self' https://*.akifast.com akifast.com https://*.akinoncloud.com akinoncloud.com http://localhost:5174 localhost:5174"
49
53
  }
50
54
  ]
51
55
  }
52
56
  ];
53
57
  },
54
58
  webpack: (config, options) => {
59
+ // Alias 'redux/store' to the app's local redux/store module to prevent
60
+ // collision with the npm 'redux' package's exports field
61
+ config.resolve.alias = {
62
+ ...config.resolve.alias,
63
+ 'redux/store': path.resolve(process.cwd(), 'src/redux/store')
64
+ };
55
65
  config.resolve.fallback = {
56
66
  ...config.resolve.fallback,
57
67
  ...pzPlugins.reduce((acc, plugin) => {
@@ -60,10 +70,14 @@ const defaultConfig = {
60
70
  }, {}),
61
71
  translations: false
62
72
  };
73
+ // Ensure webpack can resolve deps from the app's node_modules when
74
+ // compiling transpiled packages (e.g. @akinon/next) whose imports
75
+ // may not be hoisted to the monorepo root.
76
+ const appNodeModules = path.resolve(process.cwd(), 'node_modules');
77
+ if (!config.resolve.modules.includes(appNodeModules)) {
78
+ config.resolve.modules.push(appNodeModules);
79
+ }
63
80
  return config;
64
- },
65
- sentry: {
66
- hideSourceMaps: true
67
81
  }
68
82
  };
69
83