@akinon/projectzero 2.0.16-rc.0 → 2.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -4
- package/app-template/.env.example +0 -1
- package/app-template/CHANGELOG.md +36 -70
- package/app-template/README.md +1 -25
- package/app-template/next.config.mjs +1 -4
- package/app-template/package.json +42 -42
- package/app-template/src/hooks/index.ts +0 -2
- package/app-template/src/plugins.js +0 -1
- package/app-template/src/proxy.ts +1 -2
- package/app-template/src/settings.js +4 -1
- package/app-template/src/views/header/search/index.tsx +5 -13
- package/app-template/src/views/product/slider.tsx +38 -85
- package/codemods/migrate-page-types/transform.js +216 -29
- package/commands/plugins.ts +0 -4
- package/dist/commands/plugins.js +0 -4
- package/package.json +1 -1
- package/app-template/src/app/global-error.tsx +0 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# @akinon/projectzero
|
|
2
2
|
|
|
3
|
-
## 2.0.16
|
|
3
|
+
## 2.0.16
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- d99a6a7d5: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
|
|
9
|
-
- e18836b2: ZERO-4160: Restore scope in Sentry addon configuration in akinon.json
|
|
7
|
+
- 2fc1f6f8: ZERO-4481: migrate-page-types: use-client heuristic + destructure-to-use refactor for client components
|
|
10
8
|
|
|
11
9
|
## 2.0.15
|
|
12
10
|
|
|
@@ -7,7 +7,6 @@ NEXT_PUBLIC_URL=http://localhost:3000
|
|
|
7
7
|
SERVICE_BACKEND_URL=https://02fde10fee4440269e695aa10707dfaf.lb.akinoncloud.com
|
|
8
8
|
SITEMAP_S3_BUCKET_NAME=0fb534
|
|
9
9
|
NEXT_PUBLIC_VIRTUAL_TRY_ON_API_URL=https://d2a26507c7094f359aba349b96a96881.lb.akinoncloud.com
|
|
10
|
-
NEXT_PUBLIC_ENABLE_IMAGE_SEARCH=true
|
|
11
10
|
|
|
12
11
|
# LOG_LEVEL_DEV=debug # For more details, please refer to the Logging documentation.
|
|
13
12
|
|
|
@@ -1,75 +1,41 @@
|
|
|
1
1
|
# projectzeronext
|
|
2
2
|
|
|
3
|
-
## 2.0.16
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- Updated dependencies [95b139dc1]
|
|
40
|
-
- Updated dependencies [1d00f2d0]
|
|
41
|
-
- Updated dependencies [4ac7b2a1]
|
|
42
|
-
- Updated dependencies [4998a963]
|
|
43
|
-
- Updated dependencies [c0228aff]
|
|
44
|
-
- Updated dependencies [3909d3224]
|
|
45
|
-
- Updated dependencies [e18836b2]
|
|
46
|
-
- @akinon/next@2.0.16-rc.0
|
|
47
|
-
- @akinon/pz-checkout-gift-pack@2.0.16-rc.0
|
|
48
|
-
- @akinon/pz-one-click-checkout@2.0.16-rc.0
|
|
49
|
-
- @akinon/pz-basket-gift-pack@2.0.16-rc.0
|
|
50
|
-
- @akinon/pz-masterpass-rest@2.0.16-rc.0
|
|
51
|
-
- @akinon/pz-pay-on-delivery@2.0.16-rc.0
|
|
52
|
-
- @akinon/pz-credit-payment@2.0.16-rc.0
|
|
53
|
-
- @akinon/pz-click-collect@2.0.16-rc.0
|
|
54
|
-
- @akinon/pz-multi-basket@2.0.16-rc.0
|
|
55
|
-
- @akinon/pz-masterpass@2.0.16-rc.0
|
|
56
|
-
- @akinon/pz-gpay@2.0.16-rc.0
|
|
57
|
-
- @akinon/pz-b2b@2.0.16-rc.0
|
|
58
|
-
- @akinon/pz-bkm@2.0.16-rc.0
|
|
59
|
-
- @akinon/pz-otp@2.0.16-rc.0
|
|
60
|
-
- @akinon/pz-similar-products@2.0.16-rc.0
|
|
61
|
-
- @akinon/pz-virtual-try-on@2.0.16-rc.0
|
|
62
|
-
- @akinon/pz-theme@2.0.16-rc.0
|
|
63
|
-
- @akinon/pz-akifast@2.0.16-rc.0
|
|
64
|
-
- @akinon/pz-apple-pay@2.0.16-rc.0
|
|
65
|
-
- @akinon/pz-cybersource-uc@2.0.16-rc.0
|
|
66
|
-
- @akinon/pz-flow-payment@2.0.16-rc.0
|
|
67
|
-
- @akinon/pz-google-pay@2.0.16-rc.0
|
|
68
|
-
- @akinon/pz-haso@2.0.16-rc.0
|
|
69
|
-
- @akinon/pz-hepsipay@2.0.16-rc.0
|
|
70
|
-
- @akinon/pz-saved-card@2.0.16-rc.0
|
|
71
|
-
- @akinon/pz-tabby-extension@2.0.16-rc.0
|
|
72
|
-
- @akinon/pz-tamara-extension@2.0.16-rc.0
|
|
3
|
+
## 2.0.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 378607d1: ZERO-4430: Harden CSRF handling for the BFF proxy
|
|
8
|
+
|
|
9
|
+
When `settings.csrf.httpOnly` is enabled, the Django `csrftoken` cookie is set `HttpOnly` + `Secure` + `SameSite=Lax` and the token is never exposed to the browser. The Next.js proxy validates the request `Origin` and injects the `x-csrftoken` header server-side from the cookie before forwarding state-changing requests, instead of round-tripping the token through client JavaScript.
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [378607d1]
|
|
12
|
+
- @akinon/next@2.0.16
|
|
13
|
+
- @akinon/pz-theme@2.0.16
|
|
14
|
+
- @akinon/pz-virtual-try-on@2.0.16
|
|
15
|
+
- @akinon/pz-akifast@2.0.16
|
|
16
|
+
- @akinon/pz-apple-pay@2.0.16
|
|
17
|
+
- @akinon/pz-b2b@2.0.16
|
|
18
|
+
- @akinon/pz-basket-gift-pack@2.0.16
|
|
19
|
+
- @akinon/pz-bkm@2.0.16
|
|
20
|
+
- @akinon/pz-checkout-gift-pack@2.0.16
|
|
21
|
+
- @akinon/pz-click-collect@2.0.16
|
|
22
|
+
- @akinon/pz-credit-payment@2.0.16
|
|
23
|
+
- @akinon/pz-cybersource-uc@2.0.16
|
|
24
|
+
- @akinon/pz-flow-payment@2.0.16
|
|
25
|
+
- @akinon/pz-google-pay@2.0.16
|
|
26
|
+
- @akinon/pz-gpay@2.0.16
|
|
27
|
+
- @akinon/pz-haso@2.0.16
|
|
28
|
+
- @akinon/pz-hepsipay@2.0.16
|
|
29
|
+
- @akinon/pz-masterpass@2.0.16
|
|
30
|
+
- @akinon/pz-masterpass-rest@2.0.16
|
|
31
|
+
- @akinon/pz-multi-basket@2.0.16
|
|
32
|
+
- @akinon/pz-one-click-checkout@2.0.16
|
|
33
|
+
- @akinon/pz-otp@2.0.16
|
|
34
|
+
- @akinon/pz-pay-on-delivery@2.0.16
|
|
35
|
+
- @akinon/pz-saved-card@2.0.16
|
|
36
|
+
- @akinon/pz-similar-products@2.0.16
|
|
37
|
+
- @akinon/pz-tabby-extension@2.0.16
|
|
38
|
+
- @akinon/pz-tamara-extension@2.0.16
|
|
73
39
|
|
|
74
40
|
## 2.0.15
|
|
75
41
|
|
package/app-template/README.md
CHANGED
|
@@ -12,30 +12,6 @@ Run `cp .env.example .env && yarn` command at project root and all dependencies
|
|
|
12
12
|
|
|
13
13
|
- `SITEMAP_S3_BUCKET_NAME`: S3 bucket name for XML sitemaps (e.g., "0fb534"). This is required for the sitemap route to function correctly.
|
|
14
14
|
|
|
15
|
-
### Optional Environment Variables
|
|
16
|
-
|
|
17
|
-
- `NEXT_PUBLIC_ENABLE_IMAGE_SEARCH`: Set to `true` to enable image search feature for all users. If not set or `false`, the feature will only be visible to users who have enabled it via localStorage.
|
|
18
|
-
|
|
19
|
-
## Image Search Feature
|
|
20
|
-
|
|
21
|
-
The image search functionality allows users to search for similar products by uploading images or cropping existing product images.
|
|
22
|
-
|
|
23
|
-
### Feature Visibility Control
|
|
24
|
-
|
|
25
|
-
The image search feature can be controlled in two ways:
|
|
26
|
-
|
|
27
|
-
1. **Environment Variable (Global)**: Set `NEXT_PUBLIC_ENABLE_IMAGE_SEARCH=true` to enable for all users
|
|
28
|
-
2. **localStorage (Individual Users)**: Users can enable it locally using browser developer tools
|
|
29
|
-
|
|
30
|
-
````
|
|
31
|
-
|
|
32
|
-
### Feature Locations
|
|
33
|
-
|
|
34
|
-
When enabled, the image search feature appears in:
|
|
35
|
-
|
|
36
|
-
- **Header Search**: Camera icon next to the search input
|
|
37
|
-
- **Product Detail Page**: "View Similar Styles" button on product images
|
|
38
|
-
|
|
39
15
|
### Build
|
|
40
16
|
|
|
41
17
|
To build the app, run the following command:
|
|
@@ -43,7 +19,7 @@ To build the app, run the following command:
|
|
|
43
19
|
```bash
|
|
44
20
|
cd projectzeronext # Project root
|
|
45
21
|
yarn build
|
|
46
|
-
|
|
22
|
+
```
|
|
47
23
|
|
|
48
24
|
### Develop
|
|
49
25
|
|
|
@@ -48,12 +48,9 @@ const withSerwist = withSerwistInit({
|
|
|
48
48
|
const sentryConfig = {
|
|
49
49
|
silent: true,
|
|
50
50
|
dryRun: !process.env.SENTRY_DSN,
|
|
51
|
-
org: 'akinon'
|
|
51
|
+
org: 'akinon'
|
|
52
52
|
// project: 'enter_your_project_name_here',
|
|
53
53
|
// authToken: 'enter_your_auth_token_here'
|
|
54
|
-
sourcemaps: {
|
|
55
|
-
deleteSourcemapsAfterUpload: true
|
|
56
|
-
}
|
|
57
54
|
};
|
|
58
55
|
|
|
59
56
|
const enhancedConfig = withPzConfig(nextConfig);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projectzeronext",
|
|
3
|
-
"version": "2.0.16
|
|
3
|
+
"version": "2.0.16",
|
|
4
4
|
"private": true,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"scripts": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"dev": "next dev --webpack",
|
|
9
9
|
"dev-ssl": "next dev --experimental-https --webpack",
|
|
10
10
|
"build": "next build --webpack",
|
|
11
|
-
"start": "next start -p
|
|
11
|
+
"start": "next start -p 3000",
|
|
12
12
|
"type-check": "tsc",
|
|
13
13
|
"lint": "eslint .",
|
|
14
14
|
"test": "jest",
|
|
@@ -24,33 +24,33 @@
|
|
|
24
24
|
"test:middleware": "jest middleware-matcher.test.ts --bail"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@akinon/next": "2.0.16
|
|
28
|
-
"@akinon/pz-akifast": "2.0.16
|
|
29
|
-
"@akinon/pz-apple-pay": "2.0.16
|
|
30
|
-
"@akinon/pz-b2b": "2.0.16
|
|
31
|
-
"@akinon/pz-basket-gift-pack": "2.0.16
|
|
32
|
-
"@akinon/pz-bkm": "2.0.16
|
|
33
|
-
"@akinon/pz-checkout-gift-pack": "2.0.16
|
|
34
|
-
"@akinon/pz-click-collect": "2.0.16
|
|
35
|
-
"@akinon/pz-credit-payment": "2.0.16
|
|
36
|
-
"@akinon/pz-cybersource-uc": "2.0.16
|
|
37
|
-
"@akinon/pz-flow-payment": "2.0.16
|
|
38
|
-
"@akinon/pz-google-pay": "2.0.16
|
|
39
|
-
"@akinon/pz-gpay": "2.0.16
|
|
40
|
-
"@akinon/pz-haso": "2.0.16
|
|
41
|
-
"@akinon/pz-hepsipay": "2.0.16
|
|
42
|
-
"@akinon/pz-masterpass": "2.0.16
|
|
43
|
-
"@akinon/pz-masterpass-rest": "2.0.16
|
|
44
|
-
"@akinon/pz-multi-basket": "2.0.16
|
|
45
|
-
"@akinon/pz-one-click-checkout": "2.0.16
|
|
46
|
-
"@akinon/pz-otp": "2.0.16
|
|
47
|
-
"@akinon/pz-pay-on-delivery": "2.0.16
|
|
48
|
-
"@akinon/pz-saved-card": "2.0.16
|
|
49
|
-
"@akinon/pz-similar-products": "2.0.16
|
|
50
|
-
"@akinon/pz-tabby-extension": "2.0.16
|
|
51
|
-
"@akinon/pz-tamara-extension": "2.0.16
|
|
52
|
-
"@akinon/pz-theme": "2.0.16
|
|
53
|
-
"@akinon/pz-virtual-try-on": "2.0.16
|
|
27
|
+
"@akinon/next": "2.0.16",
|
|
28
|
+
"@akinon/pz-akifast": "2.0.16",
|
|
29
|
+
"@akinon/pz-apple-pay": "2.0.16",
|
|
30
|
+
"@akinon/pz-b2b": "2.0.16",
|
|
31
|
+
"@akinon/pz-basket-gift-pack": "2.0.16",
|
|
32
|
+
"@akinon/pz-bkm": "2.0.16",
|
|
33
|
+
"@akinon/pz-checkout-gift-pack": "2.0.16",
|
|
34
|
+
"@akinon/pz-click-collect": "2.0.16",
|
|
35
|
+
"@akinon/pz-credit-payment": "2.0.16",
|
|
36
|
+
"@akinon/pz-cybersource-uc": "2.0.16",
|
|
37
|
+
"@akinon/pz-flow-payment": "2.0.16",
|
|
38
|
+
"@akinon/pz-google-pay": "2.0.16",
|
|
39
|
+
"@akinon/pz-gpay": "2.0.16",
|
|
40
|
+
"@akinon/pz-haso": "2.0.16",
|
|
41
|
+
"@akinon/pz-hepsipay": "2.0.16",
|
|
42
|
+
"@akinon/pz-masterpass": "2.0.16",
|
|
43
|
+
"@akinon/pz-masterpass-rest": "2.0.16",
|
|
44
|
+
"@akinon/pz-multi-basket": "2.0.16",
|
|
45
|
+
"@akinon/pz-one-click-checkout": "2.0.16",
|
|
46
|
+
"@akinon/pz-otp": "2.0.16",
|
|
47
|
+
"@akinon/pz-pay-on-delivery": "2.0.16",
|
|
48
|
+
"@akinon/pz-saved-card": "2.0.16",
|
|
49
|
+
"@akinon/pz-similar-products": "2.0.16",
|
|
50
|
+
"@akinon/pz-tabby-extension": "2.0.16",
|
|
51
|
+
"@akinon/pz-tamara-extension": "2.0.16",
|
|
52
|
+
"@akinon/pz-theme": "2.0.16",
|
|
53
|
+
"@akinon/pz-virtual-try-on": "2.0.16",
|
|
54
54
|
"@hookform/resolvers": "2.9.0",
|
|
55
55
|
"@next/third-parties": "16.2.6",
|
|
56
56
|
"@react-google-maps/api": "2.17.1",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"next": "16.2.6",
|
|
60
60
|
"@serwist/next": "9.5.11",
|
|
61
61
|
"next-auth": "5.0.0-beta.30",
|
|
62
|
-
"pino": "8.
|
|
62
|
+
"pino": "8.11.0",
|
|
63
63
|
"serwist": "9.5.11",
|
|
64
64
|
"postcss": "8.4.49",
|
|
65
65
|
"react": "19.2.5",
|
|
@@ -67,14 +67,14 @@
|
|
|
67
67
|
"react-google-recaptcha": "2.1.0",
|
|
68
68
|
"react-hook-form": "7.71.2",
|
|
69
69
|
"react-intersection-observer": "9.4.0",
|
|
70
|
-
"react-multi-carousel": "2.8.
|
|
71
|
-
"react-string-replace": "1.1.
|
|
72
|
-
"start-server-and-test": "2.0.
|
|
73
|
-
"tailwind-merge": "1.
|
|
70
|
+
"react-multi-carousel": "2.8.4",
|
|
71
|
+
"react-string-replace": "1.1.0",
|
|
72
|
+
"start-server-and-test": "2.0.3",
|
|
73
|
+
"tailwind-merge": "1.8.0",
|
|
74
74
|
"yup": "0.32.11"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@akinon/eslint-plugin-projectzero": "2.0.16
|
|
77
|
+
"@akinon/eslint-plugin-projectzero": "2.0.16",
|
|
78
78
|
"@semantic-release/changelog": "6.0.2",
|
|
79
79
|
"@semantic-release/exec": "6.0.3",
|
|
80
80
|
"@semantic-release/git": "10.0.1",
|
|
@@ -89,25 +89,25 @@
|
|
|
89
89
|
"@types/react": "19.2.14",
|
|
90
90
|
"@types/react-dom": "19.2.3",
|
|
91
91
|
"client-only": "0.0.1",
|
|
92
|
-
"clsx": "1.
|
|
92
|
+
"clsx": "1.1.1",
|
|
93
93
|
"currency-symbol-map": "5.1.0",
|
|
94
94
|
"eslint": "9.39.4",
|
|
95
95
|
"eslint-config-next": "16.2.4",
|
|
96
96
|
"eslint-config-prettier": "10.1.1",
|
|
97
97
|
"husky": "8.0.0",
|
|
98
98
|
"jest": "29.7.0",
|
|
99
|
-
"jest-css-modules-transform": "4.
|
|
99
|
+
"jest-css-modules-transform": "4.3.0",
|
|
100
100
|
"jest-environment-jsdom": "29.7.0",
|
|
101
|
-
"lint-staged": "13.
|
|
102
|
-
"prettier": "2.
|
|
101
|
+
"lint-staged": "13.1.0",
|
|
102
|
+
"prettier": "2.6.2",
|
|
103
103
|
"react-number-format": "5.3.4",
|
|
104
|
-
"sass": "1.
|
|
104
|
+
"sass": "1.49.9",
|
|
105
105
|
"semantic-release": "19.0.5",
|
|
106
106
|
"server-only": "0.0.1",
|
|
107
|
-
"stylelint": "14.
|
|
107
|
+
"stylelint": "14.6.0",
|
|
108
108
|
"stylelint-config-sass-guidelines": "9.0.1",
|
|
109
109
|
"stylelint-config-standard": "25.0.0",
|
|
110
|
-
"stylelint-scss": "4.
|
|
110
|
+
"stylelint-scss": "4.2.0",
|
|
111
111
|
"stylelint-selector-bem-pattern": "2.1.1",
|
|
112
112
|
"tailwindcss": "4.2.0",
|
|
113
113
|
"ts-jest": "29.2.6",
|
|
@@ -14,8 +14,7 @@ export const config = {
|
|
|
14
14
|
'/((?!api|_next|[\\w-\\/*]+\\.\\w+).*)',
|
|
15
15
|
'/(.*sitemap\\.xml)',
|
|
16
16
|
'/(.+\\.)(html|htm|aspx|asp|php)',
|
|
17
|
-
'/(.*orders\\/checkout-with-token.*)'
|
|
18
|
-
'/(.*orders\\/post-checkout.*)'
|
|
17
|
+
'/(.*orders\\/checkout-with-token.*)'
|
|
19
18
|
]
|
|
20
19
|
};
|
|
21
20
|
|
|
@@ -4,7 +4,8 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
4
4
|
import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
|
|
5
5
|
import { closeSearch } from '@akinon/next/redux/reducers/header';
|
|
6
6
|
import clsx from 'clsx';
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
import { Icon } from '@theme/components';
|
|
8
9
|
import Results from './results';
|
|
9
10
|
import { ROUTES } from '@theme/routes';
|
|
10
11
|
import { useLocalization, useRouter } from '@akinon/next/hooks';
|
|
@@ -41,14 +42,6 @@ export default function Search() {
|
|
|
41
42
|
};
|
|
42
43
|
}, [isSearchOpen, dispatch]);
|
|
43
44
|
|
|
44
|
-
const handleSearchTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
45
|
-
setSearchText(e.target.value);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const handleCloseSearch = () => {
|
|
49
|
-
dispatch(closeSearch());
|
|
50
|
-
};
|
|
51
|
-
|
|
52
45
|
return (
|
|
53
46
|
<>
|
|
54
47
|
<div
|
|
@@ -74,9 +67,9 @@ export default function Search() {
|
|
|
74
67
|
{t('common.search.results_for')}
|
|
75
68
|
</span>
|
|
76
69
|
<div className="flex items-center">
|
|
77
|
-
<
|
|
70
|
+
<input
|
|
78
71
|
value={searchText}
|
|
79
|
-
onChange={
|
|
72
|
+
onChange={(e) => setSearchText(e.target.value)}
|
|
80
73
|
onKeyDown={(e) => {
|
|
81
74
|
if (e.key === 'Enter' && searchText.trim() !== '') {
|
|
82
75
|
router.push(`${ROUTES.LIST}/?search_text=${searchText}`);
|
|
@@ -98,12 +91,11 @@ export default function Search() {
|
|
|
98
91
|
<Icon
|
|
99
92
|
name="close"
|
|
100
93
|
size={14}
|
|
101
|
-
onClick={
|
|
94
|
+
onClick={() => dispatch(closeSearch())}
|
|
102
95
|
className="cursor-pointer"
|
|
103
96
|
/>
|
|
104
97
|
</div>
|
|
105
98
|
</div>
|
|
106
|
-
|
|
107
99
|
<Results searchText={searchText} />
|
|
108
100
|
</div>
|
|
109
101
|
</div>
|
|
@@ -36,100 +36,53 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
|
|
|
36
36
|
carouselRef.current?.next();
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
const handleThumbnailClick = (index
|
|
39
|
+
const handleThumbnailClick = (index) => {
|
|
40
40
|
setActiveIndex(index);
|
|
41
41
|
carouselRef.current?.goToSlide(index);
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
return (
|
|
45
|
-
|
|
46
|
-
<div className="lg:
|
|
47
|
-
<div className="
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
disabled={activeIndex === 0}
|
|
56
|
-
>
|
|
57
|
-
<Icon name="chevron-up" size={15} className="fill-[#000000]" />
|
|
58
|
-
</button>
|
|
59
|
-
<div className="hidden flex-col items-center overflow-scroll w-[80px] max-h-[620px] lg:block">
|
|
60
|
-
{product?.productimage_set?.map((item, index) => (
|
|
61
|
-
<Image
|
|
62
|
-
key={index}
|
|
63
|
-
src={item.image}
|
|
64
|
-
alt={`Thumbnail ${index}`}
|
|
65
|
-
width={80}
|
|
66
|
-
height={128}
|
|
67
|
-
aspectRatio={80 / 128}
|
|
68
|
-
className={twMerge('cursor-pointer', [
|
|
69
|
-
activeIndex === index && 'border-2 border-primary'
|
|
70
|
-
])}
|
|
71
|
-
onClick={() => handleThumbnailClick(index)}
|
|
72
|
-
/>
|
|
73
|
-
))}
|
|
74
|
-
</div>
|
|
75
|
-
<button
|
|
76
|
-
onClick={goToNext}
|
|
77
|
-
className={twMerge(
|
|
78
|
-
'hidden justify-center p-2 mt-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
79
|
-
[
|
|
80
|
-
activeIndex === product.productimage_set.length - 1 &&
|
|
81
|
-
'cursor-not-allowed opacity-45'
|
|
82
|
-
]
|
|
83
|
-
)}
|
|
84
|
-
disabled={activeIndex === product.productimage_set.length - 1}
|
|
85
|
-
>
|
|
86
|
-
<Icon name="chevron-down" size={15} className="fill-[#000000]" />
|
|
87
|
-
</button>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
|
|
91
|
-
<div className="relative lg:col-span-5">
|
|
92
|
-
<FavButton className="absolute right-8 top-6 z-[20] sm:hidden" />
|
|
93
|
-
|
|
94
|
-
<PluginModule
|
|
95
|
-
component={Component.ProductImageSearchFeature}
|
|
96
|
-
props={{
|
|
97
|
-
product,
|
|
98
|
-
activeIndex,
|
|
99
|
-
showResetButton: true
|
|
100
|
-
}}
|
|
101
|
-
/>
|
|
102
|
-
|
|
103
|
-
<CarouselCore
|
|
104
|
-
responsive={{
|
|
105
|
-
all: {
|
|
106
|
-
breakpoint: { max: 5000, min: 0 },
|
|
107
|
-
items: 1
|
|
108
|
-
}
|
|
109
|
-
}}
|
|
110
|
-
arrows={false}
|
|
111
|
-
swipeable={true}
|
|
112
|
-
ref={carouselRef}
|
|
113
|
-
afterChange={(previousSlide, { currentSlide }) => {
|
|
114
|
-
setActiveIndex(currentSlide);
|
|
115
|
-
}}
|
|
116
|
-
containerAspectRatio={{ mobile: 520 / 798, desktop: 484 / 726 }}
|
|
45
|
+
<div className="lg:grid lg:grid-cols-6">
|
|
46
|
+
<div className="lg:col-span-1">
|
|
47
|
+
<div className="flex flex-col items-center justify-center md:mr-[6px]">
|
|
48
|
+
<button
|
|
49
|
+
onClick={goToPrev}
|
|
50
|
+
className={twMerge(
|
|
51
|
+
'hidden justify-center p-2 mb-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
52
|
+
[activeIndex === 0 && 'cursor-not-allowed opacity-45']
|
|
53
|
+
)}
|
|
54
|
+
disabled={activeIndex === 0}
|
|
117
55
|
>
|
|
118
|
-
{
|
|
56
|
+
<Icon name="chevron-up" size={15} className="fill-[#000000]" />
|
|
57
|
+
</button>
|
|
58
|
+
<div className="hidden flex-col items-center overflow-scroll w-[80px] max-h-[620px] lg:block">
|
|
59
|
+
{product?.productimage_set?.map((item, index) => (
|
|
119
60
|
<Image
|
|
120
|
-
key={
|
|
61
|
+
key={index}
|
|
121
62
|
src={item.image}
|
|
122
|
-
alt={
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
fill
|
|
63
|
+
alt={`Thumbnail ${index}`}
|
|
64
|
+
width={80}
|
|
65
|
+
height={128}
|
|
66
|
+
className={twMerge('cursor-pointer', [
|
|
67
|
+
activeIndex === index && 'border-2 border-primary'
|
|
68
|
+
])}
|
|
69
|
+
onClick={() => handleThumbnailClick(index)}
|
|
130
70
|
/>
|
|
131
71
|
))}
|
|
132
|
-
</
|
|
72
|
+
</div>
|
|
73
|
+
<button
|
|
74
|
+
onClick={goToNext}
|
|
75
|
+
className={twMerge(
|
|
76
|
+
'hidden justify-center p-2 mt-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
77
|
+
[
|
|
78
|
+
activeIndex === product.productimage_set.length - 1 &&
|
|
79
|
+
'cursor-not-allowed opacity-45'
|
|
80
|
+
]
|
|
81
|
+
)}
|
|
82
|
+
disabled={activeIndex === product.productimage_set.length - 1}
|
|
83
|
+
>
|
|
84
|
+
<Icon name="chevron-down" size={15} className="fill-[#000000]" />
|
|
85
|
+
</button>
|
|
133
86
|
</div>
|
|
134
87
|
</div>
|
|
135
88
|
|
|
@@ -187,6 +140,6 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
|
|
|
187
140
|
))}
|
|
188
141
|
</CarouselCore>
|
|
189
142
|
</div>
|
|
190
|
-
|
|
143
|
+
</div>
|
|
191
144
|
);
|
|
192
145
|
}
|
|
@@ -2,19 +2,36 @@
|
|
|
2
2
|
* jscodeshift transform: rewrite `PageProps<T>` from `@akinon/next/types`
|
|
3
3
|
* into `AsyncPageProps<T>` or `ResolvedPageProps<T>` per usage context.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* Decision tree per TSTypeReference:
|
|
6
|
+
* 1. Enclosing function is `generateMetadata` → AsyncPageProps (always)
|
|
7
|
+
* 2. File has `'use client'` directive → AsyncPageProps (always)
|
|
8
|
+
* 3. Body uses `await props.params` / `use(props.params)` → AsyncPageProps
|
|
9
|
+
* 4. Otherwise (server component, withSegmentDefaults) → ResolvedPageProps
|
|
10
|
+
*
|
|
11
|
+
* Additionally for client components (rule 2):
|
|
12
|
+
* - If the parameter is destructured (`({ params })`), refactor to
|
|
13
|
+
* `(props: AsyncPageProps<T>)` and inject `use()` calls at the top
|
|
14
|
+
* of the function body. This avoids the broken pattern where `params`
|
|
15
|
+
* is destructured synchronously but is actually a Promise in Next 16.
|
|
16
|
+
* - Only refactors when the destructure contains only `params` and/or
|
|
17
|
+
* `searchParams` (no other properties / rest spreads).
|
|
18
|
+
* - Idempotent: skipped if `use(props.params)` already exists in the body.
|
|
19
|
+
* - Adds `import { use } from 'react'` if not already present.
|
|
11
20
|
*
|
|
12
21
|
* Import is rewritten: PageProps removed, AsyncPageProps / ResolvedPageProps
|
|
13
|
-
* added as needed, sorted alphabetically
|
|
22
|
+
* added as needed, sorted alphabetically.
|
|
14
23
|
*/
|
|
15
24
|
|
|
25
|
+
'use strict';
|
|
26
|
+
|
|
16
27
|
const TARGET_IMPORT = '@akinon/next/types';
|
|
17
28
|
|
|
29
|
+
// ─── helpers ────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
function fileIsClient(source) {
|
|
32
|
+
return /^\s*['"]use client['"]/.test(source);
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
function getEnclosingFunction(path) {
|
|
19
36
|
let cur = path;
|
|
20
37
|
while (cur.parent) {
|
|
@@ -40,11 +57,7 @@ function getFunctionName(fnPath) {
|
|
|
40
57
|
if (t === 'VariableDeclarator' && p.node.id && p.node.id.name) {
|
|
41
58
|
return p.node.id.name;
|
|
42
59
|
}
|
|
43
|
-
if (
|
|
44
|
-
t === 'Property' ||
|
|
45
|
-
t === 'ObjectProperty' ||
|
|
46
|
-
t === 'MethodDefinition'
|
|
47
|
-
) {
|
|
60
|
+
if (t === 'Property' || t === 'ObjectProperty' || t === 'MethodDefinition') {
|
|
48
61
|
const key = p.node.key;
|
|
49
62
|
if (key && key.name) return key.name;
|
|
50
63
|
}
|
|
@@ -60,44 +73,191 @@ function getFunctionName(fnPath) {
|
|
|
60
73
|
return null;
|
|
61
74
|
}
|
|
62
75
|
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
const isParamsMember = (node) =>
|
|
76
|
+
function isParamsMember(node) {
|
|
77
|
+
return (
|
|
66
78
|
node &&
|
|
67
79
|
node.type === 'MemberExpression' &&
|
|
68
80
|
node.property &&
|
|
69
|
-
(node.property.name === 'params' ||
|
|
70
|
-
|
|
81
|
+
(node.property.name === 'params' || node.property.name === 'searchParams')
|
|
82
|
+
);
|
|
83
|
+
}
|
|
71
84
|
|
|
85
|
+
function bodyUsesAsyncResolve(fnPath, j) {
|
|
86
|
+
let found = false;
|
|
72
87
|
j(fnPath)
|
|
73
88
|
.find(j.AwaitExpression)
|
|
74
|
-
.forEach((
|
|
75
|
-
if (isParamsMember(
|
|
89
|
+
.forEach((p) => {
|
|
90
|
+
if (isParamsMember(p.node.argument)) found = true;
|
|
76
91
|
});
|
|
77
92
|
j(fnPath)
|
|
78
93
|
.find(j.CallExpression, { callee: { name: 'use' } })
|
|
79
|
-
.forEach((
|
|
80
|
-
const arg =
|
|
94
|
+
.forEach((p) => {
|
|
95
|
+
const arg = p.node.arguments && p.node.arguments[0];
|
|
81
96
|
if (isParamsMember(arg)) found = true;
|
|
82
97
|
});
|
|
83
98
|
return found;
|
|
84
99
|
}
|
|
85
100
|
|
|
101
|
+
// ─── destructure analysis ────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Analyse the first parameter of a function.
|
|
105
|
+
* Returns null if the param is not an ObjectPattern that contains only
|
|
106
|
+
* params / searchParams (with optional nested destructuring).
|
|
107
|
+
*
|
|
108
|
+
* On success returns:
|
|
109
|
+
* {
|
|
110
|
+
* paramsPattern: AST node for the value of the `params` property,
|
|
111
|
+
* or null if not present.
|
|
112
|
+
* searchParamsPattern: same for `searchParams`.
|
|
113
|
+
* }
|
|
114
|
+
*/
|
|
115
|
+
function analyseDestructure(fnPath) {
|
|
116
|
+
const params = fnPath.node.params;
|
|
117
|
+
if (!params || params.length !== 1) return null;
|
|
118
|
+
|
|
119
|
+
const p = params[0];
|
|
120
|
+
if (p.type !== 'ObjectPattern') return null;
|
|
121
|
+
|
|
122
|
+
// Reject if any property is a rest element or something other than
|
|
123
|
+
// params / searchParams.
|
|
124
|
+
let paramsPattern = null;
|
|
125
|
+
let searchParamsPattern = null;
|
|
126
|
+
|
|
127
|
+
for (const prop of p.properties) {
|
|
128
|
+
// Rest element: e.g. `...rest`
|
|
129
|
+
if (prop.type === 'RestElement' || prop.type === 'ExperimentalRestProperty') {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
if (prop.type !== 'ObjectProperty' && prop.type !== 'Property') {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const keyName = (prop.key && prop.key.name) || null;
|
|
136
|
+
if (keyName === 'params') {
|
|
137
|
+
paramsPattern = prop.value;
|
|
138
|
+
} else if (keyName === 'searchParams') {
|
|
139
|
+
searchParamsPattern = prop.value;
|
|
140
|
+
} else {
|
|
141
|
+
// Any other property → not safe to refactor automatically.
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!paramsPattern && !searchParamsPattern) return null;
|
|
147
|
+
|
|
148
|
+
return { paramsPattern, searchParamsPattern };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── destructure refactor ────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* For a client-component function whose first param is destructured as
|
|
155
|
+
* `({ params, ... })`, rewrite to `(props: AsyncPageProps<T>)` and insert
|
|
156
|
+
* `const x = use(props.params)` at the top of the body.
|
|
157
|
+
*
|
|
158
|
+
* Does nothing if the body already has `use(props.params)` (idempotent).
|
|
159
|
+
*/
|
|
160
|
+
function refactorDestructure(fnPath, info, j) {
|
|
161
|
+
// Idempotency: bail if already refactored
|
|
162
|
+
if (bodyUsesAsyncResolve(fnPath, j)) return false;
|
|
163
|
+
|
|
164
|
+
const body = fnPath.node.body;
|
|
165
|
+
if (!body || body.type !== 'BlockStatement') return false;
|
|
166
|
+
|
|
167
|
+
const statements = [];
|
|
168
|
+
|
|
169
|
+
const makeUseCall = (propName) =>
|
|
170
|
+
j.callExpression(j.identifier('use'), [
|
|
171
|
+
j.memberExpression(j.identifier('props'), j.identifier(propName))
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
if (info.paramsPattern) {
|
|
175
|
+
statements.push(
|
|
176
|
+
j.variableDeclaration('const', [
|
|
177
|
+
j.variableDeclarator(info.paramsPattern, makeUseCall('params'))
|
|
178
|
+
])
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (info.searchParamsPattern) {
|
|
182
|
+
statements.push(
|
|
183
|
+
j.variableDeclaration('const', [
|
|
184
|
+
j.variableDeclarator(info.searchParamsPattern, makeUseCall('searchParams'))
|
|
185
|
+
])
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Replace the first parameter with a plain `props` identifier.
|
|
190
|
+
// The type annotation will be set separately (by the type-rename step).
|
|
191
|
+
const firstParam = fnPath.node.params[0];
|
|
192
|
+
const propsId = j.identifier('props');
|
|
193
|
+
// Carry over the TSTypeAnnotation that the ObjectPattern had (if any).
|
|
194
|
+
if (firstParam.typeAnnotation) {
|
|
195
|
+
propsId.typeAnnotation = firstParam.typeAnnotation;
|
|
196
|
+
}
|
|
197
|
+
fnPath.node.params[0] = propsId;
|
|
198
|
+
|
|
199
|
+
// Insert use() calls at the start of the block.
|
|
200
|
+
body.body.unshift(...statements);
|
|
201
|
+
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ─── `use` import ────────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
function ensureUseImport(root, j) {
|
|
208
|
+
const reactImports = root.find(j.ImportDeclaration, {
|
|
209
|
+
source: { value: 'react' }
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (reactImports.length === 0) {
|
|
213
|
+
// Insert a new import before the first import declaration.
|
|
214
|
+
const firstImport = root.find(j.ImportDeclaration).at(0);
|
|
215
|
+
if (firstImport.length) {
|
|
216
|
+
firstImport.insertBefore(
|
|
217
|
+
j.importDeclaration(
|
|
218
|
+
[j.importSpecifier(j.identifier('use'))],
|
|
219
|
+
j.literal('react')
|
|
220
|
+
)
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let hasUse = false;
|
|
227
|
+
reactImports.forEach((imp) => {
|
|
228
|
+
const specs = imp.node.specifiers || [];
|
|
229
|
+
if (
|
|
230
|
+
specs.some(
|
|
231
|
+
(s) => s.type === 'ImportSpecifier' && s.imported && s.imported.name === 'use'
|
|
232
|
+
)
|
|
233
|
+
) {
|
|
234
|
+
hasUse = true;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (!hasUse) {
|
|
239
|
+
reactImports.at(0).node.specifiers.push(
|
|
240
|
+
j.importSpecifier(j.identifier('use'))
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ─── main transform ──────────────────────────────────────────────────────────
|
|
246
|
+
|
|
86
247
|
function transform(fileInfo, api) {
|
|
87
248
|
const j = api.jscodeshift;
|
|
88
249
|
const root = j(fileInfo.source);
|
|
250
|
+
const isClient = fileIsClient(fileInfo.source);
|
|
89
251
|
|
|
90
252
|
const akinonImports = root.find(j.ImportDeclaration, {
|
|
91
253
|
source: { value: TARGET_IMPORT }
|
|
92
254
|
});
|
|
93
|
-
|
|
94
255
|
if (akinonImports.length === 0) return null;
|
|
95
256
|
|
|
96
257
|
let hasPageProps = false;
|
|
97
258
|
akinonImports.forEach((imp) => {
|
|
98
|
-
const specs = imp.node.specifiers || [];
|
|
99
259
|
if (
|
|
100
|
-
|
|
260
|
+
(imp.node.specifiers || []).some(
|
|
101
261
|
(s) =>
|
|
102
262
|
s.type === 'ImportSpecifier' &&
|
|
103
263
|
s.imported &&
|
|
@@ -107,11 +267,15 @@ function transform(fileInfo, api) {
|
|
|
107
267
|
hasPageProps = true;
|
|
108
268
|
}
|
|
109
269
|
});
|
|
110
|
-
|
|
111
270
|
if (!hasPageProps) return null;
|
|
112
271
|
|
|
113
272
|
let usedAsync = false;
|
|
114
273
|
let usedResolved = false;
|
|
274
|
+
let needUseImport = false;
|
|
275
|
+
|
|
276
|
+
// Collect all functions that need destructure refactoring so we do it
|
|
277
|
+
// after all type-renames (avoids mutating the AST under the iterator).
|
|
278
|
+
const toRefactor = []; // Array<{ fnPath, info }>
|
|
115
279
|
|
|
116
280
|
root
|
|
117
281
|
.find(j.TSTypeReference, {
|
|
@@ -120,24 +284,44 @@ function transform(fileInfo, api) {
|
|
|
120
284
|
.forEach((path) => {
|
|
121
285
|
const fnPath = getEnclosingFunction(path);
|
|
122
286
|
let target = 'AsyncPageProps';
|
|
287
|
+
|
|
123
288
|
if (fnPath) {
|
|
124
289
|
const fnName = getFunctionName(fnPath);
|
|
125
290
|
if (fnName === 'generateMetadata') {
|
|
126
291
|
target = 'AsyncPageProps';
|
|
292
|
+
} else if (isClient) {
|
|
293
|
+
target = 'AsyncPageProps';
|
|
294
|
+
// Collect for destructure refactor (if applicable).
|
|
295
|
+
const info = analyseDestructure(fnPath);
|
|
296
|
+
if (info) {
|
|
297
|
+
toRefactor.push({ fnPath, info });
|
|
298
|
+
}
|
|
127
299
|
} else if (bodyUsesAsyncResolve(fnPath, j)) {
|
|
128
300
|
target = 'AsyncPageProps';
|
|
129
301
|
} else {
|
|
130
302
|
target = 'ResolvedPageProps';
|
|
131
303
|
}
|
|
132
304
|
}
|
|
305
|
+
|
|
133
306
|
path.node.typeName = j.identifier(target);
|
|
134
307
|
if (target === 'AsyncPageProps') usedAsync = true;
|
|
135
308
|
else usedResolved = true;
|
|
136
309
|
});
|
|
137
310
|
|
|
311
|
+
// Perform destructure refactors (deferred to avoid iterator invalidation).
|
|
312
|
+
for (const { fnPath, info } of toRefactor) {
|
|
313
|
+
const didRefactor = refactorDestructure(fnPath, info, j);
|
|
314
|
+
if (didRefactor) needUseImport = true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (needUseImport) {
|
|
318
|
+
ensureUseImport(root, j);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Update @akinon/next/types import.
|
|
138
322
|
akinonImports.forEach((imp) => {
|
|
139
323
|
const specs = imp.node.specifiers || [];
|
|
140
|
-
const
|
|
324
|
+
const existing = new Set(
|
|
141
325
|
specs
|
|
142
326
|
.filter((s) => s.type === 'ImportSpecifier' && s.imported)
|
|
143
327
|
.map((s) => s.imported.name)
|
|
@@ -150,10 +334,10 @@ function transform(fileInfo, api) {
|
|
|
150
334
|
s.imported.name === 'PageProps'
|
|
151
335
|
)
|
|
152
336
|
);
|
|
153
|
-
if (usedAsync && !
|
|
337
|
+
if (usedAsync && !existing.has('AsyncPageProps')) {
|
|
154
338
|
next.push(j.importSpecifier(j.identifier('AsyncPageProps')));
|
|
155
339
|
}
|
|
156
|
-
if (usedResolved && !
|
|
340
|
+
if (usedResolved && !existing.has('ResolvedPageProps')) {
|
|
157
341
|
next.push(j.importSpecifier(j.identifier('ResolvedPageProps')));
|
|
158
342
|
}
|
|
159
343
|
next.sort((a, b) => {
|
|
@@ -163,7 +347,10 @@ function transform(fileInfo, api) {
|
|
|
163
347
|
imp.node.specifiers = next;
|
|
164
348
|
});
|
|
165
349
|
|
|
166
|
-
|
|
350
|
+
const output = root.toSource({ quote: 'single' });
|
|
351
|
+
// jscodeshift occasionally emits 'use client';; (double semi) when
|
|
352
|
+
// the directive is the first token — strip the duplicate.
|
|
353
|
+
return output.replace(/^(['"]use client['"]);;/m, '$1;');
|
|
167
354
|
}
|
|
168
355
|
|
|
169
356
|
module.exports = transform;
|
package/commands/plugins.ts
CHANGED
package/dist/commands/plugins.js
CHANGED
|
@@ -183,10 +183,6 @@ exports.default = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
183
183
|
name: 'Tamara Payment Extension',
|
|
184
184
|
value: 'pz-tamara-extension'
|
|
185
185
|
},
|
|
186
|
-
{
|
|
187
|
-
name: 'Similar Products',
|
|
188
|
-
value: 'pz-similar-products'
|
|
189
|
-
},
|
|
190
186
|
{
|
|
191
187
|
name: 'Hepsipay',
|
|
192
188
|
value: 'pz-hepsipay'
|
package/package.json
CHANGED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useSentryUncaughtErrors } from '@akinon/next/hooks';
|
|
4
|
-
|
|
5
|
-
export default function GlobalError({
|
|
6
|
-
error,
|
|
7
|
-
reset
|
|
8
|
-
}: {
|
|
9
|
-
error: Error & { digest?: string };
|
|
10
|
-
reset: () => void;
|
|
11
|
-
}) {
|
|
12
|
-
useSentryUncaughtErrors(error);
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<html>
|
|
16
|
-
<body>
|
|
17
|
-
<h2>Something went wrong!</h2>
|
|
18
|
-
<button onClick={() => reset()}>Try again</button>
|
|
19
|
-
</body>
|
|
20
|
-
</html>
|
|
21
|
-
);
|
|
22
|
-
}
|