@akinon/projectzero 2.0.0-beta.12 → 2.0.0-beta.14

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 (179) hide show
  1. package/CHANGELOG.md +102 -23
  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 +1387 -310
  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 +11 -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 +11 -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 +22 -6
  105. package/app-template/src/views/checkout/steps/payment/options/funds-transfer.tsx +25 -5
  106. package/app-template/src/views/checkout/steps/payment/options/loyalty.tsx +21 -2
  107. package/app-template/src/views/checkout/steps/payment/options/redirection.tsx +22 -4
  108. package/app-template/src/views/checkout/steps/payment/options/store-credit.tsx +121 -0
  109. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +4 -4
  110. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +3 -3
  111. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +1 -1
  112. package/app-template/src/views/checkout/summary.tsx +12 -2
  113. package/app-template/src/views/find-in-store/index.tsx +2 -2
  114. package/app-template/src/views/header/action-menu.tsx +2 -6
  115. package/app-template/src/views/header/band.tsx +2 -2
  116. package/app-template/src/views/header/index.tsx +1 -1
  117. package/app-template/src/views/header/mini-basket.tsx +2 -2
  118. package/app-template/src/views/header/mobile-menu.tsx +6 -6
  119. package/app-template/src/views/header/navbar.tsx +1 -1
  120. package/app-template/src/views/header/pwa-back-button.tsx +1 -1
  121. package/app-template/src/views/header/search/index.tsx +13 -3
  122. package/app-template/src/views/header/search/results.tsx +1 -1
  123. package/app-template/src/views/header/user-menu.tsx +1 -3
  124. package/app-template/src/views/login/index.tsx +14 -13
  125. package/app-template/src/views/otp-login/index.tsx +11 -6
  126. package/app-template/src/views/product/layout.tsx +15 -1
  127. package/app-template/src/views/product/product-actions.tsx +165 -0
  128. package/app-template/src/views/product/product-info.tsx +69 -261
  129. package/app-template/src/views/product/product-share.tsx +56 -0
  130. package/app-template/src/views/product/product-variants.tsx +26 -0
  131. package/app-template/src/views/product/slider.tsx +22 -1
  132. package/app-template/src/views/product-pointer-banner-item.tsx +1 -1
  133. package/app-template/src/views/register/index.tsx +17 -21
  134. package/app-template/src/views/sales-contract-modal/index.tsx +17 -17
  135. package/app-template/src/widgets/footer-info.tsx +1 -1
  136. package/app-template/src/widgets/footer-menu.tsx +7 -3
  137. package/app-template/src/widgets/footer-subscription/index.tsx +1 -1
  138. package/app-template/src/widgets/home-stories-eng.tsx +43 -35
  139. package/app-template/tailwind.config.js +129 -1
  140. package/app-template/tsconfig.json +29 -11
  141. package/codemods/migrate-segments/index.js +591 -0
  142. package/commands/plugins.ts +62 -14
  143. package/dist/commands/plugins.js +62 -14
  144. package/package.json +1 -1
  145. package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +0 -22
  146. package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +0 -20
  147. package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +0 -74
  148. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +0 -84
  149. package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +0 -27
  150. package/app-template/src/pages/api/auth/[...nextauth].ts +0 -3
  151. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/address/page.tsx +0 -0
  152. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-email/page.tsx +0 -0
  153. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-password/page.tsx +0 -0
  154. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/contact/page.tsx +0 -0
  155. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/coupons/page.tsx +0 -0
  156. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/email-verification/page.tsx +0 -0
  157. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/faq/page.tsx +0 -0
  158. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/favourite-products/page.tsx +0 -0
  159. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/my-quotations/page.tsx +0 -0
  160. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/layout.tsx +0 -0
  161. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/page.tsx +0 -0
  162. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/anonymous-tracking/page.tsx +0 -0
  163. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/oauth-login/page.tsx +0 -0
  164. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket-b2b/page.tsx +0 -0
  165. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/loading.tsx +0 -0
  166. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/client-root.tsx +0 -0
  167. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/contact-us/page.tsx +0 -0
  168. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/error.tsx +0 -0
  169. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/loading.tsx +0 -0
  170. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/loading.tsx +0 -0
  171. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/loading.tsx +0 -0
  172. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/loading.tsx +0 -0
  173. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/checkout/page.tsx +0 -0
  174. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/layout.tsx +0 -0
  175. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/page.tsx +0 -0
  176. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/loading.tsx +0 -0
  177. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/template.tsx +0 -0
  178. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/password/reset/page.tsx +0 -0
  179. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/route.ts +0 -0
