@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
@@ -0,0 +1,74 @@
1
+ import React, { useState } from 'react';
2
+ import { useAddStockAlertMutation } from '@akinon/next/data/client/wishlist';
3
+ import { Trans } from '@akinon/next/components/trans';
4
+ import { useLocalization } from '@akinon/next/hooks';
5
+
6
+ interface UseStockAlertProps {
7
+ productPk: number;
8
+ userEmail?: string;
9
+ }
10
+
11
+ export const useStockAlert = ({ productPk, userEmail }: UseStockAlertProps) => {
12
+ const { t } = useLocalization();
13
+ const [isModalOpen, setIsModalOpen] = useState(false);
14
+ const [stockAlertResponseMessage, setStockAlertResponseMessage] = useState<React.ReactNode | null>(null);
15
+ const [productError, setProductError] = useState<React.ReactNode | null>(null);
16
+
17
+ const [addStockAlert, { isLoading: isAddToStockAlertLoading }] = useAddStockAlertMutation();
18
+
19
+ const handleSuccess = () => {
20
+ setStockAlertResponseMessage(React.createElement(
21
+ Trans,
22
+ {
23
+ i18nKey: "product.stock_alert.success_description",
24
+ components: {
25
+ Email: React.createElement('span', {}, userEmail)
26
+ }
27
+ }
28
+ ));
29
+ setIsModalOpen(true);
30
+ setProductError(null);
31
+ };
32
+
33
+ const handleError = (err: any) => {
34
+ if (err.status !== 401) {
35
+ setStockAlertResponseMessage(
36
+ t('product.stock_alert.error_description').toString()
37
+ );
38
+ setIsModalOpen(true);
39
+ }
40
+ };
41
+
42
+ const addProductToStockAlertList = async () => {
43
+ try {
44
+ await addStockAlert({
45
+ productPk,
46
+ email: userEmail
47
+ })
48
+ .unwrap()
49
+ .then(handleSuccess)
50
+ .catch(handleError);
51
+ } catch (error: any) {
52
+ setProductError(error?.data?.non_field_errors || null);
53
+ }
54
+ };
55
+
56
+ const closeModal = () => {
57
+ setIsModalOpen(false);
58
+ };
59
+
60
+ const clearError = () => {
61
+ setProductError(null);
62
+ };
63
+
64
+ return {
65
+ addProductToStockAlertList,
66
+ isModalOpen,
67
+ setIsModalOpen,
68
+ stockAlertResponseMessage,
69
+ productError,
70
+ isAddToStockAlertLoading,
71
+ closeModal,
72
+ clearError
73
+ };
74
+ };
@@ -1,4 +1,3 @@
1
- // we are holding all of the plugins because of the development enviroment.
2
1
  module.exports = [
3
2
  'pz-basket-gift-pack',
4
3
  'pz-click-collect',
@@ -12,7 +11,18 @@ module.exports = [
12
11
  'pz-masterpass',
13
12
  'pz-b2b',
14
13
  'pz-akifast',
14
+ 'pz-multi-basket',
15
15
  'pz-saved-card',
16
16
  'pz-tabby-extension',
17
- 'pz-tamara-extension'
17
+ 'pz-apple-pay',
18
+ 'pz-tamara-extension',
19
+ 'pz-hepsipay',
20
+ 'pz-flow-payment',
21
+ 'pz-virtual-try-on',
22
+ 'pz-cybersource-uc',
23
+ 'pz-hepsipay',
24
+ 'pz-masterpass-rest',
25
+ 'pz-similar-products',
26
+ 'pz-haso',
27
+ 'pz-google-pay'
18
28
  ];
@@ -1,5 +1,5 @@
1
1
  import { Facet } from '@akinon/next/types';
2
- import { Middleware } from '@reduxjs/toolkit';
2
+ import { Middleware, UnknownAction } from '@reduxjs/toolkit';
3
3
  import { setSelectedFacets } from '@theme/redux/reducers/category';
4
4
 
5
5
  const getSelectedFacets = (facets: Array<Facet>) => {
@@ -21,13 +21,14 @@ const getSelectedFacets = (facets: Array<Facet>) => {
21
21
  const categoryMiddleware: Middleware = ({ dispatch, getState }) => {
22
22
  return (next) => (action) => {
23
23
  const result = next(action);
24
+ const act = action as UnknownAction;
24
25
 
25
- if (action.type === 'category/setFacets') {
26
- const facets: Array<Facet> = result.payload;
26
+ if (act.type === 'category/setFacets') {
27
+ const facets: Array<Facet> = (result as { payload: Array<Facet> }).payload;
27
28
  const selectedFacets = getSelectedFacets(facets);
28
29
 
29
30
  dispatch(setSelectedFacets(selectedFacets));
30
- } else if (action.type === 'category/toggleFacet') {
31
+ } else if (act.type === 'category/toggleFacet') {
31
32
  const selectedFacets = getSelectedFacets(
32
33
  getState().category.selectedFacets
33
34
  );
@@ -14,6 +14,11 @@ import {
14
14
  import categoryReducer from '@theme/redux/reducers/category';
15
15
  import categoryMiddleware from '@theme/redux/middlewares/category';
16
16
 
17
+ // Import all action creators from akinon-next
18
+ import * as defaultActions from '@akinon/next/redux/actions';
19
+ import { rtkApiActionCreators } from '@akinon/next/redux/actions';
20
+ import * as categorySlice from './reducers/category';
21
+
17
22
  const _middlewares: Middleware[] = [...middlewares, categoryMiddleware];
18
23
 
19
24
  const _reducers = {
@@ -21,13 +26,28 @@ const _reducers = {
21
26
  category: categoryReducer
22
27
  };
23
28
 
29
+ const actionCreators = {
30
+ // Slice actions (automatically extracted from akinon-next)
31
+ ...defaultActions,
32
+ // RTK Query API endpoints from akinon-next
33
+ ...rtkApiActionCreators,
34
+ // Local category slice actions
35
+ ...categorySlice
36
+ };
37
+
24
38
  export const makeStore = (): Store<{
25
39
  [key in keyof typeof _reducers]: ReturnType<typeof _reducers[key]>;
26
40
  }> =>
27
41
  configureStore({
28
42
  reducer: _reducers,
29
43
  middleware: (getDefaultMiddleware) =>
30
- getDefaultMiddleware().concat(_middlewares)
44
+ getDefaultMiddleware().concat(_middlewares),
45
+ devTools:
46
+ process.env.NODE_ENV !== 'production'
47
+ ? {
48
+ actionCreators
49
+ }
50
+ : false
31
51
  });
32
52
 
33
53
  export type AppStore = ReturnType<typeof makeStore>;
@@ -27,7 +27,8 @@ enum ACCOUNT_ROUTES {
27
27
 
28
28
  enum ORDER_ROUTES {
29
29
  CHECKOUT = '/orders/checkout',
30
- CHECKOUT_COMPLETED = '/orders/completed'
30
+ CHECKOUT_COMPLETED = '/orders/completed',
31
+ PAYMENT_GATEWAY_HASO = '/payment-gateway/haso'
31
32
  }
32
33
 
33
34
  enum FLATPAGE_ROUTES {
@@ -62,5 +62,7 @@ module.exports = {
62
62
  redis: {
63
63
  defaultExpirationTime: 900 // 15 min
64
64
  },
65
- customNotFoundEnabled: false
65
+ customNotFoundEnabled: false,
66
+ usePzSegment: true,
67
+ commerceRedirectionIgnoreList: ['/users/reset']
66
68
  };
@@ -1,4 +1,58 @@
1
1
  export * from '@theme/types/widgets';
2
+ import { SignInOptions } from 'next-auth/react';
3
+
4
+ /**
5
+ * Form types for authentication
6
+ */
7
+ export enum FormType {
8
+ login = 'login',
9
+ register = 'register',
10
+ loyaltyRegister = 'loyaltyRegister',
11
+ otpLogin = 'otpLogin'
12
+ }
13
+
14
+ /**
15
+ * Extended SignInOptions with additional form type support for Project Zero Next authentication
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // For standard login
20
+ * signIn('default', {
21
+ * ...data,
22
+ * formType: FormType.login
23
+ * } as PzSignInOptions);
24
+ *
25
+ * // For standard user registration
26
+ * signIn('default', {
27
+ * ...data,
28
+ * formType: FormType.register
29
+ * } as PzSignInOptions);
30
+ *
31
+ * // For loyalty program registration
32
+ * signIn('default', {
33
+ * ...data,
34
+ * formType: FormType.loyaltyRegister
35
+ * } as PzSignInOptions);
36
+ *
37
+ * // For OTP login
38
+ * signIn('default', {
39
+ * ...data,
40
+ * formType: FormType.otpLogin
41
+ * } as PzSignInOptions);
42
+ * ```
43
+ */
44
+ export interface PzSignInOptions extends SignInOptions<boolean> {
45
+ /**
46
+ * Type of the authentication form being submitted
47
+ *
48
+ * Available options:
49
+ * - `FormType.login` - Standard email/password login
50
+ * - `FormType.register` - Standard user registration
51
+ * - `FormType.loyaltyRegister` - Loyalty program registration (use this for loyalty signups)
52
+ * - `FormType.otpLogin` - OTP-based login via SMS
53
+ */
54
+ formType?: FormType;
55
+ }
2
56
 
3
57
  export type RegisterFormType = {
4
58
  email: string;
@@ -10,14 +64,14 @@ export type RegisterFormType = {
10
64
  kvkk_confirm: boolean;
11
65
  email_allowed: boolean;
12
66
  sms_allowed: boolean;
13
- formType: string;
67
+ formType: FormType;
14
68
  locale: string;
15
69
  };
16
70
 
17
71
  export type LoginFormType = {
18
72
  email: string;
19
73
  password: string;
20
- formType: string;
74
+ formType: FormType;
21
75
  locale: string;
22
76
  };
23
77
 
@@ -25,7 +79,7 @@ export type OtpLoginFormType = {
25
79
  phone: string;
26
80
  code?: string;
27
81
  locale: string;
28
- formType: string;
82
+ formType: FormType;
29
83
  };
30
84
 
31
85
  export enum WIDGET_TYPE {
@@ -71,3 +125,20 @@ export interface SeoProps {
71
125
  ogPriceAmount?: string;
72
126
  ogPriceCurrency?: string;
73
127
  }
128
+
129
+ export interface ModalProps {
130
+ portalId: string;
131
+ children?: React.ReactNode;
132
+ open?: boolean;
133
+ setOpen?: (open: boolean) => void;
134
+ title?: React.ReactNode;
135
+ showCloseButton?: React.ReactNode;
136
+ className?: string;
137
+ overlayClassName?: string;
138
+ headerWrapperClassName?: string;
139
+ titleClassName?: string;
140
+ closeButtonClassName?: string;
141
+ iconName?: string;
142
+ iconSize?: number;
143
+ iconClassName?: string;
144
+ }
@@ -1,9 +1,9 @@
1
1
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
2
- import NextAuth from 'next-auth';
2
+ import 'next-auth';
3
3
 
4
4
  declare module 'next-auth' {
5
5
  interface User {
6
- id?: number;
6
+ id?: string;
7
7
  pk: number;
8
8
  firstName: string;
9
9
  lastName: string;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { Trans } from '@akinon/next/components/trans';
3
+ import { VariantType } from '@akinon/next/types';
4
+
5
+ export const isVariantSelectionComplete = (variants: VariantType[]): boolean => {
6
+ return variants?.every((variant) =>
7
+ variant?.options.some((opt) => opt.is_selected)
8
+ );
9
+ };
10
+
11
+ export const getUnselectedVariant = (variants: VariantType[]): VariantType | undefined => {
12
+ return variants.find((variant) =>
13
+ variant.options.every((opt) => !opt.is_selected)
14
+ );
15
+ };
16
+
17
+ export const createVariantErrorMessage = (unselectedVariant: VariantType) => {
18
+ const TransComponent = Trans as any;
19
+ return React.createElement(
20
+ TransComponent,
21
+ {
22
+ i18nKey: "product.please_select_variant",
23
+ components: {
24
+ VariantName: React.createElement('span', {}, unselectedVariant.attribute_name)
25
+ }
26
+ }
27
+ );
28
+ };
29
+
30
+ export const validateVariantSelection = (variants: VariantType[]) => {
31
+ const unselectedVariant = getUnselectedVariant(variants);
32
+
33
+ if (unselectedVariant) {
34
+ return {
35
+ isValid: false,
36
+ errorMessage: createVariantErrorMessage(unselectedVariant)
37
+ };
38
+ }
39
+
40
+ return { isValid: true, errorMessage: null };
41
+ };
@@ -259,23 +259,25 @@ export const AddressForm = (props: Props) => {
259
259
  {/* TODO: Fix select and textarea components */}
260
260
 
261
261
  <Select
262
- className="w-full border-gray-500 text-sm mt-2"
262
+ className="w-full border-gray-500 text-sm"
263
263
  options={countryOptions}
264
264
  {...register('country')}
265
265
  error={errors.country}
266
266
  data-testid="address-form-country"
267
267
  label={t('account.address_book.form.country.title')}
268
+ labelClassName="mb-3"
268
269
  required
269
270
  />
270
271
 
271
272
  {city && (
272
273
  <Select
273
- className="w-full border-gray-500 text-sm mt-2"
274
+ className="w-full border-gray-500 text-sm"
274
275
  options={cityOptions}
275
276
  {...register('city')}
276
277
  error={errors.city}
277
278
  data-testid="address-form-city"
278
279
  label={t('account.address_book.form.province.title')}
280
+ labelClassName="mb-3"
279
281
  required
280
282
  />
281
283
  )}
@@ -283,24 +285,26 @@ export const AddressForm = (props: Props) => {
283
285
  <div className="flex gap-4">
284
286
  <div className="flex-1">
285
287
  <Select
286
- className="w-full border-gray-500 text-sm mt-2"
288
+ className="w-full border-gray-500 text-sm"
287
289
  options={townshipOptions}
288
290
  {...register('township')}
289
291
  error={errors.township}
290
292
  data-testid="address-form-township"
291
293
  label={t('account.address_book.form.township.title')}
294
+ labelClassName="mb-3"
292
295
  required
293
296
  />
294
297
  </div>
295
298
  {district && (
296
299
  <div className="flex-1">
297
300
  <Select
298
- className="w-full border-gray-500 text-sm mt-2"
301
+ className="w-full border-gray-500 text-sm"
299
302
  options={districtOptions}
300
303
  {...register('district')}
301
304
  error={errors.district}
302
305
  data-testid="address-form-district"
303
306
  label={t('account.address_book.form.district.title')}
307
+ labelClassName="mb-3"
304
308
  required
305
309
  />
306
310
  </div>
@@ -111,7 +111,7 @@ const ContactForm = () => {
111
111
  resolver: yupResolver(contactFormSchema(t))
112
112
  });
113
113
 
114
- const onSubmit: SubmitHandler<ContactFormType> = (data, event) => {
114
+ const onSubmit: SubmitHandler<ContactFormType> = (data) => {
115
115
  const formData = new FormData();
116
116
 
117
117
  Object.keys(data ?? {}).forEach((key) => {
@@ -242,7 +242,7 @@ const ContactForm = () => {
242
242
  <span className="text-secondary">*</span>
243
243
  </label>
244
244
  <textarea
245
- className="border-gray-500 border w-full text-xs p-2.5 focus-visible:outline-hidden focus:border-black hover:border-black"
245
+ className="border-gray-500 border w-full text-xs p-2.5 focus-visible:outline-none focus:border-black hover:border-black"
246
246
  rows={7}
247
247
  name="message"
248
248
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -31,14 +31,15 @@ export const ContentHeader = (props: Props) => {
31
31
  </h3>
32
32
  <Select
33
33
  onChange={handleChange}
34
- className="w-full mb-4 bg-white md:mb-0 md:w-56 md:mr-4 text-xs"
34
+ className="w-full mb-4 md:mb-0 md:w-56 text-xs"
35
35
  options={orders}
36
36
  data-testid="account-orders-header-select"
37
37
  ></Select>
38
38
  <Button
39
39
  className={clsx(
40
- 'w-full md:w-56',
41
- isButtonDisabled && 'hover:bg-black/75 hover:text-white'
40
+ 'w-full md:w-56 md:ms-4',
41
+ isButtonDisabled &&
42
+ 'hover:bg-black hover:text-white disabled:opacity-75'
42
43
  )}
43
44
  onClick={handleClick}
44
45
  data-testid="account-orders-header-button"
@@ -3,6 +3,7 @@
3
3
  import { Accordion, LoaderSpinner, TabPanel, Tabs } from '@theme/components';
4
4
  import React from 'react';
5
5
  import { useGetWidgetQuery } from '@akinon/next/data/client/misc';
6
+ import { useLocalization } from '@akinon/next/hooks';
6
7
 
7
8
  interface Props {
8
9
  searchKey?: string;
@@ -11,6 +12,7 @@ interface Props {
11
12
  export function FaqTabs(props: Props) {
12
13
  const { searchKey } = props;
13
14
  const { data, isLoading } = useGetWidgetQuery('faq');
15
+ const { locale } = useLocalization();
14
16
 
15
17
  if (isLoading) {
16
18
  return <LoaderSpinner className="mt-4" />;
@@ -29,8 +31,12 @@ export function FaqTabs(props: Props) {
29
31
  {data?.attributes?.faq_contents
30
32
  ?.filter(
31
33
  (faq) =>
32
- faq.value.content.toLocaleLowerCase().includes(searchKey) ||
33
- faq.value.title.toLocaleLowerCase().includes(searchKey)
34
+ faq.value.content
35
+ .toLocaleLowerCase(locale)
36
+ .includes(searchKey) ||
37
+ faq.value.title
38
+ .toLocaleLowerCase(locale)
39
+ .includes(searchKey)
34
40
  )
35
41
  .map((faq, index) => {
36
42
  if (faq.value.category == item.value.category_id) {
@@ -60,7 +60,7 @@ export const Order = (props) => {
60
60
  <ul className="flex flex-wrap gap-3.5 mb-6 items-center md:mb-0">
61
61
  {props?.orderitem_set?.slice(0, 3).map((item, index) => (
62
62
  // TODO: Static image will change (TR)
63
- <li className="shrink-0" key={index}>
63
+ <li className="flex-shrink-0" key={index}>
64
64
  <Image
65
65
  src={
66
66
  item?.product?.image ? item.product.image : '/noimage.jpg'
@@ -45,7 +45,7 @@ export const OrderCancellationItem = ({
45
45
  onClick={handleClick}
46
46
  />
47
47
  )}
48
- <div className="shrink-0">
48
+ <div className="flex-shrink-0">
49
49
  <Link href={item.product.absolute_url}>
50
50
  <Image
51
51
  src={item.product.image ? item.product.image : '/noimage.jpg'}
@@ -136,7 +136,7 @@ export const AnonymousTrackingOrderDetail = ({ order }) => {
136
136
  key={index}
137
137
  >
138
138
  <div className="flex gap-3 mb-5 lg:mb-0">
139
- <div className="shrink-0">
139
+ <div className="flex-shrink-0">
140
140
  <Link
141
141
  className="block"
142
142
  href={item?.product?.absolute_url}
@@ -39,7 +39,12 @@ export const BasketItem = (props: Props) => {
39
39
  quantity: number,
40
40
  attributes: object = {}
41
41
  ) => {
42
- const requestParams: any = {
42
+ const requestParams: {
43
+ product: number;
44
+ quantity: number;
45
+ attributes: object;
46
+ namespace?: string;
47
+ } = {
43
48
  product: productPk,
44
49
  quantity,
45
50
  attributes
@@ -223,6 +223,22 @@ export const Summary = (props: Props) => {
223
223
  component={Component.OneClickCheckoutButtons}
224
224
  props={checkoutProviderProps}
225
225
  />
226
+
227
+ <div className="mt-4">
228
+ <PluginModule
229
+ component={Component.BasketVirtualTryOn}
230
+ props={{
231
+ basketItems: basket.basketitem_set.map((item) => ({
232
+ pk: item.product.pk,
233
+ sku: item.product.sku,
234
+ name: item.product.name,
235
+ productimage_set: item.product.productimage_set,
236
+ attributes: item.product.attributes,
237
+ category: (item.product as any).category
238
+ }))
239
+ }}
240
+ />
241
+ </div>
226
242
  </div>
227
243
  </div>
228
244
  );
@@ -12,7 +12,7 @@ export interface BreadcrumbProps {
12
12
  }
13
13
 
14
14
  export default function Breadcrumb(props: BreadcrumbProps) {
15
- const { t } = useLocalization();
15
+ const { t, locale } = useLocalization();
16
16
  const { breadcrumbList = [] } = props;
17
17
 
18
18
  const list = [
@@ -28,7 +28,7 @@ export default function Breadcrumb(props: BreadcrumbProps) {
28
28
  {list.map((item, index) => (
29
29
  <Fragment key={index}>
30
30
  <Link href={item.url}>
31
- {capitalize(item.text.toLocaleLowerCase())}
31
+ {capitalize(item.text.toLocaleLowerCase(locale))}
32
32
  </Link>
33
33
  {index !== list.length - 1 && <Icon name="chevron-end" size={8} />}
34
34
  </Fragment>
@@ -57,6 +57,7 @@ export default function ListPage(props: ListPageProps) {
57
57
  newUrl.searchParams.delete('page');
58
58
  router.push(newUrl.pathname + newUrl.search, undefined);
59
59
  }
60
+ // eslint-disable-next-line react-hooks/exhaustive-deps
60
61
  }, [searchParams, data.products, page]);
61
62
 
62
63
  const { t } = useLocalization();
@@ -79,7 +80,7 @@ export default function ListPage(props: ListPageProps) {
79
80
  className={clsx(
80
81
  'transition-opacity duration-300 ease-linear lg:hidden',
81
82
  isMenuOpen
82
- ? 'fixed bg-black/60 inset-0 z-10 opacity-100'
83
+ ? 'fixed bg-black bg-opacity-60 inset-0 z-10 opacity-100'
83
84
  : 'opacity-0'
84
85
  )}
85
86
  ></div>
@@ -6,7 +6,7 @@ import { useLocalization } from '@akinon/next/hooks';
6
6
  import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
7
7
  import { resetSelectedFacets } from '@theme/redux/reducers/category';
8
8
  import CategoryActiveFilters from '@theme/views/category/category-active-filters';
9
- import { useMemo, useState, useTransition } from 'react';
9
+ import { useMemo, useTransition } from 'react';
10
10
  import { FilterItem } from './filter-item';
11
11
 
12
12
  interface Props {
@@ -24,7 +24,7 @@ const CheckoutAuth = () => {
24
24
 
25
25
  return (
26
26
  <div className="flex flex-col w-full my-5 lg:flex-row">
27
- <div className="flex-1 shrink-0">
27
+ <div className="flex-1 flex-shrink-0">
28
28
  <Login />
29
29
  <div className="text-center text-sm text-gray-600 uppercase mt-5">
30
30
  <span>
@@ -6,7 +6,7 @@ import { Image } from '@akinon/next/components/image';
6
6
 
7
7
  const CheckoutHeader = () => {
8
8
  return (
9
- <div className="relative border-b border-gray-100 shadow-sm">
9
+ <div className="relative border-b border-gray-100 shadow">
10
10
  <header
11
11
  className={clsx(['py-8', 'px-4', 'mx-auto', 'container', 'md:px-0'])}
12
12
  >