@akinon/projectzero 1.48.0-rc.4 → 1.48.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 +1 -57
  2. package/README.md +2 -3
  3. package/app-template/.gitignore +0 -2
  4. package/app-template/.lintstagedrc.js +4 -5
  5. package/app-template/CHANGELOG.md +48 -1387
  6. package/app-template/docs/basic-setup.md +1 -1
  7. package/app-template/docs/plugins.md +7 -7
  8. package/app-template/package.json +20 -22
  9. package/app-template/public/locales/en/account.json +4 -4
  10. package/app-template/public/locales/tr/account.json +1 -1
  11. package/app-template/src/app/[commerce]/[locale]/[currency]/account/address/page.tsx +1 -1
  12. package/app-template/src/app/[commerce]/[locale]/[currency]/account/coupons/page.tsx +4 -4
  13. package/app-template/src/app/[commerce]/[locale]/[currency]/account/profile/page.tsx +0 -1
  14. package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +2 -5
  15. package/app-template/src/app/[commerce]/[locale]/[currency]/orders/completed/[token]/page.tsx +8 -12
  16. package/app-template/src/components/checkbox.tsx +2 -2
  17. package/app-template/src/components/input.tsx +7 -19
  18. package/app-template/src/components/pagination.tsx +18 -13
  19. package/app-template/src/components/price.tsx +4 -9
  20. package/app-template/src/redux/reducers/category.ts +1 -7
  21. package/app-template/src/settings.js +1 -6
  22. package/app-template/src/views/account/address-card.tsx +2 -2
  23. package/app-template/src/views/account/address-form.tsx +7 -22
  24. package/app-template/src/views/account/contact-form.tsx +6 -23
  25. package/app-template/src/views/account/favorite-item.tsx +2 -2
  26. package/app-template/src/views/account/favourite-products/favourite-products-list.tsx +1 -5
  27. package/app-template/src/views/breadcrumb.tsx +1 -4
  28. package/app-template/src/views/category/category-active-filters.tsx +6 -16
  29. package/app-template/src/views/category/category-info.tsx +17 -31
  30. package/app-template/src/views/category/filters/index.tsx +108 -16
  31. package/app-template/src/views/category/layout.tsx +3 -5
  32. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +4 -33
  33. package/app-template/src/views/checkout/steps/payment/options/redirection.tsx +37 -43
  34. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +3 -19
  35. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +2 -2
  36. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +1 -1
  37. package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +37 -230
  38. package/app-template/src/views/find-in-store/index.tsx +3 -2
  39. package/app-template/src/views/header/mobile-menu.tsx +8 -25
  40. package/app-template/src/views/product/product-info.tsx +2 -0
  41. package/app-template/tsconfig.json +4 -14
  42. package/app-template/yarn.lock +1953 -1824
  43. package/commands/create.ts +5 -29
  44. package/dist/commands/create.js +2 -25
  45. package/package.json +2 -2
  46. package/app-template/package-lock.json +0 -29303
  47. package/app-template/src/app/[commerce]/[locale]/[currency]/[...prettyurl]/page.tsx +0 -8
  48. package/app-template/src/views/category/filters/filter-item.tsx +0 -163
@@ -5,12 +5,9 @@ import { useSetPaymentOptionMutation } from '@akinon/next/data/client/checkout';
5
5
  import { CheckoutPaymentOption } from '@akinon/next/types';
6
6
  import { Radio } from '@theme/components';
7
7
  import { usePaymentOptions } from '@akinon/next/hooks/use-payment-options';
8
- import { useMemo } from 'react';
9
8
 
10
9
  const PaymentOptionButtons = () => {
11
- const { preOrder, attributeBasedShippingOptions } = useAppSelector(
12
- (state: RootState) => state.checkout
13
- );
10
+ const { preOrder } = useAppSelector((state: RootState) => state.checkout);
14
11
  const [setPaymentOption] = useSetPaymentOptionMutation();
15
12
  const { filteredPaymentOptions } = usePaymentOptions();
16
13
 
@@ -25,23 +22,10 @@ const PaymentOptionButtons = () => {
25
22
  });
26
23
  };
27
24
 
