@akinon/projectzero 2.0.15-rc.0 → 2.0.15

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 CHANGED
@@ -1,12 +1,10 @@
1
1
  # @akinon/projectzero
2
2
 
3
- ## 2.0.15-rc.0
3
+ ## 2.0.15
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - 143be2b9d: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
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
+ - 5df7a236: ZERO-4479: Add migrate-page-types codemod (PageProps -> AsyncPageProps/ResolvedPageProps) and integrate into upgrade-to-2
10
8
 
11
9
  ## 2.0.14
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,36 @@
1
1
  # projectzeronext
2
2
 
3
- ## 2.0.15-rc.0
4
-
5
- ### Patch Changes
6
-
7
- - 1a8622a9b: ZERO-3392: Implemented AI Search functionality and update env
8
- - b55acb768: ZERO-2577: Fix pagination bug and update usePagination hook and ensure pagination controls rendering correctly
9
- - 143be2b9d: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
10
- - 9f8cd3bc5: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
11
- - d99a6a7d5: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
12
- - c0228aff: ZERO-4159: Upgrade the package versions
13
- - b05c35435: ZERO-2570: Category filters routes to absolute url
14
- - e18836b2: ZERO-4160: Restore scope in Sentry addon configuration in akinon.json
15
- - c3244a31: ZERO-4013: Update runtime version to node:25-alpine in project configuration
16
- - Updated dependencies [0cf9ea23]
17
- - Updated dependencies [324f97d5]
18
- - Updated dependencies [51ea0688]
19
- - Updated dependencies
20
- - Updated dependencies [eba125b2]
21
- - Updated dependencies [823a4449]
22
- - Updated dependencies [b55acb768]
23
- - Updated dependencies [760258c1]
24
- - Updated dependencies [5da13fff]
25
- - Updated dependencies [143be2b9d]
26
- - Updated dependencies [7889b08f]
27
- - Updated dependencies [fb4aa9b4]
28
- - Updated dependencies [9f8cd3bc5]
29
- - Updated dependencies [d51fa68e]
30
- - Updated dependencies [bfafa3f4]
31
- - Updated dependencies [00b9f488]
32
- - Updated dependencies [57d7eb30]
33
- - Updated dependencies [d99a6a7d5]
34
- - Updated dependencies [9db81a71]
35
- - Updated dependencies [591e345e]
36
- - Updated dependencies [0df6bc1f]
37
- - Updated dependencies [4de5303c5]
38
- - Updated dependencies [63a72000]
39
- - Updated dependencies [95b139dc]
40
- - Updated dependencies [1d00f2d0]
41
- - Updated dependencies [4ac7b2a1]
42
- - Updated dependencies [4998a963]
43
- - Updated dependencies [c0228aff]
44
- - Updated dependencies [3909d322]
45
- - Updated dependencies [e18836b2]
46
- - @akinon/next@2.0.15-rc.0
47
- - @akinon/pz-checkout-gift-pack@2.0.15-rc.0
48
- - @akinon/pz-one-click-checkout@2.0.15-rc.0
49
- - @akinon/pz-basket-gift-pack@2.0.15-rc.0
50
- - @akinon/pz-masterpass-rest@2.0.15-rc.0
51
- - @akinon/pz-pay-on-delivery@2.0.15-rc.0
52
- - @akinon/pz-credit-payment@2.0.15-rc.0
53
- - @akinon/pz-click-collect@2.0.15-rc.0
54
- - @akinon/pz-multi-basket@2.0.15-rc.0
55
- - @akinon/pz-masterpass@2.0.15-rc.0
56
- - @akinon/pz-gpay@2.0.15-rc.0
57
- - @akinon/pz-b2b@2.0.15-rc.0
58
- - @akinon/pz-bkm@2.0.15-rc.0
59
- - @akinon/pz-otp@2.0.15-rc.0
60
- - @akinon/pz-similar-products@2.0.15-rc.0
61
- - @akinon/pz-virtual-try-on@2.0.15-rc.0
62
- - @akinon/pz-theme@2.0.15-rc.0
63
- - @akinon/pz-akifast@2.0.15-rc.0
64
- - @akinon/pz-apple-pay@2.0.15-rc.0
65
- - @akinon/pz-cybersource-uc@2.0.15-rc.0
66
- - @akinon/pz-flow-payment@2.0.15-rc.0
67
- - @akinon/pz-google-pay@2.0.15-rc.0
68
- - @akinon/pz-haso@2.0.15-rc.0
69
- - @akinon/pz-hepsipay@2.0.15-rc.0
70
- - @akinon/pz-saved-card@2.0.15-rc.0
71
- - @akinon/pz-tabby-extension@2.0.15-rc.0
72
- - @akinon/pz-tamara-extension@2.0.15-rc.0
3
+ ## 2.0.15
4
+
5
+ ### Patch Changes
6
+
7
+ - @akinon/next@2.0.15
8
+ - @akinon/pz-akifast@2.0.15
9
+ - @akinon/pz-apple-pay@2.0.15
10
+ - @akinon/pz-b2b@2.0.15
11
+ - @akinon/pz-basket-gift-pack@2.0.15
12
+ - @akinon/pz-bkm@2.0.15
13
+ - @akinon/pz-checkout-gift-pack@2.0.15
14
+ - @akinon/pz-click-collect@2.0.15
15
+ - @akinon/pz-credit-payment@2.0.15
16
+ - @akinon/pz-cybersource-uc@2.0.15
17
+ - @akinon/pz-flow-payment@2.0.15
18
+ - @akinon/pz-google-pay@2.0.15
19
+ - @akinon/pz-gpay@2.0.15
20
+ - @akinon/pz-haso@2.0.15
21
+ - @akinon/pz-hepsipay@2.0.15
22
+ - @akinon/pz-masterpass@2.0.15
23
+ - @akinon/pz-masterpass-rest@2.0.15
24
+ - @akinon/pz-multi-basket@2.0.15
25
+ - @akinon/pz-one-click-checkout@2.0.15
26
+ - @akinon/pz-otp@2.0.15
27
+ - @akinon/pz-pay-on-delivery@2.0.15
28
+ - @akinon/pz-saved-card@2.0.15
29
+ - @akinon/pz-similar-products@2.0.15
30
+ - @akinon/pz-tabby-extension@2.0.15
31
+ - @akinon/pz-tamara-extension@2.0.15
32
+ - @akinon/pz-theme@2.0.15
33
+ - @akinon/pz-virtual-try-on@2.0.15
73
34
 
