@akinon/pz-checkout-gift-pack 2.0.0-beta.1 → 2.0.0-beta.11
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 +41 -0
- package/README.md +253 -3
- package/package.json +1 -1
- package/src/index.tsx +190 -79
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @akinon/pz-checkout-gift-pack
|
|
2
2
|
|
|
3
|
+
## 2.0.0-beta.11
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ac783d6: ZERO-3482: Update tailwindcss to version 4.1.11 and enhance button cursor styles
|
|
8
|
+
|
|
9
|
+
## 2.0.0-beta.10
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 2806320: ZERO-3390: Update version tailwindcss, autoprefixer, tailwind-merge, postcss
|
|
14
|
+
|
|
15
|
+
## 2.0.0-beta.9
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- 0fe7711: ZERO-3387: Upgrade nextjs, eslint-config-next
|
|
20
|
+
|
|
21
|
+
## 2.0.0-beta.8
|
|
22
|
+
|
|
23
|
+
## 2.0.0-beta.7
|
|
24
|
+
|
|
25
|
+
## 2.0.0-beta.6
|
|
26
|
+
|
|
27
|
+
### Minor Changes
|
|
28
|
+
|
|
29
|
+
- 8f05f9b: ZERO-3250: Beta branch synchronized with Main branch
|
|
30
|
+
|
|
31
|
+
## 2.0.0-beta.5
|
|
32
|
+
|
|
33
|
+
## 2.0.0-beta.4
|
|
34
|
+
|
|
35
|
+
## 2.0.0-beta.3
|
|
36
|
+
|
|
37
|
+
## 2.0.0-beta.2
|
|
38
|
+
|
|
39
|
+
### Minor Changes
|
|
40
|
+
|
|
41
|
+
- a006015: ZERO-3116: Add not-found page and update default middleware.
|
|
42
|
+
- 1eeb3d8: ZERO-3116: Add not found page
|
|
43
|
+
|
|
3
44
|
## 2.0.0-beta.1
|
|
4
45
|
|
|
5
46
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# @akinon/pz-checkout-gift-package
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
You can use the following command to install the extension with the latest plugins:
|
|
4
6
|
|
|
5
7
|
```bash
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
npx @akinon/projectzero@latest --plugins
|
|
10
|
+
|
|
8
11
|
```
|
|
9
12
|
|
|
10
13
|
### Props
|
|
@@ -15,3 +18,250 @@ yarn add @akinon/pz-checkout-gift-package
|
|
|
15
18
|
| modalClassName | `string` | This prop is used to customize the overall style of the modal. |
|
|
16
19
|
| modalTitle | `string` | This prop sets the title of the modal. |
|
|
17
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
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 {
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
101
|
+
message: yup.string().max(maxNoteLength, _translations.warningMessage)
|
|
74
102
|
});
|
|
75
103
|
|
|
76
104
|
if (!hasGiftBox) {
|
|
@@ -95,86 +123,137 @@ 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:
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
174
|
'w-full border border-solid p-4 placeholder:text-[0.75rem] placeholder:text-[#9c9d9d] outline-none text-[0.75rem]',
|
|
124
|
-
|
|
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')}
|
|
183
|
+
data-testid="gift-note-input"
|
|
129
184
|
/>
|
|
130
185
|
{errors.message && (
|
|
131
|
-
<span
|
|
186
|
+
<span
|
|
187
|
+
className="text-sm text-[#d72a04] text-[0.875rem]"
|
|
188
|
+
data-testid="error-message"
|
|
189
|
+
>
|
|
132
190
|
{errors.message.message}
|
|
133
191
|
</span>
|
|
134
192
|
)}
|
|
135
|
-
<div className="
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
)}
|
|
140
214
|
<Button
|
|
141
|
-
|
|
142
|
-
className="
|
|
143
|
-
|
|
215
|
+
type="submit"
|
|
216
|
+
className="w-[7rem] h-[1.75rem] font-[0.75rem] uppercase"
|
|
217
|
+
data-testid="save-gift-pack-button"
|
|
144
218
|
>
|
|
145
|
-
{_translations.
|
|
219
|
+
{_translations.save}
|
|
146
220
|
</Button>
|
|
147
|
-
|
|
148
|
-
<Button
|
|
149
|
-
type="submit"
|
|
150
|
-
className="w-[7rem] h-[1.75rem] font-[0.75rem] uppercase"
|
|
151
|
-
appearance="outlined"
|
|
152
|
-
>
|
|
153
|
-
{_translations.save}
|
|
154
|
-
</Button>
|
|
221
|
+
</div>
|
|
155
222
|
</div>
|
|
156
223
|
</form>
|
|
157
224
|
);
|
|
158
225
|
|
|
159
226
|
useEffect(() => {
|
|
160
|
-
if (
|
|
161
|
-
setValue('message',
|
|
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);
|
|
162
231
|
}
|
|
163
|
-
}, [
|
|
232
|
+
}, [preOrder?.gift_box]);
|
|
233
|
+
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
const subscription = watch(({ message }) => {
|
|
236
|
+
setNote(message);
|
|
237
|
+
setTextAreaCount(message?.length || 0);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return () => subscription.unsubscribe();
|
|
241
|
+
}, [watch]);
|
|
164
242
|
|
|
165
|
-
|
|
243
|
+
const defaultRender = () => (
|
|
166
244
|
<div className={className}>
|
|
167
245
|
<div className="flex justify-between items-center py-2 px-5">
|
|
168
246
|
<div className="flex gap-2 items-center">
|
|
169
247
|
<Icon name="giftbox" size={16} className="fill-[#000000]" />
|
|
170
248
|
<span className="text-[1rem] font-light">
|
|
171
|
-
{
|
|
249
|
+
{preOrder?.gift_box ? (
|
|
172
250
|
_translations.giftPackAdded
|
|
173
251
|
) : (
|
|
174
252
|
<Button
|
|
175
253
|
appearance="ghost"
|
|
176
254
|
className="cursor-pointer border-0 px-0 py-0 text-[1rem] font-light h-auto hover:bg-transparent hover:text-[#000000]"
|
|
177
255
|
onClick={() => addGiftPack({ note: DEFAULT_NOTE })}
|
|
256
|
+
data-testid="add-gift-pack-button"
|
|
178
257
|
>
|
|
179
258
|
{_translations.addGiftPackText}
|
|
180
259
|
</Button>
|
|
@@ -186,13 +265,15 @@ export const CheckoutGiftPack = ({
|
|
|
186
265
|
className={clsx(
|
|
187
266
|
'text-[0.75rem] underline cursor-pointer border-0 px-0 py-0 h-auto hover:bg-transparent hover:text-[#e95151]',
|
|
188
267
|
{
|
|
189
|
-
hidden: !
|
|
268
|
+
hidden: !preOrder?.gift_box
|
|
190
269
|
}
|
|
191
270
|
)}
|
|
192
271
|
onClick={async () => {
|
|
193
272
|
await removeGiftPack().unwrap();
|
|
194
273
|
setIsNoteFormOpen(false);
|
|
274
|
+
setHasNote((prevstate) => ({ ...prevstate, state: false }));
|
|
195
275
|
}}
|
|
276
|
+
data-testid="remove-gift-pack-button"
|
|
196
277
|
>
|
|
197
278
|
{_translations.removeGiftPackText}
|
|
198
279
|
</Button>
|
|
@@ -201,20 +282,30 @@ export const CheckoutGiftPack = ({
|
|
|
201
282
|
className={clsx(
|
|
202
283
|
'flex flex-col gap-2 bg-[#f7f7f7] text-[#58585a] text-[0.875rem] py-2 px-5',
|
|
203
284
|
{
|
|
204
|
-
hidden: !
|
|
285
|
+
hidden: !preOrder?.gift_box
|
|
205
286
|
}
|
|
206
287
|
)}
|
|
207
288
|
>
|
|
208
289
|
<div className="flex justify-between items-center">
|
|
209
|
-
<span>
|
|
290
|
+
<span data-testid="information-text">
|
|
291
|
+
{_translations.informationText}
|
|
292
|
+
</span>
|
|
210
293
|
<Icon name="giftbox" size={14} className="fill-[#58585a]" />
|
|
211
294
|
</div>
|
|
212
295
|
<div className="flex justify-between items-center gap-8">
|
|
213
|
-
<span
|
|
296
|
+
<span data-testid="saved-gift-note">
|
|
297
|
+
{preOrder?.gift_box?.note !== DEFAULT_NOTE
|
|
298
|
+
? preOrder?.gift_box?.note
|
|
299
|
+
: ''}
|
|
300
|
+
</span>
|
|
214
301
|
<Button
|
|
215
302
|
appearance="ghost"
|
|
216
303
|
className="underline cursor-pointer flex-shrink-0 border-0 px-0 py-0 h-auto hover:bg-transparent hover:text-[#000000]"
|
|
217
|
-
onClick={() =>
|
|
304
|
+
onClick={() => {
|
|
305
|
+
setIsNoteFormOpen(true);
|
|
306
|
+
setHasNote((prevstate) => ({ ...prevstate, state: true }));
|
|
307
|
+
}}
|
|
308
|
+
data-testid="update-gift-pack-button"
|
|
218
309
|
>
|
|
219
310
|
{_translations.updateNote}
|
|
220
311
|
</Button>
|
|
@@ -229,15 +320,17 @@ export const CheckoutGiftPack = ({
|
|
|
229
320
|
'w-full sm:w-[28rem] max-h-[90vh] overflow-y-auto',
|
|
230
321
|
modalClassName
|
|
231
322
|
)}
|
|
232
|
-
open={
|
|
233
|
-
setOpen={
|
|
323
|
+
open={hasNote.state}
|
|
324
|
+
setOpen={(state) =>
|
|
325
|
+
setHasNote((prevstate) => ({ ...prevstate, state }))
|
|
326
|
+
}
|
|
234
327
|
>
|
|
235
328
|
<div className={twMerge('px-6 py-4', modalContentClassName)}>
|
|
236
329
|
{formContent}
|
|
237
330
|
</div>
|
|
238
331
|
</Modal>
|
|
239
332
|
) : (
|
|
240
|
-
<div className={clsx('py-2 px-5', { hidden: !
|
|
333
|
+
<div className={clsx('py-2 px-5', { hidden: !hasNote.state })}>
|
|
241
334
|
<Accordion
|
|
242
335
|
title={_translations.accordionTitle}
|
|
243
336
|
titleClassName="text-[0.75rem]"
|
|
@@ -249,4 +342,22 @@ export const CheckoutGiftPack = ({
|
|
|
249
342
|
)}
|
|
250
343
|
</div>
|
|
251
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();
|
|
252
363
|
};
|