28
- const displayedPaymentOptions = useMemo(() => {
29
- if (
30
- attributeBasedShippingOptions &&
31
- Object.keys(attributeBasedShippingOptions).length > 0
32
- ) {
33
- return filteredPaymentOptions.filter(
34
- (option) => option.slug.toLowerCase() !== 'pay-on-delivery'
35
- );
36
- }
37
-
38
- return filteredPaymentOptions;
39
- }, [filteredPaymentOptions, attributeBasedShippingOptions]);
40
-
41
25
  return (
42
26
  <>
43
27
  <div className="w-full space-y-4 px-4 flex flex-col mb-8 md:hidden">
44
- {displayedPaymentOptions.map((option) => (
28
+ {filteredPaymentOptions.map((option) => (
45
29
  <label
46
30
  key={`payment-option-${option.pk}`}
47
31
  className="border px-4 py-3 mt-3 flex h-12"
@@ -63,7 +47,7 @@ const PaymentOptionButtons = () => {
63
47
  </div>
64
48
 
65
49
  <div className="hidden md:flex">
66
- {displayedPaymentOptions.map((option) => (
50
+ {filteredPaymentOptions.map((option) => (
67
51
  <button
68
52
  key={`payment-option-${option.pk}`}
69
53
  onClick={() => onClickHandler(option)}
@@ -85,7 +85,7 @@ const AddressBox = ({
85
85
  };
86
86
 
87
87
  const onSubmit = (data) => {
88
- editAddress({ ...data, invalidateTag: 'Addresses' })
88
+ editAddress(data)
89
89
  .unwrap()
90
90
  .then((editAddressResponse) => {
91
91
  handleAddressClick(addressType, editAddressResponse);
@@ -96,7 +96,7 @@ const AddressBox = ({
96
96
  };
97
97
 
98
98
  const handleRemoveAddress = async (addressPk: number) => {
99
- await removeAddress({ id: addressPk, invalidateTag: 'Addresses' });
99
+ await removeAddress(addressPk);
100
100
  setRemoveAddressModalOpen(false);
101
101
  dispatch(resetCheckoutState());
102
102
 
@@ -77,7 +77,7 @@ const Addresses = () => {
77
77
  }, [billing_and_shipping_same]);
78
78
 
79
79
  const onSubmit = (data) => {
80
- addAddress({ ...data, invalidateTag: 'Addresses' })
80
+ addAddress(data)
81
81
  .unwrap()
82
82
  .then((addAddressResponse) => {
83
83
  if (addressList.length === 0) {
@@ -1,174 +1,38 @@
1
- import React, { useCallback, useEffect, useRef, useState } from 'react';
2
1
  import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
3
- import {
4
- setCurrentStep,
5
- setSelectedShippingOptions
6
- } from '@akinon/next/redux/reducers/checkout';
2
+ import { setCurrentStep } from '@akinon/next/redux/reducers/checkout';
7
3
  import { RootState } from '@theme/redux/store';
8
- import {
9
- useSetShippingOptionMutation,
10
- useSetAttributeBasedShippingOptionsMutation,
11
- useSetDataSourceShippingOptionsMutation
12
- } from '@akinon/next/data/client/checkout';
4
+ import { useSetShippingOptionMutation } from '@akinon/next/data/client/checkout';
13
5
  import { Price, Button, Radio } from '@theme/components';
14
6
  import { CheckoutStep } from '@akinon/next/types';
15
7
  import { useLocalization } from '@akinon/next/hooks';
16
8
 
17
- const ShippingOptions: React.FC = () => {
9
+ const ShippingOptions = () => {
18
10
  const { t } = useLocalization();
19
- const dispatch = useAppDispatch();
20
- const {
21
- steps,
22
- shippingOptions,
23
- attributeBasedShippingOptions,
24
- dataSourceShippingOptions,
25
- preOrder,
26
- addressList,
27
- selectedShippingOptions
28
- } = useAppSelector((state: RootState) => state.checkout);
29
- const {
30
- shipping_option,
31
- shipping_address,
32
- attribute_based_shipping_options,
33
- data_source_shipping_options
34
- } = preOrder ?? {};
35
-
36
- const [setShippingOption] = useSetShippingOptionMutation();
37
- const [setAttributeBasedShippingOptions] =
38
- useSetAttributeBasedShippingOptionsMutation();
39
- const [setDataSourceShippingOption] =
40
- useSetDataSourceShippingOptionsMutation();
41
-
42
- const prevAttributeBasedOptionsRef = useRef(attributeBasedShippingOptions);
43
-
44
- const [selectedPks, setSelectedPks] = useState<
45
- { dataSourcePk: number; optionPk: number }[] | null
46
- >(null);
47
-
48
- const initializeSelectedOptions = useCallback(() => {
49
- if (attribute_based_shipping_options) {
50
- const newSelected = { ...selectedShippingOptions };
51
- let hasChanges = false;
52
-
53
- Object.entries(attributeBasedShippingOptions).forEach(
54
- ([color, options]) => {
55
- if (
56
- !newSelected[color] ||
57
- !options.some((opt) => opt.pk === newSelected[color])
58
- ) {
59
- newSelected[color] = options[0].pk;
60
- hasChanges = true;
61
- }
62
- }
63
- );
64
-
65
- if (hasChanges) {
66
- dispatch(setSelectedShippingOptions(newSelected));
67
- setAttributeBasedShippingOptions(newSelected);
68
- }
69
- } else if (shippingOptions.length > 0 && !shipping_option) {
70
- setShippingOption(shippingOptions[0].pk);
71
- }
72
- // eslint-disable-next-line react-hooks/exhaustive-deps
73
- }, [
74
- attributeBasedShippingOptions,
75
- selectedShippingOptions,
76
- setAttributeBasedShippingOptions,
77
- attribute_based_shipping_options,
78
- shippingOptions,
79
- shipping_option,
80
- setShippingOption
81
- ]);
82
-
83
- useEffect(() => {
84
- if (!data_source_shipping_options) return;
85
-
86
- const initialSelectedPks = data_source_shipping_options.map((option) => ({
87
- dataSourcePk: option.data_source.pk,
88
- optionPk: option.pk
89
- }));
90
-
91
- setSelectedPks(initialSelectedPks);
92
- }, [data_source_shipping_options]);
93
-
94
- useEffect(() => {
95
- if (
96
- JSON.stringify(prevAttributeBasedOptionsRef.current) !==
97
- JSON.stringify(attributeBasedShippingOptions) ||
98
- Object.keys(selectedShippingOptions).length === 0 ||
99
- (!shipping_option && shippingOptions.length > 0)
100
- ) {
101
- initializeSelectedOptions();
102
- prevAttributeBasedOptionsRef.current = attributeBasedShippingOptions;
103
- }
104
- }, [
105
- attributeBasedShippingOptions,
106
- selectedShippingOptions,
107
- initializeSelectedOptions,
108
- shipping_option,
109
- shippingOptions
110
- ]);
111
-
112
- const handleAttributeBasedOptionChange = useCallback(
113
- (color: string, newPk: number) => {
114
- const updatedOptions = { ...selectedShippingOptions, [color]: newPk };
115
- dispatch(setSelectedShippingOptions(updatedOptions));
116
- setAttributeBasedShippingOptions(updatedOptions);
117
- },
118
- // eslint-disable-next-line react-hooks/exhaustive-deps
119
- [selectedShippingOptions, setAttributeBasedShippingOptions]
11
+ const { steps, shippingOptions, preOrder, addressList } = useAppSelector(
12
+ (state: RootState) => state.checkout
120
13
  );
121
-
122
- if (addressList.length < 1) {
123
- return (
124
- <div className="w-full lg:w-2/5">
125
- <div className="border-b border-gray-400 px-8 py-4">
126
- <h2 className="text-2xl">{t('checkout.address.shipping.title')}</h2>
127
- </div>
128
- <div className="py-4 px-8">
129
- <p className="text-xs">
130
- {t('checkout.address.shipping.select_address_to_continue')}
131
- </p>
132
- </div>
133
- </div>
134
- );
135
- }
136
-
137
- const updateData = (dataSourcePk: number, newPk: number) => {
138
- const updatedSelectedPks = selectedPks?.map((item) =>
139
- item.dataSourcePk === dataSourcePk ? { ...item, optionPk: newPk } : item
140
- );
141
-
142
- if (!updatedSelectedPks) return;
143
-
144
- setSelectedPks(updatedSelectedPks);
145
-
146
- const pks = updatedSelectedPks.map((item) => item.optionPk);
147
- setDataSourceShippingOption(pks);
148
- };
149
-
150
- const handleRadioChange = (
151
- e: React.ChangeEvent<HTMLInputElement>,
152
- dataSourcePk: number
153
- ) => {
154
- const newPk = parseInt(e.currentTarget.value);
155
- updateData(dataSourcePk, newPk);
156
- };
14
+ const { shipping_option, shipping_address } = preOrder ?? {};
15
+ const [setShippingOption] = useSetShippingOptionMutation();
16
+ const dispatch = useAppDispatch();
157
17
 
158
18
  return (
159
19
  <div className="w-full lg:w-2/5">
160
20
  <div className="border-b border-gray-400 px-8 py-4">
161
21
  <h2 className="text-2xl">{t('checkout.address.shipping.title')}</h2>
162
22
  </div>
163
- <div className="py-4 px-6">
164
- <p className="text-xs border-gray-400 pb-4">
165
- {t('checkout.address.shipping.chosen_address')}:{' '}
166
- {shipping_address?.city.name}
167
- </p>
168
-
169
- {shippingOptions &&
170
- shippingOptions.length > 0 &&
171
- shippingOptions.map((option) => (
23
+ {addressList.length < 1 ? (
24
+ <div className="py-4 px-8">
25
+ <p className="text-xs">
26
+ {t('checkout.address.shipping.select_address_to_continue')}
27
+ </p>
28
+ </div>
29
+ ) : (
30
+ <div className="py-4 px-6">
31
+ <p className="text-xs border-gray-400 pb-4">
32
+ {t('checkout.address.shipping.chosen_address')}:{' '}
33
+ {shipping_address?.city.name}
34
+ </p>
35
+ {shippingOptions.map((option) => (
172
36
  <div
173
37
  key={option.pk}
174
38
  className="py-4 border-t border-gray-400 flex justify-between"
@@ -176,7 +40,12 @@ const ShippingOptions: React.FC = () => {
176
40
  <Radio
177
41
  name="shipping"
178
42
  checked={option.pk === shipping_option?.pk}
179
- onChange={() => setShippingOption(option.pk)}
43
+ onChange={() => {
44
+ setShippingOption(option.pk);
45
+ }}
46
+ onClick={() => {
47
+ setShippingOption(option?.pk);
48
+ }}
180
49
  data-testid={`checkout-shipping-option-${option.pk}`}
181
50
  >
182
51
  {option.name}
@@ -186,78 +55,16 @@ const ShippingOptions: React.FC = () => {
186
55
  </span>
187
56
  </div>
188
57
  ))}
189
-
190
- {attributeBasedShippingOptions &&
191
- Object.keys(attributeBasedShippingOptions).length > 0 &&
192
- Object.entries(attributeBasedShippingOptions).map(
193
- ([color, options]) => (
194
- <div key={color}>
195
- <h3 className="text-lg font-bold">{color}</h3>
196
- {options.map((option) => (
197
- <div
198
- key={option.pk}
199
- className="py-4 border-t border-gray-400 flex justify-between"
200
- >
201
- <Radio
202
- name={`attribute-based-shipping-${color}`}
203
- checked={selectedShippingOptions[color] === option.pk}
204
- onChange={() =>
205
- handleAttributeBasedOptionChange(color, option.pk)
206
- }
207
- data-testid={`checkout-attribute-based-shipping-${option.pk}`}
208
- >
209
- {`${option.shipping_option_name}`}
210
- </Radio>
211
- <span className="text-xs">
212
- <Price value={option.shipping_amount} />
213
- </span>
214
- </div>
215
- ))}
216
- </div>
217
- )
218
- )}
219
-
220
- {dataSourceShippingOptions &&
221
- dataSourceShippingOptions.length > 0 &&
222
- dataSourceShippingOptions.map((option) => (
223
- <div key={option.pk}>
224
- <h3 className="text-lg font-bold">{option?.name}</h3>
225
- {option.data_source_shipping_options.map((opt) => (
226
- <div
227
- key={opt.pk}
228
- className="py-4 border-t border-gray-400 flex justify-between"
229
- >
230
- <Radio
231
- name={`data-source-shipping-${option.pk}`}
232
- checked={
233
- selectedPks?.some((item) => item.optionPk === opt.pk) ||
234
- false
235
- }
236
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
237
- handleRadioChange(e, option.pk)
238
- }
239
- value={opt.pk}
240
- data-testid={`checkout-data-source-shipping-${opt.pk}`}
241
- >
242
- {opt?.shipping_option_name}
243
- </Radio>
244
- <span className="text-xs">
245
- <Price value={opt?.shipping_amount} />
246
- </span>
247
- </div>
248
- ))}
249
- </div>
250
- ))}
251
-
252
- <Button
253
- className="mt-2 w-full"
254
- disabled={!steps.shipping.completed}
255
- onClick={() => dispatch(setCurrentStep(CheckoutStep.Payment))}
256
- data-testid="checkout-shipping-save"
257
- >
258
- {t('checkout.address.shipping.button')}
259
- </Button>
260
- </div>
58
+ <Button
59
+ className="mt-2 w-full"
60
+ disabled={!steps.shipping.completed}
61
+ onClick={() => dispatch(setCurrentStep(CheckoutStep.Payment))}
62
+ data-testid="checkout-shipping-save"
63
+ >
64
+ {t('checkout.address.shipping.button')}
65
+ </Button>
66
+ </div>
67
+ )}
261
68
  </div>
262
69
  );
263
70
  };
@@ -162,9 +162,10 @@ export const FindInStore = ({ productPk, productName, variants }) => {
162
162
  </div>
163
163
  <Link
164
164
  href={`https://maps.google.com/?q=${store.latitude},${store.longitude}`}
165
- target="_blank"
166
165
  >
167
- <Button>{t('product.find_in_store.directions')}</Button>
166
+ <a target="_blank">
167
+ <Button>{t('product.find_in_store.directions')}</Button>
168
+ </a>
168
169
  </Link>
169
170
  </div>
170
171
  </Accordion>
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useState, useEffect, useRef } from 'react';
3
+ import { useState } from 'react';
4
4
  import { MenuItemType } from '@akinon/next/types';
5
5
  import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
6
6
  import { closeMobileMenu } from '@akinon/next/redux/reducers/header';
@@ -32,30 +32,9 @@ export default function MobileMenu(props: MobileMenuProps) {
32
32
  (state) => state.header.isMobileMenuOpen
33
33
  );
34
34
 
35
- const menuRef = useRef<HTMLDivElement>(null);
36
-
37
- useEffect(() => {
38
- function handleClickOutside(event: MouseEvent) {
39
- if (
40
- isMobileMenuOpen &&
41
- menuRef.current &&
42
- !menuRef.current.contains(event.target as Node)
43
- ) {
44
- dispatch(closeMobileMenu());
45
- setSelectedSubMenu(null);
46
- }
47
- }
48
-
49
- document.addEventListener('mousedown', handleClickOutside);
50
-
51
- return () => {
52
- document.removeEventListener('mousedown', handleClickOutside);
53
- };
54
- // eslint-disable-next-line react-hooks/exhaustive-deps
55
- }, [isMobileMenuOpen]);
56
-
57
35
  return (
58
36
  <>
37
+ {/* MENU OVERLAY */}
59
38
  <div
60
39
  className={clsx(
61
40
  'fixed top-0 left-0 z-30 w-screen h-screen invisible opacity-0 bg-black bg-opacity-80 transition duration-500',
@@ -63,10 +42,14 @@ export default function MobileMenu(props: MobileMenuProps) {
63
42
  '!visible !opacity-100 scroll-lock': isMobileMenuOpen
64
43
  }
65
44
  )}
45
+ // TODO: Remove this after we have a better solution for clicking outside of the menu
46
+ onClick={() => {
47
+ dispatch(closeMobileMenu());
48
+ setSelectedSubMenu(null);
49
+ }}
66
50
  />
67
-
51
+ {/* TODO: Add a way to close the menu when clicking outside of it */}
68
52
  <div
69
- ref={menuRef}
70
53
  className={clsx(
71
54
  '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
55
  {
@@ -91,6 +91,8 @@ export default function ProductInfo({ data }: ProductPageProps) {
91
91
  .unwrap()
92
92
  .then(handleSuccess)
93
93
  .catch((err) => handleError(err));
94
+
95
+ // TODO: handle success response
94
96
  } catch (error) {
95
97
  setProductError(error?.data?.non_field_errors || null);
96
98
  }
@@ -3,19 +3,7 @@
3
3
  "display": "Default",
4
4
  "compilerOptions": {
5
5
  "baseUrl": "./src",
6
- "paths": {
7
- "@theme/*": ["./*"],
8
- "@root/*": ["./app/[commerce]/[locale]/[currency]/*"],
9
- "@product/*": ["./app/[commerce]/[locale]/[currency]/product/*"],
10
- "@group-product/*": [
11
- "./app/[commerce]/[locale]/[currency]/group-product/*"
12
- ],
13
- "@category/*": ["./app/[commerce]/[locale]/[currency]/category/*"],
14
- "@special-page/*": [
15
- "./app/[commerce]/[locale]/[currency]/special-page/*"
16
- ],
17
- "@flat-page/*": ["./app/[commerce]/[locale]/[currency]/flat-page/*"]
18
- },
6
+ "paths": { "@theme/*": ["./*"] },
19
7
  "allowSyntheticDefaultImports": true,
20
8
  "composite": false,
21
9
  "declaration": true,
@@ -52,5 +40,7 @@
52
40
  ".next/types/**/*.ts",
53
41
  "../../packages/**/*"
54
42
  ],
55
- "exclude": ["node_modules", "../../packages/projectzero/app-template"]
43
+ "exclude": ["node_modules",
44
+ "../../packages/projectzero/app-template"
45
+ ]
56
46
  }