ui-soxo-bootstrap-core 2.6.1-dev.1 → 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/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 +107 -5
- 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/common/common.utils.js +36 -0
- package/core/models/users/users.js +7 -0
- package/package.json +1 -1
|
@@ -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, safeJSON } 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 = {
|
|
@@ -51,6 +53,8 @@ const LICENSE_EXPIRY = '2026-12-12';
|
|
|
51
53
|
const headers = {
|
|
52
54
|
db_ptr: 'nuraho',
|
|
53
55
|
};
|
|
56
|
+
//password valdity expire
|
|
57
|
+
const PASSWORD_VALIDITY_DAYS = 90;
|
|
54
58
|
|
|
55
59
|
/**
|
|
56
60
|
*
|
|
@@ -89,11 +93,70 @@ function LoginPhone({ history, appSettings }) {
|
|
|
89
93
|
const [communicationMode, setCommunicationMode] = useState(null); // default selected email
|
|
90
94
|
const [modeError, setModeError] = useState(false);
|
|
91
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
|
+
|
|
92
105
|
const isAuthenticated = Boolean(getAccessToken());
|
|
93
106
|
const isRefreshTokenExist = Boolean(getRefreshToken());
|
|
94
107
|
|
|
95
108
|
const path = window.location.pathname;
|
|
96
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
|
+
|
|
97
160
|
const onFinish = (values) => {
|
|
98
161
|
setLoading(true);
|
|
99
162
|
|
|
@@ -131,6 +194,8 @@ function LoginPhone({ history, appSettings }) {
|
|
|
131
194
|
if (insider_token) localStorage.insider_token = insider_token;
|
|
132
195
|
|
|
133
196
|
if (result.success) {
|
|
197
|
+
handlePasswordExpiryCheck(user);
|
|
198
|
+
|
|
134
199
|
//two_factor_authentication variable is present then proceed Two factor authentication
|
|
135
200
|
if (result.data && result.data.two_factor_authentication) {
|
|
136
201
|
let data;
|
|
@@ -183,7 +248,19 @@ function LoginPhone({ history, appSettings }) {
|
|
|
183
248
|
history.push('/');
|
|
184
249
|
}
|
|
185
250
|
} else {
|
|
186
|
-
|
|
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
|
+
}
|
|
187
264
|
}
|
|
188
265
|
})
|
|
189
266
|
.catch((error) => {
|
|
@@ -342,6 +419,8 @@ function LoginPhone({ history, appSettings }) {
|
|
|
342
419
|
// set user info into local storage
|
|
343
420
|
localStorage.setItem('userInfo', JSON.stringify(userInfo));
|
|
344
421
|
|
|
422
|
+
handlePasswordExpiryCheck(result.user);
|
|
423
|
+
|
|
345
424
|
setTimeout(() => history.push('/'), 500);
|
|
346
425
|
} else {
|
|
347
426
|
// OTP FAILED (wrong OTP)
|
|
@@ -560,7 +639,7 @@ function LoginPhone({ history, appSettings }) {
|
|
|
560
639
|
: {
|
|
561
640
|
width: '100%',
|
|
562
641
|
height: '100vh',
|
|
563
|
-
|
|
642
|
+
background: 'linear-gradient(to bottom, #F7F6E3 0%, #EEF1DE 20%, #D5E4DA 45%, #9DBFC8 75%, #4F89A6 100%)',
|
|
564
643
|
backgroundPosition: 'center bottom, center',
|
|
565
644
|
backgroundRepeat: 'no-repeat, no-repeat',
|
|
566
645
|
backgroundSize: 'cover, cover',
|
|
@@ -706,7 +785,7 @@ function LoginPhone({ history, appSettings }) {
|
|
|
706
785
|
)}
|
|
707
786
|
|
|
708
787
|
{/* Login Form Section */}
|
|
709
|
-
{!otpVerification && !otpVisible && (
|
|
788
|
+
{!otpVerification && !otpVisible && !showResetpassword && (
|
|
710
789
|
<Form {...layout} layout="vertical" name="basic" onFinish={onFinish} onFinishFailed={onFinishFailed}>
|
|
711
790
|
<div className="form-title">
|
|
712
791
|
<h4></h4>
|
|
@@ -728,19 +807,42 @@ function LoginPhone({ history, appSettings }) {
|
|
|
728
807
|
<Input.Password autoComplete="off" />
|
|
729
808
|
</Form.Item>
|
|
730
809
|
|
|
731
|
-
<Form.Item {...tailLayout}>
|
|
810
|
+
<Form.Item {...tailLayout} style={{ marginBottom: '0px' }}>
|
|
732
811
|
<Button loading={loading} type="primary" htmlType="submit" className="SubmitBtn">
|
|
733
812
|
Submit
|
|
734
813
|
</Button>
|
|
814
|
+
<div className="forgot-password" style={{ marginTop: '8px', textAlign: 'center' }}>
|
|
815
|
+
<Link onClick={() => setShowResetpassword(true)}>Forgot Password?</Link>
|
|
816
|
+
</div>
|
|
735
817
|
</Form.Item>
|
|
736
818
|
</Form>
|
|
737
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
|
+
)}
|
|
738
839
|
</div>
|
|
739
840
|
</div>
|
|
740
841
|
{!otpSuccess && otpVerification && (
|
|
741
842
|
<div className="otp-actions">
|
|
742
843
|
<div className="resend-action">
|
|
743
844
|
<Text disabled>Didn't receive OTP?</Text>
|
|
845
|
+
|
|
744
846
|
<Link className="resend-otp-link" disabled={!otpExpired} onClick={handleResendOTP}>
|
|
745
847
|
Resend OTP
|
|
746
848
|
</Link>
|
|
@@ -1,17 +1,35 @@
|
|
|
1
|
+
// Variables
|
|
2
|
+
$primary-color: #0c66e4;
|
|
3
|
+
$bg-color-light: #f5f5f5;
|
|
4
|
+
$border-color-light: #e8e8e8;
|
|
5
|
+
$border-color-medium: #e0e0e0;
|
|
6
|
+
$border-color-dark: #d9d9d9;
|
|
7
|
+
$text-color-dark: #071822;
|
|
8
|
+
$white: #ffffff;
|
|
9
|
+
$error-color: red;
|
|
10
|
+
|
|
1
11
|
body {
|
|
2
12
|
overflow: hidden;
|
|
3
13
|
}
|
|
4
14
|
|
|
15
|
+
.full-page {
|
|
16
|
+
height: 100vh;
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
width: 100%;
|
|
19
|
+
background-repeat: no-repeat;
|
|
20
|
+
background-position: bottom center;
|
|
21
|
+
background-size: 100% auto;
|
|
22
|
+
}
|
|
23
|
+
|
|
5
24
|
.user-authentication-section {
|
|
6
25
|
min-height: 90vh;
|
|
7
26
|
display: flex;
|
|
8
|
-
// margin: 0px 15%;
|
|
9
27
|
justify-content: center;
|
|
10
|
-
padding:
|
|
11
|
-
// align-items: center;
|
|
28
|
+
padding: 0 15%;
|
|
12
29
|
|
|
13
30
|
@media (max-width: 1200px) {
|
|
14
31
|
margin: 0 5%;
|
|
32
|
+
padding: 0 5%;
|
|
15
33
|
}
|
|
16
34
|
|
|
17
35
|
@media (max-width: 768px) {
|
|
@@ -21,63 +39,22 @@ body {
|
|
|
21
39
|
align-items: center;
|
|
22
40
|
}
|
|
23
41
|
|
|
42
|
+
@media only screen and (min-width: 608px) {
|
|
43
|
+
justify-content: center;
|
|
44
|
+
align-items: center;
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
.page-background {
|
|
25
48
|
position: absolute;
|
|
26
|
-
left:
|
|
27
|
-
top:
|
|
49
|
+
left: 0;
|
|
50
|
+
top: 0;
|
|
28
51
|
height: 40vh;
|
|
29
52
|
width: 100%;
|
|
30
|
-
|
|
31
|
-
// background-image:url("./../../../assets/images/vector.png"); /* Set Image */ ;
|
|
32
|
-
// background: url("./../../../assets/images/vector.png"),
|
|
33
|
-
// linear-gradient(to right, #D5F1FB, #24AEB8);
|
|
34
|
-
// background-size: cover;
|
|
35
|
-
// background-position: center;
|
|
36
|
-
// background: linear-gradient(to right, #7ddafac2, #24AEB8); /* Gradient */
|
|
37
|
-
// url("./../../../assets/images/vector.png"),
|
|
38
|
-
// /* Background image */ linear-gradient(to right, #7ddafac2, #10c7ef61); /* Gradient */
|
|
39
|
-
|
|
40
|
-
// background: #00b4db; /* fallback for old browsers */
|
|
41
|
-
// background: -webkit-linear-gradient(to right, #0083b0, #00b4db); /* Chrome 10-25, Safari 5.1-6 */
|
|
42
|
-
// background: linear-gradient(
|
|
43
|
-
// to right,
|
|
44
|
-
// #0083b0,
|
|
45
|
-
// #00b4db
|
|
46
|
-
// ); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
|
|
47
|
-
|
|
48
|
-
// background: rgb(131, 58, 180);
|
|
49
|
-
// background: -moz-linear-gradient(
|
|
50
|
-
// 90deg,
|
|
51
|
-
// rgba(131, 58, 180, 1) 0%,
|
|
52
|
-
// rgba(253, 29, 29, 1) 50%,
|
|
53
|
-
// rgba(252, 176, 69, 1) 100%
|
|
54
|
-
// );
|
|
55
|
-
// background: -webkit-linear-gradient(
|
|
56
|
-
// 90deg,
|
|
57
|
-
// rgba(131, 58, 180, 1) 0%,
|
|
58
|
-
// rgba(253, 29, 29, 1) 50%,
|
|
59
|
-
// rgba(252, 176, 69, 1) 100%
|
|
60
|
-
// );
|
|
61
|
-
|
|
62
|
-
// background: linear-gradient(90deg, rgba(131, 58, 180, 1) 0%, rgba(253, 29, 29, 1) 50%, rgba(252, 176, 69, 1) 100%);
|
|
63
|
-
// filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#833ab4",endColorstr="#fcb045",GradientType=1);
|
|
64
|
-
|
|
65
|
-
// background-image: linear-gradient(to right top, #051937, #384164, #6b6e95, #a09ec9, #d9d1ff);
|
|
66
|
-
// background: #BAD7FF;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
@media screen and (max-width: 1200px) {
|
|
70
|
-
margin: 0px 5%;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
@media only screen and (min-width: 608px) {
|
|
74
|
-
justify-content: center;
|
|
75
|
-
align-items: center;
|
|
76
53
|
}
|
|
77
54
|
|
|
78
55
|
.homescreen {
|
|
79
56
|
width: 100%;
|
|
80
|
-
margin: 10px
|
|
57
|
+
margin: 10px 0;
|
|
81
58
|
border-radius: 4px;
|
|
82
59
|
background-color: aliceblue;
|
|
83
60
|
padding: 10px;
|
|
@@ -85,222 +62,267 @@ body {
|
|
|
85
62
|
overflow: hidden;
|
|
86
63
|
}
|
|
87
64
|
|
|
88
|
-
.customers
|
|
65
|
+
.customers,
|
|
66
|
+
.customers2 {
|
|
89
67
|
width: 50%;
|
|
90
|
-
margin: 20px
|
|
68
|
+
margin: 20px 0;
|
|
91
69
|
|
|
92
70
|
@media only screen and (min-width: 768px) {
|
|
93
71
|
width: 50%;
|
|
94
|
-
/* margin: 20px 0px; */
|
|
95
|
-
/* float: right; */
|
|
96
|
-
// position: absolute;
|
|
97
|
-
// right: 0px;
|
|
98
|
-
// top: 20%;
|
|
99
72
|
}
|
|
73
|
+
@media only screen and (max-width: 768px) {
|
|
74
|
+
width: 85%;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
100
77
|
|
|
78
|
+
.customers {
|
|
101
79
|
@media only screen and (min-width: 769px) {
|
|
102
80
|
display: none;
|
|
103
81
|
}
|
|
104
|
-
|
|
105
|
-
@media only screen and (max-width: 768px) {
|
|
106
|
-
width: 85%;
|
|
107
|
-
}
|
|
108
82
|
}
|
|
109
83
|
|
|
110
84
|
.customers2 {
|
|
111
|
-
width: 50%;
|
|
112
|
-
margin: 20px 0px;
|
|
113
85
|
padding: 0 20px;
|
|
114
|
-
|
|
115
86
|
@media only screen and (min-width: 768px) {
|
|
116
87
|
width: 40%;
|
|
117
|
-
/* margin: 20px 0px; */
|
|
118
|
-
/* float: right; */
|
|
119
|
-
// position: absolute;
|
|
120
|
-
// right: 0px;
|
|
121
|
-
// top: 20%;
|
|
122
88
|
}
|
|
123
|
-
|
|
124
89
|
@media only screen and (max-width: 768px) {
|
|
125
90
|
display: none;
|
|
126
91
|
}
|
|
127
92
|
}
|
|
128
|
-
.brand-logo {
|
|
129
|
-
width: 80px;
|
|
130
|
-
box-shadow:
|
|
131
|
-
1px 0 8px 0 rgba(0, 0, 0, 0.05),
|
|
132
|
-
8px 8px 18px 0 rgba(0, 0, 0, 0.05);
|
|
133
|
-
border: 1px solid #b7b7b7;
|
|
134
|
-
text-align: center;
|
|
135
|
-
border-radius: 2px;
|
|
136
|
-
margin: 22px 0px;
|
|
137
|
-
padding: 4px;
|
|
138
|
-
|
|
139
|
-
img {
|
|
140
|
-
width: 80px;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.footer-logo {
|
|
145
|
-
width: 180px;
|
|
146
|
-
// box-shadow: 1px 0 8px 0 rgba(0, 0, 0, 0.05), 8px 8px 18px 0 rgba(0, 0, 0, 0.05);
|
|
147
|
-
// border: 1px solid #b7b7b7;
|
|
148
|
-
padding: 4px;
|
|
149
|
-
border-radius: 2px;
|
|
150
|
-
margin: 20px 0px 30px;
|
|
151
|
-
}
|
|
152
93
|
|
|
153
94
|
.auth-form-wrapper {
|
|
154
|
-
// padding: 15px 10px;
|
|
155
95
|
box-sizing: border-box;
|
|
156
|
-
// box-shadow: 0 2px 4px rgba(104, 97, 97, 0.5);
|
|
157
|
-
// display: flex;
|
|
158
|
-
// align-items: center;
|
|
159
96
|
border-radius: 16px;
|
|
160
|
-
background-color: white;
|
|
97
|
+
background-color: $white;
|
|
161
98
|
overflow: hidden;
|
|
162
|
-
border: 1px solid
|
|
163
|
-
|
|
164
|
-
|
|
99
|
+
// border: 1px solid $border-color-light;
|
|
100
|
+
width: 40%;
|
|
101
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
|
165
102
|
|
|
166
103
|
@media only screen and (max-width: 768px) {
|
|
167
104
|
width: 100%;
|
|
168
105
|
padding: 0 16px;
|
|
169
|
-
|
|
170
|
-
// display: flex;
|
|
171
|
-
// flex-direction: column;
|
|
172
|
-
// justify-content: center;
|
|
173
|
-
// text-align: center;
|
|
174
|
-
// align-items: center;
|
|
106
|
+
flex-direction: column;
|
|
175
107
|
}
|
|
176
108
|
|
|
177
109
|
.login-form-container {
|
|
178
|
-
border: 1px solid
|
|
110
|
+
// border: 1px solid $border-color-medium;
|
|
179
111
|
flex-basis: 50%;
|
|
180
|
-
// border: none !important;
|
|
181
|
-
// box-shadow: none !important;
|
|
182
|
-
// padding: 20px 30px;
|
|
183
112
|
padding: 20px;
|
|
184
|
-
padding-top: 2px;
|
|
113
|
+
// padding-top: 2px;
|
|
185
114
|
|
|
186
115
|
@media only screen and (max-width: 768px) {
|
|
187
116
|
padding: 16px;
|
|
188
|
-
// display: flex;
|
|
189
|
-
// flex-direction: column;
|
|
190
|
-
// justify-content: center;
|
|
191
|
-
// text-align: center;
|
|
192
|
-
// align-items: center;
|
|
193
117
|
}
|
|
194
118
|
|
|
195
|
-
.branch-switcher {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
119
|
+
.branch-switcher .branches .ant-select {
|
|
120
|
+
min-width: auto !important;
|
|
121
|
+
width: 100%;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.brand-logo {
|
|
125
|
+
width: 80px;
|
|
126
|
+
box-shadow:
|
|
127
|
+
1px 0 8px 0 rgba(0, 0, 0, 0.05),
|
|
128
|
+
8px 8px 18px 0 rgba(0, 0, 0, 0.05);
|
|
129
|
+
border: 1px solid #b7b7b7;
|
|
130
|
+
text-align: center;
|
|
131
|
+
border-radius: 2px;
|
|
132
|
+
// margin: 22px 0;
|
|
133
|
+
padding: 4px;
|
|
134
|
+
|
|
135
|
+
img {
|
|
136
|
+
width: 80px;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.otp-form {
|
|
141
|
+
margin-top: 20px;
|
|
142
|
+
.otp-input-container {
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
gap: 2px;
|
|
146
|
+
|
|
147
|
+
p {
|
|
148
|
+
margin: 12px 0 0 0;
|
|
149
|
+
font-size: 12px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.otp-title {
|
|
153
|
+
@media only screen and (max-width: 768px) {
|
|
154
|
+
text-align: left;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.otp-method-section {
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
gap: 6px;
|
|
162
|
+
|
|
163
|
+
.otp-method-title {
|
|
164
|
+
font-size: 16px;
|
|
165
|
+
font-weight: 600;
|
|
166
|
+
margin-bottom: 8px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.otp-method-group {
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
margin-bottom: 10px;
|
|
173
|
+
gap: 30px;
|
|
174
|
+
font-size: 12px;
|
|
175
|
+
|
|
176
|
+
.ant-radio-wrapper {
|
|
177
|
+
display: flex;
|
|
178
|
+
gap: 6px;
|
|
179
|
+
|
|
180
|
+
svg {
|
|
181
|
+
font-size: 18px;
|
|
182
|
+
opacity: 0.8;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@media only screen and (max-width: 600px) {
|
|
187
|
+
flex-direction: column !important;
|
|
188
|
+
align-items: flex-start !important;
|
|
189
|
+
gap: 12px !important;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@media only screen and (min-width: 601px) and (max-width: 1024px) {
|
|
193
|
+
flex-direction: row !important;
|
|
194
|
+
align-items: center !important;
|
|
195
|
+
justify-content: space-between !important;
|
|
196
|
+
gap: 20px !important;
|
|
197
|
+
width: 100%;
|
|
198
|
+
|
|
199
|
+
.ant-radio-wrapper {
|
|
200
|
+
flex: 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.otp-mode-error {
|
|
206
|
+
margin: 5px;
|
|
207
|
+
font-size: 13px;
|
|
208
|
+
color: $error-color;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.otp-icon {
|
|
212
|
+
position: relative;
|
|
213
|
+
top: 2px;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.otp-container {
|
|
218
|
+
display: flex;
|
|
219
|
+
flex-direction: column;
|
|
220
|
+
gap: 7px;
|
|
221
|
+
|
|
222
|
+
input[type='text']:focus {
|
|
223
|
+
border-color: $white !important;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.otp-mode-text {
|
|
228
|
+
text-transform: capitalize;
|
|
200
229
|
}
|
|
201
230
|
}
|
|
202
231
|
}
|
|
203
232
|
}
|
|
204
233
|
|
|
205
|
-
.
|
|
206
|
-
|
|
207
|
-
|
|
234
|
+
.otp-actions {
|
|
235
|
+
background-color: $bg-color-light;
|
|
236
|
+
padding: 12px 16px;
|
|
237
|
+
display: flex;
|
|
238
|
+
justify-content: space-between;
|
|
239
|
+
gap: 6px;
|
|
240
|
+
|
|
241
|
+
.resend-action {
|
|
242
|
+
margin-bottom: 8px;
|
|
243
|
+
display: flex;
|
|
244
|
+
gap: 6px;
|
|
245
|
+
|
|
246
|
+
.resend-otp-link {
|
|
247
|
+
display: flex;
|
|
248
|
+
justify-content: center;
|
|
249
|
+
align-items: center;
|
|
250
|
+
text-align: center;
|
|
251
|
+
color: $primary-color;
|
|
252
|
+
margin-bottom: 10px;
|
|
253
|
+
margin: auto;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
208
256
|
}
|
|
209
257
|
|
|
210
258
|
.center-line {
|
|
259
|
+
border-left: 2px solid $border-color-dark;
|
|
260
|
+
height: 180px;
|
|
261
|
+
|
|
211
262
|
@media only screen and (max-width: 768px) {
|
|
212
263
|
display: none;
|
|
213
264
|
}
|
|
214
265
|
}
|
|
215
|
-
|
|
216
|
-
@media only screen and (max-width: 768px) {
|
|
217
|
-
padding: 0;
|
|
218
|
-
// min-width: 275px;
|
|
219
|
-
flex-direction: column;
|
|
220
|
-
}
|
|
221
|
-
.form-title {
|
|
222
|
-
// h4 {
|
|
223
|
-
// color: #071822;
|
|
224
|
-
// font-weight: 600;
|
|
225
|
-
// // size: 13px;
|
|
226
|
-
// margin: 10px 0px;
|
|
227
|
-
// font-size: 20px;
|
|
228
|
-
|
|
229
|
-
// @media only screen and (max-width: 1400px) {
|
|
230
|
-
// line-height: 30px;
|
|
231
|
-
// }
|
|
232
|
-
}
|
|
233
266
|
}
|
|
267
|
+
|
|
268
|
+
// Scoped Ant Design Overrides
|
|
234
269
|
.ant-form {
|
|
235
|
-
|
|
270
|
+
.ant-form-item:not(:last-child) {
|
|
271
|
+
margin: 20px 0;
|
|
272
|
+
}
|
|
236
273
|
.ant-form-item {
|
|
237
|
-
margin: 20px
|
|
274
|
+
margin: 20px 0;
|
|
275
|
+
|
|
238
276
|
.ant-form-item-label {
|
|
239
277
|
margin: 0 0 8px 0;
|
|
240
278
|
line-height: 0;
|
|
241
279
|
padding: 0;
|
|
280
|
+
|
|
281
|
+
> label {
|
|
282
|
+
color: var(--custom-text-color);
|
|
283
|
+
}
|
|
242
284
|
}
|
|
243
285
|
}
|
|
244
|
-
|
|
245
|
-
|
|
286
|
+
|
|
287
|
+
.ant-form-explain {
|
|
288
|
+
position: absolute;
|
|
289
|
+
font-size: 12px;
|
|
290
|
+
margin-left: 2px;
|
|
246
291
|
}
|
|
247
292
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
margin-left: 2px;
|
|
293
|
+
|
|
294
|
+
.ant-form-vertical .ant-form-item .ant-form-item-control {
|
|
295
|
+
min-width: 100%;
|
|
252
296
|
}
|
|
297
|
+
|
|
253
298
|
.ant-btn-primary {
|
|
254
|
-
|
|
255
|
-
background-color:
|
|
256
|
-
// height: 35px;
|
|
299
|
+
margin-top: 10px;
|
|
300
|
+
background-color: $primary-color;
|
|
257
301
|
border-radius: 4px;
|
|
258
302
|
width: 100%;
|
|
259
|
-
@media only screen and (max-width: 768px) {
|
|
260
|
-
width: 100%;
|
|
261
|
-
background-color: #0c66e4;
|
|
262
|
-
// min-width: 275px;
|
|
263
|
-
}
|
|
264
303
|
}
|
|
304
|
+
|
|
265
305
|
.ant-btn-secondary {
|
|
266
306
|
width: 100%;
|
|
267
307
|
}
|
|
268
|
-
}
|
|
269
|
-
.otp-container {
|
|
270
|
-
display: flex;
|
|
271
|
-
}
|
|
272
308
|
|
|
273
|
-
.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
.otp-input-container .otp-title {
|
|
279
|
-
@media only screen and (max-width: 768px) {
|
|
280
|
-
text-align: left;
|
|
309
|
+
.ant-divider-horizontal {
|
|
310
|
+
margin-bottom: 16px;
|
|
311
|
+
margin-top: 2px;
|
|
281
312
|
}
|
|
282
|
-
}
|
|
283
|
-
.ant-divider-horizontal {
|
|
284
|
-
margin-bottom: 16px;
|
|
285
|
-
margin-top: 2px;
|
|
286
|
-
}
|
|
287
313
|
|
|
288
|
-
.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
align-items: center;
|
|
292
|
-
text-align: center;
|
|
293
|
-
// margin-top: -10px;
|
|
294
|
-
color: #0c66e4;
|
|
295
|
-
margin-bottom: 10px;
|
|
296
|
-
}
|
|
314
|
+
.ant-input {
|
|
315
|
+
background-color: transparent !important;
|
|
316
|
+
}
|
|
297
317
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
318
|
+
input {
|
|
319
|
+
border: 1px solid $border-color-dark;
|
|
320
|
+
border-radius: 2px;
|
|
301
321
|
|
|
302
|
-
|
|
303
|
-
|
|
322
|
+
&:focus {
|
|
323
|
+
border-color: $white !important;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
304
326
|
}
|
|
305
327
|
|
|
306
328
|
.footer {
|
|
@@ -312,147 +334,11 @@ body {
|
|
|
312
334
|
@media only screen and (max-width: 768px) {
|
|
313
335
|
display: none;
|
|
314
336
|
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
.otp-input-container {
|
|
318
|
-
display: flex;
|
|
319
|
-
flex-direction: column;
|
|
320
|
-
gap: 2px;
|
|
321
|
-
}
|
|
322
|
-
.otp-actions {
|
|
323
|
-
background-color: #f5f5f5; // light gray
|
|
324
|
-
padding: 12px 16px;
|
|
325
|
-
display: flex;
|
|
326
|
-
justify-content: space-between;
|
|
327
|
-
gap: 6px;
|
|
328
|
-
// margin-top: 6px;
|
|
329
|
-
}
|
|
330
|
-
.resend-action {
|
|
331
|
-
margin-bottom: 8px;
|
|
332
|
-
display: flex;
|
|
333
|
-
gap: 6px;
|
|
334
|
-
}
|
|
335
|
-
.otp-method-section {
|
|
336
|
-
// margin-top: 10px;
|
|
337
|
-
|
|
338
|
-
display: flex;
|
|
339
|
-
flex-direction: column;
|
|
340
|
-
gap: 6px;
|
|
341
|
-
|
|
342
|
-
.otp-method-title {
|
|
343
|
-
font-size: 16px;
|
|
344
|
-
font-weight: 600;
|
|
345
|
-
margin-bottom: 8px;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
.otp-method-group {
|
|
349
|
-
display: flex;
|
|
350
|
-
align-items: center;
|
|
351
|
-
margin-bottom: 10px;
|
|
352
|
-
gap: 30px;
|
|
353
|
-
font-size: 12px;
|
|
354
|
-
}
|
|
355
|
-
.otp-icon {
|
|
356
|
-
position: relative;
|
|
357
|
-
top: 2px;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
.ant-radio-wrapper {
|
|
361
|
-
display: flex;
|
|
362
|
-
// align-items: center;
|
|
363
|
-
gap: 6px;
|
|
364
|
-
|
|
365
|
-
svg {
|
|
366
|
-
font-size: 18px;
|
|
367
|
-
opacity: 0.8;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
.otp-mode-error {
|
|
372
|
-
margin: 5px;
|
|
373
|
-
font-size: 13px;
|
|
374
|
-
color: red;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
.otp-mode-text {
|
|
379
|
-
text-transform: capitalize;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
input {
|
|
383
|
-
border: 1px solid #d9d9d9;
|
|
384
|
-
border-radius: 2px;
|
|
385
|
-
// padding: 30px;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
input:focus {
|
|
389
|
-
border-color: #fff !important;
|
|
390
|
-
}
|
|
391
|
-
.otp-container {
|
|
392
|
-
display: flex;
|
|
393
|
-
flex-direction: column;
|
|
394
|
-
gap: 7px;
|
|
395
|
-
}
|
|
396
337
|
|
|
397
|
-
.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
height: 100vh;
|
|
403
|
-
background-color: 'cover'; // Ensures full coverage
|
|
404
|
-
background-position: 'bottom center'; // Aligns image at the bottom
|
|
405
|
-
background-size: '100% auto'; // image spans full width
|
|
406
|
-
background-repeat: no-repeat;
|
|
407
|
-
|
|
408
|
-
min-height: 100vh; // Ensures full height
|
|
409
|
-
width: 100%; // Covers the full width
|
|
410
|
-
}
|
|
411
|
-
.ant-input {
|
|
412
|
-
background-color: transparent !important;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
.ant-form-item-label > label {
|
|
416
|
-
color: var(--custom-text-color);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// .user-authentication-section .auth-form-wrapper .ant-btn-primary {
|
|
420
|
-
// background-color: var(--custom-bg-color);
|
|
421
|
-
// color: var(--custom-btn-text-color);
|
|
422
|
-
// }
|
|
423
|
-
// .ant-input {
|
|
424
|
-
// background-color: var(--custom-input-bg-color);
|
|
425
|
-
// color: var(--custom-input-color);
|
|
426
|
-
// }
|
|
427
|
-
/* Mobile: stack OTP radio options in separate rows */
|
|
428
|
-
/* Mobile phones (iPhone) – stack in one column */
|
|
429
|
-
@media only screen and (max-width: 600px) {
|
|
430
|
-
.otp-method-group {
|
|
431
|
-
flex-direction: column !important;
|
|
432
|
-
align-items: flex-start !important;
|
|
433
|
-
gap: 12px !important;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/* iPad Mini + Tablets (600px to 1024px) – show in row */
|
|
438
|
-
@media only screen and (min-width: 601px) and (max-width: 1024px) {
|
|
439
|
-
.otp-method-group {
|
|
440
|
-
flex-direction: row !important;
|
|
441
|
-
align-items: center !important;
|
|
442
|
-
justify-content: space-between !important;
|
|
443
|
-
gap: 20px !important;
|
|
444
|
-
width: 100%;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
.otp-method-group .ant-radio-wrapper {
|
|
448
|
-
flex: 1; /* evenly spaced columns */
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/* Desktop – normal layout */
|
|
453
|
-
@media only screen and (min-width: 1025px) {
|
|
454
|
-
.otp-method-group {
|
|
455
|
-
flex-direction: row;
|
|
456
|
-
gap: 30px;
|
|
338
|
+
.footer-logo {
|
|
339
|
+
width: 180px;
|
|
340
|
+
padding: 4px;
|
|
341
|
+
border-radius: 2px;
|
|
342
|
+
margin: 20px 0 30px;
|
|
457
343
|
}
|
|
458
344
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResetPassword Component
|
|
3
|
+
*
|
|
4
|
+
* Handles staff password reset flow:
|
|
5
|
+
* - Accepts username
|
|
6
|
+
* - Selects communication mode (email/SMS)
|
|
7
|
+
* - Calls API to send reset link
|
|
8
|
+
* - Displays success/error messages
|
|
9
|
+
* - Allows navigation back to login
|
|
10
|
+
*
|
|
11
|
+
* Props:
|
|
12
|
+
* @param {Function} onBack - Triggered when "Back to Login" is clicked
|
|
13
|
+
* @param {string} title - Page title
|
|
14
|
+
* @param {string} subtitle - Page subtitle
|
|
15
|
+
* @param {string} buttonText - Submit button text
|
|
16
|
+
* @param {string} defaultUsername - Pre-filled username
|
|
17
|
+
* @param {boolean} disabledUserName - Disable username field
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import React, { useState, useEffect } from 'react';
|
|
21
|
+
import { Divider, Form, Input, message } from 'antd';
|
|
22
|
+
import { Button } from '../../elements';
|
|
23
|
+
import CommunicationModeSelection from './commnication-mode-selection';
|
|
24
|
+
import { motion } from 'framer-motion';
|
|
25
|
+
import './reset-password.scss';
|
|
26
|
+
import { UsersAPI } from '../../../models';
|
|
27
|
+
|
|
28
|
+
function ResetPassword({ onBack, title, subtitle, buttonText, defaultUsername, disabledUserName }) {
|
|
29
|
+
// Selected communication mode (default: email) */
|
|
30
|
+
const [communicationMode, setCommunicationMode] = useState('email');
|
|
31
|
+
|
|
32
|
+
const [modeError, setModeError] = useState(false);
|
|
33
|
+
|
|
34
|
+
// Loading state for submit button */
|
|
35
|
+
const [loading, setLoading] = useState(false);
|
|
36
|
+
|
|
37
|
+
const [form] = Form.useForm();
|
|
38
|
+
|
|
39
|
+
// Pre-fills username if provided and disabled.
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (disabledUserName && defaultUsername) {
|
|
42
|
+
form.setFieldsValue({ username: defaultUsername });
|
|
43
|
+
}
|
|
44
|
+
}, [disabledUserName, defaultUsername]);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Sends forgot password request to backend.
|
|
48
|
+
* Payload: { mode, username, user_type: 'staff' }
|
|
49
|
+
*/
|
|
50
|
+
const handleSendForgetPassword = async (values) => {
|
|
51
|
+
const payload = {
|
|
52
|
+
mode: communicationMode,
|
|
53
|
+
username: values.username,
|
|
54
|
+
user_type: 'staff',
|
|
55
|
+
};
|
|
56
|
+
setLoading(true);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const res = await UsersAPI.createForgotePassword(payload);
|
|
60
|
+
|
|
61
|
+
if (!res?.success) {
|
|
62
|
+
throw new Error(res?.message || 'Reset password failed');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
message.success(res?.message || 'Reset password link sent');
|
|
66
|
+
} catch (err) {
|
|
67
|
+
message.warning(err?.message || 'Reset password failed');
|
|
68
|
+
} finally {
|
|
69
|
+
setLoading(false);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Clears auth storage and navigates back.
|
|
75
|
+
*/
|
|
76
|
+
const handleBackToLogin = () => {
|
|
77
|
+
localStorage.removeItem('access_token');
|
|
78
|
+
localStorage.removeItem('refresh_token');
|
|
79
|
+
sessionStorage.clear();
|
|
80
|
+
onBack();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<motion.div
|
|
85
|
+
className="forgot-password-container"
|
|
86
|
+
initial={{ opacity: 0, y: 70 }}
|
|
87
|
+
animate={{ opacity: 1, y: 0 }}
|
|
88
|
+
exit={{ opacity: 0, y: -50 }}
|
|
89
|
+
transition={{
|
|
90
|
+
y: {
|
|
91
|
+
duration: 1,
|
|
92
|
+
ease: [0.22, 0.08, 0.26, 1],
|
|
93
|
+
},
|
|
94
|
+
opacity: {
|
|
95
|
+
duration: 0.45,
|
|
96
|
+
ease: 'easeOut',
|
|
97
|
+
},
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
<h3 className="password-title">{title}</h3>
|
|
101
|
+
<p className="password-subtitle">{subtitle}</p>
|
|
102
|
+
<Divider />
|
|
103
|
+
<Form layout="vertical" form={form} onFinish={handleSendForgetPassword}>
|
|
104
|
+
<Form.Item label="Username" name="username" rules={[{ required: true, message: 'Please input your username' }]}>
|
|
105
|
+
<Input placeholder="Enter your username" disabled={disabledUserName} />
|
|
106
|
+
</Form.Item>
|
|
107
|
+
|
|
108
|
+
<CommunicationModeSelection communicationMode={communicationMode} setCommunicationMode={setCommunicationMode} modeError={modeError} />
|
|
109
|
+
|
|
110
|
+
<Button type="primary" htmlType="submit" loading={loading}>
|
|
111
|
+
{buttonText}
|
|
112
|
+
</Button>
|
|
113
|
+
|
|
114
|
+
<div style={{ marginTop: '10px', textAlign: 'center' }}>
|
|
115
|
+
<a style={{ cursor: 'pointer' }} onClick={handleBackToLogin}>
|
|
116
|
+
Back to Login
|
|
117
|
+
</a>
|
|
118
|
+
</div>
|
|
119
|
+
</Form>
|
|
120
|
+
</motion.div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default ResetPassword;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.forgot-password-container {
|
|
2
|
+
|
|
3
|
+
padding: 4px;
|
|
4
|
+
.password-title{
|
|
5
|
+
color: #1f1f1f;
|
|
6
|
+
margin: 0 0 8px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.password-subtitle {
|
|
10
|
+
font-size: 14px;
|
|
11
|
+
font-weight: 400;
|
|
12
|
+
color: #6b7280;
|
|
13
|
+
line-height: 1.5;
|
|
14
|
+
margin: 0 0 20px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ant-divider {
|
|
18
|
+
margin: 0;
|
|
19
|
+
border-top: 1px solid #e5e7eb;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
@@ -122,6 +122,42 @@ export const checkLicenseStatus = (expiryDate) => {
|
|
|
122
122
|
return { valid: true, daysLeft, message: null, level: null };
|
|
123
123
|
};
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Checks password expiry status.
|
|
127
|
+
*
|
|
128
|
+
* @param {string|Date} expiryDate
|
|
129
|
+
* @param {number} warningDays
|
|
130
|
+
* @param {string} expiredMessage
|
|
131
|
+
* @param {(daysLeft: number) => string} warningMessage
|
|
132
|
+
*
|
|
133
|
+
* @returns {{ valid: boolean, daysLeft: number, message: string|null, level: "error"|"warning"|null }}
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
export const checkExpiryStatus = ({ expiryDate, warningDays, expiredMessage, warningMessage }) => {
|
|
137
|
+
const expiry = new Date(expiryDate);
|
|
138
|
+
|
|
139
|
+
if (isNaN(expiry)) {
|
|
140
|
+
return { valid: false, daysLeft: 0, message: 'Invalid date', level: 'error' };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
expiry.setHours(0, 0, 0, 0);
|
|
144
|
+
const today = new Date();
|
|
145
|
+
today.setHours(0, 0, 0, 0);
|
|
146
|
+
|
|
147
|
+
const msDiff = expiry.getTime() - today.getTime();
|
|
148
|
+
const daysLeft = Math.ceil(msDiff / (1000 * 60 * 60 * 24));
|
|
149
|
+
|
|
150
|
+
if (daysLeft < 0) {
|
|
151
|
+
return { valid: false, daysLeft, message: expiredMessage, level: 'error' };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (daysLeft <= warningDays) {
|
|
155
|
+
return { valid: true, daysLeft, message: warningMessage(daysLeft), level: 'warning' };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { valid: true, daysLeft, message: null, level: null };
|
|
159
|
+
};
|
|
160
|
+
|
|
125
161
|
/**
|
|
126
162
|
* Masks a mobile number by hiding all but the last `visibleDigits`.
|
|
127
163
|
* Adds spacing only to the masked portion (groups of 3),
|
|
@@ -359,6 +359,13 @@ class Users extends Base {
|
|
|
359
359
|
url: 'staff/get-all-staff',
|
|
360
360
|
});
|
|
361
361
|
};
|
|
362
|
+
|
|
363
|
+
createForgotePassword = (formBody) => {
|
|
364
|
+
return ApiUtils.post({
|
|
365
|
+
url: `bookings/trigger-reset-password-link`,
|
|
366
|
+
formBody,
|
|
367
|
+
});
|
|
368
|
+
};
|
|
362
369
|
}
|
|
363
370
|
|
|
364
371
|
export default Users;
|