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

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 (189) hide show
  1. package/.eslintrc.js +12 -0
  2. package/CHANGELOG.md +377 -7
  3. package/__tests__/next-config.test.ts +83 -0
  4. package/__tests__/tsconfig.json +23 -0
  5. package/api/auth.ts +133 -44
  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-prebuild.js +1 -0
  19. package/bin/pz-predev.js +1 -0
  20. package/bin/pz-run-tests.js +99 -0
  21. package/bin/run-prebuild-tests.js +46 -0
  22. package/components/accordion.tsx +20 -5
  23. package/components/button.tsx +51 -36
  24. package/components/client-root.tsx +138 -2
  25. package/components/file-input.tsx +65 -3
  26. package/components/index.ts +1 -0
  27. package/components/input.tsx +1 -1
  28. package/components/link.tsx +46 -16
  29. package/components/logger-popup.tsx +213 -0
  30. package/components/modal.tsx +32 -16
  31. package/components/plugin-module.tsx +62 -3
  32. package/components/price.tsx +2 -2
  33. package/components/select.tsx +1 -1
  34. package/components/selected-payment-option-view.tsx +21 -0
  35. package/components/theme-editor/blocks/accordion-block.tsx +136 -0
  36. package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
  37. package/components/theme-editor/blocks/button-block.tsx +593 -0
  38. package/components/theme-editor/blocks/counter-block.tsx +348 -0
  39. package/components/theme-editor/blocks/divider-block.tsx +20 -0
  40. package/components/theme-editor/blocks/embed-block.tsx +208 -0
  41. package/components/theme-editor/blocks/group-block.tsx +116 -0
  42. package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
  43. package/components/theme-editor/blocks/icon-block.tsx +230 -0
  44. package/components/theme-editor/blocks/image-block.tsx +137 -0
  45. package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
  46. package/components/theme-editor/blocks/input-block.tsx +123 -0
  47. package/components/theme-editor/blocks/link-block.tsx +216 -0
  48. package/components/theme-editor/blocks/lottie-block.tsx +325 -0
  49. package/components/theme-editor/blocks/map-block.tsx +89 -0
  50. package/components/theme-editor/blocks/slider-block.tsx +595 -0
  51. package/components/theme-editor/blocks/tab-block.tsx +10 -0
  52. package/components/theme-editor/blocks/text-block.tsx +52 -0
  53. package/components/theme-editor/blocks/video-block.tsx +122 -0
  54. package/components/theme-editor/components/action-toolbar.tsx +305 -0
  55. package/components/theme-editor/components/designer-overlay.tsx +74 -0
  56. package/components/theme-editor/components/with-designer-features.tsx +142 -0
  57. package/components/theme-editor/dynamic-font-loader.tsx +79 -0
  58. package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
  59. package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
  60. package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
  61. package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
  62. package/components/theme-editor/placeholder-registry.ts +31 -0
  63. package/components/theme-editor/sections/before-after-section.tsx +245 -0
  64. package/components/theme-editor/sections/contact-form-section.tsx +563 -0
  65. package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
  66. package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
  67. package/components/theme-editor/sections/divider-section.tsx +62 -0
  68. package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
  69. package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
  70. package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
  71. package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
  72. package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
  73. package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
  74. package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
  75. package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
  76. package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
  77. package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
  78. package/components/theme-editor/sections/section-wrapper.tsx +135 -0
  79. package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
  80. package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
  81. package/components/theme-editor/sections/tabs-section.tsx +578 -0
  82. package/components/theme-editor/theme-block.tsx +102 -0
  83. package/components/theme-editor/theme-placeholder-client.tsx +218 -0
  84. package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
  85. package/components/theme-editor/theme-placeholder.tsx +288 -0
  86. package/components/theme-editor/theme-section.tsx +1224 -0
  87. package/components/theme-editor/theme-settings-context.tsx +13 -0
  88. package/components/theme-editor/utils/index.ts +792 -0
  89. package/components/theme-editor/utils/iterator-utils.ts +234 -0
  90. package/components/theme-editor/utils/publish-window.ts +86 -0
  91. package/components/theme-editor/utils/visibility-rules.ts +188 -0
  92. package/data/client/account.ts +17 -2
  93. package/data/client/api.ts +2 -0
  94. package/data/client/basket.ts +66 -5
  95. package/data/client/checkout.ts +391 -99
  96. package/data/client/misc.ts +38 -2
  97. package/data/client/product.ts +19 -2
  98. package/data/client/user.ts +16 -8
  99. package/data/server/category.ts +11 -9
  100. package/data/server/flatpage.ts +11 -4
  101. package/data/server/form.ts +15 -4
  102. package/data/server/landingpage.ts +11 -4
  103. package/data/server/list.ts +5 -4
  104. package/data/server/menu.ts +11 -3
  105. package/data/server/product.ts +111 -55
  106. package/data/server/seo.ts +14 -4
  107. package/data/server/special-page.ts +5 -4
  108. package/data/server/widget.ts +90 -5
  109. package/data/urls.ts +16 -5
  110. package/hocs/client/with-segment-defaults.tsx +2 -2
  111. package/hocs/server/with-segment-defaults.tsx +65 -20
  112. package/hooks/index.ts +4 -0
  113. package/hooks/use-localization.ts +24 -10
  114. package/hooks/use-logger-context.tsx +114 -0
  115. package/hooks/use-logger.ts +92 -0
  116. package/hooks/use-loyalty-availability.ts +21 -0
  117. package/hooks/use-payment-options.ts +2 -1
  118. package/hooks/use-pz-params.ts +37 -0
  119. package/hooks/use-router.ts +51 -14
  120. package/hooks/use-sentry-uncaught-errors.ts +24 -0
  121. package/instrumentation/index.ts +10 -1
  122. package/instrumentation/node.ts +2 -20
  123. package/jest.config.js +25 -0
  124. package/lib/cache-handler.mjs +534 -16
  125. package/lib/cache.ts +272 -37
  126. package/localization/index.ts +2 -1
  127. package/localization/provider.tsx +2 -5
  128. package/middlewares/bfcache-headers.ts +18 -0
  129. package/middlewares/checkout-provider.ts +1 -1
  130. package/middlewares/complete-gpay.ts +32 -26
  131. package/middlewares/complete-masterpass.ts +33 -26
  132. package/middlewares/complete-wallet.ts +182 -0
  133. package/middlewares/default.ts +360 -215
  134. package/middlewares/index.ts +10 -2
  135. package/middlewares/locale.ts +34 -11
  136. package/middlewares/masterpass-rest-callback.ts +230 -0
  137. package/middlewares/oauth-login.ts +200 -57
  138. package/middlewares/pretty-url.ts +21 -8
  139. package/middlewares/redirection-payment.ts +32 -26
  140. package/middlewares/saved-card-redirection.ts +33 -26
  141. package/middlewares/three-d-redirection.ts +32 -26
  142. package/middlewares/url-redirection.ts +11 -1
  143. package/middlewares/wallet-complete-redirection.ts +206 -0
  144. package/package.json +25 -10
  145. package/plugins.d.ts +19 -4
  146. package/plugins.js +10 -1
  147. package/redux/actions.ts +47 -0
  148. package/redux/middlewares/checkout.ts +63 -138
  149. package/redux/middlewares/index.ts +14 -10
  150. package/redux/middlewares/pre-order/address.ts +7 -2
  151. package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
  152. package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
  153. package/redux/middlewares/pre-order/delivery-option.ts +7 -1
  154. package/redux/middlewares/pre-order/index.ts +16 -10
  155. package/redux/middlewares/pre-order/installment-option.ts +8 -1
  156. package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
  157. package/redux/middlewares/pre-order/payment-option.ts +7 -1
  158. package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
  159. package/redux/middlewares/pre-order/redirection.ts +8 -2
  160. package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
  161. package/redux/middlewares/pre-order/shipping-option.ts +7 -1
  162. package/redux/middlewares/pre-order/shipping-step.ts +5 -1
  163. package/redux/reducers/checkout.ts +23 -3
  164. package/redux/reducers/index.ts +11 -3
  165. package/redux/reducers/root.ts +7 -2
  166. package/redux/reducers/widget.ts +80 -0
  167. package/sentry/index.ts +69 -13
  168. package/tailwind/content.js +16 -0
  169. package/types/commerce/account.ts +5 -1
  170. package/types/commerce/checkout.ts +35 -1
  171. package/types/commerce/widget.ts +33 -0
  172. package/types/index.ts +101 -6
  173. package/types/next-auth.d.ts +2 -2
  174. package/types/widget.ts +80 -0
  175. package/utils/app-fetch.ts +7 -2
  176. package/utils/generate-commerce-search-params.ts +3 -2
  177. package/utils/get-checkout-path.ts +3 -0
  178. package/utils/get-root-hostname.ts +28 -0
  179. package/utils/index.ts +64 -10
  180. package/utils/localization.ts +4 -0
  181. package/utils/mobile-3d-iframe.ts +8 -2
  182. package/utils/override-middleware.ts +7 -12
  183. package/utils/pz-segments.ts +92 -0
  184. package/utils/redirect-ignore.ts +35 -0
  185. package/utils/redirect.ts +9 -3
  186. package/utils/redirection-iframe.ts +8 -2
  187. package/utils/widget-styles.ts +107 -0
  188. package/views/error-page.tsx +93 -0
  189. package/with-pz-config.js +13 -6
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const findBaseDir = require('../utils/find-base-dir');
6
+
7
+ const generateRoutes = () => {
8
+ const baseDir = findBaseDir();
9
+ const srcDir = path.join(baseDir, 'src');
10
+ const appDir = path.join(srcDir, 'app');
11
+
12
+ const routesDir = path.join(srcDir, 'routes');
13
+ const jsOutputPath = path.join(routesDir, 'generated-routes.js');
14
+
15
+ if (!fs.existsSync(routesDir)) {
16
+ fs.mkdirSync(routesDir, { recursive: true });
17
+ }
18
+
19
+ if (!fs.existsSync(appDir)) {
20
+ console.error(`Error: app directory not found at ${appDir}`);
21
+ console.error('Make sure you have an app directory in your project');
22
+ process.exit(1);
23
+ }
24
+
25
+ const routes = [];
26
+ const excludedDirs = ['api', 'pz-not-found'];
27
+
28
+ const skipSegments = [
29
+ '[pz]',
30
+ '[commerce]',
31
+ '[locale]',
32
+ '[currency]',
33
+ '[session]',
34
+ '[segment]',
35
+ '[url]',
36
+ '[theme]',
37
+ '[member_type]'
38
+ ];
39
+ const skipCatchAllRoutes = ['[...prettyurl]', '[...not_found]'];
40
+
41
+ const walkDirectory = (dir, basePath = '') => {
42
+ const files = fs.readdirSync(dir);
43
+
44
+ files.forEach((file) => {
45
+ const filePath = path.join(dir, file);
46
+ const stat = fs.statSync(filePath);
47
+
48
+ if (stat.isDirectory()) {
49
+ if (excludedDirs.includes(file)) {
50
+ return;
51
+ }
52
+
53
+ if (skipCatchAllRoutes.includes(file.toLowerCase())) {
54
+ return;
55
+ }
56
+
57
+ let routePath = basePath;
58
+ if (skipSegments.includes(file.toLowerCase())) {
59
+ routePath = basePath;
60
+ } else if (file.startsWith('[') && file.endsWith(']')) {
61
+ if (file.startsWith('[...')) {
62
+ routePath = `${basePath}/.+`;
63
+ } else {
64
+ routePath = `${basePath}/[^/]+`;
65
+ }
66
+ } else if (file.startsWith('(') && file.endsWith(')')) {
67
+ routePath = basePath;
68
+ } else {
69
+ routePath = `${basePath}/${file}`;
70
+ }
71
+
72
+ const pageFiles = ['page.tsx'];
73
+ const hasPage = pageFiles.some((pageFile) =>
74
+ fs.existsSync(path.join(filePath, pageFile))
75
+ );
76
+
77
+ if (hasPage && routePath) {
78
+ routes.push(routePath === '' ? '/' : routePath);
79
+ }
80
+
81
+ walkDirectory(filePath, routePath);
82
+ }
83
+ });
84
+ };
85
+
86
+ walkDirectory(appDir);
87
+
88
+ routes.push('/');
89
+
90
+ const cleanedRoutes = routes
91
+ .map((route) => {
92
+ return route.replace(/\/+/g, '/');
93
+ })
94
+ .filter((route, index, self) => {
95
+ return self.indexOf(route) === index;
96
+ })
97
+ .sort();
98
+
99
+ const jsContent = `// Generated by pz-generate-routes script
100
+ // Do not edit manually
101
+ module.exports = ${JSON.stringify(cleanedRoutes, null, 2)};`;
102
+
103
+ fs.writeFileSync(jsOutputPath, jsContent);
104
+
105
+ console.info(
106
+ `Routes generated successfully. (Found ${cleanedRoutes.length} routes)`
107
+ );
108
+ };
109
+
110
+ try {
111
+ generateRoutes();
112
+ } catch (error) {
113
+ console.error('Error generating routes:', error);
114
+ process.exit(1);
115
+ }
@@ -5,3 +5,4 @@ const runScript = require('./run-script');
5
5
  runScript('pz-install-theme.js');
