datastake-daf 0.6.557 → 0.6.558
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/build/favicon.ico +0 -0
- package/build/logo192.png +0 -0
- package/build/logo512.png +0 -0
- package/build/manifest.json +25 -0
- package/build/robots.txt +3 -0
- package/dist/components/index.js +182 -0
- package/dist/hooks/index.js +4655 -16
- package/dist/layouts/index.css +1 -1
- package/dist/layouts/index.js +0 -132
- package/package.json +1 -1
- package/src/@daf/core/components/AuthForm/AuthForm.stories.js +360 -0
- package/src/@daf/core/components/AuthForm/index.jsx +197 -0
- package/src/index.js +1 -0
- package/src/layouts.js +1 -2
- package/src/@daf/layouts/AuthLayout/components/NavbarComponent.jsx +0 -53
- package/src/@daf/layouts/AuthLayout/components/NavbarComponent.scss +0 -51
- package/src/@daf/layouts/AuthLayout/components/index.js +0 -2
- package/src/@daf/layouts/AuthLayout/index.jsx +0 -137
- package/src/@daf/layouts/AuthLayout/index.scss +0 -413
|
@@ -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/index.jsx";
|
|
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;
|
package/src/index.js
CHANGED
|
@@ -87,6 +87,7 @@ export { Sections as EditFormSections } from "./@daf/core/components/EditForm/se
|
|
|
87
87
|
export { default as ViewFormNavigation } from "./@daf/core/components/ViewForm/navigation.jsx";
|
|
88
88
|
export { default as PhoneInput } from "./@daf/core/components/Inputs/PhoneInput/index.js";
|
|
89
89
|
export { AjaxSelectMain as AjaxSelect } from "./@daf/core/components/EditForm/components/ajaxSelect.js";
|
|
90
|
+
export { default as AuthForm } from "./@daf/core/components/AuthForm/index.jsx";
|
|
90
91
|
|
|
91
92
|
// Progress Bar
|
|
92
93
|
export { default as ProgressBar } from "./@daf/core/components/ProgressBar/index.jsx";
|
package/src/layouts.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export { default as AppLayout } from './@daf/layouts/AppLayout/index.jsx';
|
|
2
|
-
export { default as AuthLayout } from './@daf/layouts/AuthLayout/index.jsx';
|
|
1
|
+
export { default as AppLayout } from './@daf/layouts/AppLayout/index.jsx';
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/* eslint-disable react/prop-types */
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { formatClassname } from '../../../../helpers/ClassesHelper.js';
|
|
4
|
-
import './NavbarComponent.scss';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* NavbarComponent for AuthLayout
|
|
8
|
-
*
|
|
9
|
-
* A simple, reusable navbar component that can be used with AuthLayout
|
|
10
|
-
*
|
|
11
|
-
* @param {Object} props
|
|
12
|
-
* @param {React.ReactNode} props.leftContent - Content to display on the left side
|
|
13
|
-
* @param {React.ReactNode} props.rightContent - Content to display on the right side
|
|
14
|
-
* @param {string} props.className - Additional CSS classes
|
|
15
|
-
* @param {string} props.logo - Logo image URL
|
|
16
|
-
* @param {string} props.logoAlt - Alt text for logo
|
|
17
|
-
* @param {Function} props.onLogoClick - Callback when logo is clicked
|
|
18
|
-
* @param {boolean} props.bordered - Whether to show bottom border
|
|
19
|
-
*/
|
|
20
|
-
const NavbarComponent = ({
|
|
21
|
-
leftContent,
|
|
22
|
-
rightContent,
|
|
23
|
-
className = '',
|
|
24
|
-
logo,
|
|
25
|
-
logoAlt = 'Logo',
|
|
26
|
-
onLogoClick,
|
|
27
|
-
bordered = false,
|
|
28
|
-
}) => {
|
|
29
|
-
return (
|
|
30
|
-
<div className={formatClassname(['auth-navbar', bordered && 'bordered', className])}>
|
|
31
|
-
<div className="auth-navbar-container">
|
|
32
|
-
<div className="auth-navbar-left">
|
|
33
|
-
{logo && (
|
|
34
|
-
<img
|
|
35
|
-
src={logo}
|
|
36
|
-
alt={logoAlt}
|
|
37
|
-
className="auth-navbar-logo"
|
|
38
|
-
onClick={onLogoClick}
|
|
39
|
-
style={{ cursor: onLogoClick ? 'pointer' : 'default' }}
|
|
40
|
-
/>
|
|
41
|
-
)}
|
|
42
|
-
{leftContent}
|
|
43
|
-
</div>
|
|
44
|
-
<div className="auth-navbar-right">
|
|
45
|
-
{rightContent}
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export default NavbarComponent;
|
|
53
|
-
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
.auth-navbar {
|
|
2
|
-
width: 100%;
|
|
3
|
-
padding: 1rem 2rem;
|
|
4
|
-
background-color: transparent;
|
|
5
|
-
transition: all 0.3s ease;
|
|
6
|
-
|
|
7
|
-
&.bordered {
|
|
8
|
-
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.auth-navbar-container {
|
|
12
|
-
display: flex;
|
|
13
|
-
align-items: center;
|
|
14
|
-
justify-content: space-between;
|
|
15
|
-
max-width: 1400px;
|
|
16
|
-
margin: 0 auto;
|
|
17
|
-
width: 100%;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.auth-navbar-left {
|
|
21
|
-
display: flex;
|
|
22
|
-
align-items: center;
|
|
23
|
-
gap: 1rem;
|
|
24
|
-
|
|
25
|
-
.auth-navbar-logo {
|
|
26
|
-
height: 40px;
|
|
27
|
-
width: auto;
|
|
28
|
-
object-fit: contain;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.auth-navbar-right {
|
|
33
|
-
display: flex;
|
|
34
|
-
align-items: center;
|
|
35
|
-
gap: 1rem;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Responsive styles
|
|
40
|
-
@media (max-width: 768px) {
|
|
41
|
-
.auth-navbar {
|
|
42
|
-
padding: 0.75rem 1rem;
|
|
43
|
-
|
|
44
|
-
.auth-navbar-left {
|
|
45
|
-
.auth-navbar-logo {
|
|
46
|
-
height: 32px;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/* eslint-disable react/prop-types */
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { Select } from "antd";
|
|
4
|
-
import { formatClassname } from "../../../helpers/ClassesHelper.js";
|
|
5
|
-
import "./index.scss";
|
|
6
|
-
|
|
7
|
-
const AuthLayout = ({
|
|
8
|
-
children,
|
|
9
|
-
NavbarComponent,
|
|
10
|
-
StyleComponent,
|
|
11
|
-
t = (key) => key,
|
|
12
|
-
title = "Welcome Back",
|
|
13
|
-
subTitle = "Please introduce yourself",
|
|
14
|
-
header,
|
|
15
|
-
logo,
|
|
16
|
-
appName = "default",
|
|
17
|
-
containerClassName = "",
|
|
18
|
-
leftClassName = "",
|
|
19
|
-
rightClassName = "",
|
|
20
|
-
rightImgClassName = "",
|
|
21
|
-
rightImageUrl,
|
|
22
|
-
showNavbar = false,
|
|
23
|
-
navbarProps = {},
|
|
24
|
-
additionalContent,
|
|
25
|
-
languageConfig = [
|
|
26
|
-
{ value: "en", label: "EN", flagUrl: "/assets/images/countries/gb.png" },
|
|
27
|
-
{ value: "fr", label: "FR", flagUrl: "/assets/images/countries/fr.png" },
|
|
28
|
-
{ value: "sp", label: "ES", flagUrl: "/assets/images/countries/sp.png" },
|
|
29
|
-
],
|
|
30
|
-
updateLanguage,
|
|
31
|
-
showLanguageSelector = false,
|
|
32
|
-
|
|
33
|
-
showTopHeader = false, // shows the header bar (logo image + language + back)
|
|
34
|
-
topHeaderLogo, // full logo image (contains both icon and text)
|
|
35
|
-
onBack, // callback when “Back” clicked
|
|
36
|
-
showBack = false, // show/hide back button
|
|
37
|
-
backLabel = "Back",
|
|
38
|
-
}) => {
|
|
39
|
-
|
|
40
|
-
/** Header block (single logo image + back + language) */
|
|
41
|
-
const renderTopHeader = () => {
|
|
42
|
-
if (!showTopHeader) return null;
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<div className="auth-top-header">
|
|
46
|
-
<div className="left">
|
|
47
|
-
{topHeaderLogo && (
|
|
48
|
-
<img
|
|
49
|
-
className="top-header-logo-combined"
|
|
50
|
-
src={topHeaderLogo}
|
|
51
|
-
alt={`${appName} logo`}
|
|
52
|
-
/>
|
|
53
|
-
)}
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<div className="right">
|
|
57
|
-
{showLanguageSelector && updateLanguage && (
|
|
58
|
-
<Select
|
|
59
|
-
className="language-select"
|
|
60
|
-
defaultValue={localStorage.getItem("datastakeLng") || "en"}
|
|
61
|
-
bordered={false}
|
|
62
|
-
onChange={(lng) => updateLanguage(lng)}
|
|
63
|
-
popupClassName={formatClassname(["language-select-popup", appName])}
|
|
64
|
-
>
|
|
65
|
-
{languageConfig.map((lang) => (
|
|
66
|
-
<Select.Option key={lang.value} value={lang.value}>
|
|
67
|
-
<div className="row-cont flex items-center gap-2">
|
|
68
|
-
<img
|
|
69
|
-
src={lang.flagUrl}
|
|
70
|
-
alt={lang.label}
|
|
71
|
-
style={{ width: 20, height: 12 }}
|
|
72
|
-
/>
|
|
73
|
-
<span>{lang.label}</span>
|
|
74
|
-
</div>
|
|
75
|
-
</Select.Option>
|
|
76
|
-
))}
|
|
77
|
-
</Select>
|
|
78
|
-
)}
|
|
79
|
-
|
|
80
|
-
{showBack && (
|
|
81
|
-
<button className="back-btn" onClick={onBack}>
|
|
82
|
-
{backLabel}
|
|
83
|
-
</button>
|
|
84
|
-
)}
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const renderHeader = () =>
|
|
91
|
-
header ? (
|
|
92
|
-
header
|
|
93
|
-
) : (
|
|
94
|
-
<div className="left-header">
|
|
95
|
-
{logo && <img className="app-logo" src={logo} alt={`${appName} logo`} />}
|
|
96
|
-
{title && <h2>{t(title)}</h2>}
|
|
97
|
-
{subTitle && <p>{typeof subTitle === "string" ? t(subTitle) : subTitle}</p>}
|
|
98
|
-
</div>
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const layoutContent = (
|
|
102
|
-
<div className="main-cont">
|
|
103
|
-
<div className={formatClassname(["d-cont left", leftClassName])}>
|
|
104
|
-
<div className="main">
|
|
105
|
-
{renderHeader()}
|
|
106
|
-
{children}
|
|
107
|
-
{additionalContent}
|
|
108
|
-
</div>
|
|
109
|
-
</div>
|
|
110
|
-
<div
|
|
111
|
-
className={formatClassname(["d-cont right", rightClassName])}
|
|
112
|
-
style={
|
|
113
|
-
rightImageUrl
|
|
114
|
-
? { backgroundImage: `url(${rightImageUrl})`, backgroundSize: "cover" }
|
|
115
|
-
: {}
|
|
116
|
-
}
|
|
117
|
-
>
|
|
118
|
-
<div className={formatClassname(["right-img", rightImgClassName])} />
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const Wrapper = StyleComponent || "div";
|
|
124
|
-
const wrapperClassName = formatClassname(["auth-layout", appName, containerClassName]);
|
|
125
|
-
|
|
126
|
-
return (
|
|
127
|
-
<>
|
|
128
|
-
{showNavbar && NavbarComponent && <NavbarComponent {...navbarProps} />}
|
|
129
|
-
<Wrapper className={wrapperClassName}>
|
|
130
|
-
{renderTopHeader()}
|
|
131
|
-
{layoutContent}
|
|
132
|
-
</Wrapper>
|
|
133
|
-
</>
|
|
134
|
-
);
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
export default AuthLayout;
|