@akinon/pz-checkout-gift-pack 2.0.0-beta.10 → 2.0.0-beta.12

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,17 @@
1
1
  # @akinon/pz-checkout-gift-pack
2
2
 
3
+ ## 2.0.0-beta.12
4
+
5
+ ### Minor Changes
6
+
7
+ - 1d79e32: ZERO-3540: Next.js Upgrade to 15.4.5
8
+
9
+ ## 2.0.0-beta.11
10
+
11
+ ### Minor Changes
12
+
13
+ - ac783d6: ZERO-3482: Update tailwindcss to version 4.1.11 and enhance button cursor styles
14
+
3
15
  ## 2.0.0-beta.10
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -18,3 +18,250 @@ npx @akinon/projectzero@latest --plugins
18
18
  | modalClassName | `string` | This prop is used to customize the overall style of the modal. |
19
19
  | modalTitle | `string` | This prop sets the title of the modal. |
20
20
  | modalContentClassName | `string` | This prop is used to customize the style of the modal content. |
21
+ | maxNoteLength? | `number` | Sets the maximum number of characters allowed for the gift note. If not provided, a default value is used. |
22
+ | customUIRender | `(props: { hasGiftPack: boolean; hasNote: { state: boolean }; note: string; onAddGiftPack: () => void; onRemoveGiftPack: () => void; onAddNote: () => void; formContent: React.ReactNode; translations: Record<string, string> }) => React.ReactNode` | A custom render function to fully override the default gift pack UI. This gives complete control over the display and interactions. |
23
+ | customGiftNoteFormUIRender | `(props: { register: any; errors: any; note: string; textAreaCount: number; onSubmit: (data: any) => void; removeNote: () => void; handleSubmit: any; translations: Record<string, string> }) => React.ReactNode` | A render function that replaces the default gift note form UI. Useful for providing a customized textarea, button layout, or validation display. |
24
+
25
+ ### Customized sample code
26
+
27
+ ```js
28
+ <PluginModule
29
+ component={Component.CheckoutGiftPack}
30
+ props={{
31
+ className:
32
+ 'flex flex-col w-full mb-4 border border-solid border-gray-400',
33
+ translations: {
34
+ addGiftPackText: 'Add Gift Pack',
35
+ giftPackAdded: 'Gift Pack Added',
36
+ removeGiftPackText: 'Remove Gift Pack',
37
+ informationText: 'This order will be gift packaged*',
38
+ updateNote: 'Update Note',
39
+ removeGiftNoteText: 'Remove Gift Note',
40
+ charactersLength: 'characters left',
41
+ placeholderInput:
42
+ 'You can leave empty this area. However it will be a gift package without note.',
43
+ accordionTitle: 'Gift Note',
44
+ warningMessage:
45
+ 'Make sure that this field is not longer than the character limit.',
46
+ save: 'SAVE',
47
+ close: 'Close'
48
+ },
49
+ customUIRender: ({
50
+ hasGiftPack,
51
+ hasNote,
52
+ note,
53
+ onAddGiftPack,
54
+ onRemoveGiftPack,
55
+ onAddNote,
56
+ formContent,
57
+ translations
58
+ }) => (
59
+ <div className="custom-gift-pack">
60
+ {hasGiftPack ? (
61
+ <div className="gift-pack-added p-4 border border-gray-200">
62
+ <div className="flex justify-between items-center mb-4">
63
+ <span className="font-medium">
64
+ {translations.giftPackAdded}
65
+ </span>
66
+ <button
67
+ onClick={onRemoveGiftPack}
68
+ className="text-red-500 hover:text-red-700"
69
+ >
70
+ {translations.removeGiftPackText}
71
+ </button>
72
+ </div>
73
+
74
+ <div className="gift-note-info bg-gray-50 p-3 rounded">
75
+ <span className="text-sm text-gray-600">
76
+ {translations.informationText}
77
+ </span>
78
+ {note && <p className="mt-2 text-sm">{note}</p>}
79
+ <button
80
+ onClick={onAddNote}
81
+ className="mt-2 text-blue-500 hover:text-blue-700"
82
+ >
83
+ {translations.updateNote}
84
+ </button>
85
+ </div>
86
+
87
+ {hasNote.state && <div className="mt-4">{formContent}</div>}
88
+ </div>
89
+ ) : (
90
+ <button
91
+ onClick={onAddGiftPack}
92
+ className="w-full p-2 text-center border border-gray-200 hover:bg-gray-50"
93
+ >
94
+ {translations.addGiftPackText}
95
+ </button>
96
+ )}
97
+ </div>
98
+ ),
99
+ customGiftNoteFormUIRender: ({
100
+ register,
101
+ errors,
102
+ note,
103
+ textAreaCount,
104
+ onSubmit,
105
+ removeNote,
106
+ handleSubmit,
107
+ translations: formTranslations
108
+ }) => (
109
+ <form onSubmit={handleSubmit(onSubmit)}>
110
+ <textarea
111
+ {...register('message')}
112
+ value={note}
113
+ placeholder={formTranslations.placeholderInput}
114
+ />
115
+ {errors.message && <span>{errors.message.message}</span>}
116
+ <div className="character-count">
117
+ {textAreaCount}/160 {formTranslations.charactersLength}
118
+ </div>
119
+ <div className="buttons">
120
+ <button type="button" onClick={removeNote}>
121
+ {formTranslations.removeGiftNoteText}
122
+ </button>
123
+ <button type="submit">{formTranslations.save}</button>
124
+ </div>
125
+ </form>
126
+ )
127
+ }}
128
+ />
129
+ <div className="flex flex-col w-full border border-solid border-gray-400">
130
+ <div className="flex justify-between items-center flex-row border-b border-solid border-gray-400 px-4 py-2 sm:px-5 sm:py-4 sm:min-h-15">
131
+ <span className="text-black-800 text-xl font-light sm:text-2xl">
132
+ {t('checkout.summary.title')}
133
+ </span>
134
+ <span className="text-gray-950 text-xs">
135
+ {preOrder.basket.basketitem_set.length}{' '}
136
+ {t('checkout.summary.items').toUpperCase()}
137
+ </span>
138
+ </div>
139
+ <div className="border-b border-solid border-gray-400 max-h-64 overflow-y-auto">
140
+ {preOrder.basket.basketitem_set.map((item, index) => (
141
+ <div
142
+ key={`summary-basketitem-${index}`}
143
+ className="flex flex-row border-b border-solid border-gray-400 py-3 px-4 last:border-b-0 sm:px-5"
144
+ >
145
+ <Link
146
+ href={'#'}
147
+ className={twMerge(
148
+ 'min-w-max flex items-center justify-center',
149
+ isMobileApp && 'pointer-events-none'
150
+ )}
151
+ passHref
152
+ >
153
+ <Image
154
+ src={item.product.productimage_set[0]?.image}
155
+ alt="Checkout Summary Image"
156
+ width={64}
157
+ height={96}
158
+ />
159
+ </Link>
160
+ <div className="w-full flex flex-wrap justify-between pl-4">
161
+ <div className="flex justify-center flex-col w-1/2">
162
+ <Link
163
+ href={item.product.absolute_url}
164
+ className={twMerge(
165
+ 'text-xs text-black-800 transition-all mb-1 hover:text-secondary',
166
+ isMobileApp && 'pointer-events-none'
167
+ )}
168
+ >
169
+ {item.product.name}
170
+ </Link>
171
+ <div className="flex flex-col">
172
+ <div className="flex text-xs text-black-800">
173
+ <span>{t('checkout.summary.quantity')}:</span>
174
+ <span className="ml-1 min-w-max">{item.quantity}</span>
175
+ </div>
176
+ </div>
177
+ </div>
178
+ <div className="flex items-end justify-center flex-col w-1/2">
179
+ {item.product.retail_price !== item.product.price && (
180
+ <div className="text-xs text-black-800 line-through min-w-max sm:text-sm">
181
+ <Price value={item.product.retail_price} />
182
+ </div>
183
+ )}
184
+ <div className="text-xs text-secondary min-w-max sm:text-sm">
185
+ <Price value={item.product.price} />
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ ))}
191
+ </div>
192
+ <div className="pt-3">
193
+ <div className="flex items-center justify-between w-full text-xs text-black-800 py-1 px-4 sm:px-5">
194
+ <span>
195
+ {t('checkout.summary.subtotal')} (
196
+ {preOrder.basket.basketitem_set.length}{' '}
197
+ {t('checkout.summary.items')})
198
+ </span>
199
+ <span>
200
+ <Price value={preOrder?.basket?.total_amount} />
201
+ </span>
202
+ </div>
203
+ <div className="flex items-center justify-between w-full text-xs text-black-800 py-1 px-4 sm:px-5">
204
+ <span>{t('checkout.summary.shipping')}</span>
205
+ <span>
206
+ <Price value={preOrder?.shipping_amount} />
207
+ </span>
208
+ </div>
209
+ <div className="flex items-center justify-between w-full text-xs text-black-800 py-1 px-4 sm:px-5">
210
+ <span>{t('checkout.summary.discounts_total')}</span>
211
+ <span>
212
+ <Price
213
+ value={preOrder?.basket?.total_discount_amount}
214
+ useNegative
215
+ />
216
+ </span>
217
+ </div>
218
+ <div className="flex items-center justify-between w-full text-black-800 px-4 sm:px-5 text-lg border-t border-solid border-gray-400 py-2 mt-3 sm:text-xl sm:py-3">
219
+ <span className="font-light">{t('checkout.summary.total')}</span>
220
+ <span className="min-w-max pl-4">
221
+ <Price value={preOrder?.unpaid_amount} />
222
+ </span>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ {currentStep === CheckoutStep.Payment && (
227
+ <div className="flex flex-col w-full border border-solid border-gray-400 mt-4">
228
+ <div className="flex justify-between items-center flex-row border-b border-solid border-gray-400 px-4 py-2 sm:px-5 sm:py-3 sm:min-h-15">
229
+ <div className="text-black-800 text-xl font-light sm:text-2xl">
230
+ {t('checkout.summary.delivery_info')}
231
+ </div>
232
+ <div
233
+ className="text-xs text-black-800 italic cursor-pointer underline transition-all hover:text-secondary"
234
+ onClick={() => dispatch(setCurrentStep(CheckoutStep.Shipping))}
235
+ >
236
+ {t('checkout.summary.change')}
237
+ </div>
238
+ </div>
239
+ <div className="flex flex-col py-4 px-4 text-black-800 text-xs sm:px-5">
240
+ <div className="w-full overflow-hidden overflow-ellipsis mb-1 last:mb-0">
241
+ <Trans
242
+ i18nKey="checkout.summary.info"
243
+ components={{
244
+ ShippingAddress: (
245
+ <Text title={preOrder.shipping_address?.title} />
246
+ ),
247
+ ShippingOption: (
248
+ <Text title={preOrder.shipping_option?.name} />
249
+ )
250
+ }}
251
+ />
252
+ </div>
253
+ <div className="w-full overflow-hidden overflow-ellipsis mb-1 last:mb-0">
254
+ {preOrder.shipping_address?.line}{' '}
255
+ {preOrder.shipping_address?.postcode}{' '}
256
+ {preOrder.shipping_address?.district && (
257
+ <>{preOrder.shipping_address?.district.name} / </>
258
+ )}
259
+ {preOrder.shipping_address?.township && (
260
+ <>{preOrder.shipping_address?.township.name} / </>
261
+ )}
262
+ {preOrder.shipping_address?.city?.name}
263
+ </div>
264
+ </div>
265
+ </div>
266
+ )}
267
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/pz-checkout-gift-pack",
3
- "version": "2.0.0-beta.10",
3
+ "version": "2.0.0-beta.12",
4
4
  "license": "MIT",
5
5
  "main": "src/index.tsx",
6
6
  "peerDependencies": {
package/src/index.tsx CHANGED
@@ -1,37 +1,74 @@
1
+ import React from 'react';
1
2
  import { useAppSelector } from '@akinon/next/redux/hooks';
2
3
  import { yupResolver } from '@hookform/resolvers/yup';
3
4
  import clsx from 'clsx';
4
5
  import { twMerge } from 'tailwind-merge';
5
6
  import { Accordion, Button, Icon, Modal } from '@akinon/next/components';
6
7
  import { useEffect, useState } from 'react';
7
- import { SubmitHandler, useForm } from 'react-hook-form';
8
+ import {
9
+ SubmitHandler,
10
+ useForm,
11
+ UseFormRegister,
12
+ FieldErrors,
13
+ UseFormHandleSubmit
14
+ } from 'react-hook-form';
8
15
  import { RootState } from 'redux/store';
9
16
  import * as yup from 'yup';
10
17
  import { useAddGiftPackMutation, useRemoveGiftPackMutation } from './endpoints';
11
18
 
19
+ const defaultTranslations = {
20
+ addGiftPackText: 'Add Gift Pack',
21
+ giftPackAdded: 'Gift Pack Added',
22
+ removeGiftPackText: 'Remove Gift Pack',
23
+ informationText: 'This order will be gift packaged*',
24
+ updateNote: 'Update Note',
25
+ removeGiftNoteText: 'Remove Gift Note',
26
+ charactersLength: 'characters left',
27
+ placeholderInput:
28
+ 'You can leave empty this area. However it will be a gift package without note.',
29
+ accordionTitle: 'Gift Note',
30
+ warningMessage:
31
+ 'Make sure that this field is not longer than the character limit.',
32
+ save: 'SAVE',
33
+ close: 'Close'
34
+ };
35
+
12
36
  interface GiftPackForm {
13
37
  message: string;
14
38
  }
15
39
 
40
+ interface CustomUIRenderParams {
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
+ }
52
+
16
53
  interface CheckoutGiftPackProps {
17
54
  className?: string;
18
55
  useModal?: boolean;
19
56
  modalClassName?: string;
20
57
  modalTitle?: string;
58
+ maxNoteLength?: number;
21
59
  modalContentClassName?: string;
22
- translations: {
23
- addGiftPackText: string;
24
- giftPackAdded: string;
25
- removeGiftPackText: string;
26
- informationText: string;
27
- updateNote: string;
28
- removeGiftNoteText: string;
29
- charactersLength: string;
30
- placeholderInput: string;
31
- accordionTitle: string;
32
- warningMessage: string;
33
- save: string;
34
- };
60
+ translations?: Partial<typeof defaultTranslations>;
61
+ customUIRender?: (params: CustomUIRenderParams) => React.ReactNode;
62
+ customGiftNoteFormUIRender?: (params: {
63
+ register: UseFormRegister<GiftPackForm>;
64
+ errors: FieldErrors<GiftPackForm>;
65
+ note: string;
66
+ textAreaCount: number;
67
+ onSubmit: () => void;
68
+ removeNote: () => void;
69
+ handleSubmit: UseFormHandleSubmit<GiftPackForm>;
70
+ translations: typeof defaultTranslations;
71
+ }) => React.ReactNode;
35
72
  }
36
73
 
37
74
  export const CheckoutGiftPack = ({
@@ -40,37 +77,28 @@ export const CheckoutGiftPack = ({
40
77
  modalClassName,
41
78
  modalContentClassName,
42
79
  modalTitle = 'Gift Note',
43
- translations
80
+ maxNoteLength = 160,
81
+ translations,
82
+ customUIRender,
83
+ customGiftNoteFormUIRender
44
84
  }: CheckoutGiftPackProps) => {
45
85
  const [isNoteFormOpen, setIsNoteFormOpen] = useState(false);
86
+ const [hasNote, setHasNote] = useState({
87
+ title: translations?.updateNote || defaultTranslations.updateNote,
88
+ state: false
89
+ });
90
+ const [textAreaCount, setTextAreaCount] = useState(0);
91
+ const [note, setNote] = useState('');
46
92
 
47
93
  const { preOrder, hasGiftBox } = useAppSelector(
48
94
  (state: RootState) => state.checkout
49
95
  );
50
96
 
51
- const defaultTranslations = {
52
- addGiftPackText: 'Add Gift Pack',
53
- giftPackAdded: 'Gift Pack Added',
54
- removeGiftPackText: 'Remove Gift Pack',
55
- informationText: 'This order will be gift packaged*',
56
- updateNote: 'Update Note',
57
- removeGiftNoteText: 'Remove Gift Note',
58
- charactersLength: 'characters left',
59
- placeholderInput:
60
- 'You can leave empty this area. However it will be a gift package without note.',
61
- accordionTitle: 'Gift Note',
62
- warningMessage: 'Ensure this field has no more than 160 characters.',
63
- save: 'SAVE'
64
- };
65
-
66
- const _translations = {
67
- ...defaultTranslations,
68
- ...translations
69
- };
97
+ const _translations = { ...defaultTranslations, ...translations };
70
98
 
71
99
  const giftPackFormSchema = () =>
72
100
  yup.object().shape({
73
- message: yup.string().max(160, _translations.warningMessage)
101
+ message: yup.string().max(maxNoteLength, _translations.warningMessage)
74
102
  });
75
103
 
76
104
  if (!hasGiftBox) {
@@ -95,35 +123,61 @@ export const CheckoutGiftPack = ({
95
123
  resolver: yupResolver(giftPackFormSchema())
96
124
  });
97
125
 
98
- const subscribeNote = watch('message');
99
- const noteLength = subscribeNote?.length ?? 0;
100
- const giftBox = preOrder?.gift_box;
101
-
102
126
  const onSubmit: SubmitHandler<GiftPackForm> = async (data) => {
103
127
  await addGiftPack({
104
- note: noteLength > 0 ? data.message : DEFAULT_NOTE
128
+ note: textAreaCount > 0 ? data.message : DEFAULT_NOTE
105
129
  }).unwrap();
130
+
106
131
  setIsNoteFormOpen(false);
132
+ setHasNote((prevstate) => ({ ...prevstate, state: false }));
107
133
  };
108
134
 
109
135
  const removeGiftNote = async () => {
110
136
  await addGiftPack({ note: DEFAULT_NOTE }).unwrap();
111
137
  resetField('message');
138
+ setNote('');
139
+ setTextAreaCount(0);
112
140
  };
113
141
 
114
- const formContent = (
115
- <form
116
- className={clsx({
117
- hidden: !isNoteFormOpen
118
- })}
119
- onSubmit={handleSubmit(onSubmit)}
120
- >
142
+ const formContent = customGiftNoteFormUIRender ? (
143
+ customGiftNoteFormUIRender({
144
+ register,
145
+ errors,
146
+ note,
147
+ textAreaCount,
148
+ onSubmit: () => handleSubmit(onSubmit)(),
149
+ removeNote: removeGiftNote,
150
+ handleSubmit,
151
+ translations: _translations
152
+ })
153
+ ) : (
154
+ <form onSubmit={handleSubmit(onSubmit)}>
155
+ {!useModal && (
156
+ <div className="flex justify-between mb-3">
157
+ <span className="text-[0.75rem] font-bold">
158
+ {_translations.accordionTitle}
159
+ </span>
160
+ <Button
161
+ appearance="ghost"
162
+ 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]"
163
+ onClick={(e) => {
164
+ e.preventDefault();
165
+ setHasNote((prevstate) => ({ ...prevstate, state: false }));
166
+ }}
167
+ >
168
+ {_translations.close}
169
+ </Button>
170
+ </div>
171
+ )}
121
172
  <textarea
122
173
  className={clsx(
123
- 'w-full border border-solid p-4 placeholder:text-[0.75rem] placeholder:text-[#9c9d9d] outline-hidden text-[0.75rem]',
124
- noteLength > 160 ? 'border-[#e85150]' : 'border-[#7b9d75]'
174
+ 'w-full border border-solid p-4 placeholder:text-[0.75rem] placeholder:text-[#9c9d9d] outline-none text-[0.75rem]',
175
+ textAreaCount > maxNoteLength
176
+ ? 'border-[#e85150]'
177
+ : 'border-[#7b9d75]'
125
178
  )}
126
179
  rows={4}
180
+ value={note !== DEFAULT_NOTE ? note : ' '}
127
181
  placeholder={_translations.placeholderInput}
128
182
  {...register('message')}
129
183
  data-testid="gift-note-input"
@@ -136,45 +190,63 @@ export const CheckoutGiftPack = ({
136
190
  {errors.message.message}
137
191
  </span>
138
192
  )}
139
- <div className="text-[0.75rem]" data-testid="characters-length">
140
- {noteLength} / 160 {_translations.charactersLength}
141
- </div>
142
- <div className="flex justify-end items-end gap-2">
143
- {giftBox?.note !== DEFAULT_NOTE && (
193
+ <div className="flex justify-between items-center mt-2">
194
+ <span
195
+ className={clsx(
196
+ 'text-[0.75rem]',
197
+ textAreaCount > maxNoteLength ? 'text-[#e85150]' : 'text-[#82a27c]'
198
+ )}
199
+ data-testid="characters-length"
200
+ >
201
+ {textAreaCount}/{maxNoteLength} {_translations.charactersLength}
202
+ </span>
203
+ <div className="flex items-center gap-3">
204
+ {preOrder?.gift_box?.note !== DEFAULT_NOTE && (
205
+ <Button
206
+ appearance="ghost"
207
+ className="text-[0.75rem] underline cursor-pointer border-0 px-0 py-0 h-auto hover:bg-transparent hover:text-[#e95151]"
208
+ onClick={removeGiftNote}
209
+ data-testid="remove-gift-note-button"
210
+ >
211
+ {_translations.removeGiftNoteText}
212
+ </Button>
213
+ )}
144
214
  <Button
145
- appearance="ghost"
146
- className="text-[0.75rem] underline cursor-pointer border-0 px-0 py-0 h-auto hover:bg-transparent hover:text-[#e95151]"
147
- onClick={removeGiftNote}
148
- data-testid="remove-gift-note-button"
215
+ type="submit"
216
+ className="w-[7rem] h-[1.75rem] font-[0.75rem] uppercase"
217
+ data-testid="save-gift-pack-button"
149
218
  >
150
- {_translations.removeGiftNoteText}
219
+ {_translations.save}
151
220
  </Button>
152
- )}
153
- <Button
154
- type="submit"
155
- className="w-[7rem] h-[1.75rem] font-[0.75rem] uppercase"
156
- appearance="outlined"
157
- data-testid="save-gift-pack-button"
158
- >
159
- {_translations.save}
160
- </Button>
221
+ </div>
161
222
  </div>
162
223
  </form>
163
224
  );
164
225
 
165
226
  useEffect(() => {
166
- if (giftBox?.note !== DEFAULT_NOTE) {
167
- setValue('message', giftBox?.note);
227
+ if (preOrder?.gift_box?.note !== DEFAULT_NOTE) {
228
+ setValue('message', preOrder?.gift_box?.note);
229
+ setNote(preOrder?.gift_box?.note);
230
+ setTextAreaCount(preOrder?.gift_box?.note?.length || 0);
168
231
  }
169
- }, [giftBox]);
232
+ }, [preOrder?.gift_box]);
170
233
 
171
- return (
234
+ useEffect(() => {
235
+ const subscription = watch(({ message }) => {
236
+ setNote(message);
237
+ setTextAreaCount(message?.length || 0);
238
+ });
239
+
240
+ return () => subscription.unsubscribe();
241
+ }, [watch]);
242
+
243
+ const defaultRender = () => (
172
244
  <div className={className}>
173
245
  <div className="flex justify-between items-center py-2 px-5">
174
246
  <div className="flex gap-2 items-center">
175
247
  <Icon name="giftbox" size={16} className="fill-[#000000]" />
176
248
  <span className="text-[1rem] font-light">
177
- {giftBox ? (
249
+ {preOrder?.gift_box ? (
178
250
  _translations.giftPackAdded
179
251
  ) : (
180
252
  <Button
@@ -193,12 +265,13 @@ export const CheckoutGiftPack = ({
193
265
  className={clsx(
194
266
  'text-[0.75rem] underline cursor-pointer border-0 px-0 py-0 h-auto hover:bg-transparent hover:text-[#e95151]',
195
267
  {
196
- hidden: !giftBox
268
+ hidden: !preOrder?.gift_box
197
269
  }
198
270
  )}
199
271
  onClick={async () => {
200
272
  await removeGiftPack().unwrap();
201
273
  setIsNoteFormOpen(false);
274
+ setHasNote((prevstate) => ({ ...prevstate, state: false }));
202
275
  }}
203
276
  data-testid="remove-gift-pack-button"
204
277
  >
@@ -209,7 +282,7 @@ export const CheckoutGiftPack = ({
209
282
  className={clsx(
210
283
  'flex flex-col gap-2 bg-[#f7f7f7] text-[#58585a] text-[0.875rem] py-2 px-5',
211
284
  {
212
- hidden: !giftBox
285
+ hidden: !preOrder?.gift_box
213
286
  }
214
287
  )}
215
288
  >
@@ -221,12 +294,17 @@ export const CheckoutGiftPack = ({
221
294
  </div>
222
295
  <div className="flex justify-between items-center gap-8">
223
296
  <span data-testid="saved-gift-note">
224
- {giftBox?.note !== DEFAULT_NOTE ? giftBox?.note : ''}
297
+ {preOrder?.gift_box?.note !== DEFAULT_NOTE
298
+ ? preOrder?.gift_box?.note
299
+ : ''}
225
300
  </span>
226
301
  <Button
227
302
  appearance="ghost"
228
- className="underline cursor-pointer shrink-0 border-0 px-0 py-0 h-auto hover:bg-transparent hover:text-[#000000]"
229
- onClick={() => setIsNoteFormOpen(true)}
303
+ className="underline cursor-pointer flex-shrink-0 border-0 px-0 py-0 h-auto hover:bg-transparent hover:text-[#000000]"
304
+ onClick={() => {
305
+ setIsNoteFormOpen(true);
306
+ setHasNote((prevstate) => ({ ...prevstate, state: true }));
307
+ }}
230
308
  data-testid="update-gift-pack-button"
231
309
  >
232
310
  {_translations.updateNote}
@@ -242,15 +320,17 @@ export const CheckoutGiftPack = ({
242
320
  'w-full sm:w-[28rem] max-h-[90vh] overflow-y-auto',
243
321
  modalClassName
244
322
  )}
245
- open={isNoteFormOpen}
246
- setOpen={setIsNoteFormOpen}
323
+ open={hasNote.state}
324
+ setOpen={(state) =>
325
+ setHasNote((prevstate) => ({ ...prevstate, state }))
326
+ }
247
327
  >
248
328
  <div className={twMerge('px-6 py-4', modalContentClassName)}>
249
329
  {formContent}
250
330
  </div>
251
331
  </Modal>
252
332
  ) : (
253
- <div className={clsx('py-2 px-5', { hidden: !isNoteFormOpen })}>
333
+ <div className={clsx('py-2 px-5', { hidden: !hasNote.state })}>
254
334
  <Accordion
255
335
  title={_translations.accordionTitle}
256
336
  titleClassName="text-[0.75rem]"
@@ -262,4 +342,22 @@ export const CheckoutGiftPack = ({
262
342
  )}
263
343
  </div>
264
344
  );
345
+
346
+ return customUIRender
347
+ ? customUIRender({
348
+ hasGiftPack: !!preOrder?.gift_box,
349
+ hasNote,
350
+ note,
351
+ textAreaCount,
352
+ onAddGiftPack: () => addGiftPack({ note: DEFAULT_NOTE }),
353
+ onRemoveGiftPack: () => removeGiftPack().unwrap(),
354
+ onAddNote: () => {
355
+ setIsNoteFormOpen(true);
356
+ setHasNote((prevstate) => ({ ...prevstate, state: true }));
357
+ },
358
+ onRemoveNote: removeGiftNote,
359
+ formContent,
360
+ translations: _translations
361
+ })
362
+ : defaultRender();
265
363
  };