74
35
  ## 2.0.14
75
36
 
@@ -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.15-rc.0",
3
+ "version": "2.0.15",
4
4
  "private": true,
5
5
  "license": "MIT",
6
6
  "scripts": {
@@ -24,33 +24,33 @@
24
24
  "test:middleware": "jest middleware-matcher.test.ts --bail"
25
25
  },
26
26
  "dependencies": {
27
- "@akinon/next": "2.0.15-rc.0",
28
- "@akinon/pz-akifast": "2.0.15-rc.0",
29
- "@akinon/pz-apple-pay": "2.0.15-rc.0",
30
- "@akinon/pz-b2b": "2.0.15-rc.0",
31
- "@akinon/pz-basket-gift-pack": "2.0.15-rc.0",
32
- "@akinon/pz-bkm": "2.0.15-rc.0",
33
- "@akinon/pz-checkout-gift-pack": "2.0.15-rc.0",
34
- "@akinon/pz-click-collect": "2.0.15-rc.0",
35
- "@akinon/pz-credit-payment": "2.0.15-rc.0",
36
- "@akinon/pz-cybersource-uc": "2.0.15-rc.0",
37
- "@akinon/pz-flow-payment": "2.0.15-rc.0",
38
- "@akinon/pz-google-pay": "2.0.15-rc.0",
39
- "@akinon/pz-gpay": "2.0.15-rc.0",
40
- "@akinon/pz-haso": "2.0.15-rc.0",
41
- "@akinon/pz-hepsipay": "2.0.15-rc.0",
42
- "@akinon/pz-masterpass": "2.0.15-rc.0",
43
- "@akinon/pz-masterpass-rest": "2.0.15-rc.0",
44
- "@akinon/pz-multi-basket": "2.0.15-rc.0",
45
- "@akinon/pz-one-click-checkout": "2.0.15-rc.0",
46
- "@akinon/pz-otp": "2.0.15-rc.0",
47
- "@akinon/pz-pay-on-delivery": "2.0.15-rc.0",
48
- "@akinon/pz-saved-card": "2.0.15-rc.0",
49
- "@akinon/pz-similar-products": "2.0.15-rc.0",
50
- "@akinon/pz-tabby-extension": "2.0.15-rc.0",
51
- "@akinon/pz-tamara-extension": "2.0.15-rc.0",
52
- "@akinon/pz-theme": "2.0.15-rc.0",
53
- "@akinon/pz-virtual-try-on": "2.0.15-rc.0",
27
+ "@akinon/next": "2.0.15",
28
+ "@akinon/pz-akifast": "2.0.15",
29
+ "@akinon/pz-apple-pay": "2.0.15",
30
+ "@akinon/pz-b2b": "2.0.15",
31
+ "@akinon/pz-basket-gift-pack": "2.0.15",
32
+ "@akinon/pz-bkm": "2.0.15",
33
+ "@akinon/pz-checkout-gift-pack": "2.0.15",
34
+ "@akinon/pz-click-collect": "2.0.15",
35
+ "@akinon/pz-credit-payment": "2.0.15",
36
+ "@akinon/pz-cybersource-uc": "2.0.15",
37
+ "@akinon/pz-flow-payment": "2.0.15",
38
+ "@akinon/pz-google-pay": "2.0.15",
39
+ "@akinon/pz-gpay": "2.0.15",
40
+ "@akinon/pz-haso": "2.0.15",
41
+ "@akinon/pz-hepsipay": "2.0.15",
42
+ "@akinon/pz-masterpass": "2.0.15",
43
+ "@akinon/pz-masterpass-rest": "2.0.15",
44
+ "@akinon/pz-multi-basket": "2.0.15",
45
+ "@akinon/pz-one-click-checkout": "2.0.15",
46
+ "@akinon/pz-otp": "2.0.15",
47
+ "@akinon/pz-pay-on-delivery": "2.0.15",
48
+ "@akinon/pz-saved-card": "2.0.15",
49
+ "@akinon/pz-similar-products": "2.0.15",
50
+ "@akinon/pz-tabby-extension": "2.0.15",
51
+ "@akinon/pz-tamara-extension": "2.0.15",
52
+ "@akinon/pz-theme": "2.0.15",
53
+ "@akinon/pz-virtual-try-on": "2.0.15",
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.21.0",
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.5",
71
- "react-string-replace": "1.1.1",
72
- "start-server-and-test": "2.0.8",
73
- "tailwind-merge": "1.14.0",
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.15-rc.0",
77
+ "@akinon/eslint-plugin-projectzero": "2.0.15",
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.2.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.4.2",
99
+ "jest-css-modules-transform": "4.3.0",
100
100
  "jest-environment-jsdom": "29.7.0",
101
- "lint-staged": "13.3.0",
102
- "prettier": "2.8.8",
101
+ "lint-staged": "13.1.0",
102
+ "prettier": "2.6.2",
103
103
  "react-number-format": "5.3.4",
104
- "sass": "1.83.0",
104
+ "sass": "1.49.9",
105
105
  "semantic-release": "19.0.5",
106
106
  "server-only": "0.0.1",
107
- "stylelint": "14.16.1",
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.7.0",
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",
@@ -1,3 +1 @@
1
- export * from './use-contract';
2
- export * from './use-fav-button';
3
1
  export * from './use-add-product-to-basket';
@@ -19,7 +19,6 @@ module.exports = [
19
19
  'pz-hepsipay',
20
20
  'pz-flow-payment',
21
21
  'pz-virtual-try-on',
22
- 'pz-similar-products',
23
22
  'pz-cybersource-uc',
24
23
  'pz-hepsipay',
25
24
  'pz-masterpass-rest',
@@ -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
- import { Icon, Input } from '@theme/components';
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
- <Input
70
+ <input
78
71
  value={searchText}
79
- onChange={handleSearchTextChange}
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={handleCloseSearch}
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: number) => {
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:grid lg:grid-cols-6">
47
- <div className="lg:col-span-1">
48
- <div className="flex flex-col items-center justify-center md:mr-[6px]">
49
- <button
50
- onClick={goToPrev}
51
- className={twMerge(
52
- 'hidden justify-center p-2 mb-3 border border-gray-100 rounded-full cursor-pointer lg:block',
53
- [activeIndex === 0 && 'cursor-not-allowed opacity-45']
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
- {product?.productimage_set?.map((item, i) => (
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={i}
61
+ key={index}
121
62
  src={item.image}
122
- alt={product?.name || 'Product image'}
123
- draggable={false}
124
- aspectRatio={484 / 726}
125
- sizes="(min-width: 425px) 512px,
126
- (min-width: 601px) 576px,
127
- (min-width: 768px) 336px,
128
- (min-width: 1024px) 484px, 368px"
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
- </CarouselCore>
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
  }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * migrate-page-types: rewrites `PageProps<T>` from `@akinon/next/types` into
3
+ * `AsyncPageProps<T>` / `ResolvedPageProps<T>` across a brand's `src/`.
4
+ *
5
+ * Why: `@next/codemod next-async-request-api` updates the call sites
6
+ * (`await props.params`, `use(props.params)`), but the @akinon/next legacy
7
+ * `PageProps<T>` type is still sync and clashes with Next 16's internal
8
+ * `.next/types/.../page.ts` validator. Manually fixing every brand on every
9
+ * upgrade is the same change repeated — this codemod automates it.
10
+ *
11
+ * Usage:
12
+ * cd <brand-root>
13
+ * npx @akinon/projectzero codemod --codemod=migrate-page-types [--dry-run]
14
+ *
15
+ * Or via the upgrade-to-2 chain (no flag needed).
16
+ */
17
+
18
+ const path = require('path');
19
+ const fs = require('fs');
20
+ const jscodeshift = require('jscodeshift/src/Runner');
21
+
22
+ function log(msg) {
23
+ const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
24
+ console.log(`[${ts}] [migrate-page-types] ${msg}`);
25
+ }
26
+
27
+ const transform = () => {
28
+ const workingDir = path.resolve(process.cwd());
29
+ const dryRun = process.argv.includes('--dry-run');
30
+
31
+ log(`starting in ${workingDir}${dryRun ? ' (DRY RUN)' : ''}`);
32
+
33
+ const srcDir = path.join(workingDir, 'src');
34
+ if (!fs.existsSync(srcDir)) {
35
+ log('No src/ directory found, nothing to migrate.');
36
+ return Promise.resolve();
37
+ }
38
+
39
+ const transformPath = path.resolve(__dirname, 'transform.js');
40
+
41
+ return jscodeshift
42
+ .run(transformPath, [srcDir], {
43
+ verbose: 0,
44
+ dry: Boolean(dryRun),
45
+ print: Boolean(dryRun),
46
+ extensions: 'ts,tsx',
47
+ parser: 'tsx',
48
+ ignorePattern: '**/node_modules/**',
49
+ silent: true
50
+ })
51
+ .then(
52
+ (stats) => {
53
+ log(
54
+ `${stats.ok} changed, ${stats.nochange} unchanged, ${stats.error} errored`
55
+ );
56
+ log('done');
57
+ },
58
+ (err) => {
59
+ log(`FAILED: ${err.message}`);
60
+ throw err;
61
+ }
62
+ );
63
+ };
64
+
65
+ module.exports = { transform };
66
+
67
+ if (require.main === module) {
68
+ const result = transform();
69
+ if (result && typeof result.then === 'function') {
70
+ result.catch((err) => {
71
+ console.error(err);
72
+ process.exit(1);
73
+ });
74
+ }
75
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * jscodeshift transform: rewrite `PageProps<T>` from `@akinon/next/types`
3
+ * into `AsyncPageProps<T>` or `ResolvedPageProps<T>` per usage context.
4
+ *
5
+ * Heuristic per occurrence:
6
+ * - Enclosing function is `generateMetadata` → AsyncPageProps
7
+ * - Body uses `await props.params` / `await props.searchParams` or
8
+ * `use(props.params)` / `use(props.searchParams)` → AsyncPageProps
9
+ * - Otherwise (destructured params accessed synchronously, typical Page
10
+ * wrapped in `withSegmentDefaults`) → ResolvedPageProps
11
+ *
12
+ * Import is rewritten: PageProps removed, AsyncPageProps / ResolvedPageProps
13
+ * added as needed, sorted alphabetically (matches prettier output).
14
+ */
15
+
16
+ const TARGET_IMPORT = '@akinon/next/types';
17
+
18
+ function getEnclosingFunction(path) {
19
+ let cur = path;
20
+ while (cur.parent) {
21
+ cur = cur.parent;
22
+ const t = cur.node && cur.node.type;
23
+ if (
24
+ t === 'FunctionDeclaration' ||
25
+ t === 'FunctionExpression' ||
26
+ t === 'ArrowFunctionExpression'
27
+ ) {
28
+ return cur;
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+
34
+ function getFunctionName(fnPath) {
35
+ const node = fnPath.node;
36
+ if (node.id && node.id.name) return node.id.name;
37
+ let p = fnPath.parent;
38
+ while (p) {
39
+ const t = p.node && p.node.type;
40
+ if (t === 'VariableDeclarator' && p.node.id && p.node.id.name) {
41
+ return p.node.id.name;
42
+ }
43
+ if (
44
+ t === 'Property' ||
45
+ t === 'ObjectProperty' ||
46
+ t === 'MethodDefinition'
47
+ ) {
48
+ const key = p.node.key;
49
+ if (key && key.name) return key.name;
50
+ }
51
+ if (
52
+ t === 'FunctionDeclaration' ||
53
+ t === 'FunctionExpression' ||
54
+ t === 'ArrowFunctionExpression'
55
+ ) {
56
+ break;
57
+ }
58
+ p = p.parent;
59
+ }
60
+ return null;
61
+ }
62
+
63
+ function bodyUsesAsyncResolve(fnPath, j) {
64
+ let found = false;
65
+ const isParamsMember = (node) =>
66
+ node &&
67
+ node.type === 'MemberExpression' &&
68
+ node.property &&
69
+ (node.property.name === 'params' ||
70
+ node.property.name === 'searchParams');
71
+
72
+ j(fnPath)
73
+ .find(j.AwaitExpression)
74
+ .forEach((awaitPath) => {
75
+ if (isParamsMember(awaitPath.node.argument)) found = true;
76
+ });
77
+ j(fnPath)
78
+ .find(j.CallExpression, { callee: { name: 'use' } })
79
+ .forEach((callPath) => {
80
+ const arg = callPath.node.arguments && callPath.node.arguments[0];
81
+ if (isParamsMember(arg)) found = true;
82
+ });
83
+ return found;
84
+ }
85
+
86
+ function transform(fileInfo, api) {
87
+ const j = api.jscodeshift;
88
+ const root = j(fileInfo.source);
89
+
90
+ const akinonImports = root.find(j.ImportDeclaration, {
91
+ source: { value: TARGET_IMPORT }
92
+ });
93
+
94
+ if (akinonImports.length === 0) return null;
95
+
96
+ let hasPageProps = false;
97
+ akinonImports.forEach((imp) => {
98
+ const specs = imp.node.specifiers || [];
99
+ if (
100
+ specs.some(
101
+ (s) =>
102
+ s.type === 'ImportSpecifier' &&
103
+ s.imported &&
104
+ s.imported.name === 'PageProps'
105
+ )
106
+ ) {
107
+ hasPageProps = true;
108
+ }
109
+ });
110
+
111
+ if (!hasPageProps) return null;
112
+
113
+ let usedAsync = false;
114
+ let usedResolved = false;
115
+
116
+ root
117
+ .find(j.TSTypeReference, {
118
+ typeName: { type: 'Identifier', name: 'PageProps' }
119
+ })
120
+ .forEach((path) => {
121
+ const fnPath = getEnclosingFunction(path);
122
+ let target = 'AsyncPageProps';
123
+ if (fnPath) {
124
+ const fnName = getFunctionName(fnPath);
125
+ if (fnName === 'generateMetadata') {
126
+ target = 'AsyncPageProps';
127
+ } else if (bodyUsesAsyncResolve(fnPath, j)) {
128
+ target = 'AsyncPageProps';
129
+ } else {
130
+ target = 'ResolvedPageProps';
131
+ }
132
+ }
133
+ path.node.typeName = j.identifier(target);
134
+ if (target === 'AsyncPageProps') usedAsync = true;
135
+ else usedResolved = true;
136
+ });
137
+
138
+ akinonImports.forEach((imp) => {
139
+ const specs = imp.node.specifiers || [];
140
+ const existingNames = new Set(
141
+ specs
142
+ .filter((s) => s.type === 'ImportSpecifier' && s.imported)
143
+ .map((s) => s.imported.name)
144
+ );
145
+ const next = specs.filter(
146
+ (s) =>
147
+ !(
148
+ s.type === 'ImportSpecifier' &&
149
+ s.imported &&
150
+ s.imported.name === 'PageProps'
151
+ )
152
+ );
153
+ if (usedAsync && !existingNames.has('AsyncPageProps')) {
154
+ next.push(j.importSpecifier(j.identifier('AsyncPageProps')));
155
+ }
156
+ if (usedResolved && !existingNames.has('ResolvedPageProps')) {
157
+ next.push(j.importSpecifier(j.identifier('ResolvedPageProps')));
158
+ }
159
+ next.sort((a, b) => {
160
+ if (a.type !== 'ImportSpecifier' || b.type !== 'ImportSpecifier') return 0;
161
+ return a.imported.name.localeCompare(b.imported.name);
162
+ });
163
+ imp.node.specifiers = next;
164
+ });
165
+
166
+ return root.toSource({ quote: 'single' });
167
+ }
168
+
169
+ module.exports = transform;
@@ -11,8 +11,9 @@
11
11
  * 6. Run migrate-auth-v5 codemod (next-auth v4 -> v5)
12
12
  * 7. Run sentry-9 codemod (only when @sentry/nextjs is present)
13
13
  * 8. Run migrate-eslint codemod (ESLint v9 flat config)
14
- * 9. yarn build (validation)
15
- * 10. Summary report
14
+ * 9. Run migrate-page-types codemod (PageProps -> Async/Resolved)
15
+ * 10. yarn build (validation)
16
+ * 11. Summary report
16
17
  *
17
18
  * Out of scope in this first version:
18
19
  * - Tailwind v3 -> v4 migration (run manually with @tailwindcss/upgrade)
@@ -32,6 +33,7 @@
32
33
  * --skip-auth Do not run migrate-auth-v5
33
34
  * --skip-sentry Do not run sentry-9 (auto-skipped if not needed)
34
35
  * --skip-eslint Do not run migrate-eslint
36
+ * --skip-page-types Do not run migrate-page-types
35
37
  * --skip-install Do not run yarn install
36
38
  * --skip-build Do not run yarn build
37
39
  * --interactive Pause for confirmation after each step
@@ -46,6 +48,7 @@ const readline = require('readline');
46
48
  const AUTH_CODEMOD = path.resolve(__dirname, '..', 'migrate-auth-v5');
47
49
  const SENTRY_CODEMOD = path.resolve(__dirname, '..', 'sentry-9');
48
50
  const ESLINT_CODEMOD = path.resolve(__dirname, '..', 'migrate-eslint');
51
+ const PAGE_TYPES_CODEMOD = path.resolve(__dirname, '..', 'migrate-page-types');
49
52
 
50
53
  const TARGET_MATRIX = {
51
54
  dependencies: {
@@ -134,6 +137,7 @@ function parseArgs(argv) {
134
137
  skipAuth: argv.includes('--skip-auth'),
135
138
  skipSentry: argv.includes('--skip-sentry'),
136
139
  skipEslint: argv.includes('--skip-eslint'),
140
+ skipPageTypes: argv.includes('--skip-page-types'),
137
141
  skipInstall: argv.includes('--skip-install'),
138
142
  skipBuild: argv.includes('--skip-build'),
139
143
  interactive: argv.includes('--interactive'),
@@ -154,7 +158,7 @@ function writePackageJson(cwd, pkg) {
154
158
  }
155
159
 
156
160
  function stepPreflight(cwd, flags) {
157
- logStep('[1/10] Preflight', 'starting');
161
+ logStep('[1/11] Preflight', 'starting');
158
162
 
159
163
  const pkg = readPackageJson(cwd);
160
164
  if (!pkg) {
@@ -188,19 +192,19 @@ function stepPreflight(cwd, flags) {
188
192
  log(` Current: next@${currentNext} react@${currentReact} next-auth@${currentAuth} @akinon/next@${currentAkinon}`);
189
193
  log(` Target: next@${TARGET_MATRIX.dependencies.next} react@${TARGET_MATRIX.dependencies.react} next-auth@${TARGET_MATRIX.dependencies['next-auth']} @akinon/next@${TARGET_MATRIX.dependencies['@akinon/next']}`);
190
194
 
191
- logStep('[1/10] Preflight', 'ok');
195
+ logStep('[1/11] Preflight', 'ok');
192
196
  }
193
197
 
194
198
  function stepBackup(cwd, flags) {
195
199
  if (flags.skipBackup) {
196
- logStep('[2/10] Backup branch', 'skipped');
200
+ logStep('[2/11] Backup branch', 'skipped');
197
201
  return null;
198
202
  }
199
203
  const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
200
204
  const branchName = `pre-upgrade-2-${today}`;
201
205
 
202
206
  if (flags.dryRun) {
203
- logStep('[2/10] Backup branch', `[DRY] git branch ${branchName}`);
207
+ logStep('[2/11] Backup branch', `[DRY] git branch ${branchName}`);
204
208
  return branchName;
205
209
  }
206
210
 
@@ -210,7 +214,7 @@ function stepBackup(cwd, flags) {
210
214
  { cwd, encoding: 'utf-8' }
211
215
  );
212
216
  if (existing.status === 0) {
213
- logStep('[2/10] Backup branch', `already exists: ${branchName}`);
217
+ logStep('[2/11] Backup branch', `already exists: ${branchName}`);
214
218
  return branchName;
215
219
  }
216
220
 
@@ -221,7 +225,7 @@ function stepBackup(cwd, flags) {
221
225
  if (result.status !== 0) {
222
226
  throw new Error(`Failed to create backup branch ${branchName}`);
223
227
  }
224
- logStep('[2/10] Backup branch', `created ${branchName}`);
228
+ logStep('[2/11] Backup branch', `created ${branchName}`);
225
229
  return branchName;
226
230
  }
227
231
 
@@ -319,7 +323,7 @@ function pinNextScriptsToWebpack(pkg) {
319
323
 
320
324
  function stepBump(cwd, flags) {
321
325
  if (flags.skipBump) {
322
- logStep('[3/10] Bump deps', 'skipped');
326
+ logStep('[3/11] Bump deps', 'skipped');
323
327
  return { hasSentry: false, changes: [] };
324
328
  }
325
329
 
@@ -367,16 +371,16 @@ function stepBump(cwd, flags) {
367
371
  allChanges.push(...scriptChanges);
368
372
 
369
373
  if (allChanges.length === 0) {
370
- logStep('[3/10] Bump deps', 'already at target, no changes');
374
+ logStep('[3/11] Bump deps', 'already at target, no changes');
371
375
  return { hasSentry, changes: [] };
372
376
  }
373
377
 
374
378
  if (flags.dryRun) {
375
- logStep('[3/10] Bump deps', `[DRY] ${allChanges.length} changes`);
379
+ logStep('[3/11] Bump deps', `[DRY] ${allChanges.length} changes`);
376
380
  allChanges.forEach((c) => log(` - ${c}`));
377
381
  } else {
378
382
  writePackageJson(cwd, pkg);
379
- logStep('[3/10] Bump deps', `${allChanges.length} changes applied`);
383
+ logStep('[3/11] Bump deps', `${allChanges.length} changes applied`);
380
384
  if (flags.verbose) allChanges.forEach((c) => log(` - ${c}`));
381
385
  }
382
386
 
@@ -385,28 +389,28 @@ function stepBump(cwd, flags) {
385
389
 
386
390
  function stepNextCodemod(cwd, flags) {
387
391
  if (flags.skipNextCodemod) {
388
- logStep('[5/10] Next codemod', 'skipped');
392
+ logStep('[5/11] Next codemod', 'skipped');
389
393
  return { ok: true };
390
394
  }
391
395
  const cmd = flags.dryRun
392
396
  ? 'npx --yes @next/codemod@latest upgrade latest --verbose'
393
397
  : 'npx --yes @next/codemod@latest upgrade latest';
394
- logStep('[5/10] Next codemod', `running ${cmd}`);
398
+ logStep('[5/11] Next codemod', `running ${cmd}`);
395
399
  const result = runCommand(cmd, { cwd, dryRun: false, verbose: flags.verbose });
396
400
  if (result.status !== 0) {
397
- logStep('[5/10] Next codemod', `FAILED (exit ${result.status})`);
401
+ logStep('[5/11] Next codemod', `FAILED (exit ${result.status})`);
398
402
  return { ok: false };
399
403
  }
400
- logStep('[5/10] Next codemod', 'ok');
404
+ logStep('[5/11] Next codemod', 'ok');
401
405
  return { ok: true };
402
406
  }
403
407
 
404
408
  function stepAuthCodemod(cwd, flags) {
405
409
  if (flags.skipAuth) {
406
- logStep('[6/10] Auth codemod', 'skipped');
410
+ logStep('[6/11] Auth codemod', 'skipped');
407
411
  return { ok: true };
408
412
  }
409
- logStep('[6/10] Auth codemod', 'running migrate-auth-v5');
413
+ logStep('[6/11] Auth codemod', 'running migrate-auth-v5');
410
414
  try {
411
415
  const prevArgv = process.argv;
412
416
  process.argv = [
@@ -426,21 +430,21 @@ function stepAuthCodemod(cwd, flags) {
426
430
  return result.then(
427
431
  () => {
428
432
  finish();
429
- logStep('[6/10] Auth codemod', 'ok');
433
+ logStep('[6/11] Auth codemod', 'ok');
430
434
  return { ok: true };
431
435
  },
432
436
  (err) => {
433
437
  finish();
434
- logStep('[6/10] Auth codemod', `FAILED: ${err.message}`);
438
+ logStep('[6/11] Auth codemod', `FAILED: ${err.message}`);
435
439
  return { ok: false };
436
440
  }
437
441
  );
438
442
  }
439
443
  finish();
440
- logStep('[6/10] Auth codemod', 'ok');
444
+ logStep('[6/11] Auth codemod', 'ok');
441
445
  return { ok: true };
442
446
  } catch (err) {
443
- logStep('[6/10] Auth codemod', `FAILED: ${err.message}`);
447
+ logStep('[6/11] Auth codemod', `FAILED: ${err.message}`);
444
448
  return { ok: false };
445
449
  }
446
450
  }
@@ -448,34 +452,34 @@ function stepAuthCodemod(cwd, flags) {
448
452
  function stepSentryCodemod(cwd, flags, hasSentry) {
449
453
  if (flags.skipSentry || !hasSentry) {
450
454
  const reason = flags.skipSentry ? 'skipped' : 'no @sentry/nextjs, skipping';
451
- logStep('[7/10] Sentry codemod', reason);
455
+ logStep('[7/11] Sentry codemod', reason);
452
456
  return { ok: true };
453
457
  }
454
458
  if (flags.dryRun) {
455
- logStep('[7/10] Sentry codemod', '[DRY] would run sentry-9');
459
+ logStep('[7/11] Sentry codemod', '[DRY] would run sentry-9');
456
460
  return { ok: true };
457
461
  }
458
- logStep('[7/10] Sentry codemod', 'running sentry-9');
462
+ logStep('[7/11] Sentry codemod', 'running sentry-9');
459
463
  try {
460
464
  const savedCwd = process.cwd();
461
465
  process.chdir(cwd);
462
466
  const codemod = require(SENTRY_CODEMOD);
463
467
  codemod.transform();
464
468
  process.chdir(savedCwd);
465
- logStep('[7/10] Sentry codemod', 'ok');
469
+ logStep('[7/11] Sentry codemod', 'ok');
466
470
  return { ok: true };
467
471
  } catch (err) {
468
- logStep('[7/10] Sentry codemod', `FAILED: ${err.message}`);
472
+ logStep('[7/11] Sentry codemod', `FAILED: ${err.message}`);
469
473
  return { ok: false };
470
474
  }
471
475
  }
472
476
 
473
477
  function stepEslintCodemod(cwd, flags) {
474
478
  if (flags.skipEslint) {
475
- logStep('[8/10] ESLint codemod', 'skipped');
479
+ logStep('[8/11] ESLint codemod', 'skipped');
476
480
  return { ok: true };
477
481
  }
478
- logStep('[8/10] ESLint codemod', 'running migrate-eslint');
482
+ logStep('[8/11] ESLint codemod', 'running migrate-eslint');
479
483
  try {
480
484
  const prevArgv = process.argv;
481
485
  process.argv = [
@@ -489,24 +493,68 @@ function stepEslintCodemod(cwd, flags) {
489
493
  codemod.transform();
490
494
  process.chdir(savedCwd);
491
495
  process.argv = prevArgv;
492
- logStep('[8/10] ESLint codemod', 'ok');
496
+ logStep('[8/11] ESLint codemod', 'ok');
493
497
  return { ok: true };
494
498
  } catch (err) {
495
- logStep('[8/10] ESLint codemod', `FAILED: ${err.message}`);
499
+ logStep('[8/11] ESLint codemod', `FAILED: ${err.message}`);
500
+ return { ok: false };
501
+ }
502
+ }
503
+
504
+ function stepPageTypesCodemod(cwd, flags) {
505
+ if (flags.skipPageTypes) {
506
+ logStep('[9/11] Page types codemod', 'skipped');
507
+ return { ok: true };
508
+ }
509
+ logStep('[9/11] Page types codemod', 'running migrate-page-types');
510
+ try {
511
+ const prevArgv = process.argv;
512
+ process.argv = [
513
+ process.argv[0],
514
+ process.argv[1],
515
+ ...(flags.dryRun ? ['--dry-run'] : [])
516
+ ];
517
+ const savedCwd = process.cwd();
518
+ process.chdir(cwd);
519
+ const codemod = require(PAGE_TYPES_CODEMOD);
520
+ const result = codemod.transform();
521
+ const finish = () => {
522
+ process.chdir(savedCwd);
523
+ process.argv = prevArgv;
524
+ };
525
+ if (result && typeof result.then === 'function') {
526
+ return result.then(
527
+ () => {
528
+ finish();
529
+ logStep('[9/11] Page types codemod', 'ok');
530
+ return { ok: true };
531
+ },
532
+ (err) => {
533
+ finish();
534
+ logStep('[9/11] Page types codemod', `FAILED: ${err.message}`);
535
+ return { ok: false };
536
+ }
537
+ );
538
+ }
539
+ finish();
540
+ logStep('[9/11] Page types codemod', 'ok');
541
+ return { ok: true };
542
+ } catch (err) {
543
+ logStep('[9/11] Page types codemod', `FAILED: ${err.message}`);
496
544
  return { ok: false };
497
545
  }
498
546
  }
499
547
 
500
548
  function stepInstall(cwd, flags) {
501
549
  if (flags.skipInstall) {
502
- logStep('[4/10] yarn install', 'skipped');
550
+ logStep('[4/11] yarn install', 'skipped');
503
551
  return { ok: true };
504
552
  }
505
553
  if (flags.dryRun) {
506
- logStep('[4/10] yarn install', '[DRY] yarn clean && yarn');
554
+ logStep('[4/11] yarn install', '[DRY] yarn clean && yarn');
507
555
  return { ok: true };
508
556
  }
509
- logStep('[4/10] yarn install', 'running yarn clean && yarn');
557
+ logStep('[4/11] yarn install', 'running yarn clean && yarn');
510
558
  const clean = runCommand('yarn clean', {
511
559
  cwd,
512
560
  dryRun: false,
@@ -521,33 +569,33 @@ function stepInstall(cwd, flags) {
521
569
  verbose: flags.verbose
522
570
  });
523
571
  if (install.status !== 0) {
524
- logStep('[4/10] yarn install', `FAILED (exit ${install.status})`);
572
+ logStep('[4/11] yarn install', `FAILED (exit ${install.status})`);
525
573
  return { ok: false };
526
574
  }
527
- logStep('[4/10] yarn install', 'ok');
575
+ logStep('[4/11] yarn install', 'ok');
528
576
  return { ok: true };
529
577
  }
530
578
 
531
579
  function stepBuild(cwd, flags) {
532
580
  if (flags.skipBuild) {
533
- logStep('[9/10] yarn build', 'skipped');
581
+ logStep('[10/11] yarn build', 'skipped');
534
582
  return { ok: true };
535
583
  }
536
584
  if (flags.dryRun) {
537
- logStep('[9/10] yarn build', '[DRY] yarn build');
585
+ logStep('[10/11] yarn build', '[DRY] yarn build');
538
586
  return { ok: true };
539
587
  }
540
- logStep('[9/10] yarn build', 'running');
588
+ logStep('[10/11] yarn build', 'running');
541
589
  const result = runCommand('yarn build', {
542
590
  cwd,
543
591
  dryRun: false,
544
592
  verbose: flags.verbose
545
593
  });
546
594
  if (result.status !== 0) {
547
- logStep('[9/10] yarn build', `FAILED (exit ${result.status})`);
595
+ logStep('[10/11] yarn build', `FAILED (exit ${result.status})`);
548
596
  return { ok: false };
549
597
  }
550
- logStep('[9/10] yarn build', 'ok');
598
+ logStep('[10/11] yarn build', 'ok');
551
599
  return { ok: true };
552
600
  }
553
601
 
@@ -559,7 +607,7 @@ function countTodoMarkers(cwd) {
559
607
  }
560
608
 
561
609
  function stepReport(cwd, results) {
562
- logStep('[10/10] Report', 'summary');
610
+ logStep('[11/11] Report', 'summary');
563
611
  const pkg = readPackageJson(cwd) || {};
564
612
  const todoCount = countTodoMarkers(cwd);
565
613
 
@@ -572,6 +620,7 @@ function stepReport(cwd, results) {
572
620
  console.log(` Auth codemod: ${results.authCodemod ? 'ok' : 'FAILED'}`);
573
621
  console.log(` Sentry codemod: ${results.sentryCodemod ? 'ok' : results.sentrySkippedReason || 'FAILED'}`);
574
622
  console.log(` ESLint codemod: ${results.eslintCodemod ? 'ok' : 'FAILED'}`);
623
+ console.log(` Page types: ${results.pageTypesCodemod ? 'ok' : 'FAILED'}`);
575
624
  console.log(` Build: ${results.build ? 'ok' : 'FAILED'}`);
576
625
  console.log('');
577
626
  console.log(` Versions after:`);
@@ -618,7 +667,7 @@ async function transform() {
618
667
  results.preflight = true;
619
668
  await maybePause(flags, 'preflight');
620
669
  } else {
621
- logStep('[1/10] Preflight', 'skipped');
670
+ logStep('[1/11] Preflight', 'skipped');
622
671
  results.preflight = true;
623
672
  }
624
673
 
@@ -652,6 +701,14 @@ async function transform() {
652
701
  results.eslintCodemod = stepEslintCodemod(cwd, flags).ok;
653
702
  await maybePause(flags, 'eslint-codemod');
654
703
 
704
+ const pageTypesResult = stepPageTypesCodemod(cwd, flags);
705
+ const pageTypesAwaited =
706
+ pageTypesResult && typeof pageTypesResult.then === 'function'
707
+ ? await pageTypesResult
708
+ : pageTypesResult;
709
+ results.pageTypesCodemod = pageTypesAwaited.ok;
710
+ await maybePause(flags, 'page-types-codemod');
711
+
655
712
  results.build = stepBuild(cwd, flags).ok;
656
713
  } catch (err) {
657
714
  log(`FATAL: ${err.message}`);
@@ -164,10 +164,6 @@ export default async () => {
164
164
  name: 'Tamara Payment Extension',
165
165
  value: 'pz-tamara-extension'
166
166
  },
167
- {
168
- name: 'Similar Products',
169
- value: 'pz-similar-products'
170
- },
171
167
  {
172
168
  name: 'Hepsipay',
173
169
  value: 'pz-hepsipay'
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/projectzero",
3
- "version": "2.0.15-rc.0",
3
+ "version": "2.0.15",
4
4
  "private": false,
5
5
  "description": "CLI tool to manage your Project Zero Next project",
6
6
  "bin": {
@@ -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
- }