@akinon/pz-click-collect 2.0.0-beta.2 → 2.0.0-beta.20

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/CHANGELOG.md CHANGED
@@ -1,18 +1,126 @@
1
1
  # @akinon/pz-click-collect
2
2
 
3
- ## 2.0.0-beta.2
3
+ ## 2.0.0-beta.20
4
4
 
5
- ## 2.0.0-beta.1
5
+ ## 1.125.0
6
+
7
+ ## 1.124.0
8
+
9
+ ## 1.123.0
10
+
11
+ ## 1.122.0
12
+
13
+ ## 1.121.0
14
+
15
+ ## 1.120.0
16
+
17
+ ## 1.119.0
18
+
19
+ ## 1.118.0
20
+
21
+ ## 1.117.0
22
+
23
+ ## 1.116.0
24
+
25
+ ## 1.115.0
26
+
27
+ ## 1.114.0
28
+
29
+ ## 1.113.0
30
+
31
+ ## 1.112.0
32
+
33
+ ## 1.111.0
34
+
35
+ ## 1.110.0
36
+
37
+ ## 1.109.0
38
+
39
+ ## 1.108.0
40
+
41
+ ## 1.107.0
42
+
43
+ ## 1.106.0
44
+
45
+ ## 1.105.0
46
+
47
+ ## 1.104.0
48
+
49
+ ## 1.103.0
50
+
51
+ ### Minor Changes
52
+
53
+ - b16a370: ZERO-3403: Enhance README.md with detailed Click & Collect component documentation and usage examples
54
+
55
+ ## 1.102.0
56
+
57
+ ## 1.101.0
58
+
59
+ ## 1.100.0
60
+
61
+ ## 1.99.0
6
62
 
7
63
  ### Minor Changes
8
64
 
9
- - ZERO-3091: Upgrade Next.js to v15 and React to v19
65
+ - d58538b: ZERO-3638: Enhance RC pipeline: add fetch, merge, and pre-release setup with conditional commit
66
+
67
+ ## 1.98.0
68
+
69
+ ## 1.97.0
70
+
71
+ ## 1.96.0
72
+
73
+ ## 1.95.0
74
+
75
+ ## 1.94.0
76
+
77
+ ### Minor Changes
78
+
79
+ - 1b4c343: ZERO-3341: Add data-testid for click-collect package
80
+
81
+ ## 1.93.0
82
+
83
+ ## 1.92.0
84
+
85
+ ## 1.91.0
86
+
87
+ ## 1.90.0
88
+
89
+ ### Minor Changes
90
+
91
+ - c9b8c6f: ZERO-3374: Add Click & Collect component documentation and enhance type definitions
92
+
93
+ ## 1.89.0
94
+
95
+ ## 1.88.0
96
+
97
+ ## 1.87.0
98
+
99
+ ## 1.86.0
100
+
101
+ ## 1.85.0
102
+
103
+ ## 1.84.0
104
+
105
+ ### Minor Changes
106
+
107
+ - 624a4eb: ZERO-3276: Update installation instructions across multiple README files to standardize format and improve clarity
108
+
109
+ ## 1.83.0
110
+
111
+ ## 1.82.0
112
+
113
+ ## 1.81.0
114
+
115
+ ## 1.80.0
116
+
117
+ ## 1.79.0
10
118
 
11
- ## 2.0.0-beta.0
119
+ ## 1.78.0
12
120
 
13
- ### Major Changes
121
+ ## 1.77.0
14
122
 
15
- - be6c09d: ZERO-3114: Create beta version.
123
+ ## 1.76.0
16
124
 
17
125
  ## 1.75.0
18
126
 
package/README.md CHANGED
@@ -1,17 +1,195 @@
1
1
  # @akinon/pz-click-collect
2
2
 
3
- ### Install the npm package
3
+ ## Installation method
4
+
5
+ You can use the following command to install the extension with the latest plugins:
4
6
 
