@akinon/projectzero 2.0.0-beta.11 → 2.0.0-beta.13

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 (176) hide show
  1. package/CHANGELOG.md +104 -21
  2. package/app-template/.env.example +1 -0
  3. package/app-template/.github/instructions/account.instructions.md +749 -0
  4. package/app-template/.github/instructions/checkout.instructions.md +678 -0
  5. package/app-template/.github/instructions/default.instructions.md +279 -0
  6. package/app-template/.github/instructions/edge-cases.instructions.md +73 -0
  7. package/app-template/.github/instructions/routing.instructions.md +603 -0
  8. package/app-template/.github/instructions/settings.instructions.md +338 -0
  9. package/app-template/.gitignore +3 -0
  10. package/app-template/AGENTS.md +7 -0
  11. package/app-template/CHANGELOG.md +1348 -284
  12. package/app-template/Procfile +1 -1
  13. package/app-template/akinon.json +0 -3
  14. package/app-template/build.sh +10 -0
  15. package/app-template/docs/advanced-usage.md +101 -0
  16. package/app-template/docs/sentry-usage.md +35 -0
  17. package/app-template/next-env.d.ts +1 -0
  18. package/app-template/{next.config.ts → next.config.mjs} +6 -6
  19. package/app-template/package.json +58 -51
  20. package/app-template/postcss.config.mjs +1 -4
  21. package/app-template/public/locales/en/checkout.json +6 -0
  22. package/app-template/public/locales/en/common.json +50 -1
  23. package/app-template/public/locales/en/product.json +62 -1
  24. package/app-template/public/locales/tr/checkout.json +6 -0
  25. package/app-template/public/locales/tr/common.json +50 -1
  26. package/app-template/public/locales/tr/product.json +63 -0
  27. package/app-template/public/masterpass-javascript-sdk-web.min.js +1 -0
  28. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/[...prettyurl]/page.tsx +9 -9
  29. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/layout.tsx +2 -2
  30. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/cancellation/page.tsx +6 -6
  31. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/page.tsx +6 -6
  32. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/page.tsx +1 -1
  33. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/profile/page.tsx +2 -2
  34. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/address/stores/page.tsx +2 -2
  35. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/page.tsx +1 -1
  36. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket/page.tsx +2 -2
  37. package/app-template/src/app/[pz]/category/[pk]/page.tsx +27 -0
  38. package/app-template/src/app/[pz]/flat-page/[pk]/page.tsx +23 -0
  39. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/forms/[pk]/generate/page.tsx +2 -3
  40. package/app-template/src/app/[pz]/group-product/[pk]/page.tsx +93 -0
  41. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/page.tsx +2 -4
  42. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/layout.tsx +3 -10
  43. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/page.tsx +2 -4
  44. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/not-found.tsx +5 -7
  45. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/page.tsx +6 -4
  46. package/app-template/src/app/[pz]/product/[pk]/page.tsx +102 -0
  47. package/app-template/src/app/[pz]/special-page/[pk]/page.tsx +35 -0
  48. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/email-set-primary/[[...id]]/page.tsx +3 -4
  49. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/registration/account-confirm-email/[[...id]]/page.tsx +3 -3
  50. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/reset/[[...id]]/page.tsx +6 -12
  51. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/[node]/route.ts +2 -2
  52. package/app-template/src/app/api/auth/[...nextauth]/route.ts +3 -0
  53. package/app-template/src/app/api/form/[...id]/route.ts +1 -7
  54. package/app-template/src/app/api/image-proxy/route.ts +1 -0
  55. package/app-template/src/app/api/product-categories/route.ts +1 -0
  56. package/app-template/src/app/api/similar-product-list/route.ts +1 -0
  57. package/app-template/src/app/api/similar-products/route.ts +1 -0
  58. package/app-template/src/app/api/virtual-try-on/limited-categories/route.ts +1 -0
  59. package/app-template/src/app/api/virtual-try-on/route.ts +1 -0
  60. package/app-template/src/assets/globals.scss +4 -133
  61. package/app-template/src/auth.ts +3 -0
  62. package/app-template/src/components/__tests__/badge.test.tsx +2 -2
  63. package/app-template/src/components/__tests__/link.test.tsx +2 -0
  64. package/app-template/src/components/accordion.tsx +23 -20
  65. package/app-template/src/components/button.tsx +1 -1
  66. package/app-template/src/components/carousel-core.tsx +4 -11
  67. package/app-template/src/components/checkbox.tsx +1 -1
  68. package/app-template/src/components/currency-select.tsx +1 -0
  69. package/app-template/src/components/file-input.tsx +27 -7
  70. package/app-template/src/components/generate-form-fields.tsx +49 -10
  71. package/app-template/src/components/input.tsx +11 -5
  72. package/app-template/src/components/modal.tsx +32 -16
  73. package/app-template/src/components/pagination.tsx +1 -0
  74. package/app-template/src/components/price.tsx +1 -1
  75. package/app-template/src/components/pwa-tags.tsx +1 -0
  76. package/app-template/src/components/select.tsx +39 -27
  77. package/app-template/src/components/shimmer.tsx +1 -1
  78. package/app-template/src/components/types/index.ts +25 -1
  79. package/app-template/src/hooks/use-fav-button.tsx +4 -8
  80. package/app-template/src/hooks/use-product-cart.ts +77 -0
  81. package/app-template/src/hooks/use-stock-alert.ts +74 -0
  82. package/app-template/src/plugins.js +12 -2
  83. package/app-template/src/redux/middlewares/category.ts +5 -4
  84. package/app-template/src/redux/store.ts +21 -1
  85. package/app-template/src/routes/index.ts +2 -1
  86. package/app-template/src/settings.js +3 -1
  87. package/app-template/src/types/index.ts +74 -3
  88. package/app-template/src/types/next-auth.d.ts +2 -2
  89. package/app-template/src/utils/variant-validation.ts +41 -0
  90. package/app-template/src/views/account/address-form.tsx +8 -4
  91. package/app-template/src/views/account/contact-form.tsx +2 -2
  92. package/app-template/src/views/account/content-header.tsx +4 -3
  93. package/app-template/src/views/account/faq/faq-tabs.tsx +8 -2
  94. package/app-template/src/views/account/order.tsx +1 -1
  95. package/app-template/src/views/account/orders/order-cancellation-item.tsx +1 -1
  96. package/app-template/src/views/anonymous-tracking/order-detail/index.tsx +1 -1
  97. package/app-template/src/views/basket/basket-item.tsx +6 -1
  98. package/app-template/src/views/basket/summary.tsx +16 -0
  99. package/app-template/src/views/breadcrumb.tsx +2 -2
  100. package/app-template/src/views/category/category-info.tsx +2 -1
  101. package/app-template/src/views/category/filters/index.tsx +1 -1
  102. package/app-template/src/views/checkout/auth.tsx +1 -1
  103. package/app-template/src/views/checkout/layout/header.tsx +1 -1
  104. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +1 -1
  105. package/app-template/src/views/checkout/steps/payment/options/store-credit.tsx +121 -0
  106. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +4 -4
  107. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +3 -3
  108. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +1 -1
  109. package/app-template/src/views/checkout/summary.tsx +12 -2
  110. package/app-template/src/views/find-in-store/index.tsx +2 -2
  111. package/app-template/src/views/header/action-menu.tsx +2 -6
  112. package/app-template/src/views/header/band.tsx +2 -2
  113. package/app-template/src/views/header/index.tsx +1 -1
  114. package/app-template/src/views/header/mini-basket.tsx +2 -2
  115. package/app-template/src/views/header/mobile-menu.tsx +6 -6
  116. package/app-template/src/views/header/navbar.tsx +1 -1
  117. package/app-template/src/views/header/pwa-back-button.tsx +1 -1
  118. package/app-template/src/views/header/search/index.tsx +13 -3
  119. package/app-template/src/views/header/search/results.tsx +1 -1
  120. package/app-template/src/views/header/user-menu.tsx +1 -3
  121. package/app-template/src/views/login/index.tsx +14 -13
  122. package/app-template/src/views/otp-login/index.tsx +11 -6
  123. package/app-template/src/views/product/layout.tsx +15 -1
  124. package/app-template/src/views/product/product-actions.tsx +165 -0
  125. package/app-template/src/views/product/product-info.tsx +69 -261
  126. package/app-template/src/views/product/product-share.tsx +56 -0
  127. package/app-template/src/views/product/product-variants.tsx +26 -0
  128. package/app-template/src/views/product/slider.tsx +22 -1
  129. package/app-template/src/views/product-pointer-banner-item.tsx +1 -1
  130. package/app-template/src/views/register/index.tsx +17 -21
  131. package/app-template/src/views/sales-contract-modal/index.tsx +17 -17
  132. package/app-template/src/widgets/footer-info.tsx +1 -1
  133. package/app-template/src/widgets/footer-menu.tsx +7 -3
  134. package/app-template/src/widgets/footer-subscription/index.tsx +1 -1
  135. package/app-template/src/widgets/home-stories-eng.tsx +43 -35
  136. package/app-template/tailwind.config.js +129 -1
  137. package/app-template/tsconfig.json +29 -11
  138. package/codemods/migrate-segments/index.js +591 -0
  139. package/commands/plugins.ts +62 -14
  140. package/dist/commands/plugins.js +62 -14
  141. package/package.json +1 -1
  142. package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +0 -22
  143. package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +0 -20
  144. package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +0 -74
  145. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +0 -84
  146. package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +0 -27
  147. package/app-template/src/pages/api/auth/[...nextauth].ts +0 -3
  148. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/address/page.tsx +0 -0
  149. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-email/page.tsx +0 -0
  150. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-password/page.tsx +0 -0
  151. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/contact/page.tsx +0 -0
  152. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/coupons/page.tsx +0 -0
  153. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/email-verification/page.tsx +0 -0
  154. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/faq/page.tsx +0 -0
  155. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/favourite-products/page.tsx +0 -0
  156. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/my-quotations/page.tsx +0 -0
  157. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/layout.tsx +0 -0
  158. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/page.tsx +0 -0
  159. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/anonymous-tracking/page.tsx +0 -0
  160. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/oauth-login/page.tsx +0 -0
  161. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket-b2b/page.tsx +0 -0
  162. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/loading.tsx +0 -0
  163. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/client-root.tsx +0 -0
  164. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/contact-us/page.tsx +0 -0
  165. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/error.tsx +0 -0
  166. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/loading.tsx +0 -0
  167. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/loading.tsx +0 -0
  168. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/loading.tsx +0 -0
  169. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/loading.tsx +0 -0
  170. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/checkout/page.tsx +0 -0
  171. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/layout.tsx +0 -0
  172. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/page.tsx +0 -0
  173. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/loading.tsx +0 -0
  174. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/template.tsx +0 -0
  175. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/password/reset/page.tsx +0 -0
  176. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/route.ts +0 -0
