@akinon/projectzero 2.0.0-beta.1 → 2.0.0-beta.10

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 (101) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/app-template/.env.example +5 -0
  3. package/app-template/.gitignore +2 -0
  4. package/app-template/CHANGELOG.md +225 -0
  5. package/app-template/README.md +6 -0
  6. package/app-template/config/prebuild-tests.json +5 -0
  7. package/app-template/{next.config.mjs → next.config.ts} +6 -3
  8. package/app-template/package.json +30 -27
  9. package/app-template/postcss.config.mjs +8 -0
  10. package/app-template/public/locales/en/account.json +4 -0
  11. package/app-template/public/locales/en/common.json +10 -0
  12. package/app-template/public/locales/tr/account.json +4 -0
  13. package/app-template/public/locales/tr/common.json +10 -0
  14. package/app-template/src/__tests__/middleware-matcher.test.ts +135 -0
  15. package/app-template/src/app/[commerce]/[locale]/[currency]/account/orders/[id]/cancellation/page.tsx +93 -4
  16. package/app-template/src/app/[commerce]/[locale]/[currency]/account/orders/[id]/page.tsx +66 -4
  17. package/app-template/src/app/[commerce]/[locale]/[currency]/account/page.tsx +1 -1
  18. package/app-template/src/app/[commerce]/[locale]/[currency]/address/stores/page.tsx +2 -2
  19. package/app-template/src/app/[commerce]/[locale]/[currency]/auth/page.tsx +1 -1
  20. package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +2 -2
  21. package/app-template/src/app/[commerce]/[locale]/[currency]/error.tsx +12 -15
  22. package/app-template/src/app/[commerce]/[locale]/[currency]/forms/[pk]/generate/page.tsx +1 -1
  23. package/app-template/src/app/[commerce]/[locale]/[currency]/{pz-not-found/page.tsx → not-found.tsx} +2 -2
  24. package/app-template/src/app/[commerce]/[locale]/[currency]/orders/checkout/page.tsx +7 -4
  25. package/app-template/src/app/[commerce]/[locale]/[currency]/xml-sitemap/[node]/route.ts +47 -1
  26. package/app-template/src/assets/globals.scss +162 -34
  27. package/app-template/src/components/__tests__/badge.test.tsx +2 -2
  28. package/app-template/src/components/accordion.tsx +1 -1
  29. package/app-template/src/components/button.tsx +50 -35
  30. package/app-template/src/components/file-input.tsx +44 -2
  31. package/app-template/src/components/input.tsx +3 -3
  32. package/app-template/src/components/modal.tsx +1 -1
  33. package/app-template/src/components/select.tsx +2 -2
  34. package/app-template/src/components/shimmer.tsx +1 -1
  35. package/app-template/src/components/tabs.tsx +2 -2
  36. package/app-template/src/components/types/index.ts +4 -1
  37. package/app-template/src/middleware.ts +1 -0
  38. package/app-template/src/plugins.js +2 -1
  39. package/app-template/src/redux/middlewares/category.ts +1 -1
  40. package/app-template/src/redux/reducers/category.ts +1 -1
  41. package/app-template/src/redux/store.ts +4 -3
  42. package/app-template/src/settings.js +1 -2
  43. package/app-template/src/utils/convert-facet-search-params.ts +1 -1
  44. package/app-template/src/views/account/contact-form.tsx +3 -8
  45. package/app-template/src/views/account/content-header.tsx +2 -3
  46. package/app-template/src/views/account/order.tsx +1 -1
  47. package/app-template/src/views/account/orders/order-cancellation-item.tsx +5 -4
  48. package/app-template/src/views/anonymous-tracking/order-detail/index.tsx +1 -1
  49. package/app-template/src/views/basket/basket-item.tsx +1 -0
  50. package/app-template/src/views/category/category-active-filters.tsx +1 -1
  51. package/app-template/src/views/category/category-header.tsx +12 -6
  52. package/app-template/src/views/category/category-info.tsx +4 -4
  53. package/app-template/src/views/category/filters/index.tsx +2 -2
  54. package/app-template/src/views/checkout/auth.tsx +1 -1
  55. package/app-template/src/views/checkout/layout/header.tsx +1 -1
  56. package/app-template/src/views/checkout/steps/payment/index.tsx +1 -1
  57. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +1 -1
  58. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +4 -4
  59. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +3 -3
  60. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +1 -1
  61. package/app-template/src/views/checkout/summary.tsx +2 -2
  62. package/app-template/src/views/header/action-menu.tsx +6 -3
  63. package/app-template/src/views/header/band.tsx +2 -2
  64. package/app-template/src/views/header/mini-basket.tsx +15 -4
  65. package/app-template/src/views/header/mobile-menu.tsx +6 -6
  66. package/app-template/src/views/header/navbar.tsx +1 -1
  67. package/app-template/src/views/header/pwa-back-button.tsx +1 -1
  68. package/app-template/src/views/header/search/index.tsx +16 -4
  69. package/app-template/src/views/header/search/results.tsx +1 -1
  70. package/app-template/src/views/header/user-menu.tsx +3 -1
  71. package/app-template/src/views/installment-options/index.tsx +1 -1
  72. package/app-template/src/views/login/index.tsx +30 -6
  73. package/app-template/src/views/otp-login/index.tsx +12 -14
  74. package/app-template/src/views/product/product-info.tsx +2 -2
  75. package/app-template/src/views/product/slider.tsx +1 -1
  76. package/app-template/src/views/product-pointer-banner-item.tsx +1 -1
  77. package/app-template/src/views/register/index.tsx +29 -4
  78. package/app-template/src/views/sales-contract-modal/index.tsx +17 -17
  79. package/app-template/src/widgets/footer-info.tsx +1 -1
  80. package/app-template/src/widgets/footer-menu.tsx +1 -1
  81. package/app-template/src/widgets/footer-subscription/index.tsx +1 -1
  82. package/app-template/src/widgets/home-stories-eng.tsx +1 -1
  83. package/app-template/tailwind.config.js +1 -137
  84. package/codemods/sentry-9/index.js +30 -0
  85. package/codemods/sentry-9/remove-sentry-configs.js +14 -0
  86. package/codemods/sentry-9/remove-sentry-dependency.js +25 -0
  87. package/codemods/sentry-9/replace-error-page.js +32 -0
  88. package/commands/codemod.ts +18 -0
  89. package/commands/index.ts +3 -1
  90. package/commands/plugins.ts +4 -0
  91. package/dist/codemods/sentry-9/templates/error.js +14 -0
  92. package/dist/commands/codemod.js +16 -0
  93. package/dist/commands/index.js +3 -1
  94. package/dist/commands/plugins.js +4 -0
  95. package/package.json +1 -1
  96. package/app-template/postcss.config.js +0 -6
  97. package/app-template/sentry.client.config.ts +0 -16
  98. package/app-template/sentry.edge.config.ts +0 -3
  99. package/app-template/sentry.properties +0 -4
  100. package/app-template/sentry.server.config.ts +0 -3
  101. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +0 -67