5
7
  ```bash
6
- # For latest version
7
- yarn add @akinon/pz-click-collect
8
8
 
9
- # Preferred installation method
10
9
  npx @akinon/projectzero@latest --plugins
10
+
11
11
  ```
12
12
 
13
- ### Props
13
+ # Click & Collect Component
14
+
15
+ The ClickCollect component enables customers to choose retail store pickup instead of standard shipping. It integrates deeply with the checkout flow and allows complete UI customization.
16
+
17
+ ### Features
18
+
19
+ - Toggle between delivery to address and retail store pickup
20
+ - City and store selection
21
+ - Fully customizable UI through renderer props
22
+
23
+ ## Props
24
+
25
+ ### ClickCollectProps
26
+
27
+ | Properties | Type | Required | Description |
28
+ | --- | --- | --- | --- |
29
+ | addressTypeParam | `string` | Yes | Address Type Request Param |
30
+ | translations | `ClickCollectTranslationsProps` | Yes | Object containing localized strings. |
31
+ | renderer | `ClickCollectRendererProps` | No | Optional UI overrides for component sections. |
32
+
33
+ ### ClickCollectTranslationsProps
34
+
35
+ | Properties | Type | Description |
36
+ | --- | --- | --- |
37
+ | deliveryFromTheStore | `string` | Text for the inactive state label. |
38
+ | deliveryStore | `string` | Label text for selecting a store during the active state. |
39
+
40
+ ### Customization
41
+
42
+ The Click & Collect component is fully customizable through the `renderer` prop. You can override any part of the UI while keeping the core functionality.
43
+
44
+ #### Renderer Props
45
+
46
+ The `renderer` prop accepts an object with the following properties:
47
+
48
+ ```javascript
49
+ interface ClickCollectRendererProps {
50
+ renderContainer?: (props: {
51
+ children: React.ReactNode,
52
+ isActive: boolean,
53
+ handleActive: () => void,
54
+ handleDeactivate: () => void
55
+ }) => React.ReactNode;
56
+
57
+ renderInactiveState?: (props: {
58
+ translations: ClickCollectTranslationsProps
59
+ }) => React.ReactNode;
60
+
61
+ renderActiveState?: (props: {
62
+ translations: ClickCollectTranslationsProps,
63
+ cities: any[],
64
+ stores: any[],
65
+ selectedCity: any,
66
+ handleCityChange: (e: ChangeEvent<HTMLSelectElement>) => void,
67
+ handleStoreChange: (e: ChangeEvent<HTMLSelectElement>) => void,
68
+ handleDeactivate: () => void
69
+ }) => React.ReactNode;
70
+
71
+ renderCloseButton?: (props: {
72
+ handleDeactivate: () => void
73
+ }) => React.ReactNode;
14
74
 
15
- | Properties | Type | Description |
16
- | ---------- | -------- | -------------------- |
17
- | addressTypeParam | `string` | Address Type Request Param |
75
+ renderLoader?: () => React.ReactNode;
76
+ }
77
+ ```
78
+
79
+ ### Usage Examples
80
+
81
+ #### Default Usage
82
+
83
+ ```javascript
84
+ import { ClickCollect } from '@akinon/pz-click-collect';
85
+
86
+ <PluginModule
87
+ component={Component.ClickCollect}
88
+ props={{
89
+ addressTypeParam: addressType.requestParam,
90
+ translations: {
91
+ deliveryFromTheStore: 'DELIVERY FROM THE STORE',
92
+ deliveryStore: 'Delivery Store'
93
+ }
94
+ }}
95
+ />;
96
+ ```
97
+
98
+ ### Custom UI Usage
99
+
100
+ Here's an example of how to customize the component with branded styling:
101
+
102
+ ```javascript
103
+ const customRenderer = {
104
+ // Override the container
105
+ renderContainer: ({ children, isActive, handleActive }) => (
106
+ <div
107
+ role={!isActive ? 'button' : 'div'}
108
+ onClick={() => {
109
+ !isActive && handleActive();
110
+ }}
111
+ className={`
112
+ relative w-full min-h-[8rem] rounded-lg
113
+ ${
114
+ isActive
115
+ ? 'border-2 border-brand-primary bg-brand-primary/5'
116
+ : 'border border-gray-300 hover:border-brand-primary'
117
+ }
118
+ p-6 transition-all duration-300
119
+ `}
120
+ >
121
+ {children}
122
+ </div>
123
+ ),
124
+
125
+ // Override the inactive state display
126
+ renderInactiveState: ({ translations }) => (
127
+ <div className="text-sm flex flex-col justify-center items-center h-full gap-y-3 text-brand-primary">
128
+ <svg
129
+ xmlns="http://www.w3.org/2000/svg"
130
+ width="36"
131
+ height="36"
132
+ viewBox="0 0 24 24"
133
+ fill="none"
134
+ stroke="currentColor"
135
+ strokeWidth="2"
136
+ strokeLinecap="round"
137
+ strokeLinejoin="round"
138
+ >
139
+ <path d="M3 9h18v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9Z" />
140
+ <path d="M3 9V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v4" />
141
+ <path d="M9 13v5" />
142
+ <path d="M15 13v5" />
143
+ </svg>
144
+ <span className="font-medium tracking-wide">
145
+ {translations.deliveryFromTheStore}
146
+ </span>
147
+ </div>
148
+ )
149
+
150
+ // Custom styling for other parts...
151
+ };
152
+
153
+ <PluginModule
154
+ component={Component.ClickCollect}
155
+ props={{
156
+ addressTypeParam: 'shippingAddressPk',
157
+ translations: {
158
+ deliveryFromTheStore: 'Pick Up In-Store',
159
+ deliveryStore: 'Choose a Store'
160
+ },
161
+ renderer: customRenderer
162
+ }}
163
+ />;
164
+ ```
165
+
166
+ #### Partial Customization
167
+
168
+ You can override only specific parts of the UI while keeping the default styling for the rest:
169
+
170
+ ```javascript
171
+ <PluginModule
172
+ component={Component.ClickCollect}
173
+ props={{
174
+ addressTypeParam: "shippingAddressPk",
175
+ translations: {
176
+ deliveryFromTheStore: 'Pick Up In-Store',
177
+ deliveryStore: 'Choose a Store'
178
+ },
179
+ renderer={{
180
+ // Override only the active state
181
+ renderActiveState: ({
182
+ translations,
183
+ cities,
184
+ stores,
185
+ handleCityChange,
186
+ handleStoreChange
187
+ }) => (
188
+ <div className="custom-active-state">
189
+ {/* Your custom UI for the active state */}
190
+ </div>
191
+ )
192
+ }}
193
+ }}
194
+ />
195
+ ```
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@akinon/pz-click-collect",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0-beta.20",
4
4
  "main": "./src/index.tsx",
