@akinon/projectzero 1.48.0 → 1.50.0-rc.0

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 (48) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +3 -2
  3. package/app-template/.gitignore +2 -0
  4. package/app-template/.lintstagedrc.js +5 -4
  5. package/app-template/CHANGELOG.md +1712 -47
  6. package/app-template/docs/basic-setup.md +1 -1
  7. package/app-template/docs/plugins.md +7 -7
  8. package/app-template/package-lock.json +29303 -0
  9. package/app-template/package.json +22 -20
  10. package/app-template/public/locales/en/account.json +4 -4
  11. package/app-template/public/locales/tr/account.json +1 -1
  12. package/app-template/src/app/[commerce]/[locale]/[currency]/[...prettyurl]/page.tsx +8 -0
  13. package/app-template/src/app/[commerce]/[locale]/[currency]/account/address/page.tsx +1 -1
  14. package/app-template/src/app/[commerce]/[locale]/[currency]/account/coupons/page.tsx +4 -4
  15. package/app-template/src/app/[commerce]/[locale]/[currency]/account/profile/page.tsx +1 -0
  16. package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +5 -2
  17. package/app-template/src/app/[commerce]/[locale]/[currency]/orders/completed/[token]/page.tsx +12 -8
  18. package/app-template/src/components/checkbox.tsx +2 -2
  19. package/app-template/src/components/input.tsx +19 -7
  20. package/app-template/src/components/pagination.tsx +13 -18
  21. package/app-template/src/components/price.tsx +9 -4
  22. package/app-template/src/redux/reducers/category.ts +7 -1
  23. package/app-template/src/settings.js +6 -1
  24. package/app-template/src/views/account/address-card.tsx +2 -2
  25. package/app-template/src/views/account/address-form.tsx +22 -7
  26. package/app-template/src/views/account/contact-form.tsx +23 -6
  27. package/app-template/src/views/account/favorite-item.tsx +2 -2
  28. package/app-template/src/views/account/favourite-products/favourite-products-list.tsx +5 -1
  29. package/app-template/src/views/breadcrumb.tsx +4 -1
  30. package/app-template/src/views/category/category-active-filters.tsx +16 -6
  31. package/app-template/src/views/category/category-info.tsx +31 -17
  32. package/app-template/src/views/category/filters/filter-item.tsx +163 -0
  33. package/app-template/src/views/category/filters/index.tsx +16 -108
  34. package/app-template/src/views/category/layout.tsx +5 -3
  35. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +33 -4
  36. package/app-template/src/views/checkout/steps/payment/options/redirection.tsx +43 -37
  37. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +19 -3
  38. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +2 -2
  39. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +1 -1
  40. package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +230 -37
  41. package/app-template/src/views/find-in-store/index.tsx +2 -3
  42. package/app-template/src/views/header/mobile-menu.tsx +25 -8
  43. package/app-template/src/views/product/product-info.tsx +0 -2
  44. package/app-template/tsconfig.json +14 -4
  45. package/app-template/yarn.lock +1824 -1953
  46. package/commands/create.ts +29 -5
  47. package/dist/commands/create.js +25 -2
  48. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectzeronext",
3
- "version": "1.48.0",
3
+ "version": "1.50.0-rc.0",
4
4
  "private": true,
5
5
  "license": "MIT",
6
6
  "scripts": {
@@ -22,27 +22,26 @@
22
22
  "prestart": "pz-prestart"
23
23
  },