@@ -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>
@@ -0,0 +1,165 @@
1
+ import React from 'react';
2
+ import clsx from 'clsx';
3
+ import { Button, Icon, Modal } from '@theme/components';
4
+ import { useLocalization } from '@akinon/next/hooks';
5
+ import PluginModule, { Component } from '@akinon/next/components/plugin-module';
6
+ import { validateVariantSelection } from '../../utils/variant-validation';
7
+ import { VariantType } from '@akinon/next/types';
8
+
9
+ interface Product {
10
+ pk: number;
11
+ name: string;
12
+ [key: string]: any;
13
+ }
14
+
15
+ interface ProductActionsProps {
16
+ product: Product;
17
+ variants: VariantType[];
18
+ inStock: boolean;
19
+ isVariantLoading: boolean;
20
+ onAddToCart: () => void;
21
+ onAddToStockAlert: () => void;
22
+ onClearError: () => void;
23
+ isAddToCartLoading: boolean;
24
+ isAddToStockAlertLoading: boolean;
25
+ productError: React.ReactNode | null;
26
+ isModalOpen: boolean;
27
+ stockAlertResponseMessage: React.ReactNode | null;
28
+ onCloseModal: () => void;
29
+ }
30
+
31
+ export const ProductActions: React.FC<ProductActionsProps> = ({
32
+ product,
33
+ variants,
34
+ inStock,
35
+ isVariantLoading,
36
+ onAddToCart,
37
+ onAddToStockAlert,
38
+ onClearError,
39
+ isAddToCartLoading,
40
+ isAddToStockAlertLoading,
41
+ productError,
42
+ isModalOpen,
43
+ stockAlertResponseMessage,
44
+ onCloseModal
45
+ }) => {
46
+ const { t } = useLocalization();
47
+
48
+ const checkoutProviderProps = {
49
+ product,
50
+ clearBasket: true,
51
+ addBeforeClick: () => validateVariantSelection(variants).isValid,
52
+ openMiniBasket: false,
53
+ className: clsx([
54
+ 'py-2.5',
55
+ 'bg-black',
56
+ 'relative',
57
+ 'hover:bg-black',
58
+ 'before:content-[""]',
59
+ 'before:w-6',
60
+ 'before:h-6',
61
+ 'before:bg-white',
62
+ 'before:absolute',
63
+ 'before:rounded-r-[18px]',
64
+ 'before:left-0',
65
+ 'after:content-[""]',
66
+ 'after:absolute',
67
+ 'after:w-3',
68
+ 'after:h-3',
69
+ 'after:bg-[#d02c2f]',
70
+ 'after:rounded-xl',
71
+ 'after:left-1'
72
+ ]),
73
+ onError: (error: any) => {
74
+ const formattedError = error?.data?.non_field_errors ||
75
+ Object.keys(error?.data || {}).map(
76
+ (key) => `${key}: ${error?.data[key].join(', ')}`
77
+ );
78
+ // This would need to be handled by parent component
79
+ console.error('Checkout error:', formattedError);
80
+ }
81
+ };
82
+
83
+ const handleMainActionClick = () => {
84
+ onClearError();
85
+
86
+ if (inStock) {
87
+ onAddToCart();
88
+ } else {
89
+ onAddToStockAlert();
90
+ }
91
+ };
92
+
93
+ return (
94
+ <>
95
+ {productError && (
96
+ <div className="mt-4 text-xs text-center text-error">
97
+ {productError}
98
+ </div>
99
+ )}
100
+
101
+ <Button
102
+ disabled={isAddToCartLoading || isAddToStockAlertLoading || isVariantLoading}
103
+ className={clsx(
104
+ 'fixed bottom-0 right-0 w-1/2 h-14 z-[20] flex items-center justify-center fill-primary-foreground',
105
+ 'hover:fill-primary sm:relative sm:w-full sm:mt-3 sm:font-semibold sm:h-12'
106
+ )}
107
+ onClick={handleMainActionClick}
108
+ data-testid="product-add-to-cart"
109
+ >
110
+ {isVariantLoading ? (
111
+ <Icon
112
+ name="spinner"
113
+ size={20}
114
+ className="animate-spin mr-4 fill-primary"
115
+ />
116
+ ) : inStock ? (
117
+ <span>{t('product.add_to_cart')}</span>
118
+ ) : (
119
+ <>
120
+ <Icon name="bell" size={20} className="mr-4" />
121
+ <span>{t('product.add_stock_alert')}</span>
122
+ </>
123
+ )}
124
+ </Button>
125
+
126
+ <PluginModule
127
+ component={Component.AkifastCheckoutButton}
128
+ props={{
129
+ ...checkoutProviderProps,
130
+ isPdp: true
131
+ }}
132
+ />
133
+
134
+ <PluginModule
135
+ component={Component.OneClickCheckoutButtons}
136
+ props={checkoutProviderProps}
137
+ />
138
+
139
+ <Modal
140
+ portalId="stock-alert-modal"
141
+ open={isModalOpen}
142
+ setOpen={onCloseModal}
143
+ showCloseButton={false}
144
+ className="w-5/6 md:max-w-md"
145
+ >
146
+ <div className="flex flex-col items-center justify-center gap-4 px-6 py-9">
147
+ <Icon name="bell" size={48} />
148
+ <h2 className="text-xl font-semibold">
149
+ {t('product.stock_alert.title')}
150
+ </h2>
151
+ <div className="max-w-40 text-xs text-center leading-4">
152
+ <p>{stockAlertResponseMessage}</p>
153
+ </div>
154
+ <Button
155
+ onClick={onCloseModal}
156
+ appearance="outlined"
157
+ className="font-semibold px-10 h-12"
158
+ >
159
+ {t('product.stock_alert.close_button')}
160
+ </Button>
161
+ </div>
162
+ </Modal>
163
+ </>
164
+ );
165
+ };