ui-soxo-bootstrap-core 2.6.0 → 2.6.1-dev.2
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/.github/workflows/npm-publish.yml +49 -19
- package/core/components/menu-template-api/menu-template-api.js +2 -2
- package/core/lib/elements/basic/country-phone-input/country-phone-input.js +26 -57
- package/core/lib/elements/basic/country-phone-input/phone-input.scss +14 -0
- package/core/lib/models/forms/components/form-creator/form-creator.js +468 -502
- package/core/lib/pages/login/commnication-mode-selection.js +46 -0
- package/core/lib/pages/login/communication-mode-selection.scss +60 -0
- package/core/lib/pages/login/login.js +135 -8
- package/core/lib/pages/login/login.scss +220 -334
- package/core/lib/pages/login/reset-password.js +124 -0
- package/core/lib/pages/login/reset-password.scss +22 -0
- package/core/lib/utils/api/api.utils.js +50 -39
- package/core/lib/utils/common/common.utils.js +60 -0
- package/core/lib/utils/index.js +22 -28
- package/core/models/roles/roles.js +9 -0
- package/core/models/users/components/user-add/user-add.js +2 -2
- package/core/models/users/users.js +25 -7
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +55 -24
- package/core/modules/steps/action-buttons.js +5 -1
- package/package.json +2 -2
- package/core/components/external-window/DEVELOPER_GUIDE.md +0 -705
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommunicationModeSelection Component
|
|
3
|
+
*
|
|
4
|
+
* Renders radio options for selecting OTP delivery method.
|
|
5
|
+
* Supports Email and SMS modes.
|
|
6
|
+
*
|
|
7
|
+
* Props:
|
|
8
|
+
* @param {string} communicationMode - Currently selected mode ('email' | 'mobile')
|
|
9
|
+
* @param {Function} setCommunicationMode - Updates selected mode
|
|
10
|
+
* @param {boolean} modeError - Displays validation error if true
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import React from 'react';
|
|
14
|
+
import { Radio, Divider, Typography } from 'antd';
|
|
15
|
+
import { MailOutlined, MessageOutlined } from '@ant-design/icons';
|
|
16
|
+
|
|
17
|
+
import './communication-mode-selection.scss';
|
|
18
|
+
|
|
19
|
+
const { Text } = Typography;
|
|
20
|
+
|
|
21
|
+
function CommunicationModeSelection({ communicationMode, setCommunicationMode, modeError }) {
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<div className="otp-method-section">
|
|
25
|
+
<Text type="primary">Select Preferred OTP Verification Method</Text>
|
|
26
|
+
<div className="otp-method-group">
|
|
27
|
+
|
|
28
|
+
{/* Email Option */}
|
|
29
|
+
<Radio checked={communicationMode === 'email'} onChange={() => setCommunicationMode('email')}>
|
|
30
|
+
Email <MailOutlined className="otp-icon" style={{ marginLeft: 6 }} />
|
|
31
|
+
</Radio>
|
|
32
|
+
|
|
33
|
+
{/* SMS Option */}
|
|
34
|
+
<Radio checked={communicationMode === 'mobile'} onChange={() => setCommunicationMode('mobile')}>
|
|
35
|
+
SMS <MessageOutlined className="otp-icon" style={{ marginLeft: 6 }} />
|
|
36
|
+
</Radio>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
{/* Validation Error */}
|
|
40
|
+
{modeError && <p className="otp-mode-error">Please select a communication mode.</p>}
|
|
41
|
+
</div>
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default CommunicationModeSelection;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
$error-color: red;
|
|
2
|
+
|
|
3
|
+
.otp-method-section {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: 6px;
|
|
7
|
+
|
|
8
|
+
.otp-method-title {
|
|
9
|
+
font-size: 16px;
|
|
10
|
+
font-weight: 600;
|
|
11
|
+
margin-bottom: 8px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.otp-method-group {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
margin-bottom: 10px;
|
|
18
|
+
gap: 30px;
|
|
19
|
+
font-size: 12px;
|
|
20
|
+
|
|
21
|
+
.ant-radio-wrapper {
|
|
22
|
+
display: flex;
|
|
23
|
+
gap: 6px;
|
|
24
|
+
|
|
25
|
+
svg {
|
|
26
|
+
font-size: 18px;
|
|
27
|
+
opacity: 0.8;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@media only screen and (max-width: 600px) {
|
|
32
|
+
flex-direction: column !important;
|
|
33
|
+
align-items: flex-start !important;
|
|
34
|
+
gap: 12px !important;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@media only screen and (min-width: 601px) and (max-width: 1024px) {
|
|
38
|
+
flex-direction: row !important;
|
|
39
|
+
align-items: center !important;
|
|
40
|
+
justify-content: space-between !important;
|
|
41
|
+
gap: 20px !important;
|
|
42
|
+
width: 100%;
|
|
43
|
+
|
|
44
|
+
.ant-radio-wrapper {
|
|
45
|
+
flex: 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.otp-mode-error {
|
|
51
|
+
margin: 5px;
|
|
52
|
+
font-size: 13px;
|
|
53
|
+
color: $error-color;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.otp-icon {
|
|
57
|
+
position: relative;
|
|
58
|
+
top: 2px;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -30,10 +30,12 @@ import { getAccessToken, getRefreshToken } from '../../utils/http/auth.helper';
|
|
|
30
30
|
|
|
31
31
|
import { Location } from '../../utils';
|
|
32
32
|
|
|
33
|
-
import { checkLicenseStatus, formatMobile } from '../../utils/common/common.utils';
|
|
33
|
+
import { checkLicenseStatus, formatMobile, safeJSON ,checkExpiryStatus} from '../../utils/common/common.utils';
|
|
34
34
|
|
|
35
35
|
import { MailOutlined, MessageOutlined, WhatsAppOutlined } from '@ant-design/icons';
|
|
36
36
|
|
|
37
|
+
import ResetPassword from './reset-password';
|
|
38
|
+
|
|
37
39
|
const { Text, Title } = Typography;
|
|
38
40
|
|
|
39
41
|
const layout = {
|
|
@@ -48,13 +50,19 @@ const tailLayout = {
|
|
|
48
50
|
|
|
49
51
|
const LICENSE_EXPIRY = '2026-12-12';
|
|
50
52
|
|
|
53
|
+
const headers = {
|
|
54
|
+
db_ptr: 'nuraho',
|
|
55
|
+
};
|
|
56
|
+
//password valdity expire
|
|
57
|
+
const PASSWORD_VALIDITY_DAYS = 90;
|
|
58
|
+
|
|
51
59
|
/**
|
|
52
60
|
*
|
|
53
61
|
* @param {*} param0
|
|
54
62
|
* @returns
|
|
55
63
|
*/
|
|
56
64
|
function LoginPhone({ history, appSettings }) {
|
|
57
|
-
const { brandLogo, heroImage, footerLogo
|
|
65
|
+
const { brandLogo, heroImage, footerLogo } = appSettings;
|
|
58
66
|
|
|
59
67
|
// Hook for OTP Timer
|
|
60
68
|
const { expired: otpExpired, formatted, startFromExpiry } = useOtpTimer();
|
|
@@ -85,11 +93,70 @@ function LoginPhone({ history, appSettings }) {
|
|
|
85
93
|
const [communicationMode, setCommunicationMode] = useState(null); // default selected email
|
|
86
94
|
const [modeError, setModeError] = useState(false);
|
|
87
95
|
|
|
96
|
+
//for forgot password show
|
|
97
|
+
const [showResetpassword, setShowResetpassword] = useState(false);
|
|
98
|
+
|
|
99
|
+
//for expired password show
|
|
100
|
+
const [expiredPassword, setExpiredPassword] = useState(false);
|
|
101
|
+
|
|
102
|
+
//for default name select when expire case
|
|
103
|
+
const [defaultUsername, setDefaultUsername] = useState('');
|
|
104
|
+
|
|
88
105
|
const isAuthenticated = Boolean(getAccessToken());
|
|
89
106
|
const isRefreshTokenExist = Boolean(getRefreshToken());
|
|
90
107
|
|
|
91
108
|
const path = window.location.pathname;
|
|
92
109
|
|
|
110
|
+
/**
|
|
111
|
+
* handlePasswordExpiryCheck
|
|
112
|
+
* --------------------------
|
|
113
|
+
* Validates whether a user's password is expired or nearing expiry.
|
|
114
|
+
*
|
|
115
|
+
* - Parses `last_password_change` from user.other_details.
|
|
116
|
+
* - Calculates expiry using PASSWORD_VALIDITY_DAYS.
|
|
117
|
+
* - Uses `checkExpiryStatus()` to determine status.
|
|
118
|
+
* - Shows Ant Design warning message if expired or within warning period.
|
|
119
|
+
* - Warning message includes navigation to `/change-password`.
|
|
120
|
+
*
|
|
121
|
+
* Requires:
|
|
122
|
+
* - PASSWORD_VALIDITY_DAYS constant
|
|
123
|
+
* - checkExpiryStatus utility
|
|
124
|
+
* - React Router history
|
|
125
|
+
* - antd message component
|
|
126
|
+
*/
|
|
127
|
+
const handlePasswordExpiryCheck = (user) => {
|
|
128
|
+
const otherDetails = user?.other_details ? JSON.parse(user.other_details) : null;
|
|
129
|
+
|
|
130
|
+
const lastPasswordChange = otherDetails?.last_password_change;
|
|
131
|
+
|
|
132
|
+
if (lastPasswordChange) {
|
|
133
|
+
const passwordExpiryDate = new Date(lastPasswordChange);
|
|
134
|
+
passwordExpiryDate.setDate(passwordExpiryDate.getDate() + PASSWORD_VALIDITY_DAYS);
|
|
135
|
+
|
|
136
|
+
const passwordStatus = checkExpiryStatus({
|
|
137
|
+
expiryDate: passwordExpiryDate,
|
|
138
|
+
warningDays: 2,
|
|
139
|
+
expiredMessage: 'Your password has expired. Please reset it.',
|
|
140
|
+
warningMessage: (d) => (
|
|
141
|
+
<span>
|
|
142
|
+
Your password will expire in {d} day(s).{' '}
|
|
143
|
+
<a
|
|
144
|
+
onClick={() => {
|
|
145
|
+
history.push('/change-password');
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
Click here to update.
|
|
149
|
+
</a>
|
|
150
|
+
</span>
|
|
151
|
+
),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (passwordStatus.message) {
|
|
155
|
+
message.warning(passwordStatus.message);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
93
160
|
const onFinish = (values) => {
|
|
94
161
|
setLoading(true);
|
|
95
162
|
|
|
@@ -127,6 +194,8 @@ function LoginPhone({ history, appSettings }) {
|
|
|
127
194
|
if (insider_token) localStorage.insider_token = insider_token;
|
|
128
195
|
|
|
129
196
|
if (result.success) {
|
|
197
|
+
handlePasswordExpiryCheck(user);
|
|
198
|
+
|
|
130
199
|
//two_factor_authentication variable is present then proceed Two factor authentication
|
|
131
200
|
if (result.data && result.data.two_factor_authentication) {
|
|
132
201
|
let data;
|
|
@@ -146,7 +215,7 @@ function LoginPhone({ history, appSettings }) {
|
|
|
146
215
|
setUser(data);
|
|
147
216
|
// Set default communication mode automatically
|
|
148
217
|
if (result.data.mode) {
|
|
149
|
-
setCommunicationMode(result.data.mode);
|
|
218
|
+
setCommunicationMode(result.data.mode);
|
|
150
219
|
}
|
|
151
220
|
} else {
|
|
152
221
|
let d = user;
|
|
@@ -165,10 +234,33 @@ function LoginPhone({ history, appSettings }) {
|
|
|
165
234
|
localStorage.setItem('userInfo', JSON.stringify(userInfo));
|
|
166
235
|
if (refresh_token) localStorage.setItem('refresh_token', refresh_token);
|
|
167
236
|
|
|
237
|
+
// Setting DBPTR
|
|
238
|
+
if (user?.organization_details) {
|
|
239
|
+
const data = safeJSON(user?.organization_details);
|
|
240
|
+
|
|
241
|
+
const defaultBranch = data.branch.find((b) => b.defaultBranch === true);
|
|
242
|
+
|
|
243
|
+
const defaultDbptr = defaultBranch?.dbPtr;
|
|
244
|
+
|
|
245
|
+
localStorage.setItem('db_ptr', defaultDbptr);
|
|
246
|
+
}
|
|
247
|
+
|
|
168
248
|
history.push('/');
|
|
169
249
|
}
|
|
170
250
|
} else {
|
|
171
|
-
|
|
251
|
+
if (result.passwordChange) {
|
|
252
|
+
message.warning(result.message);
|
|
253
|
+
|
|
254
|
+
//time for redirect when expire
|
|
255
|
+
setTimeout(() => {
|
|
256
|
+
setExpiredPassword(true);
|
|
257
|
+
setDefaultUsername(values.email);
|
|
258
|
+
|
|
259
|
+
setShowResetpassword(true);
|
|
260
|
+
}, 1500);
|
|
261
|
+
} else {
|
|
262
|
+
message.warning(result.message);
|
|
263
|
+
}
|
|
172
264
|
}
|
|
173
265
|
})
|
|
174
266
|
.catch((error) => {
|
|
@@ -313,10 +405,22 @@ function LoginPhone({ history, appSettings }) {
|
|
|
313
405
|
// Setting refresh_token
|
|
314
406
|
if (result.refresh_token) localStorage.setItem('refresh_token', result.refresh_token);
|
|
315
407
|
|
|
408
|
+
// Setting DBPTR
|
|
409
|
+
if (result?.user?.organization_details) {
|
|
410
|
+
const data = safeJSON(result?.user?.organization_details);
|
|
411
|
+
|
|
412
|
+
const defaultBranch = data.branch.find((b) => b.defaultBranch === 'true');
|
|
413
|
+
const defaultDbptr = defaultBranch?.dbPtr;
|
|
414
|
+
|
|
415
|
+
localStorage.setItem('db_ptr', defaultDbptr);
|
|
416
|
+
}
|
|
417
|
+
|
|
316
418
|
dispatch({ type: 'user', payload: userInfo });
|
|
317
419
|
// set user info into local storage
|
|
318
420
|
localStorage.setItem('userInfo', JSON.stringify(userInfo));
|
|
319
421
|
|
|
422
|
+
handlePasswordExpiryCheck(result.user);
|
|
423
|
+
|
|
320
424
|
setTimeout(() => history.push('/'), 500);
|
|
321
425
|
} else {
|
|
322
426
|
// OTP FAILED (wrong OTP)
|
|
@@ -535,7 +639,7 @@ function LoginPhone({ history, appSettings }) {
|
|
|
535
639
|
: {
|
|
536
640
|
width: '100%',
|
|
537
641
|
height: '100vh',
|
|
538
|
-
|
|
642
|
+
background: 'linear-gradient(to bottom, #F7F6E3 0%, #EEF1DE 20%, #D5E4DA 45%, #9DBFC8 75%, #4F89A6 100%)',
|
|
539
643
|
backgroundPosition: 'center bottom, center',
|
|
540
644
|
backgroundRepeat: 'no-repeat, no-repeat',
|
|
541
645
|
backgroundSize: 'cover, cover',
|
|
@@ -681,13 +785,13 @@ function LoginPhone({ history, appSettings }) {
|
|
|
681
785
|
)}
|
|
682
786
|
|
|
683
787
|
{/* Login Form Section */}
|
|
684
|
-
{!otpVerification && !otpVisible && (
|
|
788
|
+
{!otpVerification && !otpVisible && !showResetpassword && (
|
|
685
789
|
<Form {...layout} layout="vertical" name="basic" onFinish={onFinish} onFinishFailed={onFinishFailed}>
|
|
686
790
|
<div className="form-title">
|
|
687
791
|
<h4></h4>
|
|
688
792
|
</div>
|
|
689
793
|
|
|
690
|
-
{!process.env.REACT_APP_SHOW_BRANCH_SWITCHER && <div className="branch-switcher">{globalCustomerHeader()}</div>}
|
|
794
|
+
{/* {!process.env.REACT_APP_SHOW_BRANCH_SWITCHER && <div className="branch-switcher">{globalCustomerHeader()}</div>} */}
|
|
691
795
|
|
|
692
796
|
{process.env.REACT_APP_ENABLE_LDAP === 'true' && (
|
|
693
797
|
<Button loading={ldaploading} type="secondary" className="SubmitBtn" onClick={loginWithLdap}>
|
|
@@ -703,19 +807,42 @@ function LoginPhone({ history, appSettings }) {
|
|
|
703
807
|
<Input.Password autoComplete="off" />
|
|
704
808
|
</Form.Item>
|
|
705
809
|
|
|
706
|
-
<Form.Item {...tailLayout}>
|
|
810
|
+
<Form.Item {...tailLayout} style={{ marginBottom: '0px' }}>
|
|
707
811
|
<Button loading={loading} type="primary" htmlType="submit" className="SubmitBtn">
|
|
708
812
|
Submit
|
|
709
813
|
</Button>
|
|
814
|
+
<div className="forgot-password" style={{ marginTop: '8px', textAlign: 'center' }}>
|
|
815
|
+
<Link onClick={() => setShowResetpassword(true)}>Forgot Password?</Link>
|
|
816
|
+
</div>
|
|
710
817
|
</Form.Item>
|
|
711
818
|
</Form>
|
|
712
819
|
)}
|
|
820
|
+
|
|
821
|
+
{/* Forgot Password Section */}
|
|
822
|
+
{showResetpassword && (
|
|
823
|
+
<ResetPassword
|
|
824
|
+
defaultUsername={expiredPassword ? defaultUsername : undefined}
|
|
825
|
+
disabledUserName={expiredPassword}
|
|
826
|
+
onBack={() => {
|
|
827
|
+
setShowResetpassword(false);
|
|
828
|
+
setExpiredPassword(false);
|
|
829
|
+
}}
|
|
830
|
+
title={expiredPassword ? 'Password Expired' : 'Forgot Password'}
|
|
831
|
+
subtitle={
|
|
832
|
+
expiredPassword
|
|
833
|
+
? 'Enter your username and choose your preferred OTP method to receive a reset link.'
|
|
834
|
+
: 'Enter your username to reset your password and select your preferred OTP method.'
|
|
835
|
+
}
|
|
836
|
+
buttonText={expiredPassword ? 'Send Reset Link' : 'Reset Password'}
|
|
837
|
+
/>
|
|
838
|
+
)}
|
|
713
839
|
</div>
|
|
714
840
|
</div>
|
|
715
841
|
{!otpSuccess && otpVerification && (
|
|
716
842
|
<div className="otp-actions">
|
|
717
843
|
<div className="resend-action">
|
|
718
844
|
<Text disabled>Didn't receive OTP?</Text>
|
|
845
|
+
|
|
719
846
|
<Link className="resend-otp-link" disabled={!otpExpired} onClick={handleResendOTP}>
|
|
720
847
|
Resend OTP
|
|
721
848
|
</Link>
|