@@ -0,0 +1,121 @@
1
+ import clsx from 'clsx';
2
+ import {
3
+ useGetCheckoutLoyaltyBalanceQuery,
4
+ usePayWithLoyaltyBalanceMutation
5
+ } from '@akinon/next/data/client/checkout';
6
+ import { useAppSelector } from '@akinon/next/redux/hooks';
7
+ import { useMemo, useState } from 'react';
8
+ import { useLocalization } from '@akinon/next/hooks';
9
+ import { twMerge } from 'tailwind-merge';
10
+ import { Icon, Price } from '@theme/components';
11
+ import { Trans } from '@akinon/next/components';
12
+ import { LoaderSpinner } from '@theme/components';
13
+
14
+ export const StoreCredits = () => {
15
+ const { t } = useLocalization();
16
+
17
+ const [payWithLoyaltyBalance, { isLoading: isPayWithLoyaltyBalanceLoading }] =
18
+ usePayWithLoyaltyBalanceMutation();
19
+
20
+ const { loyaltyBalance, preOrder } = useAppSelector(
21
+ (state) => state.checkout
22
+ );
23
+
24
+ const { isLoading: isLoyaltyBalanceLoading } =
25
+ useGetCheckoutLoyaltyBalanceQuery(undefined, {
26
+ refetchOnMountOrArgChange: true,
27
+ skip: !preOrder?.payment_option
28
+ });
29
+
30
+ const isLoyaltyBalanceUsed = useMemo(() => {
31
+ return parseFloat(preOrder?.loyalty_money ?? '0') > 0;
32
+ }, [preOrder?.loyalty_money]);
33
+
34
+ const [isLoading, setIsLoading] = useState(false);
35
+
36
+ const handleClick = async () => {
37
+ if (isLoading) return;
38
+ setIsLoading(true);
39
+
40
+ try {
41
+ await payWithLoyaltyBalance(isLoyaltyBalanceUsed ? '0' : loyaltyBalance);
42
+ } finally {
43
+ setIsLoading(false);
44
+ }
45
+ };
46
+
47
+ if (preOrder?.is_guest) {
48
+ return null;
49
+ }
50
+
51
+ if (isLoyaltyBalanceLoading) {
52
+ return (
53
+ <div className="mb-3 px-4 py-3 xs:px-0">
54
+ <LoaderSpinner />
55
+ </div>
56
+ );
57
+ }
58
+
59
+ if (parseFloat(loyaltyBalance) <= 0) {
60
+ return null;
61
+ }
62
+ return (
63
+ <div
64
+ className={twMerge(
65
+ 'hidden flex-col w-full mb-4 border border-solid border-gray-400 px-0 md:p-4',
66
+ isPayWithLoyaltyBalanceLoading && 'pointer-events-none opacity-30',
67
+ parseFloat(loyaltyBalance) > 0 && 'block'
68
+ )}
69
+ >
70
+ <div className="flex w-full items-center">
71
+ <button onClick={handleClick}>
72
+ <span
73
+ className={clsx(
74
+ 'flex h-5 w-5 items-center justify-center rounded border border-solid border-primary',
75
+ isLoyaltyBalanceUsed ? 'bg-primary' : 'bg-white'
76
+ )}
77
+ >
78
+ <Icon
79
+ name={isLoyaltyBalanceUsed ? 'check' : ''}
80
+ size={10}
81
+ className={clsx({ 'text-white': isLoyaltyBalanceUsed })}
82
+ />
83
+ </span>
84
+ </button>
85
+
86
+ <div className="w-full pl-4">
87
+ <p className="cursor-pointer text-sm" onClick={handleClick}>
88
+ {t('checkout.payment.store_credit.use_my_store_credits')}
89
+ </p>
90
+ <p className="flex text-sm text-[#606060]">
91
+ {t('checkout.payment.store_credit.available_balance')}:
92
+ <Price
93
+ value={loyaltyBalance}
94
+ currencyCode={preOrder?.currency_type_label}
95
+ className="pe-1 font-bold"
96
+ />
97
+ </p>
98
+ </div>
99
+ </div>
100
+
101
+ {isLoyaltyBalanceUsed && parseFloat(preOrder?.unpaid_amount) > 0 && (
102
+ <p className="my-4 text-[15px] font-light italic text-[#707070] max-xs:text-xs">
103
+ <Trans
104
+ i18nKey="checkout.payment.store_credit.insufficient_balance"
105
+ components={{
106
+ Amount: (
107
+ <div className="inline-flex">
108
+ <Price
109
+ value={preOrder?.unpaid_amount}
110
+ currencyCode={preOrder?.currency_type_label}
111
+ className="text-primary"
112
+ />
113
+ </div>
114
+ )
115
+ }}
116
+ />
117
+ </p>
118
+ )}
119
+ </div>
120
+ );
121
+ };
@@ -44,7 +44,7 @@ const PaymentOptionButtons = () => {
44
44
  {displayedPaymentOptions.map((option) => (
45
45
  <label
46
46
  key={`payment-option-${option.pk}`}
47
- className="border border-gray-200 px-4 py-3 mt-3 flex h-12"
47
+ className="border px-4 py-3 mt-3 flex h-12"
48
48
  onClick={scrollToTop}
49
49
  >
50
50
  <Radio
@@ -69,10 +69,10 @@ const PaymentOptionButtons = () => {
69
69
  onClick={() => onClickHandler(option)}
70
70
  className={clsx(
71
71
  'flex items-center justify-center border-r border-b border-solid',
72
- 'border-gray-400 text-xs uppercase text-black-800/60 font-medium',
73
- 'bg-white h-11 px-5 transition-colors sm:h-15 sm:px-8 sm:py-8 hover:text-secondary',
72
+ 'border-gray-400 text-xs uppercase text-black-800 font-medium',
73
+ 'text-opacity-60 bg-white h-11 px-5 transition-colors sm:h-15 sm:px-8 sm:py-8 hover:text-secondary',
74
74
  {
75
- 'text-black-800/100 border-b-transparent':
75
+ 'text-opacity-100 border-b-transparent':
76
76
  preOrder?.payment_option?.pk === option.pk
77
77
  }
78
78
  )}
@@ -130,7 +130,7 @@ const AddressBox = ({
130
130
  onClick={() => handleAddressClick(addressType, address)}
131
131
  key={address.pk}
132
132
  className={clsx(
133
- 'cursor-pointer relative w-full border border-gray-200 shadow-sm p-4 min-h-[8rem]',
133
+ 'cursor-pointer relative w-full border shadow p-4 min-h-[8rem]',
134
134
  "hover:after:content-[''] hover:after:border-4 hover:after:opacity-30 hover:after:transition-opacity",
135
135
  'after:border-secondary-400 after:absolute after:inset-0 after:duration-150 after:-z-10',
136
136
  {
@@ -167,7 +167,7 @@ const AddressBox = ({
167
167
  <div className="text-xs flex justify-between">
168
168
  <Button
169
169
  appearance="ghost"
170
- className="italic underline hover:text-secondary-500 hover:bg-white! hover:border-white! p-0 h-auto"
170
+ className="italic underline hover:text-secondary-500 hover:!bg-white hover:!border-white p-0 h-auto"
171
171
  onClick={() => setIsEditAddressModalOpen(true)}
172
172
  data-testid="checkout-address-edit"
173
173
  >
@@ -193,7 +193,7 @@ const AddressBox = ({
193
193
  </Modal>
194
194
  <Button
195
195
  appearance="ghost"
196
- className="italic underline hover:text-secondary-500 hover:bg-white! hover:border-white! p-0 h-auto"
196
+ className="italic underline hover:text-secondary-500 hover:!bg-white hover:!border-white p-0 h-auto"
197
197
  onClick={() => setRemoveAddressModalOpen(true)}
198
198
  data-testid="checkout-address-remove"
199
199
  >
@@ -153,7 +153,7 @@ const Addresses = () => {
153
153
  role="button"
154
154
  onClick={() => setIsModalOpen(true)}
155
155
  className={clsx(
156
- 'relative cursor-pointer w-full min-h-[8rem] border border-gray-200 shadow-sm p-4',
156
+ 'relative cursor-pointer w-full min-h-[8rem] border shadow p-4',
157
157
  "hover:after:content-[''] hover:after:border-4 hover:after:opacity-30 hover:after:transition-opacity",
158
158
  'after:border-secondary-400 after:absolute after:inset-0 after:opacity-0 after:duration-150 after:-z-10',
159
159
  {
@@ -8,6 +8,7 @@ import PluginModule, { Component } from '@akinon/next/components/plugin-module';
8
8
  import { twMerge } from 'tailwind-merge';
9
9
  import { Image } from '@akinon/next/components/image';
10
10
  import { Trans } from '@akinon/next/components/trans';
11
+ import { StoreCredits } from './steps/payment/options/store-credit';
11
12
 
12
13
  export const Summary = () => {
13
14
  const { t } = useLocalization();
@@ -38,6 +39,7 @@ export const Summary = () => {
38
39
  'flex flex-col w-full mb-4 border border-solid border-gray-400'
39
40
  }}
40
41
  />
42
+ <StoreCredits />
41
43
  <div className="flex flex-col w-full border border-solid border-gray-400">
42
44
  <div className="flex justify-between items-center flex-row border-b border-solid border-gray-400 px-4 py-2 sm:px-5 sm:py-4 sm:min-h-15">
43
45
  <span className="text-black-800 text-xl font-light sm:text-2xl">
@@ -118,6 +120,14 @@ export const Summary = () => {
118
120
  <Price value={preOrder?.shipping_amount} />
119
121
  </span>
120
122
  </div>
123
+ {parseFloat(preOrder?.loyalty_money) > 0 && (
124
+ <div className="flex items-center justify-between w-full text-xs text-black-800 py-1 px-4 sm:px-5">
125
+ <span>{t('checkout.summary.loyalty_money_total')}</span>
126
+ <span>
127
+ <Price value={preOrder?.loyalty_money} />
128
+ </span>
129
+ </div>
130
+ )}
121
131
  <div className="flex items-center justify-between w-full text-xs text-black-800 py-1 px-4 sm:px-5">
122
132
  <span>{t('checkout.summary.discounts_total')}</span>
123
133
  <span>
@@ -149,7 +159,7 @@ export const Summary = () => {
149
159
  </div>
150
160
  </div>
151
161
  <div className="flex flex-col py-4 px-4 text-black-800 text-xs sm:px-5">
152
- <div className="w-full overflow-hidden text-ellipsis mb-1 last:mb-0">
162
+ <div className="w-full overflow-hidden overflow-ellipsis mb-1 last:mb-0">
153
163
  <Trans
154
164
  i18nKey="checkout.summary.info"
155
165
  components={{
@@ -162,7 +172,7 @@ export const Summary = () => {
162
172
  }}
163
173
  />
164
174
  </div>
165
- <div className="w-full overflow-hidden text-ellipsis mb-1 last:mb-0">
175
+ <div className="w-full overflow-hidden overflow-ellipsis mb-1 last:mb-0">
166
176
  {preOrder.shipping_address?.line}{' '}
167
177
  {preOrder.shipping_address?.postcode}{' '}
168
178
  {preOrder.shipping_address?.district && (
@@ -123,14 +123,14 @@ export const FindInStore = ({ productPk, productName, variants }) => {
123
123
  className="w-full"
124
124
  options={retailStoreOptions}
125
125
  {...register('city_id')}
126
- error={errors.city_id}
126
+ error={errors.city_id as any}
127
127
  />
128
128
  {sizeOptions.length > 1 && (
129
129
  <Select
130
130
  className="w-full"
131
131
  options={sizeOptions}
132
132
  {...register('size')}
133
- error={errors.size}
133
+ error={errors.size as any}
134
134
  />
135
135
  )}
136
136
  </div>
@@ -76,7 +76,7 @@ export default function ActionMenu() {
76
76
  : 'bg-secondary-500 text-white'
77
77
  )}
78
78
  >
79
- {totalQuantity}
79
+ <span data-testid="header-basket-count">{totalQuantity}</span>
80
80
  </Badge>
81
81
  ),
82
82
  miniBasket: <MiniBasket />
@@ -92,11 +92,7 @@ export default function ActionMenu() {
92
92
  ref={menu.miniBasket ? miniBasketRef : null}
93
93
  >
94
94
  {menu.action ? (
95
- <button
96
- onClick={menu.action}
97
- data-testid={menu.dataTestId}
98
- className="cursor-pointer"
99
- >
95
+ <button onClick={menu.action} data-testid={menu.dataTestId}>
100
96
  <Icon name={menu.icon} size={24} />
101
97
  {menu.badge}
102
98
  </button>
@@ -16,13 +16,13 @@ export default function HeaderBand() {
16
16
  </div>
17
17
 
18
18
  <div className="header-grid-area-nav bg-gray-100 h-auto p-3 sm:header-grid-area-band-m sm:h-9 sm:py-0">
19
- <div className="text-center text-ellipsis line-clamp-2 h-full flex items-center justify-center">
19
+ <div className="text-center overflow-ellipsis line-clamp-2 h-full flex items-center justify-center">
20
20
  <HeaderBandText />
21
21
  </div>
22
22
  </div>
23
23
 
24
24
  <div className="header-grid-area-main-r h-full pr-4 sm:header-grid-area-band-r sm:pr-0">
25
- <div className="flex items-center justify-end h-full gap-10">
25
+ <div className="flex items-center justify-end h-full">
26
26
  <UserMenu isMobile={false} />
27
27
  <ActionMenu />
28
28
  </div>
@@ -13,7 +13,7 @@ import { getMenu } from '@akinon/next/data/server';
13
13
  import { Image } from '@akinon/next/components/image';
14
14
 
15
15
  export default async function Header() {
16
- const response = await getMenu();
16
+ const response = await getMenu({ depth: 3 });
17
17
  const menu = menuGenerator(response);
18
18
 
19
19
  return (
@@ -203,7 +203,7 @@ export default function MiniBasket() {
203
203
  miniBasketOpen
204
204
  ? 'opacity-100 visible lg:invisible'
205
205
  : 'opacity-0 invisible',
206
- 'fixed top-0 left-0 z-50 w-screen h-screen bg-black/80 transition-all duration-300'
206
+ 'fixed top-0 left-0 z-50 w-screen h-screen bg-black bg-opacity-80 transition-all duration-300'
207
207
  )}
208
208
  onClick={() => {
209
209
  dispatch(closeMiniBasket());
@@ -217,7 +217,7 @@ export default function MiniBasket() {
217
217
  'fixed lg:absolute bottom-0 lg:-bottom-1 right-0 w-80 h-screen lg:h-auto bg-white lg:border-l lg:border-t lg:border-r-2 lg:border-b-2 lg:border-gray-500 p-5 z-50 transition-all duration-300'
218
218
  )}
219
219
  >
220
- <header className="flex items-center gap-2 pb-3 border-b border-gray-200">
220
+ <header className="flex items-center gap-2 pb-3 border-b">
221
221
  <h3 className="text-xl font-bold">
222
222
  {t('basket.mini_basket.my_bag')}
223
223
  </h3>
@@ -58,9 +58,9 @@ export default function MobileMenu(props: MobileMenuProps) {
58
58
  <>
59
59
  <div
60
60
  className={clsx(
61
- 'fixed top-0 left-0 z-30 w-screen h-screen invisible opacity-0 bg-black/80 transition duration-500',
61
+ 'fixed top-0 left-0 z-30 w-screen h-screen invisible opacity-0 bg-black bg-opacity-80 transition duration-500',
62
62
  {
63
- 'visible! opacity-100! scroll-lock': isMobileMenuOpen
63
+ '!visible !opacity-100 scroll-lock': isMobileMenuOpen
64
64
  }
65
65
  )}
66
66
  />
@@ -70,7 +70,7 @@ export default function MobileMenu(props: MobileMenuProps) {
70
70
  className={clsx(
71
71
  'fixed top-0 left-0 z-50 flex flex-col bg-white w-72 pt-4 h-screen invisible opacity-0 transition duration-500 transform -translate-x-72',
72
72
  {
73
- 'visible! opacity-100! translate-x-0': isMobileMenuOpen
73
+ '!visible !opacity-100 translate-x-0': isMobileMenuOpen
74
74
  }
75
75
  )}
76
76
  >
@@ -106,15 +106,15 @@ export default function MobileMenu(props: MobileMenuProps) {
106
106
  className={clsx(
107
107
  'absolute top-0 left-0 right-0 px-8 bg-white invisible opacity-0 transition duration-500 transform translate-x-full',
108
108
  {
109
- 'visible! opacity-100! translate-x-0!': selectedSubMenu
109
+ '!visible !opacity-100 !translate-x-0': selectedSubMenu
110
110
  }
111
111
  )}
112
112
  >
113
- <header className="flex items-center justify-between border-b border-gray-200 h-[61px] py-4 mb-4">
113
+ <header className="flex items-center justify-between border-b h-[61px] py-4 mb-4">
114
114
  <Button
115
115
  appearance="ghost"
116
116
  onClick={() => setSelectedSubMenu(null)}
117
- className="underline text-xs font-semibold flex items-center gap-2 p-0!"
117
+ className="underline text-xs font-semibold flex items-center gap-2 !p-0"
118
118
  >
119
119
  <Icon name="chevron-start" size={12} className="shrink-0" />
120
120
  {t('common.mobile_menu.back')}
@@ -102,7 +102,7 @@ export default function Navbar(props: NavbarProps) {
102
102
  'after:bg-gray'
103
103
  ],
104
104
  {
105
- 'visible! opacity-100! delay-500':
105
+ '!visible !opacity-100 delay-500':
106
106
  openedMenu === item.pk
107
107
  }
108
108
  )}
@@ -34,7 +34,7 @@ export const PwaBackButton = () => {
34
34
  return (
35
35
  <div className="relative z-10 md:top-0 md:left-0 md:fixed">
36
36
  <button
37
- className="bg-secondary-600 text-white flex items-center justify-center shrink-0 w-12 h-12 md:w-10 md:h-9 active:bg-secondary-700"
37
+ className="bg-secondary-600 text-white flex items-center justify-center flex-shrink-0 w-12 h-12 md:w-10 md:h-9 active:bg-secondary-700"
38
38
  onClick={() => router.back()}
39
39
  >
40
40
  <svg
@@ -9,6 +9,7 @@ import { Icon } from '@theme/components';
9
9
  import Results from './results';
10
10
  import { ROUTES } from '@theme/routes';
11
11
  import { useLocalization, useRouter } from '@akinon/next/hooks';
12
+ import PluginModule, { Component } from '@akinon/next/components/plugin-module';
12
13
 
13
14
  export default function Search() {
14
15
  const { t } = useLocalization();
@@ -46,7 +47,7 @@ export default function Search() {
46
47
  <div
47
48
  className={clsx(
48
49
  // 177px is the height of the header
49
- 'absolute bg-black/75 w-screen h-screen transition duration-500 left-0 bottom-0 translate-y-full z-30',
50
+ 'absolute bg-black opacity-75 w-screen h-screen transition duration-500 left-0 bottom-0 translate-y-full z-30',
50
51
  isSearchOpen && searchText
51
52
  ? 'visible opacity-100'
52
53
  : 'invisible opacity-0'
@@ -60,7 +61,7 @@ export default function Search() {
60
61
  isSearchOpen ? 'visible opacity-100' : 'invisible opacity-0'
61
62
  )}
62
63
  >
63
- <div className="max-w-(--breakpoint-2xl) mx-auto flex flex-col gap-12">
64
+ <div className="max-w-screen-2xl mx-auto flex flex-col gap-12">
64
65
  <div className="border-b border-gray-400 flex flex-col py-1.5 gap-2 self-center items-center md:flex-row">
65
66
  <span className="text-xl lg:text-2xl">
66
67
  {t('common.search.results_for')}
@@ -74,10 +75,19 @@ export default function Search() {
74
75
  router.push(`${ROUTES.LIST}/?search_text=${searchText}`);
75
76
  }
76
77
  }}
77
- className="border-0 text-2xl outline-hidden text-secondary placeholder:text-xl lg:placeholder:text-2xl"
78
+ className="border-0 text-2xl outline-none text-secondary placeholder:text-xl placeholder:lg:text-2xl"
78
79
  placeholder={t('common.search.placeholder')}
79
80
  ref={inputRef}
80
81
  />
82
+
83
+ <PluginModule
84
+ component={Component.HeaderImageSearchFeature}
85
+ props={{
86
+ enableTextSearch: true,
87
+ isEnabled: true
88
+ }}
89
+ />
90
+
81
91
  <Icon
82
92
  name="close"
83
93
  size={14}
@@ -80,7 +80,7 @@ export default function Results(props: ResultsProps) {
80
80
  <div className="grid grid-cols-2 sm:grid-cols-4 gap-8">
81
81
  {products.map((product, index) => (
82
82
  <Link href={product?.url} key={index} className="flex flex-col">
83
- <div className="relative aspect-315/448">
83
+ <div className="relative aspect-[315/448]">
84
84
  {product.extra.image ? (
85
85
  <Image
86
86
  src={product.extra.image}
@@ -36,9 +36,7 @@ export const UserMenu = (props: UserMenuProps) => {
36
36
  <ul
37
37
  className={clsx(
38
38
  'items-center divide-x divide-black',
39
- isMobile
40
- ? 'flex pt-2 text-sm pb-6 border-b border-gray-200 mx-8'
41
- : 'hidden sm:flex'
39
+ isMobile ? 'flex pt-2 text-sm pb-6 border-b mx-8' : 'hidden sm:flex'
42
40
  )}
43
41
  id="user-menu"
44
42
  >
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
- import { signIn, SignInOptions } from 'next-auth/react';
3
+ import { signIn } from 'next-auth/react';
4
4
  import { useSearchParams } from 'next/navigation';
5
5
  import { useState } from 'react';
6
6
  import { ROUTES } from '@theme/routes';
7
- import { LoginFormType } from '@theme/types';
7
+ import { LoginFormType, FormType, PzSignInOptions } from '@theme/types';
8
8
  import { Button, Input, Link } from '@theme/components';
9
9
  import { SubmitHandler, useForm } from 'react-hook-form';
10
10
  import * as yup from 'yup';
@@ -79,11 +79,12 @@ export const Login = () => {
79
79
  redirect: false,
80
80
  callbackUrl: searchParams.get('callbackUrl') ?? '/',
81
81
  captchaValidated,
82
- ...data
83
- } as SignInOptions);
82
+ ...data,
83
+ formType: FormType.login
84
+ } as PzSignInOptions & { redirect: false });
84
85
 
85
86
  if (loginResponse.error) {
86
- const errors: AuthError[] = JSON.parse(loginResponse.error);
87
+ const errors: AuthError[] = JSON.parse(loginResponse.code);
87
88
 
88
89
  if (errors.find((error) => error.type === 'captcha')) {
89
90
  if (await validateCaptcha()) {
@@ -109,25 +110,25 @@ export const Login = () => {
109
110
  try {
110
111
  parsedValue = JSON.parse(item.value);
111
112
  } catch {
112
- parsedValue = [item.value];
113
+ parsedValue = [item.value];
113
114
  }
114
115
  } else {
115
- parsedValue = item.value;
116
+ parsedValue = item.value;
116
117
  }
117
118
 
118
119
  if (Array.isArray(parsedValue)) {
119
120
  setError(item.name as keyof LoginFormType, {
120
121
  type: 'custom',
121
- message: parsedValue.join(', '),
122
+ message: parsedValue.join(', ')
122
123
  });
123
124
  } else {
124
125
  Object.keys(parsedValue).forEach((key) => {
125
126
  const fieldName = key as keyof LoginFormType;
126
127
  const errorMessages = parsedValue[key] as string[];
127
-
128
+
128
129
  setError(fieldName, {
129
130
  type: 'custom',
130
- message: errorMessages.join(', '),
131
+ message: errorMessages.join(', ')
131
132
  });
132
133
  });
133
134
  }
@@ -161,7 +162,7 @@ export const Login = () => {
161
162
  method="post"
162
163
  onSubmit={handleSubmit(onSubmit)}
163
164
  >
164
- <input type="hidden" value="login" {...register('formType')} />
165
+ <input type="hidden" value={FormType.login} {...register('formType')} />
165
166
  <input type="hidden" value={locale} {...register('locale')} />
166
167
 
167
168
  <div className={clsx(errors.email ? 'mb-8' : 'mb-4')}>
@@ -219,7 +220,7 @@ export const Login = () => {
219
220
  {t('auth.login.form.submit')}
220
221
  </Button>
221
222
 
222
- <p className="relative text-gray-600 text-center my-4 before:absolute before:h-[1px] before:w-5/12 before:bg-gray-600/25 before:top-1/2 before:left-0 after:absolute after:h-[1px] after:w-5/12 after:bg-gray-600/25 after:top-1/2 after:right-0">
223
+ <p className="relative text-gray-600 text-center my-4 before:absolute before:h-[1px] before:w-5/12 before:bg-gray-600 before:bg-opacity-25 before:top-1/2 before:left-0 after:absolute after:h-[1px] after:w-5/12 after:bg-gray-600 after:bg-opacity-25 after:top-1/2 after:right-0">
223
224
  {t('auth.login.form.or')}
224
225
  </p>
225
226
 
@@ -248,7 +249,7 @@ export const Login = () => {
248
249
  alt={provider.label}
249
250
  width={provider.label === 'Facebook' ? 10 : 18}
250
251
  height={18}
251
- className="shrink-0"
252
+ className="flex-shrink-0"
252
253
  />
253
254
  )}
254
255
 
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
- import { signIn, SignInOptions } from 'next-auth/react';
3
+ import { signIn } from 'next-auth/react';
4
4
  import { useState } from 'react';
5
- import { OtpLoginFormType } from '@theme/types';
5
+ import { OtpLoginFormType, FormType, PzSignInOptions } from '@theme/types';
6
6
  import { Button, Input } from '@theme/components';
7
7
  import { SubmitHandler, useForm } from 'react-hook-form';
8
8
  import * as yup from 'yup';
@@ -47,11 +47,12 @@ export const OtpLogin = () => {
47
47
  const loginResponse = await signIn('default', {
48
48
  redirect: false,
49
49
  callbackUrl: '/',
50
- ...data
51
- } as SignInOptions);
50
+ ...data,
51
+ formType: FormType.otpLogin
52
+ } as PzSignInOptions & { redirect: false });
52
53
 
53
54
  if (loginResponse.error) {
54
- const errors: AuthError[] = JSON.parse(loginResponse.error);
55
+ const errors: AuthError[] = JSON.parse(loginResponse.code);
55
56
 
56
57
  const fieldErrors = errors.find((error) => error.type === 'field_errors')
57
58
  ?.data as { name: string; value: string[] }[];
@@ -100,7 +101,11 @@ export const OtpLogin = () => {
100
101
  {t('auth.login.title_gsm')}
101
102
  </h2>
102
103
  <form onSubmit={handleSubmit(onSubmit)}>
103
- <input type="hidden" value="otpLogin" {...register('formType')} />
104
+ <input
105
+ type="hidden"
106
+ value={FormType.otpLogin}
107
+ {...register('formType')}
108
+ />
104
109
  <input type="hidden" value={locale} {...register('locale')} />
105
110
 
106
111
  <div className="mb-4">
@@ -22,6 +22,16 @@ export default async function ProductLayout({
22
22
  breadcrumbData,
23
23
  children
24
24
  }: ProductPageProps) {
25
+ const categoryIds = breadcrumbData
26
+ ?.map((item) => item.extra_context?.attributes?.category_id)
27
+ .filter(Boolean)
28
+ .join(',');
29
+
30
+ const categoryPaths = breadcrumbData
31
+ ?.map((item) => item.path)
32
+ .filter(Boolean)
33
+ .join(',');
34
+
25
35
  return (
26
36
  <div className="container mx-auto">
27
37
  <div className="max-w-5xl mx-auto my-5 px-7">
@@ -31,7 +41,11 @@ export default async function ProductLayout({
31
41
  <div className="col-span-5 lg:col-span-3">
32
42
  <ProductInfoSlider product={data.product} />
33
43
  </div>
34
- <div className="flex flex-col items-center col-span-5 lg:col-span-2">
44
+ <div
45
+ className="flex flex-col items-center col-span-5 lg:col-span-2"
46
+ data-category-ids={categoryIds}
47
+ data-category-paths={categoryPaths}
48
+ >
35
49
  <div className="w-full">{children}</div>
36
50
  </div>
37
51
  </div>