datastake-daf 0.6.554 → 0.6.556
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/package.json
CHANGED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import ThemeLayout from "../ThemeLayout";
|
|
2
|
+
import AuthForm from "./index";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: "Form/AuthForm",
|
|
6
|
+
component: AuthForm,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
decorators: [
|
|
9
|
+
(Story) => (
|
|
10
|
+
<div style={{ margin: "3em" }}>
|
|
11
|
+
<ThemeLayout>
|
|
12
|
+
<Story />
|
|
13
|
+
</ThemeLayout>
|
|
14
|
+
</div>
|
|
15
|
+
),
|
|
16
|
+
],
|
|
17
|
+
argTypes: {
|
|
18
|
+
steps: {
|
|
19
|
+
control: "object",
|
|
20
|
+
description: "Array of step objects for multi-step forms. Each step should have a title and fields array.",
|
|
21
|
+
table: {
|
|
22
|
+
type: { summary: "array" },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
fields: {
|
|
26
|
+
control: "object",
|
|
27
|
+
description: "Array of field objects for single-step forms. Each field should have name, label, type, etc.",
|
|
28
|
+
table: {
|
|
29
|
+
type: { summary: "array" },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
onSubmit: {
|
|
33
|
+
action: "submitted",
|
|
34
|
+
description: "Callback function called when form is submitted with form values",
|
|
35
|
+
table: {
|
|
36
|
+
type: { summary: "function" },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
initialValues: {
|
|
40
|
+
control: "object",
|
|
41
|
+
description: "Initial values for form fields",
|
|
42
|
+
table: {
|
|
43
|
+
type: { summary: "object" },
|
|
44
|
+
defaultValue: { summary: "{}" },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
submitText: {
|
|
48
|
+
control: "text",
|
|
49
|
+
description: "Text for the submit button",
|
|
50
|
+
table: {
|
|
51
|
+
type: { summary: "string" },
|
|
52
|
+
defaultValue: { summary: "Submit" },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
form: {
|
|
56
|
+
control: false,
|
|
57
|
+
description: "Ant Design form instance",
|
|
58
|
+
table: {
|
|
59
|
+
type: { summary: "FormInstance" },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const SimpleForm = {
|
|
66
|
+
name: "Simple Form",
|
|
67
|
+
args: {
|
|
68
|
+
fields: [
|
|
69
|
+
{
|
|
70
|
+
name: "email",
|
|
71
|
+
label: "Email",
|
|
72
|
+
type: "email",
|
|
73
|
+
placeholder: "Enter your email",
|
|
74
|
+
required: true,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "password",
|
|
78
|
+
label: "Password",
|
|
79
|
+
type: "password",
|
|
80
|
+
placeholder: "Enter your password",
|
|
81
|
+
required: true,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
submitText: "Login",
|
|
85
|
+
onSubmit: (values) => {
|
|
86
|
+
console.log("Form values:", values);
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const CompleteForm = {
|
|
92
|
+
name: "Complete Form with All Field Types",
|
|
93
|
+
args: {
|
|
94
|
+
fields: [
|
|
95
|
+
{
|
|
96
|
+
name: "username",
|
|
97
|
+
label: "Username",
|
|
98
|
+
type: "input",
|
|
99
|
+
placeholder: "Enter username",
|
|
100
|
+
required: true,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "email",
|
|
104
|
+
label: "Email",
|
|
105
|
+
type: "email",
|
|
106
|
+
placeholder: "Enter email address",
|
|
107
|
+
required: true,
|
|
108
|
+
rules: [
|
|
109
|
+
{
|
|
110
|
+
type: "email",
|
|
111
|
+
message: "Please enter a valid email",
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "age",
|
|
117
|
+
label: "Age",
|
|
118
|
+
type: "number",
|
|
119
|
+
placeholder: "Enter your age",
|
|
120
|
+
required: false,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "password",
|
|
124
|
+
label: "Password",
|
|
125
|
+
type: "password",
|
|
126
|
+
placeholder: "Enter password",
|
|
127
|
+
required: true,
|
|
128
|
+
rules: [
|
|
129
|
+
{
|
|
130
|
+
min: 8,
|
|
131
|
+
message: "Password must be at least 8 characters",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "country",
|
|
137
|
+
label: "Country",
|
|
138
|
+
type: "select",
|
|
139
|
+
placeholder: "Select your country",
|
|
140
|
+
required: true,
|
|
141
|
+
options: [
|
|
142
|
+
{ value: "us", label: "United States" },
|
|
143
|
+
{ value: "uk", label: "United Kingdom" },
|
|
144
|
+
{ value: "ca", label: "Canada" },
|
|
145
|
+
{ value: "au", label: "Australia" },
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "bio",
|
|
150
|
+
label: "Bio",
|
|
151
|
+
type: "textarea",
|
|
152
|
+
placeholder: "Tell us about yourself",
|
|
153
|
+
required: false,
|
|
154
|
+
props: {
|
|
155
|
+
rows: 4,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
submitText: "Register",
|
|
160
|
+
initialValues: {
|
|
161
|
+
country: "us",
|
|
162
|
+
},
|
|
163
|
+
onSubmit: (values) => {
|
|
164
|
+
console.log("Complete form values:", values);
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export const MultiStepForm = {
|
|
170
|
+
name: "Multi-Step Form",
|
|
171
|
+
args: {
|
|
172
|
+
steps: [
|
|
173
|
+
{
|
|
174
|
+
title: "Personal Info",
|
|
175
|
+
fields: [
|
|
176
|
+
{
|
|
177
|
+
name: "firstName",
|
|
178
|
+
label: "First Name",
|
|
179
|
+
type: "input",
|
|
180
|
+
placeholder: "Enter first name",
|
|
181
|
+
required: true,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "lastName",
|
|
185
|
+
label: "Last Name",
|
|
186
|
+
type: "input",
|
|
187
|
+
placeholder: "Enter last name",
|
|
188
|
+
required: true,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "email",
|
|
192
|
+
label: "Email",
|
|
193
|
+
type: "email",
|
|
194
|
+
placeholder: "Enter email",
|
|
195
|
+
required: true,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
title: "Account Details",
|
|
201
|
+
fields: [
|
|
202
|
+
{
|
|
203
|
+
name: "username",
|
|
204
|
+
label: "Username",
|
|
205
|
+
type: "input",
|
|
206
|
+
placeholder: "Choose a username",
|
|
207
|
+
required: true,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "password",
|
|
211
|
+
label: "Password",
|
|
212
|
+
type: "password",
|
|
213
|
+
placeholder: "Create a password",
|
|
214
|
+
required: true,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "confirmPassword",
|
|
218
|
+
label: "Confirm Password",
|
|
219
|
+
type: "password",
|
|
220
|
+
placeholder: "Confirm your password",
|
|
221
|
+
required: true,
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
title: "Preferences",
|
|
227
|
+
fields: [
|
|
228
|
+
{
|
|
229
|
+
name: "language",
|
|
230
|
+
label: "Language",
|
|
231
|
+
type: "select",
|
|
232
|
+
placeholder: "Select language",
|
|
233
|
+
required: true,
|
|
234
|
+
options: [
|
|
235
|
+
{ value: "en", label: "English" },
|
|
236
|
+
{ value: "fr", label: "French" },
|
|
237
|
+
{ value: "sp", label: "Spanish" },
|
|
238
|
+
],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: "notifications",
|
|
242
|
+
label: "Notification Preferences",
|
|
243
|
+
type: "textarea",
|
|
244
|
+
placeholder: "Enter your notification preferences",
|
|
245
|
+
required: false,
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
submitText: "Complete Registration",
|
|
251
|
+
initialValues: {
|
|
252
|
+
language: "en",
|
|
253
|
+
category: "coprporation"
|
|
254
|
+
},
|
|
255
|
+
executeRecaptcha: () => {
|
|
256
|
+
return "mock-recaptcha-token";
|
|
257
|
+
},
|
|
258
|
+
onSubmit: (values) => {
|
|
259
|
+
console.log("Multi-step form values:", values);
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export const WithInitialValues = {
|
|
265
|
+
name: "Form with Initial Values",
|
|
266
|
+
args: {
|
|
267
|
+
fields: [
|
|
268
|
+
{
|
|
269
|
+
name: "name",
|
|
270
|
+
label: "Name",
|
|
271
|
+
type: "input",
|
|
272
|
+
placeholder: "Enter name",
|
|
273
|
+
required: true,
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "email",
|
|
277
|
+
label: "Email",
|
|
278
|
+
type: "email",
|
|
279
|
+
placeholder: "Enter email",
|
|
280
|
+
required: true,
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "role",
|
|
284
|
+
label: "Role",
|
|
285
|
+
type: "select",
|
|
286
|
+
placeholder: "Select role",
|
|
287
|
+
required: true,
|
|
288
|
+
options: [
|
|
289
|
+
{ value: "admin", label: "Administrator" },
|
|
290
|
+
{ value: "user", label: "User" },
|
|
291
|
+
{ value: "guest", label: "Guest" },
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
initialValues: {
|
|
296
|
+
name: "John Doe",
|
|
297
|
+
email: "john.doe@example.com",
|
|
298
|
+
role: "user",
|
|
299
|
+
},
|
|
300
|
+
submitText: "Update Profile",
|
|
301
|
+
onSubmit: (values) => {
|
|
302
|
+
console.log("Updated values:", values);
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
export const CustomValidation = {
|
|
308
|
+
name: "Form with Custom Validation",
|
|
309
|
+
args: {
|
|
310
|
+
fields: [
|
|
311
|
+
{
|
|
312
|
+
name: "email",
|
|
313
|
+
label: "Email",
|
|
314
|
+
type: "email",
|
|
315
|
+
placeholder: "Enter email",
|
|
316
|
+
required: true,
|
|
317
|
+
rules: [
|
|
318
|
+
{
|
|
319
|
+
type: "email",
|
|
320
|
+
message: "Invalid email format",
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
|
324
|
+
message: "Please enter a valid email address",
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: "phone",
|
|
330
|
+
label: "Phone Number",
|
|
331
|
+
type: "input",
|
|
332
|
+
placeholder: "Enter phone number",
|
|
333
|
+
required: true,
|
|
334
|
+
rules: [
|
|
335
|
+
{
|
|
336
|
+
pattern: /^\d{10}$/,
|
|
337
|
+
message: "Phone number must be 10 digits",
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "website",
|
|
343
|
+
label: "Website",
|
|
344
|
+
type: "input",
|
|
345
|
+
placeholder: "Enter website URL",
|
|
346
|
+
required: false,
|
|
347
|
+
rules: [
|
|
348
|
+
{
|
|
349
|
+
type: "url",
|
|
350
|
+
message: "Please enter a valid URL",
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
submitText: "Submit",
|
|
356
|
+
onSubmit: (values) => {
|
|
357
|
+
console.log("Validated values:", values);
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { Form, Input, Select } from "antd";
|
|
3
|
+
import BorderedButton from "../Button/BorderedButton";
|
|
4
|
+
|
|
5
|
+
function AuthForm ({
|
|
6
|
+
steps,
|
|
7
|
+
fields,
|
|
8
|
+
onSubmit,
|
|
9
|
+
initialValues = {},
|
|
10
|
+
submitText = "Submit",
|
|
11
|
+
form,
|
|
12
|
+
errors,
|
|
13
|
+
t = (key) => key,
|
|
14
|
+
executeRecaptcha = () => {},
|
|
15
|
+
}){
|
|
16
|
+
const [formInstance] = Form.useForm(form);
|
|
17
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
18
|
+
const [allFormValues, setAllFormValues] = useState(initialValues);
|
|
19
|
+
const [formErrors, setFormErrors] = useState(null);
|
|
20
|
+
|
|
21
|
+
const isMultiStep = !!steps;
|
|
22
|
+
const currentFields = isMultiStep ? steps?.[currentStep]?.fields : fields || [];
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (isMultiStep && Object.keys(allFormValues).length > 0) {
|
|
26
|
+
formInstance.setFieldsValue(allFormValues);
|
|
27
|
+
}
|
|
28
|
+
}, [currentStep, allFormValues, formInstance, isMultiStep]);
|
|
29
|
+
|
|
30
|
+
const handleReCaptchaVerify = useCallback(async () => {
|
|
31
|
+
if (!executeRecaptcha) {
|
|
32
|
+
console.log('Execute recaptcha not yet available');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const token = await executeRecaptcha('submit');
|
|
37
|
+
return token;
|
|
38
|
+
}, [executeRecaptcha]);
|
|
39
|
+
|
|
40
|
+
const next = async () => {
|
|
41
|
+
try {
|
|
42
|
+
await formInstance.validateFields(
|
|
43
|
+
currentFields.map((f) => f.name)
|
|
44
|
+
);
|
|
45
|
+
// Save current step values before moving to next step
|
|
46
|
+
const currentValues = formInstance.getFieldsValue();
|
|
47
|
+
setAllFormValues((prev) => ({ ...prev, ...currentValues }));
|
|
48
|
+
setCurrentStep((prev) => prev + 1);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error("Please fix the validation errors before continuing.");
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const prev = () => setCurrentStep((prev) => prev - 1);
|
|
55
|
+
|
|
56
|
+
const handleSubmit = async (values) => {
|
|
57
|
+
const token = await handleReCaptchaVerify();
|
|
58
|
+
if (!token) {
|
|
59
|
+
console.error('reCAPTCHA verification failed');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
setFormErrors(null);
|
|
63
|
+
if (!isMultiStep) return onSubmit?.(values);
|
|
64
|
+
|
|
65
|
+
if (currentStep < steps?.length - 1) return next();
|
|
66
|
+
|
|
67
|
+
const finalValues = { ...allFormValues, ...values };
|
|
68
|
+
onSubmit?.(finalValues);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const checkRequiredFieldsFilled = (changedValues, allValues) => {
|
|
72
|
+
const requiredFields = currentFields.filter(f => f.required).map(f => f.name);
|
|
73
|
+
return requiredFields.every(fieldName => {
|
|
74
|
+
const value = allValues[fieldName];
|
|
75
|
+
return value !== undefined && value !== null && value !== '';
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
setFormErrors(errors);
|
|
81
|
+
}, [errors]);
|
|
82
|
+
|
|
83
|
+
const renderErrors = (err, t) => {
|
|
84
|
+
document.querySelectorAll('.ant-form-item')
|
|
85
|
+
.forEach(input => input.classList.add('ant-form-item-has-error'))
|
|
86
|
+
return Object.keys(err).map(key => {
|
|
87
|
+
return err[key].map(error =>
|
|
88
|
+
<div
|
|
89
|
+
key={error}
|
|
90
|
+
className="ant-form-item-explain errors-cont"
|
|
91
|
+
style={{ color: '#ff4d4f' }}>
|
|
92
|
+
{t(error)}
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const renderField = (field) => {
|
|
99
|
+
const rules = [
|
|
100
|
+
...(field.required
|
|
101
|
+
? [{ required: true, message: `${field.label} is required` }]
|
|
102
|
+
: []),
|
|
103
|
+
...(field.rules || []),
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
let inputNode = null;
|
|
107
|
+
switch (field.type) {
|
|
108
|
+
case "input":
|
|
109
|
+
case "email":
|
|
110
|
+
case "number":
|
|
111
|
+
inputNode = <Input type={field.type} placeholder={field.placeholder} {...field.props} />;
|
|
112
|
+
break;
|
|
113
|
+
case "password":
|
|
114
|
+
inputNode = <Input.Password placeholder={field.placeholder} {...field.props} />;
|
|
115
|
+
break;
|
|
116
|
+
case "textarea":
|
|
117
|
+
inputNode = <Input.TextArea placeholder={field.placeholder} {...field.props} />;
|
|
118
|
+
break;
|
|
119
|
+
case "select":
|
|
120
|
+
inputNode = (
|
|
121
|
+
<Select placeholder={field.placeholder} {...field.props}>
|
|
122
|
+
{field.options?.map((opt) => (
|
|
123
|
+
<Select.Option key={opt.value} value={opt.value}>
|
|
124
|
+
{opt.label}
|
|
125
|
+
</Select.Option>
|
|
126
|
+
))}
|
|
127
|
+
</Select>
|
|
128
|
+
);
|
|
129
|
+
break;
|
|
130
|
+
case "custom":
|
|
131
|
+
inputNode = field.component;
|
|
132
|
+
break;
|
|
133
|
+
default:
|
|
134
|
+
inputNode = <Input placeholder={field.placeholder} {...field.props} />;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<Form.Item key={field.name} name={field.name} label={field.label} rules={rules}>
|
|
139
|
+
{inputNode}
|
|
140
|
+
</Form.Item>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<>
|
|
146
|
+
<Form
|
|
147
|
+
form={formInstance}
|
|
148
|
+
layout="vertical"
|
|
149
|
+
initialValues={initialValues}
|
|
150
|
+
onFinish={handleSubmit}
|
|
151
|
+
>
|
|
152
|
+
{currentFields.map(renderField)}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
{formErrors ? (
|
|
156
|
+
renderErrors(formErrors, t)
|
|
157
|
+
) : null}
|
|
158
|
+
|
|
159
|
+
{isMultiStep && currentStep > 0 ? (
|
|
160
|
+
<div className="flex flex-column gap-2 mt-4" style={{ width: '100%' }}>
|
|
161
|
+
<Form.Item noStyle shouldUpdate>
|
|
162
|
+
{(form) => {
|
|
163
|
+
const isLastStep = currentStep === steps?.length - 1;
|
|
164
|
+
const allFieldsFilled = checkRequiredFieldsFilled({}, form.getFieldsValue());
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div className="buttons">
|
|
168
|
+
<BorderedButton
|
|
169
|
+
type="primary"
|
|
170
|
+
htmlType="submit"
|
|
171
|
+
disabled={isLastStep && !allFieldsFilled}
|
|
172
|
+
block className="normal-br"
|
|
173
|
+
>
|
|
174
|
+
{isLastStep ? submitText : t("Next")}
|
|
175
|
+
</BorderedButton>
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}}
|
|
179
|
+
</Form.Item>
|
|
180
|
+
<div className="buttons">
|
|
181
|
+
<BorderedButton onClick={prev} block className="normal-br">
|
|
182
|
+
{t("Back")}
|
|
183
|
+
</BorderedButton>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
) : (
|
|
187
|
+
<div className="buttons">
|
|
188
|
+
<BorderedButton type="primary" htmlType="submit" block className="normal-br">
|
|
189
|
+
{isMultiStep ? t("Next") : submitText}
|
|
190
|
+
</BorderedButton>
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
</Form>
|
|
194
|
+
</>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
export default AuthForm;
|
|
File without changes
|