@akinon/projectzero 1.44.0 → 1.45.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 (44) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +3 -2
  3. package/app-template/.lintstagedrc.js +5 -4
  4. package/app-template/CHANGELOG.md +953 -20
  5. package/app-template/docs/basic-setup.md +1 -1
  6. package/app-template/docs/plugins.md +7 -7
  7. package/app-template/package-lock.json +29303 -0
  8. package/app-template/package.json +23 -21
  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]/[...prettyurl]/page.tsx +8 -0
  12. package/app-template/src/app/[commerce]/[locale]/[currency]/account/address/page.tsx +1 -1
  13. package/app-template/src/app/[commerce]/[locale]/[currency]/account/coupons/page.tsx +4 -4
  14. package/app-template/src/app/[commerce]/[locale]/[currency]/account/profile/page.tsx +1 -0
  15. package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +5 -2
  16. package/app-template/src/app/[commerce]/[locale]/[currency]/orders/completed/[token]/page.tsx +12 -8
  17. package/app-template/src/components/checkbox.tsx +2 -2
  18. package/app-template/src/components/input.tsx +19 -7
  19. package/app-template/src/components/price.tsx +9 -4
  20. package/app-template/src/redux/reducers/category.ts +7 -1
  21. package/app-template/src/settings.js +6 -1
  22. package/app-template/src/views/account/address-card.tsx +2 -2
  23. package/app-template/src/views/account/address-form.tsx +22 -7
  24. package/app-template/src/views/account/contact-form.tsx +23 -6
  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 +5 -1
  27. package/app-template/src/views/breadcrumb.tsx +4 -1
  28. package/app-template/src/views/category/category-info.tsx +31 -17
  29. package/app-template/src/views/category/filters/filter-item.tsx +131 -0
  30. package/app-template/src/views/category/filters/index.tsx +5 -105
  31. package/app-template/src/views/category/layout.tsx +5 -3
  32. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +33 -4
  33. package/app-template/src/views/checkout/steps/payment/options/redirection.tsx +43 -37
  34. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +19 -3
  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 +230 -37
  38. package/app-template/src/views/find-in-store/index.tsx +2 -3
  39. package/app-template/src/views/header/mobile-menu.tsx +25 -8
  40. package/app-template/tsconfig.json +14 -4
  41. package/app-template/yarn.lock +1824 -1953
  42. package/commands/create.ts +29 -5
  43. package/dist/commands/create.js +25 -2
  44. package/package.json +2 -2
@@ -1,38 +1,174 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
1
2
  import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
2
- import { setCurrentStep } from '@akinon/next/redux/reducers/checkout';
3
+ import {
4
+ setCurrentStep,
5
+ setSelectedShippingOptions
6
+ } from '@akinon/next/redux/reducers/checkout';
3
7
  import { RootState } from '@theme/redux/store';
4
- import { useSetShippingOptionMutation } from '@akinon/next/data/client/checkout';
8
+ import {
9
+ useSetShippingOptionMutation,
10
+ useSetAttributeBasedShippingOptionsMutation,
11
+ useSetDataSourceShippingOptionsMutation
12
+ } from '@akinon/next/data/client/checkout';
5
13
  import { Price, Button, Radio } from '@theme/components';
6
14
  import { CheckoutStep } from '@akinon/next/types';
7
15
  import { useLocalization } from '@akinon/next/hooks';
8
16
 
9
- const ShippingOptions = () => {
17
+ const ShippingOptions: React.FC = () => {
10
18
  const { t } = useLocalization();
11
- const { steps, shippingOptions, preOrder, addressList } = useAppSelector(
12
- (state: RootState) => state.checkout
13
- );
14
- const { shipping_option, shipping_address } = preOrder ?? {};
15
- const [setShippingOption] = useSetShippingOptionMutation();
16
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 ?? {};
17
35
 
18
- return (
19
- <div className="w-full lg:w-2/5">
20
- <div className="border-b border-gray-400 px-8 py-4">
21
- <h2 className="text-2xl">{t('checkout.address.shipping.title')}</h2>
22
- </div>
23
- {addressList.length < 1 ? (
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]
120
+ );
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>
24
128
  <div className="py-4 px-8">
25
129
  <p className="text-xs">
26
130
  {t('checkout.address.shipping.select_address_to_continue')}
27
131
  </p>
28
132
  </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) => (
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
+ };
157
+
158
+ return (
159
+ <div className="w-full lg:w-2/5">
160
+ <div className="border-b border-gray-400 px-8 py-4">
161
+ <h2 className="text-2xl">{t('checkout.address.shipping.title')}</h2>
162
+ </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) => (
36
172
  <div
37
173
  key={option.pk}
38
174
  className="py-4 border-t border-gray-400 flex justify-between"
@@ -40,12 +176,7 @@ const ShippingOptions = () => {
40
176
  <Radio
41
177
  name="shipping"
42
178
  checked={option.pk === shipping_option?.pk}
43
- onChange={() => {
44
- setShippingOption(option.pk);
45
- }}
46
- onClick={() => {
47
- setShippingOption(option?.pk);
48
- }}
179
+ onChange={() => setShippingOption(option.pk)}
49
180
  data-testid={`checkout-shipping-option-${option.pk}`}
50
181
  >
51
182
  {option.name}
@@ -55,16 +186,78 @@ const ShippingOptions = () => {
55
186
  </span>
56
187
  </div>
57
188
  ))}
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
- )}
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>
68
261
  </div>
69
262
  );
70
263
  };
@@ -162,10 +162,9 @@ 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"
165
166
  >
166
- <a target="_blank">
167
- <Button>{t('product.find_in_store.directions')}</Button>
168
- </a>
167
+ <Button>{t('product.find_in_store.directions')}</Button>
169
168
  </Link>
170
169
  </div>
171
170
  </Accordion>
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useState } from 'react';
3
+ import { useState, useEffect, useRef } 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,9 +32,30 @@ 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
+
35
57
  return (
36
58
  <>
37
- {/* MENU OVERLAY */}
38
59
  <div
39
60
  className={clsx(
40
61
  'fixed top-0 left-0 z-30 w-screen h-screen invisible opacity-0 bg-black bg-opacity-80 transition duration-500',
@@ -42,14 +63,10 @@ export default function MobileMenu(props: MobileMenuProps) {
42
63
  '!visible !opacity-100 scroll-lock': isMobileMenuOpen
43
64
  }
44
65
  )}
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
- }}
50
66
  />
51
- {/* TODO: Add a way to close the menu when clicking outside of it */}
67
+
52
68
  <div
69
+ ref={menuRef}
53
70
  className={clsx(
54
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',
55
72
  {
@@ -3,7 +3,19 @@
3
3
  "display": "Default",
4
4
  "compilerOptions": {
5
5
  "baseUrl": "./src",
6
- "paths": { "@theme/*": ["./*"] },
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
+ },
7
19
  "allowSyntheticDefaultImports": true,
8
20
  "composite": false,
9
21
  "declaration": true,
@@ -40,7 +52,5 @@
40
52
  ".next/types/**/*.ts",
41
53
  "../../packages/**/*"
42
54
  ],
43
- "exclude": ["node_modules",
44
- "../../packages/projectzero/app-template"
45
- ]
55
+ "exclude": ["node_modules", "../../packages/projectzero/app-template"]
46
56
  }