24
24
  "dependencies": {
25
- "@akinon/next": "1.48.0",
26
- "@akinon/pz-akifast": "1.48.0",
27
- "@akinon/pz-b2b": "1.48.0",
28
- "@akinon/pz-basket-gift-pack": "1.48.0",
29
- "@akinon/pz-bkm": "1.48.0",
30
- "@akinon/pz-checkout-gift-pack": "1.48.0",
31
- "@akinon/pz-click-collect": "1.48.0",
32
- "@akinon/pz-credit-payment": "1.48.0",
33
- "@akinon/pz-gpay": "1.48.0",
34
- "@akinon/pz-masterpass": "1.48.0",
35
- "@akinon/pz-one-click-checkout": "1.48.0",
36
- "@akinon/pz-otp": "1.48.0",
37
- "@akinon/pz-pay-on-delivery": "1.48.0",
25
+ "@akinon/next": "1.50.0-rc.0",
26
+ "@akinon/pz-akifast": "1.50.0-rc.0",
27
+ "@akinon/pz-b2b": "1.50.0-rc.0",
28
+ "@akinon/pz-basket-gift-pack": "1.50.0-rc.0",
29
+ "@akinon/pz-bkm": "1.50.0-rc.0",
30
+ "@akinon/pz-checkout-gift-pack": "1.50.0-rc.0",
31
+ "@akinon/pz-click-collect": "1.50.0-rc.0",
32
+ "@akinon/pz-credit-payment": "1.50.0-rc.0",
33
+ "@akinon/pz-gpay": "1.50.0-rc.0",
34
+ "@akinon/pz-masterpass": "1.50.0-rc.0",
35
+ "@akinon/pz-one-click-checkout": "1.50.0-rc.0",
36
+ "@akinon/pz-otp": "1.50.0-rc.0",
37
+ "@akinon/pz-pay-on-delivery": "1.50.0-rc.0",
38
38
  "@hookform/resolvers": "2.9.0",
39
39
  "@next/third-parties": "14.1.0",
40
40
  "@react-google-maps/api": "2.17.1",
41
- "@sentry/nextjs": "7.105.0",
41
+ "@sentry/nextjs": "7.116.0",
42
42
  "dayjs": "1.11.5",
43
- "eslint-config-next": "14.2.2",
44
43
  "lossless-json": "2.0.5",
45
- "next": "14.2.2",
44
+ "next": "14.2.5",
46
45
  "next-auth": "4.24.5",
47
46
  "next-pwa": "5.6.0",
48
47
  "pino": "8.11.0",
@@ -59,7 +58,7 @@
59
58
  "yup": "0.32.11"
60
59
  },
61
60
  "devDependencies": {
62
- "@akinon/eslint-plugin-projectzero": "1.48.0",
61
+ "@akinon/eslint-plugin-projectzero": "1.50.0-rc.0",
63
62
  "@semantic-release/changelog": "6.0.2",
64
63
  "@semantic-release/exec": "6.0.3",
65
64
  "@semantic-release/git": "10.0.1",
@@ -79,14 +78,14 @@
79
78
  "clsx": "1.1.1",
80
79
  "currency-symbol-map": "5.1.0",
81
80
  "eslint": "8.14.0",
82
- "eslint-config-next": "14.0.3",
81
+ "eslint-config-next": "14.2.3",
83
82
  "eslint-config-prettier": "8.5.0",
84
83
  "husky": "8.0.0",
85
84
  "jest": "29.7.0",
86
85
  "jest-css-modules-transform": "4.3.0",
87
86
  "lint-staged": "13.1.0",
88
87
  "prettier": "2.6.2",
89
- "react-number-format": "4.9.3",
88
+ "react-number-format": "5.3.4",
90
89
  "sass": "1.49.9",
91
90
  "semantic-release": "19.0.5",
92
91
  "server-only": "0.0.1",
@@ -100,5 +99,8 @@
100
99
  "ts-node": "10.7.0",
101
100
  "typescript": "5.2.2"
102
101
  },
102
+ "resolutions": {
103
+ "**/postcss": "8.4.31"
104
+ },
103
105
  "packageManager": "yarn@1.22.17"
104
106
  }
@@ -286,11 +286,11 @@
286
286
  "empty_coupon": "You don't have any coupons"
287
287
  },
