@akinon/pz-basket-gift-pack 1.89.0-rc.11 → 1.89.0-rc.13

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,10 +1,15 @@
1
1
  # @akinon/pz-basket-gift-pack
2
2
 
3
+ ## 1.89.0-rc.13
4
+
5
+ ## 1.89.0-rc.12
6
+
3
7
  ## 1.89.0-rc.11
4
8
 
5
9
  ### Minor Changes
6
10
 
7
11
  - 72bfcbf: ZERO-3347: Manage save button disable status
12
+ - 2ba89b3: ZERO-3361: Refactor basket gift pack component
8
13
 
9
14
  ## 1.89.0-rc.10
10
15
 
package/README.md CHANGED
@@ -19,3 +19,143 @@ npx @akinon/projectzero@latest --plugins
19
19
  | modalClassName | `string` | This prop is used to customize the overall style of the modal. |
20
20
  | modalTitle | `string` | This prop sets the title of the modal. |
21
21
  | modalContentClassName | `string` | This prop is used to customize the style of the modal content. |
22
+ | customUIRender | `React.ReactNode` | Optional function to fully customize the gift pack rendering. Receives hasGiftPack, hasNote, note, textAreaCount, onAddGiftPack, onRemoveGiftPack, onAddNote, onRemoveNote, formContent and translations props. |
23
+ | customGiftNoteFormUIRender | `React.ReactNode` | Optional function to fully customize the form rendering. Receives register, errors, note, textAreaCount, onSubmit, removeNote and handleSubmit props. |
24
+
25
+ ### Customizing Gift Pack
26
+
27
+ ```javascript
28
+ import PluginModule, { Component } from '@akinon/next/components/plugin-module';
29
+
30
+ <PluginModule
31
+ component={Component.BasketGiftPack}
32
+ props={{
33
+ basketItem,
34
+ customUIRender: ({
35
+ hasGiftPack,
36
+ hasNote,
37
+ onAddGiftPack,
38
+ onRemoveGiftPack,
39
+ onAddNote,
40
+ formContent,
41
+ translations
42
+ }) => (
43
+ <>
44
+ <div className="flex gap-4">
45
+ <div className="flex items-center gap-2">
46
+ <Icon
47
+ name="giftbox"
48
+ size={15}
49
+ className={clsx(
50
+ hasGiftPack ? 'text-[#e85150]' : 'text-[#000000]'
51
+ )}
52
+ />
53
+ {hasGiftPack ? (
54
+ <span className="text-[0.75rem]">
55
+ {translations.giftPackAdded}
56
+ </span>
57
+ ) : (
58
+ <Button
59
+ appearance="ghost"
60
+ className="text-[#000000] cursor-pointer underline border-0 px-0 py-0 text-[0.75rem] h-auto hover:bg-transparent hover:text-[#000000]"
61
+ onClick={onAddGiftPack}
62
+ data-testid="add-basket-gift-pack"
63
+ >
64
+ {translations.addGiftPack}
65
+ </Button>
66
+ )}
67
+ </div>
68
+
69
+ {hasGiftPack && (
70
+ <>
71
+ <Button
72
+ appearance="ghost"
73
+ className="text-[#000000] cursor-pointer border-0 px-0 py-0 text-[0.75rem] select-none underline h-auto hover:bg-transparent hover:text-[#000000]"
74
+ onClick={onAddNote}
75
+ data-testid="add-basket-gift-pack-note"
76
+ >
77
+ {hasNote.title}
78
+ </Button>
79
+ <Button
80
+ appearance="ghost"
81
+ className={clsx(
82
+ 'text-[#000000] cursor-pointer border-0 px-0 py-0 text-[0.75rem] underline h-auto hover:bg-transparent hover:text-[#000000]',
83
+ {
84
+ hidden: !hasGiftPack
85
+ }
86
+ )}
87
+ onClick={onRemoveGiftPack}
88
+ data-testid="remove-basket-gift-pack"
89
+ >
90
+ {translations.removeGiftPack}
91
+ </Button>
92
+ </>
93
+ )}
94
+ </div>
95
+
96
+ <div className="w-full">
97
+ {hasNote.state ? <div className="mt-4">{formContent}</div> : null}
98
+ </div>
99
+ </>
100
+ )
101
+ }}
102
+ />;
103
+ ```
104
+
105
+ ### Customizing Gift Note Form
106
+
107
+ ```javascript
108
+ import PluginModule, { Component } from '@akinon/next/components/plugin-module';
109
+
110
+ <PluginModule
111
+ component={Component.BasketGiftPack}
112
+ props={{
113
+ basketItem,
114
+ customGiftNoteFormUIRender: ({
115
+ register,
116
+ errors,
117
+ note,
118
+ textAreaCount,
119
+ onSubmit,
120
+ removeNote,
121
+ handleSubmit
122
+ }) => (
123
+ <div className="bg-gray-100 p-4 rounded-md shadow-lg">
124
+ <h4 className="text-lg font-semibold mb-2">🎁 Add Your Note</h4>
125
+ <form onSubmit={handleSubmit(onSubmit)}>
126
+ <textarea
127
+ className="w-full border border-gray-300 p-2"
128
+ {...register('message')}
129
+ placeholder="Mesajını yaz..."
130
+ rows={5}
131
+ value={note}
132
+ />
133
+ {errors.message && (
134
+ <p className="text-red-500 text-sm">{errors.message.message}</p>
135
+ )}
136
+ <div className="flex justify-between items-center mt-2">
137
+ <span className="text-sm text-gray-500">
138
+ {textAreaCount}/ 160 characters
139
+ </span>
140
+ <div className="flex gap-2">
141
+ <button
142
+ type="button"
143
+ onClick={removeNote}
144
+ className="text-sm underline"
145
+ >
146
+ Delete Note
147
+ </button>
148
+ <button
149
+ type="submit"
150
+ className="bg-black text-white px-3 py-1 text-sm rounded"
151
+ >
152
+ Save
153
+ </button>
154
+ </div>
155
+ </div>
156
+ </form>
157
+ </div>
158
+ )
159
+ }}
160
+ />;
161
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/pz-basket-gift-pack",
3
- "version": "1.89.0-rc.11",
3
+ "version": "1.89.0-rc.13",
4
4
  "license": "MIT",
5
5
  "main": "src/index.tsx",
6
6
  "peerDependencies": {
package/src/index.tsx CHANGED
@@ -4,38 +4,69 @@ import {
4
4
  } from './endpoints';
5
5
  import clsx from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
7
- import { useForm, SubmitHandler } from 'react-hook-form';
7
+ import {
8
+ useForm,
9
+ SubmitHandler,
10
+ UseFormRegister,
11
+ FieldErrors,
12
+ UseFormHandleSubmit
13
+ } from 'react-hook-form';
8
14
  import { yupResolver } from '@hookform/resolvers/yup';
9
15
  import * as yup from 'yup';
10
16
  import { useEffect, useState } from 'react';
11
17
  import { Button, Icon, Modal } from '@akinon/next/components';
12
18
  import { BasketItem as BasketItemType } from '@akinon/next/types';
13
19
 
20
+ const defaultTranslations = {
21
+ placeholderInput:
22
+ 'You can leave this area empty. However it will be a gift package without note.',
23
+ warningMessage: 'Ensure this field has no more than 160 characters.',
24
+ addNote: 'Add Note',
25
+ addGiftNote: 'Add Gift Note',
26
+ changeNote: 'Change Note',
27
+ giftPackAdded: 'Gift Pack Added',
28
+ addGiftPack: 'Add Gift Pack',
29
+ removeGiftPack: 'Remove Gift Pack',
30
+ charactersLength: 'characters left',
31
+ removeNote: 'Remove Note',
32
+ save: 'SAVE',
33
+ saving: 'SAVING...',
34
+ close: 'Close'
35
+ };
36
+
14
37
  interface GiftPackForm {
15
38
  message: string;
16
39
  }
17
-
40
+ interface CustomRenderParams {
41
+ hasGiftPack: boolean;
42
+ hasNote: { title: string; state: boolean };
43
+ note: string;
44
+ textAreaCount: number;
45
+ onAddGiftPack: () => void;
46
+ onRemoveGiftPack: () => void;
47
+ onAddNote: () => void;
48
+ onRemoveNote: () => void;
49
+ formContent: React.ReactNode;
50
+ translations: typeof defaultTranslations;
51
+ }
18
52
  interface Props {
19
53
  basketItem: BasketItemType;
20
54
  useModal?: boolean;
21
55
  modalTitle?: string;
22
56
  modalClassName?: string;
23
57
  modalContentClassName?: string;
24
- translations?: {
25
- placeholderInput?: string;
26
- warningMessage?: string;
27
- addNote?: string;
28
- addGiftNote?: string;
29
- changeNote?: string;
30
- giftPackAdded?: string;
31
- addGiftPack?: string;
32
- removeGiftPack?: string;
33
- charactersLength?: string;
34
- removeNote?: string;
35
- save?: string;
36
- close?: string;
37
- };
58
+ translations?: Partial<typeof defaultTranslations>;
38
59
  className?: string;
60
+ customUIRender?: (params: CustomRenderParams) => React.ReactNode;
61
+ customGiftNoteFormUIRender?: (params: {
62
+ register: UseFormRegister<GiftPackForm>;
63
+ errors: FieldErrors<GiftPackForm>;
64
+ note: string;
65
+ textAreaCount: number;
66
+ onSubmit: () => void;
67
+ removeNote: () => void;
68
+ handleSubmit: UseFormHandleSubmit<GiftPackForm>;
69
+ }) => React.ReactNode;
39
70
  }
40
71
 
41
72
  export const BasketGiftPack = ({
@@ -45,30 +76,11 @@ export const BasketGiftPack = ({
45
76
  useModal = false,
46
77
  modalTitle = 'Gift Note',
47
78
  modalClassName,
48
- modalContentClassName
79
+ modalContentClassName,
80
+ customUIRender,
81
+ customGiftNoteFormUIRender
49
82
  }: Props) => {
50
- const defaultTranslations = {
51
- placeholderInput:
52
- 'You can leave this area empty. However it will be a gift package without note.',
53
- warningMessage: 'Ensure this field has no more than 160 characters.',
54
- addNote: 'Add Note',
55
- addGiftNote: 'Add Gift Note',
56
- changeNote: 'Change Note',
57
- giftPackAdded: 'Gift Pack Added',
58
- addGiftPack: 'Add Gift Pack',
59
- removeGiftPack: 'Remove Gift Pack',
60
- charactersLength: 'characters left',
61
- removeNote: 'Remove Note',
62
- save: 'SAVE',
63
- saving: 'SAVING...',
64
- close: 'Close'
65
- };
66
-
67
- const _translations = {
68
- ...defaultTranslations,
69
- ...translations
70
- };
71
-
83
+ const _translations = { ...defaultTranslations, ...translations };
72
84
  const [addGiftPackage, { isLoading: isAddingGiftPackage }] =
73
85
  useAddGiftPackageMutation();
74
86
  const [removeGiftPackage] = useRemoveGiftPackageMutation();
@@ -81,10 +93,9 @@ export const BasketGiftPack = ({
81
93
  const [note, setNote] = useState('');
82
94
  const defaultMessage = 'NO-GIFT-NOTE';
83
95
 
84
- const giftPackFormSchema = () =>
85
- yup.object().shape({
86
- message: yup.string().max(160, _translations.warningMessage)
87
- });
96
+ const schema = yup.object().shape({
97
+ message: yup.string().max(160, _translations.warningMessage)
98
+ });
88
99
 
89
100
  const {
90
101
  register,
@@ -92,54 +103,83 @@ export const BasketGiftPack = ({
92
103
  handleSubmit,
93
104
  formState: { errors }
94
105
  } = useForm<GiftPackForm>({
95
- resolver: yupResolver(giftPackFormSchema())
106
+ resolver: yupResolver(schema)
96
107
  });
97
108
 
98
109
  const onSubmit: SubmitHandler<GiftPackForm> = async (data) => {
99
- addGiftPackage({ id: basketItem.id, message: data.message })
100
- .unwrap()
101
- .then(() => {
102
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
103
- changeNoteTitle(data.message);
104
- });
110
+ await addGiftPackage({ id: basketItem.id, message: data.message }).unwrap();
111
+ setHasNote({ ...hasNote, state: false });
112
+ changeNoteTitle(data.message);
105
113
  };
106
114
 
107
- const addGiftPack = () => {
108
- addGiftPackage({ id: basketItem.id, message: defaultMessage })
109
- .unwrap()
110
- .then(() => {
111
- setHasGiftPack(true);
112
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
113
- });
115
+ const addGiftPack = async () => {
116
+ await addGiftPackage({
117
+ id: basketItem.id,
118
+ message: defaultMessage
119
+ }).unwrap();
120
+ setHasGiftPack(true);
121
+ setHasNote({ ...hasNote, state: false });
114
122
  };
115
123
 
116
124
  const removeGiftPack = () => {
117
125
  removeGiftPackage({ id: basketItem.id });
118
126
  setHasGiftPack(false);
119
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
127
+ setHasNote({ ...hasNote, state: false });
120
128
  };
121
129
 
122
130
  const removeNote = () => {
123
131
  addGiftPackage({ id: basketItem.id, message: defaultMessage });
124
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
132
+ setHasNote({ ...hasNote, state: false });
125
133
  changeNoteTitle(defaultMessage);
126
134
  };
127
135
 
128
- const changeNoteTitle = (value) => {
129
- if (value !== defaultMessage) {
130
- setHasNote((prevstate) => ({
131
- ...prevstate,
132
- title: _translations.changeNote
133
- }));
134
- } else {
135
- setHasNote((prevstate) => ({
136
- ...prevstate,
137
- title: _translations.addNote
138
- }));
139
- }
136
+ const changeNoteTitle = (value: string) => {
137
+ const title =
138
+ value !== defaultMessage
139
+ ? _translations.changeNote
140
+ : _translations.addNote;
141
+ setHasNote((prev) => ({ ...prev, title }));
140
142
  };
141
143
 
142
- const formContent = (
144
+ useEffect(() => {
145
+ for (const [key, value] of Object.entries(basketItem.attributes)) {
146
+ if (value === '') {
147
+ setHasGiftPack(false);
148
+ setHasNote({ ...hasNote, state: false });
149
+ setNote('');
150
+ setTextAreaCount(0);
151
+ return;
152
+ }
153
+
154
+ if (key) {
155
+ changeNoteTitle(value);
156
+ setNote(value);
157
+ setHasGiftPack(true);
158
+ setTextAreaCount(value === defaultMessage ? 0 : value.length);
159
+ }
160
+ }
161
+ }, [basketItem.attributes]);
162
+
163
+ useEffect(() => {
164
+ const subscription = watch(({ message }) => {
165
+ setNote(message);
166
+ setTextAreaCount(message.length);
167
+ });
168
+
169
+ return () => subscription.unsubscribe();
170
+ }, [watch]);
171
+
172
+ const formContent = customGiftNoteFormUIRender ? (
173
+ customGiftNoteFormUIRender({
174
+ register,
175
+ errors,
176
+ note,
177
+ textAreaCount,
178
+ onSubmit: () => handleSubmit(onSubmit)(),
179
+ removeNote,
180
+ handleSubmit
181
+ })
182
+ ) : (
143
183
  <form onSubmit={handleSubmit(onSubmit)}>
144
184
  {!useModal && (
145
185
  <div className="flex justify-between mb-3">
@@ -151,7 +191,7 @@ export const BasketGiftPack = ({
151
191
  className="text-[#000000] cursor-pointer border-0 px-0 py-0 text-[0.75rem] select-none underline h-auto hover:bg-transparent hover:text-[#000000]"
152
192
  onClick={(e) => {
153
193
  e.preventDefault();
154
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
194
+ setHasNote({ ...hasNote, state: false });
155
195
  }}
156
196
  >
157
197
  {_translations.close}
@@ -159,27 +199,26 @@ export const BasketGiftPack = ({
159
199
  </div>
160
200
  )}
161
201
  <textarea
162
- className="w-full border border-solid border-[#4a4f53] p-4 placeholder:text-[0.75rem] placeholder:text-[#c8c9c8] outline-none text-[0.75rem]"
202
+ className="w-full border border-[#4a4f53] p-4 text-[0.75rem] placeholder:text-[#c8c9c8]"
163
203
  rows={7}
164
- name="message"
165
- value={note !== defaultMessage ? note : ' '}
166
204
  placeholder={_translations.placeholderInput}
167
205
  {...register('message')}
206
+ value={note !== defaultMessage ? note : ''}
168
207
  data-testid="basket-gift-pack-note-input"
169
208
  />
170
209
  {errors.message && (
171
210
  <span className="text-sm text-[#d72a04]">{errors.message.message}</span>
172
211
  )}
173
- <div className="flex justify-between items-center mt-2">
212
+ <div className="flex justify-between mt-2 items-center">
174
213
  <span
175
214
  className={clsx(
176
- 'text-[0.75rem] ',
215
+ 'text-[0.75rem]',
177
216
  textAreaCount > 160 ? 'text-[#e85150]' : 'text-[#82a27c]'
178
217
  )}
179
218
  >
180
219
  {textAreaCount}/160 {_translations.charactersLength}
181
220
  </span>
182
- <div className="flex items-center gap-3">
221
+ <div className="flex gap-3 items-center">
183
222
  <Button
184
223
  appearance="ghost"
185
224
  className="text-[#000000] cursor-pointer border-0 px-0 py-0 text-[0.75rem] select-none underline h-auto hover:bg-transparent hover:text-[#000000]"
@@ -206,44 +245,10 @@ export const BasketGiftPack = ({
206
245
  </form>
207
246
  );
208
247
 
209
- useEffect(() => {
210
- for (const [key, value] of Object.entries(basketItem.attributes)) {
211
- if (value === '') {
212
- setHasGiftPack(false);
213
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
214
- setNote('');
215
- setTextAreaCount(0);
216
- return;
217
- }
218
-
219
- if (key) {
220
- changeNoteTitle(value);
221
- setNote(value);
222
- setHasGiftPack(true);
223
- setTextAreaCount(value?.length);
224
- }
225
- }
226
- }, [basketItem.attributes]);
227
-
228
- useEffect(() => {
229
- const subscription = watch(({ message }) => {
230
- setNote(message);
231
- setTextAreaCount(message.length);
232
- });
233
-
234
- return () => subscription.unsubscribe();
235
- }, [watch]);
236
-
237
- return (
248
+ const defaultRender = () => (
238
249
  <div
239
250
  className={twMerge(
240
- clsx(
241
- 'mt-4 sm:mt-0',
242
- {
243
- 'sm:mt-8': hasNote.state
244
- },
245
- className
246
- )
251
+ clsx('mt-4 sm:mt-0', { 'sm:mt-8': hasNote.state }, className)
247
252
  )}
248
253
  >
249
254
  <div className="flex gap-4">
@@ -253,7 +258,6 @@ export const BasketGiftPack = ({
253
258
  size={15}
254
259
  className={clsx(hasGiftPack ? 'text-[#e85150]' : 'text-[#000000]')}
255
260
  />
256
-
257
261
  {hasGiftPack ? (
258
262
  <span className="text-[0.75rem]">
259
263
  {_translations.giftPackAdded}
@@ -269,36 +273,34 @@ export const BasketGiftPack = ({
269
273
  </Button>
270
274
  )}
271
275
  </div>
272
- <Button
273
- appearance="ghost"
274
- className={clsx(
275
- 'text-[#000000] underline cursor-pointer border-0 px-0 py-0 text-[0.75rem] h-auto hover:bg-transparent hover:text-[#000000]',
276
- {
277
- hidden: !hasGiftPack
278
- }
279
- )}
280
- onClick={() =>
281
- setHasNote((prevstate) => ({ ...prevstate, state: true }))
282
- }
283
- data-testid="add-basket-gift-pack-note"
284
- >
285
- {hasNote.title}
286
- </Button>
287
276
 
288
- <Button
289
- appearance="ghost"
290
- className={clsx(
291
- 'text-[#000000] cursor-pointer border-0 px-0 py-0 text-[0.75rem] underline h-auto hover:bg-transparent hover:text-[#000000]',
292
- {
293
- hidden: !hasGiftPack
294
- }
295
- )}
296
- onClick={removeGiftPack}
297
- data-testid="remove-basket-gift-pack"
298
- >
299
- {_translations.removeGiftPack}
300
- </Button>
277
+ {hasGiftPack && (
278
+ <>
279
+ <Button
280
+ appearance="ghost"
281
+ className="text-[#000000] cursor-pointer border-0 px-0 py-0 text-[0.75rem] select-none underline h-auto hover:bg-transparent hover:text-[#000000]"
282
+ onClick={() => setHasNote({ ...hasNote, state: true })}
283
+ data-testid="add-basket-gift-pack-note"
284
+ >
285
+ {hasNote.title}
286
+ </Button>
287
+ <Button
288
+ appearance="ghost"
289
+ className={clsx(
290
+ 'text-[#000000] cursor-pointer border-0 px-0 py-0 text-[0.75rem] underline h-auto hover:bg-transparent hover:text-[#000000]',
291
+ {
292
+ hidden: !hasGiftPack
293
+ }
294
+ )}
295
+ onClick={removeGiftPack}
296
+ data-testid="remove-basket-gift-pack"
297
+ >
298
+ {_translations.removeGiftPack}
299
+ </Button>
300
+ </>
301
+ )}
301
302
  </div>
303
+
302
304
  {useModal ? (
303
305
  <Modal
304
306
  portalId="basket-gift-pack-modal"
@@ -308,23 +310,30 @@ export const BasketGiftPack = ({
308
310
  modalClassName
309
311
  )}
310
312
  open={hasNote.state}
311
- setOpen={(state) =>
312
- setHasNote((prevstate) => ({ ...prevstate, state }))
313
- }
313
+ setOpen={(state) => setHasNote((prev) => ({ ...prev, state }))}
314
314
  >
315
315
  <div className={twMerge('px-6 py-4', modalContentClassName)}>
316
316
  {formContent}
317
317
  </div>
318
318
  </Modal>
319
- ) : (
320
- <div
321
- className={clsx('mt-4', {
322
- hidden: !hasNote.state
323
- })}
324
- >
325
- {formContent}
326
- </div>
327
- )}
319
+ ) : hasNote.state ? (
320
+ <div className="mt-4">{formContent}</div>
321
+ ) : null}
328
322
  </div>
329
323
  );
324
+
325
+ return customUIRender
326
+ ? customUIRender({
327
+ hasGiftPack,
328
+ hasNote,
329
+ note,
330
+ textAreaCount,
331
+ onAddGiftPack: addGiftPack,
332
+ onRemoveGiftPack: removeGiftPack,
333
+ onAddNote: () => setHasNote({ ...hasNote, state: true }),
334
+ onRemoveNote: removeNote,
335
+ formContent,
336
+ translations: _translations
337
+ })
338
+ : defaultRender();
330
339
  };