@invoice-sdk/widget 1.5.2 → 1.6.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.
@@ -1,222 +0,0 @@
1
- import { zodResolver } from '@hookform/resolvers/zod';
2
- import type { SubmitHandler } from 'react-hook-form';
3
- import { Controller, useForm } from 'react-hook-form';
4
- import { useNavigate } from 'react-router-dom';
5
- import z from "zod";
6
- import Button from '../components/button';
7
- import FileUpload from '../components/form/file-upload';
8
- import InputField from '../components/form/input-field';
9
- import { useProcessStore } from '../store/process';
10
- import { useRegisterStore } from '../store/register';
11
-
12
- const formSchema = z.object({
13
- taxCode: z.string().nonempty('Mã số thuế là bắt buộc'),
14
- representative: z.string().nonempty('Người đại diện là bắt buộc'),
15
- email: z
16
- .string()
17
- .nonempty('Email là bắt buộc')
18
- .email('Email không hợp lệ'),
19
- address: z.string().nonempty('Địa chỉ là bắt buộc'),
20
- companyName: z.string().nonempty('Tên công ty là bắt buộc'),
21
- position: z.string().optional(),
22
- phone: z
23
- .string()
24
- .regex(/^[0-9()+\-\s]+$/, 'SĐT không hợp lệ')
25
- .optional(),
26
-
27
- // ← here we make it `.optional()`
28
- license: z
29
- .instanceof(File, {
30
- message: 'Giấy phép đăng ký kinh doanh là bắt buộc',
31
- })
32
- .optional(),
33
- });
34
-
35
- export type FormValues = z.infer<typeof formSchema>;
36
-
37
-
38
- const RegistrationForm = () => {
39
- const navigate = useNavigate();
40
- const form = useRegisterStore((state) => state.form);
41
- const {
42
- control,
43
- handleSubmit,
44
- formState: { errors, isSubmitting, isValid },
45
- } = useForm<FormValues>({
46
- resolver: zodResolver(formSchema),
47
- defaultValues: {
48
- taxCode: form.taxCode || '',
49
- representative: form.representative || '',
50
- email: form.email || '',
51
- address: form.address || '',
52
- companyName: form.companyName || '',
53
- position: form.position || '',
54
- phone: form.phoneNumber || '',
55
- license: form.license || undefined, // handle optional license
56
- },
57
- });
58
-
59
- const setForm = useRegisterStore((state) => state.setForm);
60
- const nextStep = useProcessStore((state) => state.nextStep);
61
- const prevStep = useProcessStore((state) => state.prevStep);
62
-
63
- const onSubmit: SubmitHandler<FormValues> = (data) => {
64
- // here data.license is a File
65
- console.log(data);
66
-
67
- setForm({
68
- taxCode: data.taxCode,
69
- representative: data.representative,
70
- email: data.email,
71
- address: data.address,
72
- companyName: data.companyName,
73
- position: data.position,
74
- phoneNumber: data.phone || null, // handle optional phone
75
- license: data.license || null, // handle optional license
76
- });
77
-
78
- };
79
-
80
- return (
81
- <form
82
- onSubmit={handleSubmit(onSubmit)}
83
- className="w-full mx-auto p-6 bg-white shadow rounded"
84
- >
85
- <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
86
- {/* Left */}
87
- <div className="space-y-4">
88
- <Controller
89
- name="taxCode"
90
- control={control}
91
- render={({ field }) => (
92
- <InputField
93
- {...field}
94
- label="Mã số thuế"
95
- required
96
- error={errors.taxCode?.message}
97
- />
98
- )}
99
- />
100
-
101
- <Controller
102
- name="representative"
103
- control={control}
104
- render={({ field }) => (
105
- <InputField
106
- {...field}
107
- label="Người đại diện"
108
- required
109
- error={errors.representative?.message}
110
- />
111
- )}
112
- />
113
-
114
- <Controller
115
- name="email"
116
- control={control}
117
- render={({ field }) => (
118
- <InputField
119
- {...field}
120
- label="Email"
121
- type="email"
122
- required
123
- error={errors.email?.message}
124
- />
125
- )}
126
- />
127
-
128
- <Controller
129
- name="address"
130
- control={control}
131
- render={({ field }) => (
132
- <InputField
133
- {...field}
134
- label="Địa chỉ"
135
- required
136
- error={errors.address?.message}
137
- />
138
- )}
139
- />
140
-
141
- <Controller
142
- name="license"
143
- control={control}
144
- render={({ field }) => (
145
- <FileUpload
146
- label="Giấy phép đăng ký kinh doanh"
147
- required
148
- file={field.value ?? null}
149
- onFileChange={field.onChange}
150
- error={errors.license?.message}
151
- />
152
- )}
153
- />
154
- </div>
155
-
156
- {/* Right */}
157
- <div className="space-y-4">
158
- <Controller
159
- name="companyName"
160
- control={control}
161
- render={({ field }) => (
162
- <InputField
163
- {...field}
164
- label="Tên công ty"
165
- required
166
- error={errors.companyName?.message}
167
- />
168
- )}
169
- />
170
-
171
- <Controller
172
- name="position"
173
- control={control}
174
- render={({ field }) => (
175
- <InputField
176
- {...field}
177
- label="Chức vụ"
178
- error={errors.position?.message}
179
- />
180
- )}
181
- />
182
-
183
- <Controller
184
- name="phone"
185
- control={control}
186
- render={({ field }) => (
187
- <InputField
188
- {...field}
189
- label="SĐT"
190
- type="tel"
191
- error={errors.phone?.message}
192
- />
193
- )}
194
- />
195
- </div>
196
- </div>
197
-
198
- <div className='flex items-center justify-end gap-2'>
199
- <Button title={'Back'}
200
- handleClick={() => {
201
- prevStep()
202
- navigate('/select-provider');
203
- }
204
- }
205
-
206
- className=' !bg-gray-200 !text-gray-700 hover:!bg-gray-300 disabled:!bg-gray-200 disabled:!text-gray-500'
207
- />
208
- <Button
209
- type='submit'
210
- title={isSubmitting ? 'Submitting...' : 'Submit'} isDisabled={isSubmitting || Object.keys(errors).length > 0 || !isValid}
211
- handleClick={() => {
212
- nextStep()
213
- navigate('/select-plan');
214
- }
215
- }
216
- />
217
- </div>
218
- </form>
219
- );
220
- };
221
-
222
- export default RegistrationForm;
@@ -1,39 +0,0 @@
1
- import { useState } from "react";
2
- import { useNavigate } from "react-router-dom";
3
- import Button from "../components/button";
4
- import SubscriptionSelector from "../components/form/select-option";
5
- import { useProcessStore } from "../store/process";
6
-
7
-
8
- const SelectPlan = () => {
9
- const navigate = useNavigate();
10
- const nextStep = useProcessStore((state) => state.nextStep);
11
- const [plan, setPlan] = useState('300');
12
- const options = [
13
- { value: '100', label: '100 đơn/tháng' },
14
- { value: '300', label: '300 đơn/tháng' },
15
- { value: '500', label: '500 đơn/tháng' },
16
- ];
17
-
18
- return (
19
- <div className="w-full flex flex-col gap-6">
20
- <h2 className="heading">Select subscription package</h2>
21
- <SubscriptionSelector
22
- options={options}
23
- value={plan}
24
- onChange={setPlan}
25
- />
26
- <div className='text-right'>
27
- <Button title={'Next'}
28
- isDisabled={!plan}
29
- handleClick={() => {
30
- nextStep()
31
- navigate("/status")
32
- }}
33
- />
34
- </div>
35
- </div>
36
- );
37
- }
38
-
39
- export default SelectPlan;
@@ -1,74 +0,0 @@
1
- import { useNavigate } from "react-router-dom";
2
- import Button from "../components/button";
3
- import CustomCheckbox from "../components/form/custom-checkbox";
4
- import { useProcessStore } from "../store/process";
5
- import { useRegisterStore } from "../store/register";
6
-
7
-
8
-
9
- interface IProvider {
10
- name: string;
11
- route: string;
12
- };
13
-
14
- const providers: IProvider[] = [
15
- { name: 'M-Invoice', route: 'm-invoice' },
16
- { name: 'FPT', route: 'fpt' },
17
- { name: 'Misa', route: 'misa' },
18
- ];
19
-
20
- const ProviderItem = ({ provider, handleProviderChange }: { provider: IProvider,
21
- handleProviderChange: (provider: string) => void
22
- } ) => {
23
- const selectedProvider = useRegisterStore((state) => state.selectedProvider)
24
- return (
25
- <div
26
- data-checked={selectedProvider === provider.route}
27
- className="flex items-center justify-center gap-2 px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-100 cursor-pointer
28
- transition-colors duration-200 data-[checked=true]:border-green-500 data-[checked=true]:text-green-700
29
- "
30
- onClick={() => handleProviderChange(provider.route)}
31
- >
32
- <CustomCheckbox checked={selectedProvider === provider.route}
33
- setChecked={() => handleProviderChange(provider.route)}
34
- />
35
- <label className="">{provider.name}</label>
36
- </div>
37
- )
38
- }
39
-
40
- const SelectProvider = () => {
41
-
42
- const navigate = useNavigate()
43
- const selectedProvider = useRegisterStore((state) => state.selectedProvider);
44
- const setSelectedProvider = useRegisterStore((state) => state.setSelectedProvider);
45
- const nextStep = useProcessStore(state => state.nextStep);
46
-
47
- const handleProviderChange = (provider: string) => {
48
- setSelectedProvider(provider);
49
- };
50
-
51
- return (
52
- <div className="w-full flex flex-col gap-6">
53
- <h2 className="heading">Select Provider</h2>
54
- <div className="flex gap-20">
55
- {
56
- providers.map((provider) => (
57
- <ProviderItem key={provider.route} provider={provider} handleProviderChange={handleProviderChange} />
58
- ))
59
- }
60
- </div>
61
- <div className='text-right'>
62
- <Button title={'Next'}
63
- isDisabled={!selectedProvider}
64
- handleClick={() => {
65
- nextStep()
66
- navigate("/register")
67
- }}
68
- />
69
- </div>
70
- </div>
71
- )
72
- }
73
-
74
- export default SelectProvider
@@ -1,9 +0,0 @@
1
-
2
-
3
- const Status = () => {
4
- return (
5
- <div>Status</div>
6
- )
7
- }
8
-
9
- export default Status
@@ -1,18 +0,0 @@
1
- import {create} from 'zustand';
2
-
3
- type State = {
4
- step: number;
5
-
6
- }
7
-
8
- type Actions = {
9
- nextStep: () => void
10
- prevStep: () => void
11
- }
12
-
13
- export const useProcessStore = create<State & Actions>((set) => ({
14
- step: 1,
15
- nextStep: () => set((state) => ({ step: state.step + 1 })),
16
- prevStep: () => set((state) => ({ step: Math.max(state.step - 1, 1) })),
17
- reset: () => set({ step: 1})
18
- }));
@@ -1,60 +0,0 @@
1
- import {create } from 'zustand';
2
-
3
- type State = {
4
- selectedProvider: string | null;
5
- form: {
6
- taxCode: string;
7
- companyName: string;
8
- representative: string;
9
- position: string;
10
- email: string;
11
- phoneNumber: string | null;
12
- address: string ;
13
- license: File | null;
14
- },
15
- selectedPlan: string | null;
16
- }
17
-
18
- type Actions = {
19
- setSelectedProvider: (provider: string | null) => void;
20
- setForm: (form: Partial<State['form']>) => void;
21
- setSelectedPlan: (plan: string | null) => void;
22
- reset: () => void;
23
- }
24
- export const useRegisterStore = create<State & Actions>((set) => ({
25
- selectedProvider: null,
26
- form: {
27
- taxCode: '',
28
- companyName: '',
29
- representative: '',
30
- position: '',
31
- email: '',
32
- phoneNumber: null,
33
- address: '',
34
- license: null,
35
- },
36
- selectedPlan: null,
37
-
38
- setSelectedProvider: (provider) => set({ selectedProvider: provider }),
39
-
40
- setForm: (form) => set((state) => ({
41
- form: { ...state.form, ...form }
42
- })),
43
-
44
- setSelectedPlan: (plan) => set({ selectedPlan: plan }),
45
-
46
- reset: () => set({
47
- selectedProvider: null,
48
- form: {
49
- taxCode: '',
50
- companyName: '',
51
- representative: '',
52
- position: '',
53
- email: '',
54
- phoneNumber: null,
55
- address: '',
56
- license: null,
57
- },
58
- selectedPlan: null,
59
- }),
60
- }));
package/src/vite-env.d.ts DELETED
@@ -1 +0,0 @@
1
- /// <reference types="vite/client" />
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2019",
4
- "module": "CommonJS",
5
- "jsx": "react-jsx",
6
- "outDir": "dist",
7
- "rootDir": "src",
8
- "strict": true,
9
- "declaration": true,
10
- "declarationMap": true,
11
- "sourceMap": true,
12
- "esModuleInterop": true,
13
- "forceConsistentCasingInFileNames": true,
14
- "skipLibCheck": true,
15
- "emitDeclarationOnly": true,
16
- },
17
- "include": ["src/**/*"],
18
- "exclude": ["node_modules", "dist"]
19
- }
@@ -1 +0,0 @@
1
- {"root":["./src/App.tsx","./src/index.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/button.tsx","./src/components/layout.tsx","./src/components/process.tsx","./src/components/form/custom-checkbox.tsx","./src/components/form/file-upload.tsx","./src/components/form/input-field.tsx","./src/components/form/select-option.tsx","./src/pages/register.tsx","./src/pages/select-plan.tsx","./src/pages/select-provider.tsx","./src/pages/status.tsx","./src/store/process.ts","./src/store/register.ts"],"version":"5.8.3"}
package/vite.config.ts DELETED
@@ -1,46 +0,0 @@
1
- import { defineConfig } from 'vite'
2
- import react from '@vitejs/plugin-react'
3
- import tailwindcss from '@tailwindcss/vite'
4
- import autoprefixer from 'autoprefixer'
5
- import path from 'path'
6
- // https://vite.dev/config/
7
- export default defineConfig({
8
- root: ".",
9
- resolve: {
10
- alias: {
11
- "@": path.resolve(__dirname, "./src"),
12
- },
13
- },
14
- build: {
15
- outDir: "dist",
16
- lib: {
17
- entry: path.resolve(__dirname, "src/index.ts"),
18
- name: "SdkWidget",
19
- formats: ["es", "cjs"],
20
- fileName: (format) => `index.${format}.js`,
21
- },
22
- rollupOptions: {
23
- // Do not bundle React/ReactDOM/ReactRouter – treat them as externals
24
- external: ["react", "react-dom", "react-router-dom"],
25
- output: {
26
- globals: {
27
- react: "React",
28
- "react-dom": "ReactDOM",
29
- "react-router-dom": "ReactRouterDOM",
30
- },
31
- },
32
- },
33
- },
34
- server: {
35
- port: 3000,
36
- open: true,
37
- },
38
- plugins: [react(),tailwindcss(),],
39
- css: {
40
- postcss: {
41
- plugins: [
42
- autoprefixer(),
43
- ]
44
- }
45
- }
46
- })