288
288
  "title": {
289
- "campaings": {
289
+ "campaigns": {
290
290
  "active": "Active Campaigns",
291
- "to_be_active": "Campaings to be Active",
292
- "expired": "Expired Campaings",
293
- "used": "Used Campaings"
291
+ "to_be_active": "Campaigns to be Active",
292
+ "expired": "Expired Campaigns",
293
+ "used": "Used Campaigns"
294
294
  },
295
295
  "coupons": {
296
296
  "active": "Active Coupons",
@@ -286,7 +286,7 @@
286
286
  "empty_coupon": "Herhangi bir kuponunuz yok"
287
287
  },
288
288
  "title": {
289
- "campaings": {
289
+ "campaigns": {
290
290
  "active": "Aktif Kampanyalar",
291
291
  "to_be_active": "Aktif Olacak Kampanyalar",
292
292
  "expired": "Süresi Dolmuş Kampanyalar",
@@ -0,0 +1,8 @@
1
+ import Page, {
2
+ dynamic,
3
+ revalidate,
4
+ generateMetadata
5
+ } from '@akinon/next/routes/pretty-url';
6
+
7
+ export { dynamic, revalidate, generateMetadata };
8
+ export default Page;
@@ -17,7 +17,7 @@ export default function Page() {
17
17
  const [addAddress] = useAddAddressMutation();
18
18
 
19
19
  const onSubmit = async (data) => {
20
- await addAddress(data);
20
+ await addAddress({ ...data, invalidateTag: 'Addresses' });
21
21
  setIsNewAddressModalOpen(false);
22
22
  };
23
23
 
@@ -51,7 +51,7 @@ export default function Page() {
51
51
  {basketOffersLoading && <LoaderSpinner className="mb-8" />}
52
52
  {basketOffersSuccess && (
53
53
  <CouponItem
54
- mainTitle={t('account.my_vouchers.title.campaings.active')}
54
+ mainTitle={t('account.my_vouchers.title.campaigns.active')}
55
55
  subTitles={[
56
56
  t('account.my_vouchers.card.campaign_name'),
57
57
  t('account.my_vouchers.card.starting_date'),
@@ -68,7 +68,7 @@ export default function Page() {
68
68
  {futureBasketOffersLoading && <LoaderSpinner className="mb-8" />}
69
69
  {futureBasketOffersSuccess && (
70
70
  <CouponItem
71
- mainTitle={t('account.my_vouchers.title.campaings.to_be_active')}
71
+ mainTitle={t('account.my_vouchers.title.campaigns.to_be_active')}
72
72
  subTitles={[
73
73
  t('account.my_vouchers.card.campaign_name'),
74
74
  t('account.my_vouchers.card.starting_date'),
@@ -85,7 +85,7 @@ export default function Page() {
85
85
  {expiredBasketOffersLoading && <LoaderSpinner className="mb-8" />}
86
86
  {expiredBasketOffersSuccess && (
87
87
  <CouponItem
88
- mainTitle={t('account.my_vouchers.title.campaings.expired')}
88
+ mainTitle={t('account.my_vouchers.title.campaigns.expired')}
89
89
  subTitles={[
90
90
  t('account.my_vouchers.card.campaign_name'),
91
91
  t('account.my_vouchers.card.starting_date'),
@@ -102,7 +102,7 @@ export default function Page() {
102
102
  {discountItemsLoading && <LoaderSpinner className="mb-8" />}
103
103
  {discountItemsSuccess && (
104
104
  <CouponItem
105
- mainTitle={t('account.my_vouchers.title.campaings.used')}
105
+ mainTitle={t('account.my_vouchers.title.campaigns.used')}
106
106
  subTitles={[
107
107
  t('account.my_vouchers.card.campaign_name'),
108
108
  t('account.my_vouchers.card.starting_date'),
@@ -247,6 +247,7 @@ export default function Page() {
247
247
  className="mb-5"
248
248
  required
249
249
  />
250
+
250
251
  <div className="mb-5">
251
252
  <Input
252
253
  label={t('account.my_profile.form.phone.placeholder')}
@@ -4,11 +4,14 @@ import { PageProps } from '@akinon/next/types';
4
4
  import CategoryLayout from '@theme/views/category/layout';
5
5
 
6
6
  async function Page({ params, searchParams }: PageProps<{ pk: number }>) {
7
- const { data } = await getCategoryData({ pk: params.pk, searchParams });
7
+ const { data, breadcrumbData } = await getCategoryData({
8
+ pk: params.pk,
9
+ searchParams
10
+ });
8
11
 
9
12
  return (
10
13
  <>
11
- <CategoryLayout data={data} />
14
+ <CategoryLayout data={data} breadcrumbData={breadcrumbData} />
12
15
  </>
13
16
  );
14
17
  }
@@ -182,21 +182,25 @@ const CheckoutCompleted = ({
182
182
  }}
183
183
  >
184
184
  {data.order.orderitem_set.map((item) => (
185
- <div key={`order-item-${item.id}`} className="flex">
186
- <Link href={item.product.absolute_url} passHref>
185
+ <div
186
+ key={`order-item-${item.id}`}
187
+ className="flex justify-between gap-x-4 w-full"
188
+ >
189
+ <Link
190
+ className="flex justify-between gap-x-4 flex-1 items-center transition-all text-xs text-black-800 hover:text-secondary"
191
+ href={item.product.absolute_url}
192
+ passHref
193
+ >
187
194
  <Image
188
195
  src={item.product.image}
189
196
  alt={item.product.name}
190
197
  width={64}
191
198
  height={96}
192
199
  />
200
+
201
+ <span>{item.product.name}</span>
193
202
  </Link>
194
- <div className="flex justify-between flex-1 items-center ml-4">
195
- <>
196
- <div className="text-xs text-black-800 transition-all w-full hover:text-secondary">
197
- {item.product.name}
198
- </div>
199
- </>
203
+ <div className="flex justify-end items-center">
200
204
  <div>
201
205
  {item.retail_price !== item.price && (
202
206
  <div className="text-black-800 line-through text-xs min-w-max sm:text-sm">
@@ -3,7 +3,7 @@ import { CheckboxProps } from '@theme/components/types';
3
3
  import { twMerge } from 'tailwind-merge';
4
4
 
5
5
  const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
6
- const { children, checked, error, ...rest } = props;
6
+ const { children, checked = false, error, ...rest } = props;
7
7
 
8
8
  return (
9
9
  <label className={twMerge('flex flex-col text-xs', props.className)}>
@@ -12,7 +12,7 @@ const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
12
12
  type="checkbox"
13
13
  {...rest}
14
14
  ref={ref}
15
- checked={checked ?? false}
15
+ defaultChecked={checked}
16
16
  className="w-4 h-4 shrink-0"
17
17
  />
18
18
  {children && <span className="ml-2">{children}</span>}
@@ -1,17 +1,28 @@
1
1
  import clsx from 'clsx';
2
- import { forwardRef, FocusEvent, useState } from 'react';
2
+ import { forwardRef, FocusEvent, useState, Ref } from 'react';
3
3
  import { Controller } from 'react-hook-form';
4
- import NumberFormat, { NumberFormatProps } from 'react-number-format';
4
+ import { PatternFormat, PatternFormatProps } from 'react-number-format';
5
5
  import { InputProps } from '@theme/components/types';
6
6
  import { twMerge } from 'tailwind-merge';
7
7
 
8
+ const PatternFormatWithRef = forwardRef(
9
+ (props: PatternFormatProps, ref: Ref<HTMLInputElement>) => {
10
+ return <PatternFormat {...props} getInputRef={ref} />;
11
+ }
12
+ );
13
+ PatternFormatWithRef.displayName = 'PatternFormatWithRef';
14
+
8
15
  export const Input = forwardRef<
9
16
  HTMLInputElement,
10
17
  InputProps &
11
18
  Pick<
12
- NumberFormatProps,
13
- 'format' | 'mask' | 'allowEmptyFormatting' | 'onValueChange'
14
- >
19
+ PatternFormatProps,
20
+ 'mask' | 'allowEmptyFormatting' | 'onValueChange'
21
+ > & {
22
+ format?: string;
23
+ defaultValue?: string;
24
+ type?: string;
25
+ }
15
26
  >((props, ref) => {
16
27
  const [focused, setFocused] = useState(false);
17
28
  const [hasValue, setHasValue] = useState(false);
@@ -37,6 +48,7 @@ export const Input = forwardRef<
37
48
  ),
38
49
  props.className
39
50
  );
51
+
40
52
  const inputProps: any = {
41
53
  id,
42
54
  ref,
@@ -79,14 +91,14 @@ export const Input = forwardRef<
79
91
  <Controller
80
92
  name={props.name ?? ''}
81
93
  control={props.control}
82
- defaultValue={false}
83
94
  render={({ field }) => (
84
- <NumberFormat
95
+ <PatternFormatWithRef
85
96
  format={format}
86
97
  mask={mask ?? ''}
87
98
  {...rest}
88
99
  {...field}
89
100
  {...inputProps}
101
+ type={props.type as 'text' | 'password' | 'tel'}
90
102
  />
91
103
  )}
92
104
  />
@@ -1,8 +1,8 @@
1
- import { MouseEvent, useCallback, useEffect, useState } from 'react';
1
+ import { useCallback, useEffect, useState } from 'react';
2
2
  import { PaginationProps } from '@theme/components/types';
3
3
  import { twMerge } from 'tailwind-merge';
4
4
  import clsx from 'clsx';
5
- import { Link, Button } from '@theme/components';
5
+ import { Button } from '@theme/components';
6
6
  import usePagination from '@akinon/next/hooks/use-pagination';
7
7
  import { useLocalization } from '@akinon/next/hooks';
8
8
  import { useRouter } from '@akinon/next/hooks';
@@ -92,9 +92,7 @@ export const Pagination = (props: PaginationProps) => {
92
92
  }
93
93
  }, [numberOfPages, page, pageList, threshold]);
94
94
 
95
- const handleClick = (e: MouseEvent<HTMLAnchorElement>, url: string) => {
96
- e.preventDefault();
97
-
95
+ const handleClick = (url: string) => {
98
96
  const newUrl = new URL(url, window.location.origin);
99
97
  const page = newUrl.searchParams.get('page');
100
98
 
@@ -129,7 +127,7 @@ export const Pagination = (props: PaginationProps) => {
129
127
  if (type === 'list') {
130
128
  createListItems();
131
129
  }
132
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
130
+ }, [createListItems, type]);
133
131
 
134
132
  useEffect(() => {
135
133
  if (total && total !== paginationTotal) {
@@ -200,9 +198,8 @@ export const Pagination = (props: PaginationProps) => {
200
198
  >
201
199
  {prev && currentPage !== 1 && (
202
200
  <li>
203
- <Link
204
- onClick={(e) => handleClick(e, prev)}
205
- href={prev}
201
+ <button
202
+ onClick={() => handleClick(prev)}
206
203
  className={twMerge(
207
204
  'flex cursor-pointer text-sm px-2',
208
205
  prevClassName
@@ -212,16 +209,15 @@ export const Pagination = (props: PaginationProps) => {
212
209
  <span className="hidden lg:inline-block ms-4">
213
210
  {t('category.pagination.previous')}
214
211
  </span>
215
- </Link>
212
+ </button>
216
213
  </li>
217
214
  )}
218
215
 
219
216
  {paginationItems.map((item, i) => (
220
217
  <li key={i}>
221
218
  {item?.url != '#' ? (
222
- <Link
223
- onClick={(e) => handleClick(e, item.url)}
224
- href={item.url}
219
+ <button
220
+ onClick={() => handleClick(item.url)}
225
221
  className={twMerge(
226
222
  clsx(
227
223
  'text-xs px-2 cursor-pointer',
@@ -234,7 +230,7 @@ export const Pagination = (props: PaginationProps) => {
234
230
  )}
235
231
  >
236
232
  {item?.page}
237
- </Link>
233
+ </button>
238
234
  ) : (
239
235
  <span className="cursor-default text-xs flex items-center justify-center">
240
236
  {item?.page}
@@ -245,9 +241,8 @@ export const Pagination = (props: PaginationProps) => {
245
241
 
246
242
  {showNext && (
247
243
  <li>
248
- <Link
249
- onClick={(e) => handleClick(e, next)}
250
- href={next}
244
+ <button
245
+ onClick={() => handleClick(next)}
251
246
  className={twMerge(
252
247
  'flex cursor-pointer text-xs px-2',
253
248
  nextClassName
@@ -257,7 +252,7 @@ export const Pagination = (props: PaginationProps) => {
257
252
  {t('category.pagination.next')}
258
253
  </span>
259
254
  <span>&gt;</span>
260
- </Link>
255
+ </button>
261
256
  </li>
262
257
  )}
263
258
  </ul>
@@ -1,10 +1,11 @@
1
1
  import { useMemo } from 'react';
2
- import NumberFormat, { NumberFormatProps } from 'react-number-format';
2
+ import { NumericFormat, NumericFormatProps } from 'react-number-format';
3
3
  import { getCurrency } from '@akinon/next/utils';
4
4
  import { PriceProps } from '@theme/types';
5
5
  import { useLocalization } from '@akinon/next/hooks';
6
+ import Settings from '@theme/settings';
6
7
 
7
- export const Price = (props: NumberFormatProps & PriceProps) => {
8
+ export const Price = (props: NumericFormatProps & PriceProps) => {
8
9
  const {
9
10
  value,
10
11
  currencyCode,
@@ -37,15 +38,19 @@ export const Price = (props: NumberFormatProps & PriceProps) => {
37
38
  [currencyCode_, useCurrencySymbol, useCurrencyAfterPrice, useCurrencySpace]
38
39
  );
39
40
 
41
+ const currentCurrencyDecimalScale = Settings.localization.currencies.find(
42
+ (currency) => currency.code === currencyCode_
43
+ ).decimalScale;
44
+
40
45
  return (
41
- <NumberFormat
46
+ <NumericFormat
42
47
  value={useNegative ? `-${useNegativeSpace}${_value}` : _value}
43
48
  {...{
44
49
  [useCurrencyAfterPrice ? 'suffix' : 'prefix']: currency
45
50
  }}
46
51
  displayType={displayType}
47
52
  thousandSeparator={thousandSeparator}
48
- decimalScale={decimalScale}
53
+ decimalScale={currentCurrencyDecimalScale ?? decimalScale}
49
54
  decimalSeparator={decimalSeparator}
50
55
  fixedDecimalScale={fixedDecimalScale}
51
56
  {...rest}
@@ -7,11 +7,13 @@ import { WIDGET_TYPE } from '@theme/types';
7
7
  export interface CategoryState {
8
8
  facets: Facet[];
9
9
  selectedFacets: Facet[];
10
+ isMenuOpen: boolean;
10
11
  }
11
12
 
12
13
  const initialState: CategoryState = {
13
14
  facets: [],
14
- selectedFacets: []
15
+ selectedFacets: [],
16
+ isMenuOpen: false
15
17
  };
16
18
 
17
19
  const categorySlice = createSlice({
@@ -24,6 +26,9 @@ const categorySlice = createSlice({
24
26
  setSelectedFacets(state, action) {
25
27
  state.selectedFacets = action.payload;
26
28
  },
29
+ setMenuOpen(state, action) {
30
+ state.isMenuOpen = action.payload;
31
+ },
27
32
  toggleFacet(state, action) {
28
33
  const facets = JSON.parse(JSON.stringify(state.facets));
29
34
 
@@ -81,6 +86,7 @@ const categorySlice = createSlice({
81
86
  export const {
82
87
  setFacets,
83
88
  setSelectedFacets,
89
+ setMenuOpen,
84
90
  toggleFacet,
85
91
  removeCategoryFacet,
86
92
  resetSelectedFacets
@@ -1,6 +1,10 @@
1
1
  const { LocaleUrlStrategy } = require('@akinon/next/localization');
2
2
  const { ROUTES } = require('@theme/routes');
3
3
 
4
+ /* IMPORTANT *
5
+ * In order to use one locale in the locales array and hide the default locale in the URL, you need to set the localeUrlStrategy to LocaleUrlStrategy.ShowAllLocales.
6
+ */
7
+
4
8
  const commerceUrl = encodeURI(process.env.SERVICE_BACKEND_URL ?? 'default');
5
9
 
6
10
  /** @type {import('@akinon/next/types').Settings} */
@@ -11,6 +15,7 @@ module.exports = {
11
15
  { translationKey: 'size', key: 'size' }
12
16
  ],
13
17
  localization: {
18
+ // If there is one locale in the locales array, the default locale will be hidden in the URL.
14
19
  locales: [
15
20
  {
16
21
  label: 'EN',
@@ -38,7 +43,7 @@ module.exports = {
38
43
  }
39
44
  ],
40
45
  defaultLocaleValue: 'en',
41
- localeUrlStrategy: LocaleUrlStrategy.HideDefaultLocale,
46
+ localeUrlStrategy: LocaleUrlStrategy.HideDefaultLocale, // If there is one locale in the locales array, the default locale will be hidden in the URL and localeUrlStrategy should be set to LocaleUrlStrategy.ShowAllLocales.
42
47
  redirectToDefaultLocale: true,
43
48
  defaultCurrencyCode: 'usd'
44
49
  },
@@ -27,13 +27,13 @@ export const AddressCard = (props: Props) => {
27
27
  const [isRemoveAddressModalOpen, setRemoveAddressModalOpen] = useState(false);
28
28
 
29
29
  const onSubmit = async (data) => {
30
- await editAddress(data);
30
+ await editAddress({ ...data, invalidateTag: 'Addresses' });
31
31
  setIsEditAddressModalOpen(false);
32
32
  };
33
33
 
34
34
  const handleRemoveAddress = async (pk: number) => {
35
35
  try {
36
- await removeAddress(pk)
36
+ await removeAddress({ id: pk, invalidateTag: 'Addresses' })
37
37
  .unwrap()
38
38
  .then(() => setRemoveAddressModalOpen(false))
39
39
  .catch((err) => console.error(err));
@@ -31,7 +31,7 @@ interface Props {
31
31
  onSubmit: (data: any) => void;
32
32
  }
33
33
 
34
- const makeAddressFormSchema = (t, { phoneNumberLength }) =>
34
+ const makeAddressFormSchema = (t, { phoneNumberLength, postCodeLength }) =>
35
35
  yup.object().shape({
36
36
  title: yup.string().required(t('account.address_book.form.error.required')),
37
37
  first_name: yup
@@ -65,8 +65,9 @@ const makeAddressFormSchema = (t, { phoneNumberLength }) =>
65
65
  .max(255, t('account.address_book.form.error.line_max')),
66
66
  postcode: yup
67
67
  .string()
68
- .min(5, t('account.address_book.form.error.postcode_min'))
69
- .max(5, t('account.address_book.form.error.postcode_max'))
68
+ .transform((value: string) => value.replace(/_/g, '').replace(/ /g, ''))
69
+ .min(postCodeLength, t('account.address_book.form.error.postcode_min'))
70
+ .max(postCodeLength, t('account.address_book.form.error.postcode_max'))
70
71
  .required(t('account.address_book.form.error.required')),
71
72
  company_name: yup.string().nullable(),
72
73
  tax_no: yup.string().nullable(),
@@ -80,7 +81,8 @@ export const AddressForm = (props: Props) => {
80
81
  const { data, onSubmit } = props;
81
82
  const config = useAppSelector((state) => state.config);
82
83
  const addressFormSchema = makeAddressFormSchema(t, {
83
- phoneNumberLength: config.user_phone_format.length
84
+ phoneNumberLength: config.user_phone_format.length,
85
+ postCodeLength: config.user_post_code_format.length
84
86
  });
85
87
  const {
86
88
  register,
@@ -181,12 +183,22 @@ export const AddressForm = (props: Props) => {
181
183
  if (data && country) {
182
184
  reset({
183
185
  ...data,
184
- is_corporate:
185
- String(data.is_corporate) === AddressType.company ? 'true' : 'false' // TODO: Fix this! This hack for radio buttons can't be set to boolean value
186
+ is_corporate: String(data.is_corporate)
186
187
  });
187
188
  }
188
189
  }, [data, country, reset]);
189
190
 
191
+ useEffect(() => {
192
+ if (selectedFormType !== AddressType.company) {
193
+ reset({
194
+ ...watch(),
195
+ company_name: '',
196
+ tax_office: '',
197
+ tax_no: ''
198
+ });
199
+ }
200
+ }, [selectedFormType, reset, watch]);
201
+
190
202
  return (
191
203
  <form
192
204
  onSubmit={handleSubmit(onSubmit)}
@@ -322,12 +334,15 @@ export const AddressForm = (props: Props) => {
322
334
  )}
323
335
  </label>
324
336
  <Input
325
- type="number"
326
337
  label={t('account.address_book.form.post_code.placeholder')}
327
338
  {...register('postcode')}
328
339
  error={errors.postcode}
329
340
  data-testid="address-form-post-code"
330
341
  required
342
+ format={config.user_post_code_format.replaceAll(/\9/g, '#')}
343
+ control={control}
344
+ mask="_"
345
+ allowEmptyFormatting
331
346
  />
332
347
  {selectedFormType === AddressType.company && (
333
348
  <>
@@ -3,9 +3,9 @@ import {
3
3
  Button,
4
4
  FileInput,
5
5
  Input,
6
+ Link,
6
7
  LoaderSpinner,
7
- Select,
8
- Link
8
+ Select
9
9
  } from '@theme/components';
10
10
  import { useSession } from 'next-auth/react';
11
11
  import { useEffect, useState } from 'react';
@@ -45,7 +45,8 @@ const contactFormSchema = (t) =>
45
45
  .when('subject', {
46
46
  is: (value) => value === '2',
47
47
  then: yup.string().required(t('account.contact.form.error.required'))
48
- })
48
+ }),
49
+ file: yup.mixed()
49
50
  });
50
51
 
51
52
  const ContactForm = () => {
@@ -110,8 +111,18 @@ const ContactForm = () => {
110
111
  resolver: yupResolver(contactFormSchema(t))
111
112
  });
112
113
 
113
- const onSubmit: SubmitHandler<ContactFormType> = (data) => {
114
- sendContact(data);
114
+ const onSubmit: SubmitHandler<ContactFormType> = (data, event) => {
115
+ const formData = new FormData();
116
+
117
+ Object.keys(data ?? {}).forEach((key) => {
118
+ if (key === 'file' && data[key]) {
119
+ formData.append(key, data[key][0]);
120
+ } else if (data[key]) {
121
+ formData.append(key, data[key]);
122
+ }
123
+ });
124
+
125
+ sendContact(formData);
115
126
  };
116
127
 
117
128
  const handleChange = (e) => {
@@ -234,6 +245,7 @@ const ContactForm = () => {
234
245
  className="border-gray-500 border w-full text-xs p-2.5 focus-visible:outline-none focus:border-black hover:border-black"
235
246
  rows={7}
236
247
  name="message"
248
+ // @ts-expect-error -- awaiting fix:
237
249
  {...register('message')}
238
250
  />
239
251
  {errors.message && (
@@ -242,7 +254,12 @@ const ContactForm = () => {
242
254
  <label className="text-xs text-gray-800 mb-2 block">
243
255
  {t('account.contact.form.file.title')}
244
256
  </label>
245
- <FileInput className="w-full mb-5" title="test" />
257
+ <FileInput
258
+ name="file"
259
+ title="file"
260
+ className="w-full mb-5"
261
+ {...register('file')}
262
+ />
246
263
  <Button type="submit" className="w-full font-medium">
247
264
  {t('account.contact.form.submit_button')}
248
265
  </Button>
@@ -93,8 +93,8 @@ export const FavoriteItem = (props: Props) => {
93
93
  className={clsx(
94
94
  'absolute top-4 right-4 cursor-pointer',
95
95
  isRemoveFavoriteLoading
96
- ? 'pointer-events-none hover:cursor-wait' // TODO: Cursors not working fix!
97
- : 'hove:cursor-pointer'
96
+ ? 'hover:cursor-wait'
97
+ : 'hover:cursor-pointer'
98
98
  )}
99
99
  data-testid="favorites-remove"
100
100
  />