@akinon/pz-checkout-gift-pack 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }
@@ -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
+ };