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.
@@ -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, headers } = appSettings;
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); // <--- fix
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
- message.error(result.message);
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
- backgroundImage: `url(${backgroundImage}), ${state.theme.colors.loginPageBackground}`,
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
  &nbsp;&nbsp; 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>