@akinon/projectzero 1.106.0 → 1.107.0-rc.86

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 (34) hide show
  1. package/CHANGELOG.md +233 -4
  2. package/app-template/.env.example +1 -0
  3. package/app-template/CHANGELOG.md +4955 -314
  4. package/app-template/README.md +25 -1
  5. package/app-template/package.json +20 -20
  6. package/app-template/public/locales/en/common.json +42 -1
  7. package/app-template/public/locales/tr/common.json +42 -1
  8. package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +9 -82
  9. package/app-template/src/app/[commerce]/[locale]/[currency]/landing-page/[pk]/page.tsx +12 -1
  10. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +67 -0
  11. package/app-template/src/app/api/image-proxy/route.ts +1 -0
  12. package/app-template/src/app/api/similar-product-list/route.ts +1 -0
  13. package/app-template/src/app/api/similar-products/route.ts +1 -0
  14. package/app-template/src/assets/fonts/pz-icon.css +3 -0
  15. package/app-template/src/components/input.tsx +2 -1
  16. package/app-template/src/hooks/index.ts +2 -0
  17. package/app-template/src/hooks/use-product-cart.ts +77 -0
  18. package/app-template/src/hooks/use-stock-alert.ts +74 -0
  19. package/app-template/src/plugins.js +2 -1
  20. package/app-template/src/settings.js +6 -1
  21. package/app-template/src/utils/variant-validation.ts +41 -0
  22. package/app-template/src/views/basket/basket-content.tsx +106 -0
  23. package/app-template/src/views/basket/basket-item.tsx +16 -13
  24. package/app-template/src/views/basket/summary.tsx +10 -7
  25. package/app-template/src/views/guest-login/index.tsx +6 -1
  26. package/app-template/src/views/header/search/index.tsx +17 -5
  27. package/app-template/src/views/product/product-actions.tsx +165 -0
  28. package/app-template/src/views/product/product-info.tsx +61 -263
  29. package/app-template/src/views/product/product-share.tsx +56 -0
  30. package/app-template/src/views/product/product-variants.tsx +26 -0
  31. package/app-template/src/views/product/slider.tsx +86 -73
  32. package/commands/plugins.ts +29 -2
  33. package/dist/commands/plugins.js +23 -2
  34. package/package.json +1 -1
@@ -1,178 +1,73 @@
1
1
  'use client';
2
2
 
3
3
  import clsx from 'clsx';
4
- import { Button, Icon, Modal } from '@theme/components';
5
- import { useAddProductToBasket } from '../../hooks';
6
4
  import React, { useEffect, useState } from 'react';
7
- import { useAddStockAlertMutation } from '@akinon/next/data/client/wishlist';
8
- import { pushAddToCart, pushProductViewed } from '@theme/utils/gtm';
9
- import { PriceWrapper, Variant } from '@theme/views/product';
10
- import Share from '@theme/views/share';
5
+ import { PriceWrapper } from '@theme/views/product';
11
6
  import { ProductPageProps } from './layout';
12
7
  import MiscButtons from './misc-buttons';
13
- import { useLocalization } from '@akinon/next/hooks';
14
- import PluginModule, { Component } from '@akinon/next/components/plugin-module';
15
- import { Trans } from '@akinon/next/components/trans';
8
+ import { pushProductViewed } from '@theme/utils/gtm';
16
9
  import { useSession } from 'next-auth/react';
10
+ import { isVariantSelectionComplete } from '../../utils/variant-validation';
11
+ import { useProductCart } from '../../hooks/use-product-cart';
12
+ import { useStockAlert } from '../../hooks/use-stock-alert';
13
+ import { ProductVariants } from './product-variants';
14
+ import { ProductActions } from './product-actions';
15
+ import { ProductShare } from './product-share';
17
16
 
