@akinon/pz-checkout-gift-pack 1.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/.gitattributes +15 -0
- package/.prettierrc +13 -0
- package/README.md +29 -0
- package/package.json +18 -0
- package/src/endpoints.ts +39 -0
- package/src/index.tsx +219 -0
package/.gitattributes
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
*.js text eol=lf
|
|
2
|
+
*.jsx text eol=lf
|
|
3
|
+
*.ts text eol=lf
|
|
4
|
+
*.tsx text eol=lf
|
|
5
|
+
*.json text eol=lf
|
|
6
|
+
*.md text eol=lf
|
|
7
|
+
|
|
8
|
+
.eslintignore text eol=lf
|
|
9
|
+
.eslintrc text eol=lf
|
|
10
|
+
.gitignore text eol=lf
|
|
11
|
+
.prettierrc text eol=lf
|
|
12
|
+
.yarnrc text eol=lf
|
|
13
|
+
|
|
14
|
+
* text=auto
|
|
15
|
+
|
package/.prettierrc
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bracketSameLine": false,
|
|
3
|
+
"tabWidth": 2,
|
|
4
|
+
"singleQuote": true,
|
|
5
|
+
"jsxSingleQuote": false,
|
|
6
|
+
"bracketSpacing": true,
|
|
7
|
+
"semi": true,
|
|
8
|
+
"useTabs": false,
|
|
9
|
+
"arrowParens": "always",
|
|
10
|
+
"endOfLine": "lf",
|
|
11
|
+
"proseWrap": "never",
|
|
12
|
+
"trailingComma": "none"
|
|
13
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# pz-checkout-gift-package
|
|
2
|
+
|
|
3
|
+
### Install the npm package
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# For latest version
|
|
7
|
+
yarn add git+ssh://git@bitbucket.org:akinonteam/pz-checkout-gift-package.git
|
|
8
|
+
|
|
9
|
+
# For specific version
|
|
10
|
+
yarn add git+ssh://git@bitbucket.org:akinonteam/pz-checkout-gift-package.git#445a9da
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Next Config Configuration
|
|
14
|
+
|
|
15
|
+
##### apps/web/next.config.js**
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
const withTM = require("next-transpile-modules")(["pz-checkout-gift-package"]);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Example Usage
|
|
22
|
+
##### File Path: src/views/checkout/summary.tsx
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
import { CheckoutGiftPackage } from 'pz-checkout-gift-package';
|
|
27
|
+
|
|
28
|
+
<CheckoutGiftPackage />
|
|
29
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@akinon/pz-checkout-gift-pack",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"main": "src/index.tsx",
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"react": "^18.0.0",
|
|
8
|
+
"react-dom": "^18.0.0"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/node": "^18.7.8",
|
|
12
|
+
"@types/react": "^18.0.17",
|
|
13
|
+
"@types/react-dom": "^18.0.6",
|
|
14
|
+
"react": "^18.2.0",
|
|
15
|
+
"react-dom": "^18.2.0",
|
|
16
|
+
"typescript": "^4.7.4"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/endpoints.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CheckoutContext, PreOrder } from '@akinon/next/types';
|
|
2
|
+
import { buildClientRequestUrl } from '@akinon/next/utils';
|
|
3
|
+
import { api } from '@akinon/next/data/client/api';
|
|
4
|
+
|
|
5
|
+
interface CheckoutResponse {
|
|
6
|
+
pre_order?: PreOrder;
|
|
7
|
+
errors: {
|
|
8
|
+
non_field_errors: string;
|
|
9
|
+
};
|
|
10
|
+
context_list?: CheckoutContext[];
|
|
11
|
+
template_name?: string;
|
|
12
|
+
redirect_url?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const checkoutApi = api.injectEndpoints({
|
|
16
|
+
endpoints: (build) => ({
|
|
17
|
+
addGiftPack: build.mutation<CheckoutResponse, { note: string }>({
|
|
18
|
+
query: ({ note }) => ({
|
|
19
|
+
url: buildClientRequestUrl('/orders/checkout?page=GiftBoxPage', {
|
|
20
|
+
useFormData: true
|
|
21
|
+
}),
|
|
22
|
+
method: 'POST',
|
|
23
|
+
body: {
|
|
24
|
+
note
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}),
|
|
28
|
+
removeGiftPack: build.mutation<CheckoutResponse, void>({
|
|
29
|
+
query: () => ({
|
|
30
|
+
url: buildClientRequestUrl('/orders/checkout?page=GiftBoxIndexPage'),
|
|
31
|
+
method: 'POST'
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
}),
|
|
35
|
+
overrideExisting: false
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const { useAddGiftPackMutation, useRemoveGiftPackMutation } =
|
|
39
|
+
checkoutApi;
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { useAppSelector } from '@akinon/next/redux/hooks';
|
|
2
|
+
import { yupResolver } from '@hookform/resolvers/yup';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { Accordion, Button, Icon } from '@akinon/next/components';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { SubmitHandler, useForm } from 'react-hook-form';
|
|
7
|
+
import { RootState } from 'redux/store';
|
|
8
|
+
import * as yup from 'yup';
|
|
9
|
+
import { useAddGiftPackMutation, useRemoveGiftPackMutation } from './endpoints';
|
|
10
|
+
|
|
11
|
+
interface GiftPackForm {
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface CheckoutGiftPackProps {
|
|
16
|
+
className?: string;
|
|
17
|
+
translations: {
|
|
18
|
+
addGiftPackText: string;
|
|
19
|
+
giftPackAdded: string;
|
|
20
|
+
removeGiftPackText: string;
|
|
21
|
+
informationText: string;
|
|
22
|
+
updateNote: string;
|
|
23
|
+
removeGiftNoteText: string;
|
|
24
|
+
charactersLength: string;
|
|
25
|
+
placeholderInput: string;
|
|
26
|
+
accordionTitle: string;
|
|
27
|
+
warningMessage: string;
|
|
28
|
+
save: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const CheckoutGiftPack = ({
|
|
33
|
+
className,
|
|
34
|
+
translations
|
|
35
|
+
}: CheckoutGiftPackProps) => {
|
|
36
|
+
const [isNoteFormOpen, setIsNoteFormOpen] = useState(false);
|
|
37
|
+
|
|
38
|
+
const { preOrder, hasGiftBox } = useAppSelector(
|
|
39
|
+
(state: RootState) => state.checkout
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const defaultTranslations = {
|
|
43
|
+
addGiftPackText: 'Add Gift Pack',
|
|
44
|
+
giftPackAdded: 'Gift Pack Added',
|
|
45
|
+
removeGiftPackText: 'Remove Gift Pack',
|
|
46
|
+
informationText: 'This order will be gift packaged*',
|
|
47
|
+
updateNote: 'Update Note',
|
|
48
|
+
removeGiftNoteText: 'Remove Gift Note',
|
|
49
|
+
charactersLength: 'characters left',
|
|
50
|
+
placeholderInput:
|
|
51
|
+
'You can leave empty this area. However it will be a gift package without note.',
|
|
52
|
+
accordionTitle: 'Gift Note',
|
|
53
|
+
warningMessage: 'Ensure this field has no more than 160 characters.',
|
|
54
|
+
save: 'SAVE'
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const _translations = {
|
|
58
|
+
...defaultTranslations,
|
|
59
|
+
...translations
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const giftPackFormSchema = () =>
|
|
63
|
+
yup.object().shape({
|
|
64
|
+
message: yup.string().max(160, _translations.warningMessage)
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!hasGiftBox) {
|
|
68
|
+
console.warn(
|
|
69
|
+
'Gift box for checkout is disabled. Please enable it from Omnitron.'
|
|
70
|
+
);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const [addGiftPack] = useAddGiftPackMutation();
|
|
75
|
+
const [removeGiftPack] = useRemoveGiftPackMutation();
|
|
76
|
+
const DEFAULT_NOTE = 'NO-GIFT-MESSAGE';
|
|
77
|
+
|
|
78
|
+
const {
|
|
79
|
+
register,
|
|
80
|
+
watch,
|
|
81
|
+
handleSubmit,
|
|
82
|
+
resetField,
|
|
83
|
+
setValue,
|
|
84
|
+
formState: { errors }
|
|
85
|
+
} = useForm<GiftPackForm>({
|
|
86
|
+
resolver: yupResolver(giftPackFormSchema())
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const subscribeNote = watch('message');
|
|
90
|
+
const noteLength = subscribeNote?.length ?? 0;
|
|
91
|
+
const giftBox = preOrder?.gift_box;
|
|
92
|
+
|
|
93
|
+
const onSubmit: SubmitHandler<GiftPackForm> = async (data) => {
|
|
94
|
+
await addGiftPack({
|
|
95
|
+
note: noteLength > 0 ? data.message : DEFAULT_NOTE
|
|
96
|
+
}).unwrap();
|
|
97
|
+
setIsNoteFormOpen(false);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const removeGiftNote = async () => {
|
|
101
|
+
await addGiftPack({ note: DEFAULT_NOTE }).unwrap();
|
|
102
|
+
resetField('message');
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (giftBox?.note !== DEFAULT_NOTE) {
|
|
107
|
+
setValue('message', giftBox?.note);
|
|
108
|
+
}
|
|
109
|
+
}, [giftBox]);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className={className}>
|
|
113
|
+
<div className="flex justify-between items-center py-2 px-5">
|
|
114
|
+
<div className="flex gap-2 items-center">
|
|
115
|
+
<Icon name="giftbox" size={16} className="fill-[#000000]" />
|
|
116
|
+
<span className="text-[1rem] font-light">
|
|
117
|
+
{giftBox ? (
|
|
118
|
+
_translations.giftPackAdded
|
|
119
|
+
) : (
|
|
120
|
+
<Button
|
|
121
|
+
appearance="ghost"
|
|
122
|
+
className="cursor-pointer border-0 px-0 py-0 text-[1rem] font-light h-auto hover:bg-transparent hover:text-[#000000]"
|
|
123
|
+
onClick={() => addGiftPack({ note: DEFAULT_NOTE })}
|
|
124
|
+
>
|
|
125
|
+
{_translations.addGiftPackText}
|
|
126
|
+
</Button>
|
|
127
|
+
)}
|
|
128
|
+
</span>
|
|
129
|
+
</div>
|
|
130
|
+
<Button
|
|
131
|
+
appearance="ghost"
|
|
132
|
+
className={clsx(
|
|
133
|
+
'text-[0.75rem] underline cursor-pointer border-0 px-0 py-0 h-auto hover:bg-transparent hover:text-[#e95151]',
|
|
134
|
+
{
|
|
135
|
+
hidden: !giftBox
|
|
136
|
+
}
|
|
137
|
+
)}
|
|
138
|
+
onClick={async () => {
|
|
139
|
+
await removeGiftPack().unwrap();
|
|
140
|
+
setIsNoteFormOpen(false);
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{_translations.removeGiftPackText}
|
|
144
|
+
</Button>
|
|
145
|
+
</div>
|
|
146
|
+
<div
|
|
147
|
+
className={clsx(
|
|
148
|
+
'flex flex-col gap-2 bg-[#f7f7f7] text-[#58585a] text-[0.875rem] py-2 px-5',
|
|
149
|
+
{
|
|
150
|
+
hidden: !giftBox
|
|
151
|
+
}
|
|
152
|
+
)}
|
|
153
|
+
>
|
|
154
|
+
<div className="flex justify-between items-center">
|
|
155
|
+
<span>{_translations.informationText}</span>
|
|
156
|
+
<Icon name="giftbox" size={14} className="fill-[#58585a]" />
|
|
157
|
+
</div>
|
|
158
|
+
<div className="flex justify-between items-center gap-8">
|
|
159
|
+
<span>{giftBox?.note !== DEFAULT_NOTE ? giftBox?.note : ''}</span>
|
|
160
|
+
<Button
|
|
161
|
+
appearance="ghost"
|
|
162
|
+
className="underline cursor-pointer flex-shrink-0 border-0 px-0 py-0 h-auto hover:bg-transparent hover:text-[#000000]"
|
|
163
|
+
onClick={() => setIsNoteFormOpen(true)}
|
|
164
|
+
>
|
|
165
|
+
{_translations.updateNote}
|
|
166
|
+
</Button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
<form
|
|
170
|
+
className={clsx('py-2 px-5', {
|
|
171
|
+
hidden: !isNoteFormOpen
|
|
172
|
+
})}
|
|
173
|
+
onSubmit={handleSubmit(onSubmit)}
|
|
174
|
+
>
|
|
175
|
+
<Accordion
|
|
176
|
+
title={_translations.accordionTitle}
|
|
177
|
+
titleClassName="text-[0.75rem]"
|
|
178
|
+
isCollapse={true}
|
|
179
|
+
>
|
|
180
|
+
<textarea
|
|
181
|
+
className={clsx(
|
|
182
|
+
'w-full border border-solid p-4 placeholder:text-[0.75rem] placeholder:text-[#9c9d9d] outline-none text-[0.75rem]',
|
|
183
|
+
noteLength > 160 ? 'border-[#e85150]' : 'border-[#7b9d75]'
|
|
184
|
+
)}
|
|
185
|
+
rows={4}
|
|
186
|
+
placeholder={_translations.placeholderInput}
|
|
187
|
+
{...register('message')}
|
|
188
|
+
/>
|
|
189
|
+
{errors.message && (
|
|
190
|
+
<span className="text-sm text-[#d72a04] text-[0.875rem]">
|
|
191
|
+
{errors.message.message}
|
|
192
|
+
</span>
|
|
193
|
+
)}
|
|
194
|
+
<div className="text-[0.75rem]">
|
|
195
|
+
{noteLength} / 160 {_translations.charactersLength}
|
|
196
|
+
</div>
|
|
197
|
+
<div className="flex justify-end items-end gap-2">
|
|
198
|
+
{giftBox?.note !== DEFAULT_NOTE && (
|
|
199
|
+
<Button
|
|
200
|
+
appearance="ghost"
|
|
201
|
+
className="text-[0.75rem] underline cursor-pointer border-0 px-0 py-0 h-auto hover:bg-transparent hover:text-[#e95151]"
|
|
202
|
+
onClick={removeGiftNote}
|
|
203
|
+
>
|
|
204
|
+
{_translations.removeGiftNoteText}
|
|
205
|
+
</Button>
|
|
206
|
+
)}
|
|
207
|
+
<Button
|
|
208
|
+
type="submit"
|
|
209
|
+
className="w-[7rem] h-[1.75rem] font-[0.75rem] uppercase"
|
|
210
|
+
appearance="outlined"
|
|
211
|
+
>
|
|
212
|
+
{_translations.save}
|
|
213
|
+
</Button>
|
|
214
|
+
</div>
|
|
215
|
+
</Accordion>
|
|
216
|
+
</form>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
};
|