5
5
  "module": "./src/index.tsx",
6
6
  "license": "MIT",
7
7
  "devDependencies": {
8
- "@types/react": "^19.0.2",
9
- "@types/react-dom": "^19.0.2",
10
- "react": "^19.0.0",
11
- "react-dom": "^19.0.0",
12
- "typescript": "^5.7.2"
8
+ "@types/react": "^18.0.15",
9
+ "@types/react-dom": "^18.0.6",
10
+ "react": "^19.2.4",
11
+ "react-dom": "^19.2.4",
12
+ "typescript": "^4.7.4"
13
13
  },
14
14
  "peerDependencies": {
15
- "react": "^19.0.0",
16
- "react-dom": "^19.0.0"
15
+ "react": "^18.0.0 || ^19.0.0",
16
+ "react-dom": "^18.0.0 || ^19.0.0"
17
17
  }
18
18
  }
package/src/index.tsx CHANGED
@@ -18,22 +18,153 @@ import {
18
18
 
19
19
  import { RootState } from 'redux/store';
20
20
 
21
- interface ClickCollectTranslationsProps {
21
+ export interface ClickCollectTranslationsProps {
22
22
  deliveryFromTheStore: string;
23
23
  deliveryStore: string;
24
24
  }
25
25
 
26
+ export interface ClickCollectRendererProps {
27
+ renderContainer?: (props: {
28
+ children: React.ReactNode;
29
+ isActive: boolean;
30
+ handleActive: () => void;
31
+ handleDeactivate: () => void;
32
+ }) => React.ReactNode;
33
+ renderInactiveState?: (props: {
34
+ translations: ClickCollectTranslationsProps;
35
+ }) => React.ReactNode;
36
+ renderActiveState?: (props: {
37
+ translations: ClickCollectTranslationsProps;
38
+ cities: any[];
39
+ stores: any[];
40
+ selectedCity: any;
41
+ handleCityChange: (e: ChangeEvent<HTMLSelectElement>) => void;
42
+ handleStoreChange: (e: ChangeEvent<HTMLSelectElement>) => void;
43
+ handleDeactivate: () => void;
44
+ }) => React.ReactNode;
45
+ renderCloseButton?: (props: {
46
+ handleDeactivate: () => void;
47
+ }) => React.ReactNode;
48
+ renderLoader?: () => React.ReactNode;
49
+ }
50
+
26
51
  const defaultTranslations = {
27
52
  deliveryFromTheStore: 'DELIVERY FROM THE STORE',
28
53
  deliveryStore: 'Delivery Store'
29
54
  };
30
55
 
56
+ const DefaultContainer = ({
57
+ children,
58
+ isActive,
59
+ handleActive
60
+ }: {
61
+ children: React.ReactNode;
62
+ isActive: boolean;
63
+ handleActive: () => void;
64
+ }) => (
65
+ <div
66
+ role={!isActive ? 'button' : 'div'}
67
+ onClick={() => {
68
+ !isActive && handleActive();
69
+ }}
70
+ className={clsx(
71
+ 'relative w-full min-h-[8rem] border shadow p-4',
72
+ "hover:after:content-[''] hover:after:border-4 hover:after:opacity-30 hover:after:transition-opacity",
73
+ 'after:border-secondary-400 after:absolute after:inset-0 after:opacity-0 after:duration-150 after:-z-10'
74
+ )}
75
+ >
76
+ {children}
77
+ </div>
78
+ );
79
+
80
+ const DefaultInactiveState = ({
81
+ translations
82
+ }: {
83
+ translations: ClickCollectTranslationsProps;
84
+ }) => (
85
+ <div className="text-xs text-center flex justify-center items-center h-full gap-x-2">
86
+ <Icon name="plus" size={12} />
87
+ <span data-testid="click-collect-add-new-address">
88
+ {translations.deliveryFromTheStore}
89
+ </span>
90
+ </div>
91
+ );
92
+
93
+ const DefaultActiveState = ({
94
+ translations,
95
+ cities,
96
+ stores,
97
+ handleCityChange,
98
+ handleStoreChange
99
+ }: {
100
+ translations: ClickCollectTranslationsProps;
101
+ cities: any[];
102
+ stores: any[];
103
+ selectedCity: any;
104
+ handleCityChange: (e: ChangeEvent<HTMLSelectElement>) => void;
105
+ handleStoreChange: (e: ChangeEvent<HTMLSelectElement>) => void;
106
+ handleDeactivate: () => void;
107
+ }) => (
108
+ <div className="relative w-full">
109
+ <label
110
+ htmlFor="cities"
111
+ className="block mb-2 text-sm text-center font-light text-black-900"
112
+ >
113
+ {translations.deliveryStore}
114
+ </label>
115
+ <select
116
+ id="cities"
117
+ 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"
118
+ onChange={handleCityChange}
119
+ >
120
+ {cities.map((city) => (
121
+ <option key={city.pk} value={city.pk}>
122
+ {city.name}
123
+ </option>
124
+ ))}
125
+ </select>
126
+ <select
127
+ id="stores"
128
+ onChange={handleStoreChange}
129
+ 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"
130
+ >
131
+ {stores.map((store) => (
132
+ <option key={store.pk} value={store.pk}>
133
+ {store.name}
134
+ </option>
135
+ ))}
136
+ </select>
137
+ </div>
138
+ );
139
+
140
+ const DefaultCloseButton = ({
141
+ handleDeactivate
142
+ }: {
143
+ handleDeactivate: () => void;
144
+ }) => (
145
+ <div
146
+ role="button"
147
+ onClick={handleDeactivate}
148
+ className="absolute cursor-pointer top-2 right-2 hover:bg-byarlack-100/[.1] p-2 z-10 rounded-full"
149
+ >
150
+ <Icon name="close" size={9} />
151
+ </div>
152
+ );
153
+
154
+ const DefaultLoader = () => (
155
+ <div className="absolute top-0 left-0 w-full h-full bg-white/[.9] z-10">
156
+ <LoaderSpinner />
157
+ </div>
158
+ );
159
+
31
160
  export function ClickCollect({
32
161
  addressTypeParam,
33
- translations
162
+ translations,
163
+ renderer = {}
34
164
  }: {
35
165
  addressTypeParam: string;
36
166
  translations: ClickCollectTranslationsProps;
167
+ renderer?: ClickCollectRendererProps;
37
168
  }) {
38
169
  const _translations = {
39
170
  ...defaultTranslations,
@@ -82,7 +213,7 @@ export function ClickCollect({
82
213
 
83
214
  const defaultDeliveryOption = useMemo(
84
215
  () =>
85
- deliveryOptions.find(
216
+ deliveryOptions?.find(
86
217
  (option) => option?.delivery_option_type === 'customer'
87
218
  ),
88
219
  [deliveryOptions]
@@ -90,7 +221,7 @@ export function ClickCollect({
90
221
 
91
222
  const retailStoreDeliveryOption = useMemo(
92
223
  () =>
93
- deliveryOptions.find(
224
+ deliveryOptions?.find(
94
225
  (option) => option?.delivery_option_type === 'retail_store'
95
226
  ),
96
227
  [deliveryOptions]
@@ -240,73 +371,36 @@ export function ClickCollect({
240
371
 
241
372
  if (addressTypeParam !== 'shippingAddressPk') return;
242
373
 
374
+ const RenderContainer = renderer.renderContainer || DefaultContainer;
375
+ const RenderInactiveState =
376
+ renderer.renderInactiveState || DefaultInactiveState;
377
+ const RenderActiveState = renderer.renderActiveState || DefaultActiveState;
378
+ const RenderCloseButton = renderer.renderCloseButton || DefaultCloseButton;
379
+ const RenderLoader = renderer.renderLoader || DefaultLoader;
380
+
243
381
  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
- )}
382
+ <RenderContainer
383
+ isActive={isActive}
384
+ handleActive={handleActive}
385
+ handleDeactivate={handleDeactivate}
254
386
  >
255
387
  <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
- )}
388
+ {isActive && <RenderCloseButton handleDeactivate={handleDeactivate} />}
389
+ {loading && <RenderLoader />}
270
390
  {!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>
391
+ <RenderInactiveState translations={_translations} />
277
392
  ) : (
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>
393
+ <RenderActiveState
394
+ translations={_translations}
395
+ cities={getUniqueCities}
396
+ stores={retailStoresForCity}
397
+ selectedCity={selectedCity}
398
+ handleCityChange={handleCityChange}
399
+ handleStoreChange={handleStoreChange}
400
+ handleDeactivate={handleDeactivate}
401
+ />
308
402
  )}
309
403
  </div>
310
- </div>
404
+ </RenderContainer>
311
405
  );
312
406
  }