18
17
  export default function ProductInfo({ data }: ProductPageProps) {
19
- const { t } = useLocalization();
20
18
  const { data: session } = useSession();
21
- const [currentUrl, setCurrentUrl] = useState(null);
22
- const [productError, setProductError] = useState(null);
23
- const [isModalOpen, setIsModalOpen] = useState(false);
24
- const [stockAlertResponseMessage, setStockAlertResponseMessage] =
25
- useState(null);
26
19
  const [isVariantLoading, setIsVariantLoading] = useState(false);
27
20
 
28
- const [addProduct, { isLoading: isAddToCartLoading }] =
29
- useAddProductToBasket();
30
- const [addStockAlert, { isLoading: isAddToStockAlertLoading }] =
31
- useAddStockAlertMutation();
32
21
  const inStock = data.selected_variant !== null || data.product.in_stock;
33
22
 
34
- useEffect(() => {
35
- isVariantSelectionComplete() && setIsVariantLoading(false);
23
+ const {
24
+ addProductToCart,
25
+ productError: cartError,
26
+ clearProductError: clearCartError,
27
+ isAddToCartLoading
28
+ } = useProductCart({
29
+ product: data.product,
30
+ variants: data.variants
31
+ });
32
+
33
+ const {
34
+ addProductToStockAlertList,
35
+ isModalOpen,
36
+ stockAlertResponseMessage,
37
+ productError: stockError,
38
+ isAddToStockAlertLoading,
39
+ closeModal,
40
+ clearError: clearStockError
41
+ } = useStockAlert({
42
+ productPk: data.product.pk,
43
+ userEmail: session?.user?.email
44
+ });
45
+
46
+ const productError = cartError || stockError;
47
+ const clearProductError = () => {
48
+ clearCartError();
49
+ clearStockError();
50
+ };
36
51
 
52
+ useEffect(() => {
53
+ isVariantSelectionComplete(data.variants) && setIsVariantLoading(false);
37
54
  !inStock && setIsVariantLoading(false);
38
- }, [data]); // eslint-disable-line react-hooks/exhaustive-deps
55
+ }, [data, inStock]);
39
56
 
40
57
  useEffect(() => {
41
58
  if (isVariantLoading) {
42
- setProductError(null);
59
+ clearProductError();
43
60
  }
44
61
  // eslint-disable-next-line react-hooks/exhaustive-deps
45
62
  }, [isVariantLoading]);
46
63
 
47
- useEffect(() => {
48
- setCurrentUrl(window.location.href);
49
- }, [currentUrl]);
50
-
51
64
  useEffect(() => {
52
65
  pushProductViewed(data?.product);
53
66
  }, []); // eslint-disable-line react-hooks/exhaustive-deps
54
67
 
55
- const addProductToCart = async () => {
56
- if (!variantsSelectionCheck()) {
57
- return;
58
- }
59
-
60
- try {
61
- await addProduct({
62
- product: data.product.pk,
63
- quantity: 1,
64
- attributes: {}
65
- });
66
-
67
- pushAddToCart(data?.product);
68
- } catch (error) {
69
- setProductError(
70
- error?.data?.non_field_errors ||
71
- Object.keys(error?.data).map(
72
- (key) => `${key}: ${error?.data[key].join(', ')}`
73
- )
74
- );
75
- }
76
- };
77
-
78
- const variantsSelectionCheck = () => {
79
- const unselectedVariant = data.variants.find((variant) =>
80
- variant.options.every((opt) => !opt.is_selected)
81
- );
82
-
83
- if (unselectedVariant) {
84
- setProductError(() => (
85
- <Trans
86
- i18nKey="product.please_select_variant"
87
- components={{
88
- VariantName: <span>{unselectedVariant.attribute_name}</span>
89
- }}
90
- />
91
- ));
92
-
93
- return false;
94
- }
95
-
96
- return true;
97
- };
98
-
99
- const isVariantSelectionComplete = () => {
100
- return data?.variants.every((variant) =>
101
- variant?.options.some((opt) => opt.is_selected)
102
- );
103
- };
104
-
105
- const addProductToStockAlertList = async () => {
106
- try {
107
- await addStockAlert({
108
- productPk: data.product.pk,
109
- email: session?.user?.email
110
- })
111
- .unwrap()
112
- .then(handleSuccess)
113
- .catch((err) => handleError(err));
114
- } catch (error) {
115
- setProductError(error?.data?.non_field_errors || null);
116
- }
117
- };
118
-
119
- const handleModalClick = () => {
120
- setIsModalOpen(false);
121
- };
122
-
123
- const handleSuccess = () => {
124
- setStockAlertResponseMessage(() => (
125
- <Trans
126
- i18nKey="product.stock_alert.success_description"
127
- components={{
128
- Email: <span>{session?.user?.email}</span>
129
- }}
130
- />
131
- ));
132
- setIsModalOpen(true);
133
- };
134
-
135
- const handleError = (err) => {
136
- if (err.status !== 401) {
137
- setStockAlertResponseMessage(
138
- t('product.stock_alert.error_description').toString()
139
- );
140
- setIsModalOpen(true);
141
- }
142
- };
143
-
144
- const checkoutProviderProps = {
145
- product: data.product,
146
- clearBasket: true,
147
- addBeforeClick: variantsSelectionCheck,
148
- openMiniBasket: false,
149
- className: clsx([
150
- 'py-2.5',
151
- 'bg-black',
152
- 'relative',
153
- 'hover:bg-black',
154
- 'before:content-[""]',
155
- 'before:w-6',
156
- 'before:h-6',
157
- 'before:bg-white',
158
- 'before:absolute',
159
- 'before:rounded-r-[18px]',
160
- 'before:left-0',
161
- 'after:content-[""]',
162
- 'after:absolute',
163
- 'after:w-3',
164
- 'after:h-3',
165
- 'after:bg-[#d02c2f]',
166
- 'after:rounded-xl',
167
- 'after:left-1'
168
- ]),
169
- onError: (error) =>
170
- setProductError(
171
- error?.data?.non_field_errors ||
172
- Object.keys(error?.data).map(
173
- (key) => `${key}: ${error?.data[key].join(', ')}`
174
- )
175
- )
68
+ const handleVariantChange = () => {
69
+ clearProductError();
70
+ setIsVariantLoading(true);
176
71
  };
177
72
 
178
73
  return (
@@ -188,72 +83,26 @@ export default function ProductInfo({ data }: ProductPageProps) {
188
83
  retailPrice={data.product.retail_price}
189
84
  />
190
85
  </div>
191
- <div className="flex flex-col">
192
- {data.variants.map((variant) => (
193
- <Variant
194
- key={variant.attribute_key}
195
- {...variant}
196
- className="items-center mt-8"
197
- onChange={() => {
198
- setProductError(null);
199
- setIsVariantLoading(true);
200
- }}
201
- />
202
- ))}
203
- </div>
204
-
205
- {productError && (
206
- <div className="mt-4 text-xs text-center text-error">
207
- {productError}
208
- </div>
209
- )}
210
86
 
211
- <Button
212
- disabled={
213
- isAddToCartLoading || isAddToStockAlertLoading || isVariantLoading
214
- }
215
- className={clsx(
216
- 'fixed bottom-0 right-0 w-1/2 h-14 z-[20] flex items-center justify-center fill-primary-foreground',
217
- 'hover:fill-primary sm:relative sm:w-full sm:mt-3 sm:font-semibold sm:h-12'
218
- )}
219
- onClick={() => {
220
- setProductError(null);
221
-
222
- if (inStock) {
223
- addProductToCart();
224
- } else {
225
- addProductToStockAlertList();
226
- }
227
- }}
228
- data-testid="product-add-to-cart"
229
- >
230
- {isVariantLoading ? (
231
- <Icon
232
- name="spinner"
233
- size={20}
234
- className="animate-spin mr-4 fill-primary"
235
- />
236
- ) : inStock ? (
237
- <span>{t('product.add_to_cart')}</span>
238
- ) : (
239
- <>
240
- <Icon name="bell" size={20} className="mr-4" />
241
- <span>{t('product.add_stock_alert')}</span>
242
- </>
243
- )}
244
- </Button>
245
-
246
- <PluginModule
247
- component={Component.AkifastCheckoutButton}
248
- props={{
249
- ...checkoutProviderProps,
250
- isPdp: true
251
- }}
87
+ <ProductVariants
88
+ variants={data.variants}
89
+ onVariantChange={handleVariantChange}
252
90
  />
253
91
 
254
- <PluginModule
255
- component={Component.OneClickCheckoutButtons}
256
- props={checkoutProviderProps}
92
+ <ProductActions
93
+ product={data.product}
94
+ variants={data.variants}
95
+ inStock={inStock}
96
+ isVariantLoading={isVariantLoading}
97
+ onAddToCart={addProductToCart}
98
+ onAddToStockAlert={addProductToStockAlertList}
99
+ onClearError={clearProductError}
100
+ isAddToCartLoading={isAddToCartLoading}
101
+ isAddToStockAlertLoading={isAddToStockAlertLoading}
102
+ productError={productError}
103
+ isModalOpen={isModalOpen}
104
+ stockAlertResponseMessage={stockAlertResponseMessage}
105
+ onCloseModal={closeModal}
257
106
  />
258
107
 
259
108
  <MiscButtons
@@ -262,58 +111,7 @@ export default function ProductInfo({ data }: ProductPageProps) {
262
111
  variants={data.variants}
263
112
  />
264
113
 
265
- <Share
266
- className="my-2 sm:mb-4"
267
- buttonText={t('product.share')}
268
- items={[
269
- {
270
- href: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
271
- currentUrl
272
- )}`,
273
- iconName: 'facebook',
274
- iconSize: 22
275
- },
276
- {
277
- href: `https://twitter.com/intent/tweet?text=${encodeURIComponent(
278
- currentUrl
279
- )}`,
280
- iconName: 'twitter',
281
- iconSize: 22
282
- },
283
- {
284
- href: `https://api.whatsapp.com/send?text=${
285
- data.product.name
286
- }%20${encodeURIComponent(currentUrl)}`,
287
- iconName: 'whatsapp',
288
- iconSize: 22
289
- }
290
- ]}
291
- />
292
-
293
- <Modal
294
- portalId="stock-alert-modal"
295
- open={isModalOpen}
296
- setOpen={setIsModalOpen}
297
- showCloseButton={false}
298
- className="w-5/6 md:max-w-md"
299
- >
300
- <div className="flex flex-col items-center justify-center gap-4 px-6 py-9">
301
- <Icon name="bell" size={48} />
302
- <h2 className="text-xl font-semibold">
303
- {t('product.stock_alert.title')}
304
- </h2>
305
- <div className="max-w-40 text-xs text-center leading-4">
306
- <p>{stockAlertResponseMessage}</p>
307
- </div>
308
- <Button
309
- onClick={handleModalClick}
310
- appearance="outlined"
311
- className="font-semibold px-10 h-12"
312
- >
313
- {t('product.stock_alert.close_button')}
314
- </Button>
315
- </div>
316
- </Modal>
114
+ <ProductShare productName={data.product.name} className="my-2 sm:mb-4" />
317
115
  </>
318
116
  );
319
117
  }
@@ -0,0 +1,56 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import Share from '@theme/views/share';
3
+ import { useLocalization } from '@akinon/next/hooks';
4
+
5
+ interface ProductShareProps {
6
+ productName: string;
7
+ className?: string;
8
+ }
9
+
10
+ export const ProductShare: React.FC<ProductShareProps> = ({
11
+ productName,
12
+ className
13
+ }) => {
14
+ const { t } = useLocalization();
15
+ const [currentUrl, setCurrentUrl] = useState<string | null>(null);
16
+
17
+ useEffect(() => {
18
+ setCurrentUrl(window.location.href);
19
+ }, []);
20
+
21
+ if (!currentUrl) {
22
+ return null;
23
+ }
24
+
25
+ const shareItems = [
26
+ {
27
+ href: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
28
+ currentUrl
29
+ )}`,
30
+ iconName: 'facebook' as const,
31
+ iconSize: 22
32
+ },
33
+ {
34
+ href: `https://twitter.com/intent/tweet?text=${encodeURIComponent(
35
+ currentUrl
36
+ )}`,
37
+ iconName: 'twitter' as const,
38
+ iconSize: 22
39
+ },
40
+ {
41
+ href: `https://api.whatsapp.com/send?text=${productName}%20${encodeURIComponent(
42
+ currentUrl
43
+ )}`,
44
+ iconName: 'whatsapp' as const,
45
+ iconSize: 22
46
+ }
47
+ ];
48
+
49
+ return (
50
+ <Share
51
+ className={className}
52
+ buttonText={t('product.share')}
53
+ items={shareItems}
54
+ />
55
+ );
56
+ };
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import { Variant } from '@theme/views/product';
3
+ import { VariantType } from '@akinon/next/types';
4
+
5
+ interface ProductVariantsProps {
6
+ variants: VariantType[];
7
+ onVariantChange: () => void;
8
+ }
9
+
10
+ export const ProductVariants: React.FC<ProductVariantsProps> = ({
11
+ variants,
12
+ onVariantChange
13
+ }) => {
14
+ return (
15
+ <div className="flex flex-col">
16
+ {variants.map((variant) => (
17
+ <Variant
18
+ key={variant.attribute_key}
19
+ {...variant}
20
+ className="items-center mt-8"
21
+ onChange={onVariantChange}
22
+ />
23
+ ))}
24
+ </div>
25
+ );
26
+ };
@@ -7,6 +7,7 @@ import { Product } from '@akinon/next/types';
7
7
  import { Image } from '@akinon/next/components/image';
