@akinon/pz-click-collect 1.0.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.
package/.gitattributes ADDED
@@ -0,0 +1,15 @@
1
+ *.js text eol=lf
2
+ *.jsx text eol=lf
3
+ *.ts text eol=lf
4
+ *.tsx text eol=lf
5
+ *.json text eol=lf
6
+ *.md text eol=lf
7
+
8
+ .eslintignore text eol=lf
9
+ .eslintrc text eol=lf
10
+ .gitignore text eol=lf
11
+ .prettierrc text eol=lf
12
+ .yarnrc text eol=lf
13
+
14
+ * text=auto
15
+
package/.prettierrc ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "bracketSameLine": false,
3
+ "tabWidth": 2,
4
+ "singleQuote": true,
5
+ "jsxSingleQuote": false,
6
+ "bracketSpacing": true,
7
+ "semi": true,
8
+ "useTabs": false,
9
+ "arrowParens": "always",
10
+ "endOfLine": "lf",
11
+ "proseWrap": "never",
12
+ "trailingComma": "none"
13
+ }
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # pz-click-collect
2
+
3
+ ### Install the npm package
4
+
5
+ ```bash
6
+ # For latest version
7
+ yarn add ​git+ssh://git@bitbucket.org:akinonteam/pz-click-collect.git
8
+
9
+ # For specific version
10
+ yarn add ​git+ssh://git@bitbucket.org:akinonteam/pz-click-collect.git#COMMIT_HASH
11
+ ```
12
+
13
+ ### Next Config Configuration
14
+
15
+ ##### next.config.js**
16
+
17
+ ```javascript
18
+ const withTM = require("next-transpile-modules")(["pz-click-collect"]);
19
+ ```
20
+
21
+ ### Example Usage
22
+ ##### File Path: src/views/checkout/steps/shipping/addresses.tsx
23
+
24
+
25
+ ```javascript
26
+ import ClickCollect from 'pz-click-collect';
27
+
28
+ <ClickCollect addressTypeParam={addressType.requestParam}/>
29
+ ```
30
+
31
+ ### Props
32
+
33
+ | Properties | Type | Description |
34
+ | ---------- | -------- | -------------------- |
35
+ | addressTypeParam | `string` | Address Type Request Param |
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@akinon/pz-click-collect",
3
+ "version": "1.0.0",
4
+ "main": "./src/index.tsx",
5
+ "module": "./src/index.tsx",
6
+ "license": "MIT",
7
+ "devDependencies": {
8
+ "@types/react": "^18.0.15",
9
+ "@types/react-dom": "^18.0.6",
10
+ "react": "^18.2.0",
11
+ "react-dom": "^18.2.0",
12
+ "typescript": "^4.7.4"
13
+ },
14
+ "peerDependencies": {
15
+ "react": "^16.8.0",
16
+ "react-dom": "^16.8.0"
17
+ }
18
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,312 @@
1
+ import React, {
2
+ useEffect,
3
+ useState,
4
+ useMemo,
5
+ useCallback,
6
+ useRef,
7
+ ChangeEvent
8
+ } from 'react';
9
+ import clsx from 'clsx';
10
+ import { Icon, LoaderSpinner } from '@akinon/next/components';
11
+ import { useAppSelector } from '@akinon/next/redux/hooks';
12
+ import {
13
+ useSetDeliveryOptionMutation,
14
+ useSetRetailStoreMutation,
15
+ useSetShippingOptionMutation,
16
+ useSetAddressesMutation
17
+ } from '@akinon/next/data/client/checkout';
18
+
19
+ import { RootState } from 'redux/store';
20
+
21
+ interface ClickCollectTranslationsProps {
22
+ deliveryFromTheStore: string;
23
+ deliveryStore: string;
24
+ }
25
+
26
+ const defaultTranslations = {
27
+ deliveryFromTheStore: 'DELIVERY FROM THE STORE',
28
+ deliveryStore: 'Delivery Store'
29
+ };
30
+
31
+ export function ClickCollect({
32
+ addressTypeParam,
33
+ translations
34
+ }: {
35
+ addressTypeParam: string;
36
+ translations: ClickCollectTranslationsProps;
37
+ }) {
38
+ const _translations = {
39
+ ...defaultTranslations,
40
+ ...translations
41
+ };
42
+
43
+ const [loading, setLoading] = useState(false);
44
+
45
+ const { deliveryOptions, retailStores } = useAppSelector(
46
+ (state: RootState) => state.checkout
47
+ );
48
+
49
+ const { billing_address, shipping_address, retail_store, delivery_option } =
50
+ useAppSelector((state: RootState) => state.checkout.preOrder) ?? {};
51
+
52
+ const cityPkRef = useRef(null);
53
+
54
+ const retailStore = useMemo(
55
+ () =>
56
+ deliveryOptions?.find(
57
+ (value) => value?.delivery_option_type === 'retail_store'
58
+ ),
59
+ [deliveryOptions]
60
+ );
61
+ const [stores, setStores] = useState([]);
62
+
63
+ const [setDeliveryOption, { isLoading: deliveryLoading }] =
64
+ useSetDeliveryOptionMutation();
65
+
66
+ const [setRetailStore, { isLoading: retailLoading }] =
67
+ useSetRetailStoreMutation();
68
+
69
+ const [setShippingOption, { isLoading: shippingLoading }] =
70
+ useSetShippingOptionMutation();
71
+
72
+ const [setAddresses, { isLoading: addressLoading }] =
73
+ useSetAddressesMutation();
74
+
75
+ const [selectedCity, setSelectedCity] = useState(null);
76
+
77
+ const [selectedStore, setSelectedStore] = useState(null);
78
+
79
+ const [isActive, setIsActive] = useState(false);
80
+
81
+ const [retailStoresForCity, setRetailStoresForCity] = useState([]);
82
+
83
+ const defaultDeliveryOption = useMemo(
84
+ () =>
85
+ deliveryOptions.find(
86
+ (option) => option?.delivery_option_type === 'customer'
87
+ ),
88
+ [deliveryOptions]
89
+ );
90
+
91
+ const retailStoreDeliveryOption = useMemo(
92
+ () =>
93
+ deliveryOptions.find(
94
+ (option) => option?.delivery_option_type === 'retail_store'
95
+ ),
96
+ [deliveryOptions]
97
+ );
98
+
99
+ const postRetailStore = async ({ retailStorePk, billingAddressPk }) => {
100
+ await setRetailStore({
101
+ retailStorePk: retailStorePk,
102
+ billingAddressPk: billingAddressPk
103
+ });
104
+ };
105
+
106
+ const handleDeactivate = useCallback(async () => {
107
+ try {
108
+ await setDeliveryOption(defaultDeliveryOption?.pk);
109
+
110
+ await setAddresses({
111
+ shippingAddressPk: shipping_address?.pk,
112
+ billingAddressPk: billing_address?.pk
113
+ });
114
+ } catch (error) {
115
+ console.error('Error updating details:', error);
116
+ }
117
+ }, [setDeliveryOption, setAddresses]);
118
+
119
+ const handleActive = useCallback(async () => {
120
+ if (retailStore) {
121
+ await setShippingOption(retailStore.pk).unwrap();
122
+ }
123
+
124
+ const response = await setDeliveryOption(
125
+ retailStoreDeliveryOption.pk
126
+ ).unwrap();
127
+ const retailStores = response?.context_list?.find(
128
+ (context) => context.page_slug === 'retailstoreselectionpage'
129
+ );
130
+
131
+ setStores(retailStores?.page_context['retail_stores']);
132
+
133
+ postRetailStore({
134
+ retailStorePk: retail_store
135
+ ? retail_store.pk
136
+ : retailStores.page_context['retail_stores'][0]?.pk,
137
+ billingAddressPk: billing_address ? billing_address.pk : null
138
+ });
139
+ }, [
140
+ retailStore,
141
+ retailStoreDeliveryOption,
142
+ setStores,
143
+ postRetailStore,
144
+ setShippingOption,
145
+ setDeliveryOption,
146
+ retail_store,
147
+ billing_address
148
+ ]);
149
+
150
+ const handleCityChange = useCallback(
151
+ async (e: ChangeEvent<HTMLSelectElement>) => {
152
+ const cityPk = parseInt(e.target.value);
153
+ cityPkRef.current = cityPk;
154
+ const correspondingCity = retailStores.find(
155
+ (store) => store?.township?.city?.pk === cityPk
156
+ )?.township.city;
157
+ setSelectedCity(correspondingCity ? correspondingCity.name : null);
158
+ const storesInCity = retailStores.filter(
159
+ (store) => store?.township?.city?.pk === cityPk
160
+ );
161
+ setRetailStoresForCity(storesInCity);
162
+
163
+ if (storesInCity.length > 0) {
164
+ setSelectedStore(storesInCity[0].pk);
165
+ await postRetailStore({
166
+ retailStorePk: storesInCity[0].pk,
167
+ billingAddressPk: billing_address?.pk
168
+ });
169
+ }
170
+ },
171
+ [retailStores]
172
+ );
173
+
174
+ const handleStoreChange = useCallback(
175
+ (e: ChangeEvent<HTMLSelectElement>) => {
176
+ const storePk = parseInt(e.target.value);
177
+ setSelectedStore(storePk);
178
+ postRetailStore({
179
+ retailStorePk: storePk,
180
+ billingAddressPk: billing_address?.pk
181
+ });
182
+ },
183
+ [postRetailStore, billing_address?.pk]
184
+ );
185
+
186
+ const getUniqueCities = useMemo(() => {
187
+ const uniqueCities = [];
188
+
189
+ retailStores.forEach((store: any) => {
190
+ if (
191
+ !uniqueCities.some((city) => city?.pk === store?.township?.city?.pk)
192
+ ) {
193
+ uniqueCities.push(store?.township?.city);
194
+ }
195
+ });
196
+
197
+ return uniqueCities;
198
+ }, [retailStores]);
199
+
200
+ useEffect(() => {
201
+ setLoading(
202
+ [deliveryLoading, retailLoading, shippingLoading, addressLoading].some(
203
+ Boolean
204
+ )
205
+ );
206
+ }, [deliveryLoading, retailLoading, shippingLoading, addressLoading]);
207
+
208
+ useEffect(() => {
209
+ setIsActive(delivery_option?.pk === retailStoreDeliveryOption?.pk);
210
+ }, [delivery_option?.pk, retailStoreDeliveryOption]);
211
+
212
+ useEffect(() => {
213
+ if (retailStores.length > 0) {
214
+ const defaultCity = retailStores[0]?.township?.city;
215
+ const defaultStore = retailStores.find(
216
+ (store) => store.township.city.pk === defaultCity.pk
217
+ );
218
+
219
+ if (!selectedCity) {
220
+ setSelectedCity(defaultCity.pk);
221
+ cityPkRef.current = defaultCity.pk;
222
+ }
223
+
224
+ if (!selectedStore) {
225
+ setSelectedStore(defaultStore.pk);
226
+ postRetailStore({
227
+ retailStorePk: defaultStore.pk,
228
+ billingAddressPk: billing_address?.pk
229
+ });
230
+ }
231
+
232
+ if (cityPkRef.current) {
233
+ const storesInCity = retailStores.filter(
234
+ (store) => store.township.city.pk === cityPkRef.current
235
+ );
236
+ setRetailStoresForCity(storesInCity);
237
+ }
238
+ }
239
+ }, [retailStores, cityPkRef.current]);
240
+
241
+ if (addressTypeParam !== 'shippingAddressPk') return;
242
+
243
+ return (
244
+ <div
245
+ role={!isActive ? 'button' : 'div'}
246
+ onClick={() => {
247
+ !isActive && handleActive();
248
+ }}
249
+ className={clsx(
250
+ 'relative w-full min-h-[8rem] border shadow p-4',
251
+ "hover:after:content-[''] hover:after:border-4 hover:after:opacity-30 hover:after:transition-opacity",
252
+ 'after:border-secondary-400 after:absolute after:inset-0 after:opacity-0 after:duration-150 after:-z-10'
253
+ )}
254
+ >
255
+ <div className="text-xs flex justify-center items-center h-full gap-x-2">
256
+ {isActive && (
257
+ <div
258
+ role="button"
259
+ onClick={handleDeactivate}
260
+ className="absolute cursor-pointer top-2 right-2 hover:bg-byarlack-100/[.1] p-2 z-10 rounded-full "
261
+ >
262
+ <Icon name="close" size={9} />
263
+ </div>
264
+ )}
265
+ {loading && (
266
+ <div className="absolute top-0 left-0 w-full h-full bg-white/[.9] z-10">
267
+ <LoaderSpinner />
268
+ </div>
269
+ )}
270
+ {!isActive ? (
271
+ <div className="text-xs text-center flex justify-center items-center h-full gap-x-2">
272
+ <Icon name="plus" size={12} />
273
+ <span data-testid="click-collect-add-new-address">
274
+ {_translations.deliveryFromTheStore}
275
+ </span>
276
+ </div>
277
+ ) : (
278
+ <div className="relative w-full">
279
+ <label
280
+ htmlFor="cities"
281
+ className="block mb-2 text-sm text-center font-light text-black-900 "
282
+ >
283
+ {_translations.deliveryStore}
284
+ </label>
285
+ <select
286
+ id="cities"
287
+ className="bg-white border border-gray-300 font-light text-black-900 text-sm block w-full p-1 mb-2 focus:ring-black-500 focus:border-black-500"
288
+ onChange={handleCityChange}
289
+ >
290
+ {getUniqueCities.map((city) => (
291
+ <option key={city.pk} value={city.pk}>
292
+ {city.name}
293
+ </option>
294
+ ))}
295
+ </select>
296
+ <select
297
+ id="stores"
298
+ onChange={handleStoreChange}
299
+ className="bg-white border border-gray-300 font-light text-black-900 text-sm block w-full p-1 mb-2 focus:ring-black-500 focus:border-black-500"
300
+ >
301
+ {retailStoresForCity.map((store) => (
302
+ <option key={store.pk} value={store.pk}>
303
+ {store.name}
304
+ </option>
305
+ ))}
306
+ </select>
307
+ </div>
308
+ )}
309
+ </div>
310
+ </div>
311
+ );
312
+ }