@@ -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 shadow p-4 min-h-[8rem]',
133
+ 'cursor-pointer relative w-full border border-gray-200 shadow-sm 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 shadow p-4',
156
+ 'relative cursor-pointer w-full min-h-[8rem] border border-gray-200 shadow-sm 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
  {
@@ -149,7 +149,7 @@ export const Summary = () => {
149
149
  </div>
150
150
  </div>
151
151
  <div className="flex flex-col py-4 px-4 text-black-800 text-xs sm:px-5">
152
- <div className="w-full overflow-hidden overflow-ellipsis mb-1 last:mb-0">
152
+ <div className="w-full overflow-hidden text-ellipsis mb-1 last:mb-0">
153
153
  <Trans
154
154
  i18nKey="checkout.summary.info"
155
155
  components={{
@@ -162,7 +162,7 @@ export const Summary = () => {
162
162
  }}
163
163
  />
164
164
  </div>
165
- <div className="w-full overflow-hidden overflow-ellipsis mb-1 last:mb-0">
165
+ <div className="w-full overflow-hidden text-ellipsis mb-1 last:mb-0">
166
166
  {preOrder.shipping_address?.line}{' '}
167
167
  {preOrder.shipping_address?.postcode}{' '}
168
168
  {preOrder.shipping_address?.district && (
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { ReactNode, useMemo, useRef, useCallback } from 'react';
4
- import { useGetBasketQuery } from '@akinon/next/data/client/basket';
4
+ import { useGetMiniBasketQuery } from '@akinon/next/data/client/basket';
5
5
  import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
6
6
  import {
7
7
  closeMiniBasket,
@@ -29,8 +29,11 @@ interface MenuItem {
29
29
  export default function ActionMenu() {
30
30
  const dispatch = useAppDispatch();
31
31
 
32
- const { data } = useGetBasketQuery();
33
- const totalQuantity = useMemo(() => data?.total_quantity ?? 0, [data]);
32
+ const { data: miniBasket } = useGetMiniBasketQuery();
33
+ const totalQuantity = useMemo(
34
+ () => miniBasket?.total_quantity ?? 0,
35
+ [miniBasket]
36
+ );
34
37
 
35
38
  const { open: miniBasketOpen } = useAppSelector(
36
39
  (state) => state.root.miniBasket
@@ -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 overflow-ellipsis line-clamp-2 h-full flex items-center justify-center">
19
+ <div className="text-center text-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">
25
+ <div className="flex items-center justify-end h-full gap-10">
26
26
  <UserMenu isMobile={false} />
27
27
  <ActionMenu />
28
28
  </div>
@@ -5,6 +5,7 @@ import clsx from 'clsx';
5
5
  import {
6
6
  basketApi,
7
7
  useGetBasketQuery,
8
+ useGetMiniBasketQuery,
8
9
  useUpdateQuantityMutation
9
10
  } from '@akinon/next/data/client/basket';
10
11
  import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
@@ -150,13 +151,23 @@ export default function MiniBasket() {
150
151
  (state) => state.root.miniBasket
151
152
  );
152
153
  const dispatch = useAppDispatch();
153
- const { data: basket, isLoading, isSuccess } = useGetBasketQuery();
154
+ const {
155
+ data: basket,
156
+ isLoading,
157
+ isSuccess
158
+ } = useGetBasketQuery(undefined, {
159
+ skip: !miniBasketOpen
160
+ });
161
+ const { data: miniBasket } = useGetMiniBasketQuery();
154
162
  const { t } = useLocalization();
155
163
  const { highlightedItem } = useAppSelector((state) => state.root.miniBasket);
156
164
  const [highlightedItemPk, setHighlightedItemPk] = useState(0);
157
165
  const [sortedBasket, setSortedBasket] = useState([]);
158
166
 
159
- const totalQuantity = useMemo(() => basket?.total_quantity ?? 0, [basket]);
167
+ const totalQuantity = useMemo(
168
+ () => miniBasket?.total_quantity ?? 0,
169
+ [miniBasket]
170
+ );
160
171
  const miniBasketList = useRef(null);
161
172
 
162
173
  useEffect(() => {
@@ -192,7 +203,7 @@ export default function MiniBasket() {
192
203
  miniBasketOpen
193
204
  ? 'opacity-100 visible lg:invisible'
194
205
  : 'opacity-0 invisible',
195
- 'fixed top-0 left-0 z-50 w-screen h-screen bg-black bg-opacity-80 transition-all duration-300'
206
+ 'fixed top-0 left-0 z-50 w-screen h-screen bg-black/80 transition-all duration-300'
196
207
  )}
197
208
  onClick={() => {
198
209
  dispatch(closeMiniBasket());
@@ -206,7 +217,7 @@ export default function MiniBasket() {
206
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'
207
218
  )}
208
219
  >
209
- <header className="flex items-center gap-2 pb-3 border-b">
220
+ <header className="flex items-center gap-2 pb-3 border-b border-gray-200">
210
221
  <h3 className="text-xl font-bold">
211
222
  {t('basket.mini_basket.my_bag')}
212
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 bg-opacity-80 transition duration-500',
61
+ 'fixed top-0 left-0 z-30 w-screen h-screen invisible opacity-0 bg-black/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 h-[61px] py-4 mb-4">
113
+ <header className="flex items-center justify-between border-b border-gray-200 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 flex-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 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
@@ -22,19 +22,31 @@ export default function Search() {
22
22
  if (isSearchOpen) {
23
23
  inputRef.current?.focus();
24
24
  document.body.style.overflow = 'hidden';
25
+
26
+ const handleEscKey = (e: KeyboardEvent) => {
27
+ if (e.key === 'Escape') {
28
+ dispatch(closeSearch());
29
+ }
30
+ };
31
+
32
+ document.addEventListener('keydown', handleEscKey);
33
+ return () => {
34
+ document.removeEventListener('keydown', handleEscKey);
35
+ document.body.style.overflow = 'auto';
36
+ };
25
37
  }
26
38
 
27
39
  return () => {
28
40
  document.body.style.overflow = 'auto';
29
41
  };
30
- }, [isSearchOpen]);
42
+ }, [isSearchOpen, dispatch]);
31
43
 
32
44
  return (
33
45
  <>
34
46
  <div
35
47
  className={clsx(
36
48
  // 177px is the height of the header
37
- 'absolute bg-black opacity-75 w-screen h-screen transition duration-500 left-0 bottom-0 translate-y-full z-30',
49
+ 'absolute bg-black/75 w-screen h-screen transition duration-500 left-0 bottom-0 translate-y-full z-30',
38
50
  isSearchOpen && searchText
39
51
  ? 'visible opacity-100'
40
52
  : 'invisible opacity-0'
@@ -48,7 +60,7 @@ export default function Search() {
48
60
  isSearchOpen ? 'visible opacity-100' : 'invisible opacity-0'
49
61
  )}
50
62
  >
51
- <div className="max-w-screen-2xl mx-auto flex flex-col gap-12">
63
+ <div className="max-w-(--breakpoint-2xl) mx-auto flex flex-col gap-12">
52
64
  <div className="border-b border-gray-400 flex flex-col py-1.5 gap-2 self-center items-center md:flex-row">
53
65
  <span className="text-xl lg:text-2xl">
54
66
  {t('common.search.results_for')}
@@ -62,7 +74,7 @@ export default function Search() {
62
74
  router.push(`${ROUTES.LIST}/?search_text=${searchText}`);
63
75
  }
64
76
  }}
65
- className="border-0 text-2xl outline-none text-secondary placeholder:text-xl placeholder:lg:text-2xl"
77
+ className="border-0 text-2xl outline-hidden text-secondary placeholder:text-xl lg:placeholder:text-2xl"
66
78
  placeholder={t('common.search.placeholder')}
67
79
  ref={inputRef}
68
80
  />
@@ -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,7 +36,9 @@ export const UserMenu = (props: UserMenuProps) => {
36
36
  <ul
37
37
  className={clsx(
38
38
  'items-center divide-x divide-black',
39
- isMobile ? 'flex pt-2 text-sm pb-6 border-b mx-8' : 'hidden sm:flex'
39
+ isMobile
40
+ ? 'flex pt-2 text-sm pb-6 border-b border-gray-200 mx-8'
41
+ : 'hidden sm:flex'
40
42
  )}
41
43
  id="user-menu"
42
44
  >
@@ -22,7 +22,7 @@ const InstallmentOptions = (props: InstallmentProps) => {
22
22
 
23
23
  useEffect(() => {
24
24
  if (isSuccess && data) {
25
- setActiveCardSlug(data.results[0].slug);
25
+ setActiveCardSlug(data.results?.[0]?.slug);
26
26
  }
27
27
  }, [isSuccess, data]);
28
28
 
@@ -103,10 +103,34 @@ export const Login = () => {
103
103
  )?.data as string[];
104
104
 
105
105
  fieldErrors?.forEach((item) => {
106
- setError(item.name as keyof LoginFormType, {
107
- type: 'custom',
108
- message: item.value.join(', ')
109
- });
106
+ let parsedValue: Record<string, string[]> | string[] = [];
107
+
108
+ if (typeof item.value === 'string') {
109
+ try {
110
+ parsedValue = JSON.parse(item.value);
111
+ } catch {
112
+ parsedValue = [item.value];
113
+ }
114
+ } else {
115
+ parsedValue = item.value;
116
+ }
117
+
118
+ if (Array.isArray(parsedValue)) {
119
+ setError(item.name as keyof LoginFormType, {
120
+ type: 'custom',
121
+ message: parsedValue.join(', '),
122
+ });
123
+ } else {
124
+ Object.keys(parsedValue).forEach((key) => {
125
+ const fieldName = key as keyof LoginFormType;
126
+ const errorMessages = parsedValue[key] as string[];
127
+
128
+ setError(fieldName, {
129
+ type: 'custom',
130
+ message: errorMessages.join(', '),
131
+ });
132
+ });
133
+ }
110
134
  });
111
135
 
112
136
  if (nonFieldErrors?.length) {
@@ -195,7 +219,7 @@ export const Login = () => {
195
219
  {t('auth.login.form.submit')}
196
220
  </Button>
197
221
 
198
- <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">
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">
199
223
  {t('auth.login.form.or')}
200
224
  </p>
201
225
 
@@ -224,7 +248,7 @@ export const Login = () => {
224
248
  alt={provider.label}
225
249
  width={provider.label === 'Facebook' ? 10 : 18}
226
250
  height={18}
227
- className="flex-shrink-0"
251
+ className="shrink-0"
228
252
  />
229
253
  )}
230
254
 
@@ -10,9 +10,10 @@ import { yupResolver } from '@hookform/resolvers/yup';
10
10
  import clsx from 'clsx';
11
11
  import { useLocalization } from '@akinon/next/hooks';
12
12
  import { useOtpLoginMutation } from '@akinon/next/data/client/user';
13
- import { useAppSelector } from '@akinon/next/redux/hooks';
13
+ import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
14
14
  import PluginModule, { Component } from '@akinon/next/components/plugin-module';
15
15
  import { AuthError } from '@akinon/next/types';
16
+ import { showPopup } from '@akinon/pz-otp/src/redux/reducer';
16
17
 
17
18
  const loginFormSchema = (t) =>
18
19
  yup.object().shape({
@@ -25,9 +26,9 @@ const loginFormSchema = (t) =>
25
26
  });
26
27
 
27
28
  export const OtpLogin = () => {
29
+ const dispatch = useAppDispatch();
28
30
  const { user_phone_format } = useAppSelector((state) => state.config);
29
31
  const { t, locale } = useLocalization();
30
- const [showOtpModal, setShowOtpModal] = useState(false);
31
32
  const [otpLoginMutation] = useOtpLoginMutation();
32
33
 
33
34
  const {
@@ -79,7 +80,7 @@ export const OtpLogin = () => {
79
80
  })
80
81
  .unwrap()
81
82
  .then(() => {
82
- setShowOtpModal(true);
83
+ dispatch(showPopup());
83
84
  })
84
85
  .catch((error) => {
85
86
  if (error.status === 429) {
@@ -136,17 +137,14 @@ export const OtpLogin = () => {
136
137
  </Button>
137
138
  </form>
138
139
 
139
- {showOtpModal && (
140
- <PluginModule
141
- component={Component.Otp}
142
- props={{
143
- setShowPopup: setShowOtpModal,
144
- data: getValues(),
145
- submitAction: loginHandler,
146
- error: formError
147
- }}
148
- />
149
- )}
140
+ <PluginModule
141
+ component={Component.Otp}
142
+ props={{
143
+ data: getValues(),
144
+ submitAction: loginHandler,
145
+ error: formError
146
+ }}
147
+ />
150
148
  </section>
151
149
  );
152
150
  };
@@ -159,7 +159,7 @@ export default function ProductInfo({ data }: ProductPageProps) {
159
159
  <>
160
160
  <div
161
161
  className={clsx(
162
- 'fixed bottom-0 left-0 w-1/2 h-14 z-[20] bg-white mt-0 border-t border-gray-500 items-center justify-center',
162
+ 'fixed bottom-0 left-0 w-1/2 h-14 z-20 bg-white mt-0 border-t border-gray-500 items-center justify-center',
163
163
  'sm:relative sm:flex sm:items-center sm:mt-5 sm:border-none'
164
164
  )}
165
165
  >
@@ -188,7 +188,7 @@ export default function ProductInfo({ data }: ProductPageProps) {
188
188
  <Button
189
189
  disabled={isAddToCartLoading || isAddToStockAlertLoading}
190
190
  className={clsx(
191
- 'fixed bottom-0 right-0 w-1/2 h-14 z-[20] flex items-center justify-center fill-primary-foreground',
191
+ 'fixed bottom-0 right-0 w-1/2 h-14 z-20 flex items-center justify-center fill-primary-foreground',
192
192
  'hover:fill-primary sm:relative sm:w-full sm:mt-3 sm:font-semibold sm:h-12'
193
193
  )}
194
194
  onClick={() => {
@@ -86,7 +86,7 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
86
86
  </div>
87
87
 
88
88
  <div className="relative lg:col-span-5">
89
- <FavButton className="absolute right-8 top-6 z-[20] sm:hidden" />
89
+ <FavButton className="absolute right-8 top-6 z-20 sm:hidden" />
90
90
 
91
91
  <CarouselCore
92
92
  responsive={{
@@ -109,7 +109,7 @@ const ProductPointerWidget = (props: ProductPointerWidgetProps) => {
109
109
  hidden: buttonStatus
110
110
  })}
111
111
  >
112
- <div className="w-full h-full flex items-center gap-2 flex-shrink-0">
112
+ <div className="w-full h-full flex items-center gap-2 shrink-0">
113
113
  <Image
114
114
  src={productItem?.kwargs?.value?.card_image?.url}
115
115
  alt={productItem?.value?.alt}
@@ -143,6 +143,7 @@ export const Register = () => {
143
143
  if (registerResponse.error) {
144
144
  const errors: AuthError[] = JSON.parse(registerResponse.error);
145
145
 
146
+
146
147
  if (errors.find((error) => error.type === 'captcha')) {
147
148
  if (await validateCaptcha()) {
148
149
  onSubmit(data);
@@ -164,10 +165,34 @@ export const Register = () => {
164
165
  )?.data as string[];
165
166
 
166
167
  fieldErrors?.forEach((item) => {
167
- setError(item.name as keyof RegisterFormType, {
168
- type: 'custom',
169
- message: item.value.join(', ')
170
- });
168
+ let parsedValue: Record<string, string[]> | string[] = [];
169
+
170
+ if (typeof item.value === 'string') {
171
+ try {
172
+ parsedValue = JSON.parse(item.value);
173
+ } catch {
174
+ parsedValue = [item.value];
175
+ }
176
+ } else {
177
+ parsedValue = item.value;
178
+ }
179
+
180
+ if (Array.isArray(parsedValue)) {
181
+ setError(item.name as keyof RegisterFormType, {
182
+ type: 'custom',
183
+ message: parsedValue.join(', '),
184
+ });
185
+ } else {
186
+ Object.keys(parsedValue).forEach((key) => {
187
+ const fieldName = key as keyof RegisterFormType;
188
+ const errorMessages = parsedValue[key] as string[];
189
+
190
+ setError(fieldName, {
191
+ type: 'custom',
192
+ message: errorMessages.join(', '),
193
+ });
194
+ });
195
+ }
171
196
  });
172
197
 
173
198
  if (nonFieldErrors?.length) {
@@ -169,19 +169,19 @@ export const SalesContractModal = ({ data }: Props) => {
169
169
  </div>
170
170
  </div>
171
171
 
172
- <table className="w-full border">
172
+ <table className="w-full border border-gray-200">
173
173
  <thead className="text-sm">
174
174
  <tr>
175
- <th className="border font-normal text-left py-2 px-3">
175
+ <th className="border border-gray-200 font-normal text-left py-2 px-3">
176
176
  {t('account.sales_contract.product')}
177
177
  </th>
178
- <th className="border font-normal py-2 px-3">
178
+ <th className="border border-gray-200 font-normal py-2 px-3">
179
179
  {t('account.sales_contract.quantity')}
180
180
  </th>
181
- <th className="border font-normal text-left py-2 px-3">
181
+ <th className="border border-gray-200 font-normal text-left py-2 px-3">
182
182
  {t('account.sales_contract.product_price')}
183
183
  </th>
184
- <th className="border font-normal text-left py-2 px-3">
184
+ <th className="border border-gray-200 font-normal text-left py-2 px-3">
185
185
  {t('account.sales_contract.total_price')}
186
186
  </th>
187
187
  </tr>
@@ -191,20 +191,20 @@ export const SalesContractModal = ({ data }: Props) => {
191
191
  {data.orderitem_set.map((value, index) => {
192
192
  return (
193
193
  <tr key={index.toString()}>
194
- <td className="border font-light text-left py-2 px-3">
194
+ <td className="border border-gray-200 font-light text-left py-2 px-3">
195
195
  {value.product.name}
196
196
  </td>
197
- <td className="border font-light text-center py-2 px-3">
197
+ <td className="border border-gray-200 font-light text-center py-2 px-3">
198
198
  {value.quantity ? value.quantity : '1'}
199
199
  </td>
200
- <td className="border font-light text-center py-2 px-3">
200
+ <td className="border border-gray-200 font-light text-center py-2 px-3">
201
201
  <Price
202
202
  value={
203
203
  value.unit_price ? value.unit_price : value.price
204
204
  }
205
205
  />
206
206
  </td>
207
- <td className="border font-light text-right py-2 px-3">
207
+ <td className="border border-gray-200 font-light text-right py-2 px-3">
208
208
  <Price
209
209
  value={
210
210
  value.total_amount
@@ -222,12 +222,12 @@ export const SalesContractModal = ({ data }: Props) => {
222
222
  <tr>
223
223
  <th
224
224
  colSpan={3}
225
- className="border text-left py-2 px-3 font-normal"
225
+ className="border border-gray-200 text-left py-2 px-3 font-normal"
226
226
  >
227
227
  {t('account.sales_contract.products_total')}
228
228
  </th>
229
229
  <td
230
- className="border text-right py-2 px-3 font-light"
230
+ className="border border-gray-200 text-right py-2 px-3 font-light"
231
231
  colSpan={1}
232
232
  >
233
233
  <Price value={data.discount_amount} />
@@ -237,12 +237,12 @@ export const SalesContractModal = ({ data }: Props) => {
237
237
  <tr>
238
238
  <th
239
239
  colSpan={3}
240
- className="border text-left py-2 px-3 font-normal"
240
+ className="border border-gray-200 text-left py-2 px-3 font-normal"
241
241
  >
242
242
  {t('account.sales_contract.shipping_price')}
243
243
  </th>
244
244
  <td
245
- className="border text-right py-2 px-3 font-light"
245
+ className="border border-gray-200 text-right py-2 px-3 font-light"
246
246
  colSpan={1}
247
247
  >
248
248
  <Price value={data.shipping_amount} />
@@ -252,12 +252,12 @@ export const SalesContractModal = ({ data }: Props) => {
252
252
  <tr>
253
253
  <th
254
254
  colSpan={3}
255
- className="border text-left py-2 px-3 font-normal"
255
+ className="border border-gray-200 text-left py-2 px-3 font-normal"
256
256
  >
257
257
  {t('account.sales_contract.payment_type')}
258
258
  </th>
259
259
  <td
260
- className="border text-right py-2 px-3 font-light"
260
+ className="border border-gray-200 text-right py-2 px-3 font-light"
261
261
  colSpan={1}
262
262
  >
263
263
  {data.payment_option_slug}
@@ -267,12 +267,12 @@ export const SalesContractModal = ({ data }: Props) => {
267
267
  <tr>
268
268
  <th
269
269
  colSpan={3}
270
- className="border text-left py-2 px-3 font-normal"
270
+ className="border border-gray-200 text-left py-2 px-3 font-normal"
271
271
  >
272
272
  {t('account.sales_contract.order_total')}
273
273
  </th>
274
274
  <td
275
- className="border text-right py-2 px-3 font-light"
275
+ className="border border-gray-200 text-right py-2 px-3 font-light"
276
276
  colSpan={1}
277
277
  >
278
278
  <Price value={data.amount} />
@@ -19,7 +19,7 @@ export default async function FooterInfo() {
19
19
  const data = await getWidgetData<FooterInfoType>({ slug: 'footer-info' });
20
20
 
21
21
  return (
22
- <div className="flex flex-col w-full mb-4 pb-4 text-xs border-b md:border-b-0 md:pb-0 md:mb-0 md:w-3/12 lg:w-1/6 md:pr-7">
22
+ <div className="flex flex-col w-full mb-4 pb-4 text-xs border-b border-gray-200 md:border-b-0 md:pb-0 md:mb-0 md:w-3/12 lg:w-1/6 md:pr-7">
23
23
  <div className="flex items-center mb-8">
24
24
  <div className="mr-3">
25
25
  <svg
@@ -50,7 +50,7 @@ export default async function FooterMenu() {
50
50
 
51
51
  return (
52
52
  <div className="flex-1">
53
- <div className="hidden justify-between text-xs md:flex md:px-6 md:py-4 md:border-r md:border-l">
53
+ <div className="hidden justify-between text-xs md:flex md:px-6 md:py-4 md:border-r md:border-l md:border-gray-200">
54
54
  <div>
55
55
  <div className="mb-4 font-medium" data-testid="footer-categories">
56
56
  {data?.attributes?.first_column_title?.value}
@@ -19,7 +19,7 @@ export default async function FooterSubscription() {
19
19
  });
20
20
 
21
21
  return (
22
- <div className="py-4 border-t md:border-t-0 lg:pl-7">
22
+ <div className="py-4 border-t border-gray-200 md:border-t-0 lg:pl-7">
23
23
  <h3 className="mb-1 text-xs font-medium">
24
24
  {data?.attributes?.title?.value}
25
25
  </h3>
@@ -82,7 +82,7 @@ export default async function HomeStoriesEng() {
82
82
  {data?.attributes?.stories?.map((story, index) => {
83
83
  return (
84
84
  <div
85
- className="mr-4 flex-shrink-0 first:ms-4 w-32 md:w-32"
85
+ className="mr-4 shrink-0 first:ms-4 w-32 md:w-32"
86
86
  key={`story__${index}`}
87
87
  >
88
88
  <Link href={story?.value?.url} aria-label={story?.value?.alt}>