8
8
  import useFavButton from '../../hooks/use-fav-button';
9
9
  import { twMerge } from 'tailwind-merge';
10
+ import PluginModule, { Component } from '@akinon/next/components/plugin-module';
10
11
 
11
12
  type ProductSliderItem = {
12
13
  product: Product;
@@ -35,90 +36,102 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
35
36
  carouselRef.current?.next();
36
37
  };
37
38
 
38
- const handleThumbnailClick = (index) => {
39
+ const handleThumbnailClick = (index: number) => {
39
40
  setActiveIndex(index);
40
41
  carouselRef.current?.goToSlide(index);
41
42
  };
42
43
 
43
44
  return (
44
- <div className="lg:grid lg:grid-cols-6">
45
- <div className="lg:col-span-1">
46
- <div className="flex flex-col items-center justify-center md:mr-[6px]">
47
- <button
48
- onClick={goToPrev}
49
- className={twMerge(
50
- 'hidden justify-center p-2 mb-3 border border-gray-100 rounded-full cursor-pointer lg:block',
51
- [activeIndex === 0 && 'cursor-not-allowed opacity-45']
52
- )}
53
- disabled={activeIndex === 0}
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 }}
54
117
  >
55
- <Icon name="chevron-up" size={15} className="fill-[#000000]" />
56
- </button>
57
- <div className="hidden flex-col items-center overflow-scroll w-[80px] max-h-[620px] lg:block">
58
- {product?.productimage_set?.map((item, index) => (
118
+ {product?.productimage_set?.map((item, i) => (
59
119
  <Image
60
- key={index}
120
+ key={i}
61
121
  src={item.image}
62
- alt={`Thumbnail ${index}`}
63
- width={80}
64
- height={128}
65
- className={twMerge('cursor-pointer', [
66
- activeIndex === index && 'border-2 border-primary'
67
- ])}
68
- onClick={() => handleThumbnailClick(index)}
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
69
130
  />
70
131
  ))}
71
- </div>
72
- <button
73
- onClick={goToNext}
74
- className={twMerge(
75
- 'hidden justify-center p-2 mt-3 border border-gray-100 rounded-full cursor-pointer lg:block',
76
- [
77
- activeIndex === product.productimage_set.length - 1 &&
78
- 'cursor-not-allowed opacity-45'
79
- ]
80
- )}
81
- disabled={activeIndex === product.productimage_set.length - 1}
82
- >
83
- <Icon name="chevron-down" size={15} className="fill-[#000000]" />
84
- </button>
132
+ </CarouselCore>
85
133
  </div>
86
134
  </div>
87
-
88
- <div className="relative lg:col-span-5">
89
- <FavButton className="absolute right-8 top-6 z-[20] sm:hidden" />
90
-
91
- <CarouselCore
92
- responsive={{
93
- all: {
94
- breakpoint: { max: 5000, min: 0 },
95
- items: 1
96
- }
97
- }}
98
- arrows={false}
99
- swipeable={true}
100
- ref={carouselRef}
101
- afterChange={(previousSlide, { currentSlide }) => {
102
- setActiveIndex(currentSlide);
103
- }}
104
- containerAspectRatio={{ mobile: 520 / 798, desktop: 484 / 726 }}
105
- >
106
- {product?.productimage_set?.map((item, i) => (
107
- <Image
108
- key={i}
109
- src={item.image}
110
- alt={product.name}
111
- draggable={false}
112
- aspectRatio={484 / 726}
113
- sizes="(min-width: 425px) 512px,
114
- (min-width: 601px) 576px,
115
- (min-width: 768px) 336px,
116
- (min-width: 1024px) 484px, 368px"
117
- fill
118
- />
119
- ))}
120
- </CarouselCore>
121
- </div>
122
- </div>
135
+ </>
123
136
  );
124
137
  }
@@ -29,7 +29,14 @@ async function checkVersion(pkg: PackageJson) {
29
29
 
30
30
  if (!semver.satisfies(pkg.dependencies['@akinon/next'], latestVersion)) {
31
31
  console.warn(
32
- `\x1b[43mWarning: The "${packageName}" package is currently at version ${pkg.dependencies['@akinon/next']}. Please upgrade it to the latest version (${latestVersion}) to ensure plugin compatibility.`,
32
+ `\x1b[43m Warning: The "${packageName}" package is currently at`,
33
+ `\x1b[41m version ${pkg.dependencies['@akinon/next']}`,
34
+ `\x1b[43m Please upgrade it to the latest version (${latestVersion}) to ensure plugin compatibility.`,
35
+ '\x1b[0m\n'
36
+ );
37
+ } else {
38
+ console.log(
39
+ `\x1b[42m Info: The package "${packageName}" is currently in the current version (${latestVersion}).`,
33
40
  '\x1b[0m\n'
34
41
  );
35
42
  }
@@ -54,7 +61,27 @@ export default async () => {
54
61
  }
55
62
  }
56
63
 
57
- const pkg: PackageJson = require(path.resolve(rootDir, './package.json'));
64
+ function findPackageJson(): PackageJson {
65
+ const packageJsonPaths = [
66
+ path.resolve(rootDir, './package.json'),
67
+ path.resolve(rootDir, './apps/projectzeronext/package.json')
68
+ ];
69
+
70
+ for (const packageJsonPath of packageJsonPaths) {
71
+ try {
72
+ const pkg = require(packageJsonPath);
73
+ if (pkg.dependencies['@akinon/next']) {
74
+ return pkg;
75
+ }
76
+ } catch (error) {
77
+ continue;
78
+ }
79
+ }
80
+
81
+ throw new Error('Could not find package.json with @akinon/next dependency');
82
+ }
83
+
84
+ const pkg = findPackageJson();
58
85
  await checkVersion(pkg);
59
86
 
60
87
  const pluginsFilePath = findPluginsFilePath();