6
6
  runScript('pz-pre-check-dist.js');
7
7
  runScript('pz-generate-translations.js');
8
+ runScript('pz-generate-routes.js');
package/bin/pz-predev.js CHANGED
@@ -6,3 +6,4 @@ runScript('pz-install-extensions.js');
6
6
  runScript('pz-check-env.js');
7
7
  runScript('pz-install-theme.js');
8
8
  runScript('pz-generate-translations.js');
9
+ runScript('pz-generate-routes.js');
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn } = require('child_process');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const glob = require('glob');
7
+ const findBaseDir = require('../utils/find-base-dir');
8
+ const checkMonorepo = require('../utils/check-monorepo');
9
+
10
+ const IS_MONOREPO = checkMonorepo() !== null;
11
+ const BASE_DIR = findBaseDir();
12
+ const PLUGINS = require(path.join(BASE_DIR, 'src', 'plugins.js'));
13
+
14
+ function findPluginTestFiles(akinonNextPackagePath) {
15
+ const pluginsRootPath = path.join(
16
+ akinonNextPackagePath,
17
+ '..',
18
+ '..',
19
+ IS_MONOREPO ? 'packages' : '@akinon'
20
+ );
21
+
22
+ if (!fs.existsSync(pluginsRootPath)) {
23
+ console.log('Plugins directory not found:', pluginsRootPath);
24
+ return [];
25
+ }
26
+
27
+ return PLUGINS.reduce((testFiles, pluginName) => {
28
+ const pluginDirectoryPath = path.join(pluginsRootPath, pluginName);
29
+ if (fs.existsSync(pluginDirectoryPath)) {
30
+ const pluginTestFiles = glob.sync('**/*.test.ts', {
31
+ cwd: pluginDirectoryPath,
32
+ absolute: true
33
+ });
34
+
35
+ return testFiles.concat(pluginTestFiles);
36
+ } else {
37
+ console.log(`Plugin directory not found: ${pluginName}`);
38
+ }
39
+ return testFiles;
40
+ }, []);
41
+ }
42
+
43
+ function isJestInstalled() {
44
+ try {
45
+ const jestExecutablePath = path.join(
46
+ BASE_DIR,
47
+ 'node_modules',
48
+ '.bin',
49
+ 'jest'
50
+ );
51
+ return fs.existsSync(jestExecutablePath);
52
+ } catch (error) {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ function getAkinonNextPackagePath() {
58
+ if (!IS_MONOREPO) {
59
+ return path.resolve(__dirname, '..');
60
+ }
61
+
62
+ return path.resolve(__dirname, '../../../packages/akinon-next');
63
+ }
64
+
65
+ if (!isJestInstalled()) {
66
+ console.error('\x1b[31mError: Jest is not installed in the project!\x1b[0m');
67
+ console.error(
68
+ 'Please install all dependencies by running one of the following commands:'
69
+ );
70
+ console.error(' npm install');
71
+ console.error(' yarn install');
72
+ process.exit(1);
73
+ }
74
+
75
+ const jestExecutablePath = path.join(BASE_DIR, 'node_modules', '.bin', 'jest');
76
+ const akinonNextPackagePath = getAkinonNextPackagePath();
77
+
78
+ const testFiles = [
79
+ ...glob.sync('__tests__/**/*.test.ts', {
80
+ cwd: akinonNextPackagePath,
81
+ absolute: true
82
+ }),
83
+ ...findPluginTestFiles(akinonNextPackagePath)
84
+ ];
85
+
86
+ console.log(`Found ${testFiles.length} test files to run`);
87
+
88
+ const jestArguments = [
89
+ ...testFiles,
90
+ `--config ${path.join(akinonNextPackagePath, 'jest.config.js')}`,
91
+ '--runTestsByPath',
92
+ '--passWithNoTests'
93
+ ];
94
+
95
+ spawn(jestExecutablePath, jestArguments, {
96
+ cwd: akinonNextPackagePath,
97
+ stdio: 'inherit',
98
+ shell: true
99
+ });
@@ -0,0 +1,46 @@
1
+ const path = require('path')
2
+ const fs = require('fs')
3
+
4
+ function runPrebuildTests() {
5
+ const workspaceRoot = process.cwd()
6
+ const configPath = path.join(workspaceRoot, 'config/prebuild-tests.json')
7
+
8
+ try {
9
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'))
10
+
11
+ if (config.tests && Array.isArray(config.tests)) {
12
+ for (const testScript of config.tests) {
13
+ console.log(`🧪 Running test script: ${testScript}`)
14
+ const result = require('child_process').spawnSync('yarn', [testScript], {
15
+ stdio: 'inherit',
16
+ shell: true
17
+ })
18
+
19
+ if (result.status !== 0) {
20
+ console.error(`❌ Test script '${testScript}' failed`)
21
+ process.exit(1)
22
+ }
23
+ }
24
+ console.log('✅ All prebuild tests passed successfully!')
25
+ }
26
+ } catch (error) {
27
+ if (error.code === 'ENOENT') {
28
+ console.log('🧪 Running default test: test:middleware')
29
+ const result = require('child_process').spawnSync('yarn', ['test:middleware'], {
30
+ stdio: 'inherit',
31
+ shell: true
32
+ })
33
+
34
+ if (result.status !== 0) {
35
+ console.error('❌ Middleware test failed')
36
+ process.exit(1)
37
+ }
38
+ console.log('✅ Default test passed successfully!')
39
+ } else {
40
+ console.error('❌ Error reading test configuration:', error)
41
+ process.exit(1)
42
+ }
43
+ }
44
+ }
45
+
46
+ module.exports = runPrebuildTests
@@ -7,15 +7,19 @@ import { AccordionProps } from '../types';
7
7
 
8
8
  export const Accordion = ({
9
9
  isCollapse = false,
10
+ collapseClassName,
10
11
  title,
11
12
  subTitle,
12
13
  icons = ['chevron-up', 'chevron-down'],
13
14
  iconSize = 16,
14
15
  iconColor = 'fill-[#000000]',
15
16
  children,
17
+ headerClassName,
16
18
  className,
17
19
  titleClassName,
18
- dataTestId
20
+ subTitleClassName,
21
+ dataTestId,
22
+ contentClassName
19
23
  }: AccordionProps) => {
20
24
  const [collapse, setCollapse] = useState(isCollapse);
21
25
 
@@ -27,15 +31,22 @@ export const Accordion = ({
27
31
  )}
28
32
  >
29
33
  <div
30
- className="flex items-center justify-between cursor-pointer"
34
+ className={twMerge(
35
+ 'flex items-center justify-between cursor-pointer',
36
+ headerClassName
37
+ )}
31
38
  onClick={() => setCollapse(!collapse)}
32
39
  data-testid={dataTestId}
33
40
  >
34
- <div className="flex flex-col">
41
+ <div className={twMerge('flex flex-col', contentClassName)}>
35
42
  {title && (
36
43
  <h3 className={twMerge('text-sm', titleClassName)}>{title}</h3>
37
44
  )}
38
- {subTitle && <h4 className="text-xs text-gray-700">{subTitle}</h4>}
45
+ {subTitle && (
46
+ <h4 className={twMerge('text-xs text-gray-700', subTitleClassName)}>
47
+ {subTitle}
48
+ </h4>
49
+ )}
39
50
  </div>
40
51
 
41
52
  {icons && (
@@ -46,7 +57,11 @@ export const Accordion = ({
46
57
  />
47
58
  )}
48
59
  </div>
49
- {collapse && <div className="mt-3 text-sm">{children}</div>}
60
+ {collapse && (
61
+ <div className={twMerge('mt-3 text-sm', collapseClassName)}>
62
+ {children}
63
+ </div>
64
+ )}
50
65
  </div>
51
66
  );
52
67
  };
@@ -1,46 +1,61 @@
1
1
  'use client';
2
2
 
3
- import { ButtonProps } from '../types/index';
3
+ import { ButtonProps } from '../types';
4
4
  import clsx from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
+ import { Link } from './link';
6
7
 
7
8
  export const Button = (props: ButtonProps) => {
8
- return (
9
- <button
10
- {...props}
11
- className={twMerge(
12
- clsx(
13
- [
14
- 'px-4',
15
- 'h-10',
16
- 'text-xs',
17
- 'bg-primary',
18
- 'text-primary-foreground',
19
- 'border',
20
- 'border-primary',
21
- 'transition-all',
22
- 'hover:bg-white',
23
- 'hover:border-primary',
24
- 'hover:text-primary'
25
- ],
26
- props.appearance === 'outlined' && [
27
- 'bg-transparent ',
28
- 'text-primary ',
29
- 'hover:bg-primary ',
30
- 'hover:text-primary-foreground'
31
- ],
32
- props.appearance === 'ghost' && [
33
- 'bg-transparent',
34
- 'border-transparent',
35
- 'text-primary',
36
- 'hover:bg-primary',
37
- 'hover:text-primary-foreground'
38
- ]
39
- ),
40
- props.className
41
- )}
9
+ const {
10
+ appearance = 'filled',
11
+ size = 'md',
12
+ href,
13
+ target,
14
+ children,
15
+ className,
16
+ ...rest
17
+ } = props;
18
+
19
+ const variants = {
20
+ filled:
21
+ 'bg-primary text-primary-foreground border border-primary hover:bg-white hover:border-primary hover:text-primary',
22
+ outlined:
23
+ 'bg-transparent text-primary hover:bg-primary hover:text-primary-foreground',
24
+ ghost:
25
+ 'bg-transparent border-transparent text-primary hover:bg-primary hover:text-primary-foreground',
26
+ link: 'px-0 h-auto underline underline-offset-2'
27
+ };
28
+
29
+ const sizes = {
30
+ sm: 'h-8',
31
+ md: 'h-10',
32
+ lg: 'h-12',
33
+ xl: 'h-14'
34
+ };
35
+
36
+ const buttonClasses = twMerge(
37
+ clsx(
38
+ 'px-4 text-xs transition-all duration-200',
39
+ 'inline-flex gap-2 justify-center items-center',
40
+ variants[appearance],
41
+ sizes[size],
42
+ className
43
+ ),
44
+ className
45
+ );
46
+
47
+ return props.href ? (
48
+ <Link
49
+ prefetch={false}
50
+ target={target}
51
+ href={href}
52
+ className={buttonClasses}
42
53
  >
43
- {props.children}
54
+ {children}
55
+ </Link>
56
+ ) : (
57
+ <button {...rest} className={buttonClasses}>
58
+ {children}
44
59
  </button>
45
60
  );
46
61
  };
@@ -1,6 +1,24 @@
1
1
  'use client';
2
2
 
3
+ import React from 'react';
4
+ import { useCallback, useEffect } from 'react';
3
5
  import { useMobileIframeHandler } from '../hooks';
6
+ import { useAppDispatch, useAppSelector } from '../redux/hooks';
7
+ import {
8
+ setComponents,
9
+ setDataSources,
10
+ setDesignMode,
11
+ setDraggingActive,
12
+ setPlaceholders,
13
+ setResponsive,
14
+ setSelectedComponentId,
15
+ setSelectedPlaceholder,
16
+ setSelectedWidget
17
+ } from '../redux/reducers/widget';
18
+ import { LoggerPopup } from './logger-popup';
19
+ import { LoggerProvider } from '../hooks/use-logger-context';
20
+ import * as Sentry from '@sentry/nextjs';
21
+ import { initSentry } from '../sentry';
4
22
 
5
23
  export default function ClientRoot({
6
24
  children,
@@ -9,11 +27,129 @@ export default function ClientRoot({
9
27
  children: React.ReactNode;
10
28
  sessionId?: string;
11
29
  }) {
12
- const { preventPageRender } = useMobileIframeHandler({ sessionId });
30
+ const { preventPageRender } = useMobileIframeHandler({
31
+ sessionId: sessionId || ''
32
+ });
33
+ const { components } = useAppSelector((state) => state.widget);
34
+ const dispatch = useAppDispatch();
35
+
36
+ const postMessage = (message: {
37
+ type: string;
38
+ data: Record<string, unknown>;
39
+ }) => {
40
+ window.parent.postMessage(message, '*');
41
+ };
42
+
43
+ const handleWidgetMessage = useCallback(
44
+ (event: MessageEvent) => {
45
+ if (event.data.type === 'UPDATE_COMPONENTS') {
46
+ const { components } = event.data.data;
47
+
48
+ dispatch(setComponents(components));
49
+ } else if (event.data.type === 'UPDATE_WIDGET_SYSTEM') {
50
+ const {
51
+ responsive,
52
+ designMode,
53
+ selectedComponentId,
54
+ placeholders,
55
+ dataSources
56
+ } = event.data.data;
57
+
58
+ dispatch(setResponsive(responsive));
59
+ dispatch(setDesignMode(designMode));
60
+ dispatch(setSelectedComponentId(selectedComponentId));
61
+ dispatch(setPlaceholders(placeholders));
62
+ dispatch(setDataSources(dataSources));
63
+ } else if (event.data.type === 'SELECT_PLACEHOLDER') {
64
+ const { placeholderSlug } = event.data.data;
65
+
66
+ dispatch(setSelectedPlaceholder(placeholderSlug));
67
+ } else if (event.data.type === 'SELECT_WIDGET') {
68
+ const { widgetSlug } = event.data.data;
69
+
70
+ dispatch(setSelectedWidget(widgetSlug));
71
+
72
+ postMessage({
73
+ type: 'SELECT_COMPONENT',
74
+ data: {
75
+ componentId: null
76
+ }
77
+ });
78
+ } else if (event.data.type === 'DRAG_START') {
79
+ dispatch(setDraggingActive(true));
80
+ } else if (event.data.type === 'DRAG_END') {
81
+ dispatch(setDraggingActive(false));
82
+ } else if (event.data.type === 'GET_DROP_TARGET') {
83
+ const { x, y, component } = event.data.data;
84
+ const elements = document.elementsFromPoint(x, y);
85
+ const dropTargetId = elements
86
+ .find((element) => element.hasAttribute('data-component'))
87
+ ?.getAttribute('data-id');
88
+
89
+ postMessage({
90
+ type: 'DROP_TARGET_RESPONSE',
91
+ data: {
92
+ targetId: dropTargetId,
93
+ component
94
+ }
95
+ });
96
+ } else if (event.data.type === 'SET_COOKIE') {
97
+ const { key, value } = event.data.data.value;
98
+
99
+ if (key && value) {
100
+ let cookieString = '';
101
+ if (window.parent !== window) {
102
+ cookieString = `${key}=${value}; path=/; SameSite=None; Secure`;
103
+ } else if (key === 'widget_builder') {
104
+ cookieString = `${key}=false; path=/; SameSite=None; Secure`;
105
+ }
106
+
107
+ if (cookieString) {
108
+ document.cookie = cookieString;
109
+ }
110
+ }
111
+ }
112
+ },
113
+ [dispatch, components]
114
+ );
115
+
116
+ useEffect(() => {
117
+ window.addEventListener('message', handleWidgetMessage);
118
+
119
+ if (window.parent === window) {
120
+ document.cookie = 'widget_builder=false; path=/; SameSite=None; Secure';
121
+ }
122
+
123
+ return () => {
124
+ window.removeEventListener('message', handleWidgetMessage);
125
+ };
126
+ }, [handleWidgetMessage]);
127
+
128
+ const initializeSentry = async () => {
129
+ const response = await fetch('/api/sentry', { next: { revalidate: 0 } });
130
+ const data = await response.json();
131
+
132
+ const options = {
133
+ dsn: data.dsn
134
+ };
135
+
136
+ initSentry('Client', options);
137
+ };
138
+
139
+ useEffect(() => {
140
+ if (!Sentry.isInitialized()) {
141
+ initializeSentry();
142
+ }
143
+ }, []);
13
144
 
14
145
  if (preventPageRender) {
15
146
  return null;
16
147
  }
17
148
 
18
- return <>{children}</>;
149
+ return (
150
+ <LoggerProvider>
151
+ {children}
152
+ <LoggerPopup />
153
+ </LoggerProvider>
154
+ );
19
155
  }
@@ -1,8 +1,70 @@
1
+ import { useState } from 'react';
1
2
  import { forwardRef } from 'react';
2
- import { FileInputProps } from '../types/index';
3
+ import { useLocalization } from '@akinon/next/hooks';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { FileInputProps } from '../types';
3
6
 
4
7
  export const FileInput = forwardRef<HTMLInputElement, FileInputProps>(
5
- function fileInput(props, ref) {
6
- return <input type="file" {...props} ref={ref} />;
8
+ function FileInput(
9
+ {
10
+ buttonClassName,
11
+ onChange,
12
+ fileClassName,
13
+ fileNameWrapperClassName,
14
+ fileInputClassName,
15
+ ...props
16
+ },
17
+ ref
18
+ ) {
19
+ const { t } = useLocalization();
20
+ const [fileNames, setFileNames] = useState<string[]>([]);
21
+
22
+ const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
23
+ const files = Array.from(event.target.files || []);
24
+ setFileNames(files.map((file) => file.name));
25
+
26
+ if (onChange) {
27
+ onChange(event);
28
+ }
29
+ };
30
+
31
+ return (
32
+ <div className="relative">
33
+ <input
34
+ type="file"
35
+ {...props}
36
+ ref={ref}
37
+ className={twMerge(
38
+ 'absolute inset-0 w-full h-full opacity-0 cursor-pointer',
39
+ fileInputClassName
40
+ )}
41
+ onChange={handleFileChange}
42
+ />
43
+ <button
44
+ type="button"
45
+ className={twMerge(
46
+ 'bg-primary text-white py-2 px-4 text-sm',
47
+ buttonClassName
48
+ )}
49
+ >
50
+ {t('common.file_input.select_file')}
51
+ </button>
52
+ <div
53
+ className={twMerge('mt-1 text-gray-500', fileNameWrapperClassName)}
54
+ >
55
+ {fileNames.length > 0 ? (
56
+ <ul className={twMerge('list-disc pl-4 text-xs', fileClassName)}>
57
+ {fileNames.map((name, index) => (
58
+ <li key={index}>{name}</li>
59
+ ))}
60
+ </ul>
61
+ ) : (
62
+ <span className={twMerge('text-xs', fileClassName)}>
63
+ {t('common.file_input.no_file')}
64
+ </span>
65
+ )}
66
+ </div>
67
+ </div>
68
+ );
7
69
  }
8
70
  );
@@ -21,3 +21,4 @@ export * from './link';
21
21
  export * from './pagination';
22
22
  export * from './live-commerce';
23
23
  export * from './file-input';
24
+ export * from './logger-popup';
@@ -112,7 +112,7 @@ export const Input = forwardRef<
112
112
  )}
113
113
  </div>
114
114
  {error && (
115
- <span className="mt-1 text-sm text-error">{error.message}</span>
115
+ <span className="mt-1 text-sm text-error">{String(error.message)}</span>
116
116
  )}
117
117
  </div>
118
118
  );