@akinon/pz-basket-gift-pack 1.103.0 → 1.104.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,5 +1,11 @@
1
1
  # @akinon/pz-basket-gift-pack
2
2
 
3
+ ## 1.104.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2ba89b3: ZERO-3361: Refactor basket gift pack component
8
+
3
9
  ## 1.103.0
4
10
 
5
11
  ## 1.102.0
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.103.0",
3
+ "version": "1.104.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.tsx",
6
6
  "peerDependencies": {
package/src/index.tsx CHANGED
@@ -4,38 +4,68 @@ 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
+ close: 'Close'
34
+ };
35
+
14
36
  interface GiftPackForm {
15
37
  message: string;
16
38
  }
17
-
39
+ interface CustomRenderParams {
40
+ hasGiftPack: boolean;
41
+ hasNote: { title: string; state: boolean };
42
+ note: string;
43
+ textAreaCount: number;
44
+ onAddGiftPack: () => void;
45
+ onRemoveGiftPack: () => void;
46
+ onAddNote: () => void;
47
+ onRemoveNote: () => void;
48
+ formContent: React.ReactNode;
49
+ translations: typeof defaultTranslations;
50
+ }
18
51
  interface Props {
19
52
  basketItem: BasketItemType;
20
53
  useModal?: boolean;
21
54
  modalTitle?: string;
22
55
  modalClassName?: string;
23
56
  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
- };
57
+ translations?: Partial<typeof defaultTranslations>;
38
58
  className?: string;
59
+ customUIRender?: (params: CustomRenderParams) => React.ReactNode;
60
+ customGiftNoteFormUIRender?: (params: {
61
+ register: UseFormRegister<GiftPackForm>;
62
+ errors: FieldErrors<GiftPackForm>;
63
+ note: string;
64
+ textAreaCount: number;
65
+ onSubmit: () => void;
66
+ removeNote: () => void;
67
+ handleSubmit: UseFormHandleSubmit<GiftPackForm>;
68
+ }) => React.ReactNode;
39
69
  }
40
70
 
