@akinon/pz-basket-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,35 @@
1
+ # pz-basket-gift-pack
2
+
3
+ ### Install the npm package
4
+
5
+ ```bash
6
+ # For latest version
7
+ yarn add ​git+ssh://git@bitbucket.org:akinonteam/pz-basket-gift-pack.git
8
+
9
+ # For specific version
10
+ yarn add ​git+ssh://git@bitbucket.org:akinonteam/pz-basket-gift-pack.git#0e7cde5
11
+ ```
12
+
13
+ ### Next Config Configuration
14
+
15
+ ##### apps/web/next.config.js**
16
+
17
+ ```javascript
18
+ const withTM = require("next-transpile-modules")(["pz-basket-gift-pack"]);
19
+ ```
20
+
21
+ ### Example Usage
22
+ ##### File Path: src/views/basket/basket-item.tsx
23
+
24
+
25
+ ```javascript
26
+ import { BasketGiftPackage } from 'pz-basket-gift-pack';
27
+
28
+ <BasketGiftPackage basketItem={basketItem}/>
29
+ ```
30
+
31
+ ### Props
32
+
33
+ | Properties | Type | Description |
34
+ | ---------- | -------- | -------------------- |
35
+ | basketItem | `object` | Product information. |
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@akinon/pz-basket-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,47 @@
1
+ import { Basket } from "@akinon/next/types";
2
+ import { buildClientRequestUrl } from "@akinon/next/utils";
3
+ import { api } from "@akinon/next/data/client/api";
4
+
5
+ export const basketApi = api.injectEndpoints({
6
+ endpoints: (build) => ({
7
+ addGiftPackage: build.mutation<Basket, { message: string; id: number }>({
8
+ query: ({ id, message }) => ({
9
+ url: buildClientRequestUrl("/baskets/basket_items", {
10
+ useTrailingSlash: false,
11
+ contentType: "application/json",
12
+ }),
13
+ method: "PATCH",
14
+ body: {
15
+ attributes: {
16
+ gift_note: message,
17
+ },
18
+ id,
19
+ is_all: true,
20
+ },
21
+ }),
22
+ transformResponse: (response: { basket: Basket }) => response.basket,
23
+ invalidatesTags: ["Basket"],
24
+ }),
25
+ removeGiftPackage: build.mutation<Basket, { id: number }>({
26
+ query: ({ id }) => ({
27
+ url: buildClientRequestUrl("/baskets/basket_items", {
28
+ contentType: "application/json",
29
+ }),
30
+ method: "PATCH",
31
+ body: {
32
+ attributes: {
33
+ gift_note: "",
34
+ },
35
+ id,
36
+ is_all: true,
37
+ },
38
+ }),
39
+ transformResponse: (response: { basket: Basket }) => response.basket,
40
+ invalidatesTags: ["Basket"],
41
+ }),
42
+ }),
43
+ overrideExisting: true,
44
+ });
45
+
46
+ export const { useAddGiftPackageMutation, useRemoveGiftPackageMutation } =
47
+ basketApi;
package/src/index.tsx ADDED
@@ -0,0 +1,283 @@
1
+ import {
2
+ useAddGiftPackageMutation,
3
+ useRemoveGiftPackageMutation
4
+ } from './endpoints';
5
+ import clsx from 'clsx';
6
+ import { twMerge } from 'tailwind-merge';
7
+ import { useForm, SubmitHandler } from 'react-hook-form';
8
+ import { yupResolver } from '@hookform/resolvers/yup';
9
+ import * as yup from 'yup';
10
+ import { useEffect, useState } from 'react';
11
+ import { Button } from '@akinon/next/components/button';
12
+ import { Icon } from '@akinon/next/components/icon';
13
+ import { BasketItem as BasketItemType } from '@akinon/next/types';
14
+
15
+ interface GiftPackForm {
16
+ message: string;
17
+ }
18
+
19
+ interface Props {
20
+ basketItem: BasketItemType;
21
+ translations?: {
22
+ placeholderInput?: string;
23
+ warningMessage?: string;
24
+ addNote?: string;
25
+ addGiftNote?: string;
26
+ changeNote?: string;
27
+ giftPackAdded?: string;
28
+ addGiftPack?: string;
29
+ removeGiftPack?: string;
30
+ charactersLength?: string;
31
+ removeNote?: string;
32
+ save?: string;
33
+ close?: string;
34
+ };
35
+ className?: string;
36
+ }
37
+
38
+ export const BasketGiftPack = ({
39
+ basketItem,
40
+ translations,
41
+ className
42
+ }: Props) => {
43
+ const defaultTranslations = {
44
+ placeholderInput:
45
+ 'You can leave this area empty. However it will be a gift package without note.',
46
+ warningMessage: 'Ensure this field has no more than 160 characters.',
47
+ addNote: 'Add Note',
48
+ addGiftNote: 'Add Gift Note',
49
+ changeNote: 'Change Note',
50
+ giftPackAdded: 'Gift Pack Added',
51
+ addGiftPack: 'Add Gift Pack',
52
+ removeGiftPack: 'Remove Gift Pack',
53
+ charactersLength: 'characters left',
54
+ removeNote: 'Remove Note',
55
+ save: 'SAVE',
56
+ close: 'Close'
57
+ };
58
+
59
+ const _translations = {
60
+ ...defaultTranslations,
61
+ ...translations
62
+ };
63
+
64
+ const [addGiftPackage] = useAddGiftPackageMutation();
65
+ const [removeGiftPackage] = useRemoveGiftPackageMutation();
66
+ const [hasGiftPack, setHasGiftPack] = useState(false);
67
+ const [hasNote, setHasNote] = useState({
68
+ title: _translations.addNote,
69
+ state: false
70
+ });
71
+ const [textAreaCount, setTextAreaCount] = useState(0);
72
+ const [note, setNote] = useState('');
73
+ const defaultMessage = 'NO-GIFT-NOTE';
74
+
75
+ const giftPackFormSchema = () =>
76
+ yup.object().shape({
77
+ message: yup.string().max(160, _translations.warningMessage)
78
+ });
79
+
80
+ const {
81
+ register,
82
+ watch,
83
+ handleSubmit,
84
+ formState: { errors }
85
+ } = useForm<GiftPackForm>({
86
+ resolver: yupResolver(giftPackFormSchema())
87
+ });
88
+
89
+ const onSubmit: SubmitHandler<GiftPackForm> = async (data) => {
90
+ addGiftPackage({ id: basketItem.id, message: data.message })
91
+ .unwrap()
92
+ .then(() => {
93
+ setHasNote((prevstate) => ({ ...prevstate, state: false }));
94
+ changeNoteTitle(data.message);
95
+ });
96
+ };
97
+
98
+ const addGiftPack = () => {
99
+ addGiftPackage({ id: basketItem.id, message: defaultMessage })
100
+ .unwrap()
101
+ .then(() => {
102
+ setHasGiftPack(true);
103
+ setHasNote((prevstate) => ({ ...prevstate, state: false }));
104
+ });
105
+ };
106
+
107
+ const removeGiftPack = () => {
108
+ removeGiftPackage({ id: basketItem.id });
109
+ setHasGiftPack(false);
110
+ setHasNote((prevstate) => ({ ...prevstate, state: false }));
111
+ };
112
+
113
+ const removeNote = () => {
114
+ addGiftPackage({ id: basketItem.id, message: defaultMessage });
115
+ setHasNote((prevstate) => ({ ...prevstate, state: false }));
116
+ changeNoteTitle(defaultMessage);
117
+ };
118
+
119
+ const changeNoteTitle = (value) => {
120
+ if (value !== defaultMessage) {
121
+ setHasNote((prevstate) => ({
122
+ ...prevstate,
123
+ title: _translations.changeNote
124
+ }));
125
+ } else {
126
+ setHasNote((prevstate) => ({
127
+ ...prevstate,
128
+ title: _translations.addNote
129
+ }));
130
+ }
131
+ };
132
+
133
+ useEffect(() => {
134
+ for (const [key, value] of Object.entries(basketItem.attributes)) {
135
+ if (value === '') {
136
+ setHasGiftPack(false);
137
+ setHasNote((prevstate) => ({ ...prevstate, state: false }));
138
+ setNote('');
139
+ setTextAreaCount(0);
140
+ return;
141
+ }
142
+
143
+ if (key) {
144
+ changeNoteTitle(value);
145
+ setNote(value);
146
+ setHasGiftPack(true);
147
+ setTextAreaCount(value?.length);
148
+ }
149
+ }
150
+ }, [basketItem.attributes]);
151
+
152
+ useEffect(() => {
153
+ const subscription = watch(({ message }) => {
154
+ setNote(message);
155
+ setTextAreaCount(message.length);
156
+ });
157
+
158
+ return () => subscription.unsubscribe();
159
+ }, [watch]);
160
+
161
+ return (
162
+ <div
163
+ className={twMerge(
164
+ clsx(
165
+ 'mt-4 sm:mt-0',
166
+ {
167
+ 'sm:mt-8': hasNote.state
168
+ },
169
+ className
170
+ )
171
+ )}
172
+ >
173
+ <div className="flex gap-4">
174
+ <div className="flex items-center gap-2">
175
+ <Icon
176
+ name="giftbox"
177
+ size={15}
178
+ className={clsx(hasGiftPack ? 'text-[#e85150]' : 'text-[#000000]')}
179
+ />
180
+
181
+ {hasGiftPack ? (
182
+ <span className="text-[0.75rem]">
183
+ {_translations.giftPackAdded}
184
+ </span>
185
+ ) : (
186
+ <Button
187
+ appearance="ghost"
188
+ className="text-[#000000] cursor-pointer underline border-0 px-0 py-0 text-[0.75rem] h-auto hover:bg-transparent hover:text-[#000000]"
189
+ onClick={addGiftPack}
190
+ >
191
+ {_translations.addGiftPack}
192
+ </Button>
193
+ )}
194
+ </div>
195
+ <Button
196
+ appearance="ghost"
197
+ className={clsx(
198
+ 'text-[#000000] underline cursor-pointer border-0 px-0 py-0 text-[0.75rem] h-auto hover:bg-transparent hover:text-[#000000]',
199
+ {
200
+ hidden: !hasGiftPack
201
+ }
202
+ )}
203
+ onClick={() =>
204
+ setHasNote((prevstate) => ({ ...prevstate, state: true }))
205
+ }
206
+ >
207
+ {hasNote.title}
208
+ </Button>
209
+
210
+ <Button
211
+ appearance="ghost"
212
+ className={clsx(
213
+ 'text-[#000000] cursor-pointer border-0 px-0 py-0 text-[0.75rem] underline h-auto hover:bg-transparent hover:text-[#000000]',
214
+ {
215
+ hidden: !hasGiftPack
216
+ }
217
+ )}
218
+ onClick={removeGiftPack}
219
+ >
220
+ {_translations.removeGiftPack}
221
+ </Button>
222
+ </div>
223
+ <div
224
+ className={clsx('mt-4', {
225
+ hidden: !hasNote.state
226
+ })}
227
+ >
228
+ <form onSubmit={handleSubmit(onSubmit)}>
229
+ <div className="flex justify-between mb-3">
230
+ <span className="text-[0.75rem] font-bold">
231
+ {_translations.addGiftNote}
232
+ </span>
233
+ <Button
234
+ appearance="ghost"
235
+ 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]"
236
+ onClick={(e) => {
237
+ e.preventDefault();
238
+ setHasNote((prevstate) => ({ ...prevstate, state: false }));
239
+ }}
240
+ >
241
+ {_translations.close}
242
+ </Button>
243
+ </div>
244
+ <textarea
245
+ className="w-full border border-solid border-[#4a4f53] p-4 placeholder:text-[0.75rem] placeholder:text-[#c8c9c8] outline-none text-[0.75rem]"
246
+ rows={7}
247
+ name="message"
248
+ value={note !== defaultMessage ? note : ' '}
249
+ placeholder={_translations.placeholderInput}
250
+ {...register('message')}
251
+ />
252
+ {errors.message && (
253
+ <span className="text-sm text-[#d72a04]">
254
+ {errors.message.message}
255
+ </span>
256
+ )}
257
+ <div className="flex justify-between items-center mt-2">
258
+ <span
259
+ className={clsx(
260
+ 'text-[0.75rem] ',
261
+ textAreaCount > 160 ? 'text-[#e85150]' : 'text-[#82a27c]'
262
+ )}
263
+ >
264
+ {textAreaCount}/160 {_translations.charactersLength}
265
+ </span>
266
+ <div className="flex items-center gap-3">
267
+ <Button
268
+ appearance="ghost"
269
+ 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]"
270
+ onClick={removeNote}
271
+ >
272
+ {_translations.removeNote}
273
+ </Button>
274
+ <Button type="submit" className="w-28 h-7 font-medium uppercase">
275
+ {_translations.save}
276
+ </Button>
277
+ </div>
278
+ </div>
279
+ </form>
280
+ </div>
281
+ </div>
282
+ );
283
+ };