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.
@@ -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,2 +0,0 @@
1
- export { default as NavbarComponent } from './NavbarComponent.jsx';
2
-
@@ -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;