@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 +15 -0
- package/.prettierrc +13 -0
- package/README.md +35 -0
- package/package.json +18 -0
- package/src/index.tsx +312 -0
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
|
+
}
|