@akinon/pz-click-collect 2.0.0-beta.8 → 2.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/CHANGELOG.md CHANGED
@@ -1,39 +1,148 @@
1
1
  # @akinon/pz-click-collect
2
2
 
3
- ## 2.0.0-beta.8
3
+ ## 2.0.0
4
4
 
5
- ## 2.0.0-beta.7
5
+ ## 2.0.0-beta.27
6
6
 
7
- ## 2.0.0-beta.6
7
+ ## 2.0.0-beta.26
8
+
9
+ ## 2.0.0-beta.25
10
+
11
+ ## 2.0.0-beta.24
12
+
13
+ ## 2.0.0-beta.23
14
+
15
+ ## 2.0.0-beta.22
16
+
17
+ ## 2.0.0-beta.21
18
+
19
+ ## 2.0.0-beta.20
20
+
21
+ ## 1.126.0
22
+
23
+ ## 1.125.2
24
+
25
+ ## 1.125.1
26
+
27
+ ## 1.125.0
28
+
29
+ ## 1.124.0
30
+
31
+ ## 1.123.0
32
+
33
+ ## 1.122.0
34
+
35
+ ## 1.121.0
36
+
37
+ ## 1.120.0
38
+
39
+ ## 1.119.0
40
+
41
+ ## 1.118.0
42
+
43
+ ## 1.117.0
44
+
45
+ ## 1.116.0
46
+
47
+ ## 1.115.0
48
+
49
+ ## 1.114.0
50
+
51
+ ## 1.113.0
52
+
53
+ ## 1.112.0
54
+
55
+ ## 1.111.0
56
+
57
+ ## 1.110.0
58
+
59
+ ## 1.109.0
60
+
61
+ ## 1.108.0
62
+
63
+ ## 1.107.0
64
+
65
+ ## 1.106.0
66
+
67
+ ## 1.105.0
68
+
69
+ ## 1.104.0
70
+
71
+ ## 1.103.0
72
+
73
+ ### Minor Changes
74
+
75
+ - b16a370: ZERO-3403: Enhance README.md with detailed Click & Collect component documentation and usage examples
76
+
77
+ ## 1.102.0
78
+
79
+ ## 1.101.0
80
+
81
+ ## 1.100.0
82
+
83
+ ## 1.99.0
84
+
85
+ ### Minor Changes
86
+
87
+ - d58538b: ZERO-3638: Enhance RC pipeline: add fetch, merge, and pre-release setup with conditional commit
88
+
89
+ ## 1.98.0
90
+
91
+ ## 1.97.0
92
+
93
+ ## 1.96.0
94
+
95
+ ## 1.95.0
96
+
97
+ ## 1.94.0
8
98
 
9
99
  ### Minor Changes
10
100
 
11
- - 8f05f9b: ZERO-3250: Beta branch synchronized with Main branch
101
+ - 1b4c343: ZERO-3341: Add data-testid for click-collect package
12
102
 
13
- ## 2.0.0-beta.5
103
+ ## 1.93.0
14
104
 
15
- ## 2.0.0-beta.4
105
+ ## 1.92.0
16
106
 
17
- ## 2.0.0-beta.3
107
+ ## 1.91.0
18
108
 
19
- ## 2.0.0-beta.2
109
+ ## 1.90.0
20
110
 
21
111
  ### Minor Changes
22
112
 
23
- - a006015: ZERO-3116: Add not-found page and update default middleware.
24
- - 1eeb3d8: ZERO-3116: Add not found page
113
+ - c9b8c6f: ZERO-3374: Add Click & Collect component documentation and enhance type definitions
114
+
115
+ ## 1.89.0
116
+
117
+ ## 1.88.0
118
+
119
+ ## 1.87.0
25
120
 
26
- ## 2.0.0-beta.1
121
+ ## 1.86.0
122
+
123
+ ## 1.85.0
124
+
125
+ ## 1.84.0
27
126
 
28
127
  ### Minor Changes
29
128
 
30
- - ZERO-3091: Upgrade Next.js to v15 and React to v19
129
+ - 624a4eb: ZERO-3276: Update installation instructions across multiple README files to standardize format and improve clarity
130
+
131
+ ## 1.83.0
132
+
133
+ ## 1.82.0
134
+
135
+ ## 1.81.0
136
+
137
+ ## 1.80.0
138
+
139
+ ## 1.79.0
31
140
 
32
- ## 2.0.0-beta.0
141
+ ## 1.78.0
33
142
 
34
- ### Major Changes
143
+ ## 1.77.0
35
144
 
36
- - be6c09d: ZERO-3114: Create beta version.
145
+ ## 1.76.0
37
146
 
38
147
  ## 1.75.0
39
148
 
package/README.md CHANGED
@@ -10,12 +10,186 @@ npx @akinon/projectzero@latest --plugins
10
10
 
11
11
  ```
12
12
 
13
- ### Props
13
+ # Click & Collect Component
14
14
 
15
- | Properties | Type | Description |
16
- | ---------------- | -------- | -------------------------- |
17
- | addressTypeParam | `string` | Address Type Request Param |
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.
18
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;
74
+
75
+ renderLoader?: () => React.ReactNode;
76
+ }
19
77
  ```
20
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
+ />
21
195
  ```
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@akinon/pz-click-collect",
3
- "version": "2.0.0-beta.8",
3
+ "version": "2.0.0",
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.5",
11
+ "react-dom": "19.2.5",
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 border-gray-200 shadow-sm 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
  }