41
71
  export const BasketGiftPack = ({
@@ -45,29 +75,11 @@ export const BasketGiftPack = ({
45
75
  useModal = false,
46
76
  modalTitle = 'Gift Note',
47
77
  modalClassName,
48
- modalContentClassName
78
+ modalContentClassName,
79
+ customUIRender,
80
+ customGiftNoteFormUIRender
49
81
  }: 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
- close: 'Close'
64
- };
65
-
66
- const _translations = {
67
- ...defaultTranslations,
68
- ...translations
69
- };
70
-
82
+ const _translations = { ...defaultTranslations, ...translations };
71
83
  const [addGiftPackage] = useAddGiftPackageMutation();
72
84
  const [removeGiftPackage] = useRemoveGiftPackageMutation();
73
85
  const [hasGiftPack, setHasGiftPack] = useState(false);
@@ -79,10 +91,9 @@ export const BasketGiftPack = ({
79
91
  const [note, setNote] = useState('');
80
92
  const defaultMessage = 'NO-GIFT-NOTE';
81
93
 
82
- const giftPackFormSchema = () =>
83
- yup.object().shape({
84
- message: yup.string().max(160, _translations.warningMessage)
85
- });
94
+ const schema = yup.object().shape({
95
+ message: yup.string().max(160, _translations.warningMessage)
96
+ });
86
97
 
87
98
  const {
88
99
  register,
@@ -90,54 +101,83 @@ export const BasketGiftPack = ({
90
101
  handleSubmit,
91
102
  formState: { errors }
92
103
  } = useForm<GiftPackForm>({
93
- resolver: yupResolver(giftPackFormSchema())
104
+ resolver: yupResolver(schema)
94
105
  });
95
106
 
96
107
  const onSubmit: SubmitHandler<GiftPackForm> = async (data) => {
97
- addGiftPackage({ id: basketItem.id, message: data.message })
98
- .unwrap()
99
- .then(() => {
100
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
101
- changeNoteTitle(data.message);
102
- });
108
+ await addGiftPackage({ id: basketItem.id, message: data.message }).unwrap();
109
+ setHasNote({ ...hasNote, state: false });
110
+ changeNoteTitle(data.message);
103
111
  };
104
112
 
105
- const addGiftPack = () => {
106
- addGiftPackage({ id: basketItem.id, message: defaultMessage })
107
- .unwrap()
108
- .then(() => {
109
- setHasGiftPack(true);
110
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
111
- });
113
+ const addGiftPack = async () => {
114
+ await addGiftPackage({
115
+ id: basketItem.id,
116
+ message: defaultMessage
117
+ }).unwrap();
118
+ setHasGiftPack(true);
119
+ setHasNote({ ...hasNote, state: false });
112
120
  };
113
121
 
114
122
  const removeGiftPack = () => {
115
123
  removeGiftPackage({ id: basketItem.id });
116
124
  setHasGiftPack(false);
117
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
125
+ setHasNote({ ...hasNote, state: false });
118
126
  };
119
127
 
120
128
  const removeNote = () => {
121
129
  addGiftPackage({ id: basketItem.id, message: defaultMessage });
122
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
130
+ setHasNote({ ...hasNote, state: false });
123
131
  changeNoteTitle(defaultMessage);
124
132
  };
125
133
 
126
- const changeNoteTitle = (value) => {
127
- if (value !== defaultMessage) {
128
- setHasNote((prevstate) => ({
129
- ...prevstate,
130
- title: _translations.changeNote
131
- }));
132
- } else {
133
- setHasNote((prevstate) => ({
134
- ...prevstate,
135
- title: _translations.addNote
136
- }));
137
- }
134
+ const changeNoteTitle = (value: string) => {
135
+ const title =
136
+ value !== defaultMessage
137
+ ? _translations.changeNote
138
+ : _translations.addNote;
139
+ setHasNote((prev) => ({ ...prev, title }));
138
140
  };
139
141
 
140
- const formContent = (
142
+ useEffect(() => {
143
+ for (const [key, value] of Object.entries(basketItem.attributes)) {
144
+ if (value === '') {
145
+ setHasGiftPack(false);
146
+ setHasNote({ ...hasNote, state: false });
147
+ setNote('');
148
+ setTextAreaCount(0);
149
+ return;
150
+ }
151
+
152
+ if (key) {
153
+ changeNoteTitle(value);
154
+ setNote(value);
155
+ setHasGiftPack(true);
156
+ setTextAreaCount(value === defaultMessage ? 0 : value.length);
157
+ }
158
+ }
159
+ }, [basketItem.attributes]);
160
+
161
+ useEffect(() => {
162
+ const subscription = watch(({ message }) => {
163
+ setNote(message);
164
+ setTextAreaCount(message.length);
165
+ });
166
+
167
+ return () => subscription.unsubscribe();
168
+ }, [watch]);
169
+
170
+ const formContent = customGiftNoteFormUIRender ? (
171
+ customGiftNoteFormUIRender({
172
+ register,
173
+ errors,
174
+ note,
175
+ textAreaCount,
176
+ onSubmit: () => handleSubmit(onSubmit)(),
177
+ removeNote,
178
+ handleSubmit
179
+ })
180
+ ) : (
141
181
  <form onSubmit={handleSubmit(onSubmit)}>
142
182
  {!useModal && (
143
183
  <div className="flex justify-between mb-3">
@@ -149,7 +189,7 @@ export const BasketGiftPack = ({
149
189
  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]"
150
190
  onClick={(e) => {
151
191
  e.preventDefault();
152
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
192
+ setHasNote({ ...hasNote, state: false });
153
193
  }}
154
194
  >
155
195
  {_translations.close}
@@ -157,27 +197,26 @@ export const BasketGiftPack = ({
157
197
  </div>
158
198
  )}
159
199
  <textarea
160
- className="w-full border border-solid border-[#4a4f53] p-4 placeholder:text-[0.75rem] placeholder:text-[#c8c9c8] outline-none text-[0.75rem]"
200
+ className="w-full border border-[#4a4f53] p-4 text-[0.75rem] placeholder:text-[#c8c9c8]"
161
201
  rows={7}
162
- name="message"
163
- value={note !== defaultMessage ? note : ' '}
164
202
  placeholder={_translations.placeholderInput}
165
203
  {...register('message')}
204
+ value={note !== defaultMessage ? note : ''}
166
205
  data-testid="basket-gift-pack-note-input"
167
206
  />
168
207
  {errors.message && (
169
208
  <span className="text-sm text-[#d72a04]">{errors.message.message}</span>
170
209
  )}
171
- <div className="flex justify-between items-center mt-2">
210
+ <div className="flex justify-between mt-2 items-center">
172
211
  <span
173
212
  className={clsx(
174
- 'text-[0.75rem] ',
213
+ 'text-[0.75rem]',
175
214
  textAreaCount > 160 ? 'text-[#e85150]' : 'text-[#82a27c]'
176
215
  )}
177
216
  >
178
217
  {textAreaCount}/160 {_translations.charactersLength}
179
218
  </span>
180
- <div className="flex items-center gap-3">
219
+ <div className="flex gap-3 items-center">
181
220
  <Button
182
221
  appearance="ghost"
183
222
  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]"
@@ -198,44 +237,10 @@ export const BasketGiftPack = ({
198
237
  </form>
199
238
  );
200
239
 
201
- useEffect(() => {
202
- for (const [key, value] of Object.entries(basketItem.attributes)) {
203
- if (value === '') {
204
- setHasGiftPack(false);
205
- setHasNote((prevstate) => ({ ...prevstate, state: false }));
206
- setNote('');
207
- setTextAreaCount(0);
208
- return;
209
- }
210
-
211
- if (key) {
212
- changeNoteTitle(value);
213
- setNote(value);
214
- setHasGiftPack(true);
215
- setTextAreaCount(value?.length);
216
- }
217
- }
218
- }, [basketItem.attributes]);
219
-
220
- useEffect(() => {
221
- const subscription = watch(({ message }) => {
222
- setNote(message);
223
- setTextAreaCount(message.length);
224
- });
225
-
226
- return () => subscription.unsubscribe();
227
- }, [watch]);
228
-
229
- return (
240
+ const defaultRender = () => (
230
241
  <div
231
242
  className={twMerge(
232
- clsx(
233
- 'mt-4 sm:mt-0',
234
- {
235
- 'sm:mt-8': hasNote.state
236
- },
237
- className
238
- )
243
+ clsx('mt-4 sm:mt-0', { 'sm:mt-8': hasNote.state }, className)
239
244
  )}
240
245
  >
241
246
  <div className="flex gap-4">
@@ -245,7 +250,6 @@ export const BasketGiftPack = ({
245
250
  size={15}
246
251
  className={clsx(hasGiftPack ? 'text-[#e85150]' : 'text-[#000000]')}
247
252
  />
248
-
249
253
  {hasGiftPack ? (
250
254
  <span className="text-[0.75rem]">
251
255
  {_translations.giftPackAdded}
@@ -261,36 +265,34 @@ export const BasketGiftPack = ({
261
265
  </Button>
262
266
  )}
263
267
  </div>
264
- <Button
265
- appearance="ghost"
266
- className={clsx(
267
- 'text-[#000000] underline cursor-pointer border-0 px-0 py-0 text-[0.75rem] h-auto hover:bg-transparent hover:text-[#000000]',
268
- {
269
- hidden: !hasGiftPack
270
- }
271
- )}
272
- onClick={() =>
273
- setHasNote((prevstate) => ({ ...prevstate, state: true }))
274
- }
275
- data-testid="add-basket-gift-pack-note"
276
- >
277
- {hasNote.title}
278
- </Button>
279
268
 
280
- <Button
281
- appearance="ghost"
282
- className={clsx(
283
- 'text-[#000000] cursor-pointer border-0 px-0 py-0 text-[0.75rem] underline h-auto hover:bg-transparent hover:text-[#000000]',
284
- {
285
- hidden: !hasGiftPack
286
- }
287
- )}
288
- onClick={removeGiftPack}
289
- data-testid="remove-basket-gift-pack"
290
- >
291
- {_translations.removeGiftPack}
292
- </Button>
269
+ {hasGiftPack && (
270
+ <>
271
+ <Button
272
+ appearance="ghost"
273
+ 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]"
274
+ onClick={() => setHasNote({ ...hasNote, state: true })}
275
+ data-testid="add-basket-gift-pack-note"
276
+ >
277
+ {hasNote.title}
278
+ </Button>
279
+ <Button
280
+ appearance="ghost"
281
+ className={clsx(
282
+ 'text-[#000000] cursor-pointer border-0 px-0 py-0 text-[0.75rem] underline h-auto hover:bg-transparent hover:text-[#000000]',
283
+ {
284
+ hidden: !hasGiftPack
285
+ }
286
+ )}
287
+ onClick={removeGiftPack}
288
+ data-testid="remove-basket-gift-pack"
289
+ >
290
+ {_translations.removeGiftPack}
291
+ </Button>
292
+ </>
293
+ )}
293
294
  </div>
295
+
294
296
  {useModal ? (
295
297
  <Modal
296
298
  portalId="basket-gift-pack-modal"
@@ -300,23 +302,30 @@ export const BasketGiftPack = ({
300
302
  modalClassName
301
303
  )}
302
304
  open={hasNote.state}
303
- setOpen={(state) =>
304
- setHasNote((prevstate) => ({ ...prevstate, state }))
305
- }
305
+ setOpen={(state) => setHasNote((prev) => ({ ...prev, state }))}
306
306
  >
307
307
  <div className={twMerge('px-6 py-4', modalContentClassName)}>
308
308
  {formContent}
309
309
  </div>
310
310
  </Modal>
311
- ) : (
312
- <div
313
- className={clsx('mt-4', {
314
- hidden: !hasNote.state
315
- })}
316
- >
317
- {formContent}
318
- </div>
319
- )}
311
+ ) : hasNote.state ? (
312
+ <div className="mt-4">{formContent}</div>
313
+ ) : null}
320
314
  </div>
321
315
  );
316
+
317
+ return customUIRender
318
+ ? customUIRender({
319
+ hasGiftPack,
320
+ hasNote,
321
+ note,
322
+ textAreaCount,
323
+ onAddGiftPack: addGiftPack,
324
+ onRemoveGiftPack: removeGiftPack,
325
+ onAddNote: () => setHasNote({ ...hasNote, state: true }),
326
+ onRemoveNote: removeNote,
327
+ formContent,
328
+ translations: _translations
329
+ })
330
+ : defaultRender();
322
331
  };