ui-soxo-bootstrap-core 2.4.24 → 2.4.25-dev.6

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.
Files changed (33) hide show
  1. package/.github/workflows/npm-publish.yml +37 -15
  2. package/README.md +260 -0
  3. package/core/components/extra-info/extra-info-details.js +109 -126
  4. package/core/components/landing-api/landing-api.js +22 -30
  5. package/core/lib/Store.js +20 -18
  6. package/core/lib/components/index.js +4 -1
  7. package/core/lib/components/sidemenu/sidemenu.js +153 -256
  8. package/core/lib/components/sidemenu/sidemenu.scss +39 -26
  9. package/core/lib/hooks/index.js +2 -12
  10. package/core/lib/hooks/use-otp-timer.js +99 -0
  11. package/core/lib/pages/login/login.js +255 -139
  12. package/core/lib/pages/login/login.scss +140 -32
  13. package/core/models/dashboard/dashboard.js +14 -0
  14. package/core/models/doctor/components/doctor-add/doctor-add.js +403 -0
  15. package/core/models/doctor/components/doctor-add/doctor-add.scss +32 -0
  16. package/core/models/menus/components/menu-add/menu-add.js +230 -268
  17. package/core/models/menus/components/menu-lists/menu-lists.js +126 -89
  18. package/core/models/menus/components/menu-lists/menu-lists.scss +9 -0
  19. package/core/models/menus/menus.js +247 -267
  20. package/core/models/roles/components/role-add/role-add.js +269 -227
  21. package/core/models/roles/components/role-list/role-list.js +8 -6
  22. package/core/models/roles/roles.js +182 -174
  23. package/core/models/users/components/user-add/user-add.js +619 -365
  24. package/core/models/users/components/user-add/user-edit.js +90 -0
  25. package/core/models/users/users.js +261 -165
  26. package/core/modules/index.js +5 -8
  27. package/core/modules/reporting/components/index.js +5 -0
  28. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +65 -2
  29. package/core/modules/steps/action-buttons.js +79 -0
  30. package/core/modules/steps/steps.js +553 -0
  31. package/core/modules/steps/steps.scss +158 -0
  32. package/core/modules/steps/timeline.js +49 -0
  33. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useContext, useEffect } from 'react';
2
2
 
3
- import { Form, Input, message, Result } from 'antd';
3
+ import { Form, Input, message, Result, Radio, Divider, Typography } from 'antd';
4
4
 
5
5
  import { withRouter, Link } from 'react-router-dom';
6
6
 
@@ -8,6 +8,8 @@ import backgroundImage from './../../../assets/images/vector.png';
8
8
 
9
9
  import OTPInput from 'otp-input-react';
10
10
 
11
+ import { useOtpTimer } from '../../hooks/use-otp-timer';
12
+
11
13
  import { motion } from 'framer-motion';
12
14
 
13
15
  import { Button } from '../../elements';
@@ -30,6 +32,10 @@ import { Location } from '../../utils';
30
32
 
31
33
  import { checkLicenseStatus } from '../../utils/common/common.utils';
32
34
 
35
+ import { MailOutlined, MessageOutlined, WhatsAppOutlined } from '@ant-design/icons';
36
+
37
+ const { Text, Title } = Typography;
38
+
33
39
  const layout = {
34
40
  labelCol: { span: 24 },
35
41
 
@@ -50,7 +56,10 @@ const LICENSE_EXPIRY = '2026-12-12';
50
56
  function LoginPhone({ history, appSettings }) {
51
57
  const { brandLogo, heroImage, footerLogo, headers } = appSettings;
52
58
 
53
- const { dispatch, state } = useContext(GlobalContext);
59
+ // Hook for OTP Timer
60
+ const { expired: otpExpired, formatted, startFromExpiry } = useOtpTimer();
61
+
62
+ const { dispatch, state, isMobile } = useContext(GlobalContext);
54
63
  // variabel used for btnloading
55
64
 
56
65
  const [loading, setLoading] = useState(false);
@@ -63,11 +72,19 @@ function LoginPhone({ history, appSettings }) {
63
72
  // variabale used to show otp entering screen
64
73
 
65
74
  const [otpVerification, setotpVerification] = useState(false);
75
+ // otp sucess
76
+ const [otpSuccess, setOtpSuccess] = useState(false);
77
+ // otp fail err
78
+ const [otpError, setOtpError] = useState(false);
66
79
 
67
80
  const [otpValue, setOtpValue] = useState('');
68
81
  // setting user credentials
69
82
  const [user, setUser] = useState([]);
70
83
 
84
+ // for communication mode [mobile,sms,email]
85
+ const [communicationMode, setCommunicationMode] = useState(null); // default selected email
86
+ const [modeError, setModeError] = useState(false);
87
+
71
88
  const isAuthenticated = Boolean(getAccessToken());
72
89
  const isRefreshTokenExist = Boolean(getRefreshToken());
73
90
 
@@ -125,6 +142,10 @@ function LoginPhone({ history, appSettings }) {
125
142
 
126
143
  // set user values
127
144
  setUser(data);
145
+ // Set default communication mode automatically
146
+ if (result.data.mode) {
147
+ setCommunicationMode(result.data.mode); // <--- fix
148
+ }
128
149
  } else {
129
150
  let d = user;
130
151
 
@@ -206,12 +227,22 @@ function LoginPhone({ history, appSettings }) {
206
227
  *
207
228
  */
208
229
  const sendAuthenticationOtp = () => {
230
+ // Reset previous error
231
+ setModeError(false);
232
+
233
+ // Validate communication mode
234
+ if (!communicationMode) {
235
+ setModeError(true); // Show error
236
+ return; // Stop OTP send
237
+ }
238
+
209
239
  setLoading(true);
240
+
210
241
  // set formbody
211
242
  let formBody;
212
243
  formBody = {
213
244
  email: user.username,
214
- mode: user.mode,
245
+ mode: communicationMode,
215
246
  };
216
247
 
217
248
  // api used to send otp corresponding mail address
@@ -222,6 +253,8 @@ function LoginPhone({ history, appSettings }) {
222
253
  }).then((result) => {
223
254
  // if the api is sucess then go for otpverification step
224
255
  if (result.success) {
256
+ // for expiry_time
257
+ startFromExpiry(result?.expiry_time);
225
258
  // if the api is sucess then go for otpverification step
226
259
  setotpVerification(true);
227
260
  // set button loading false
@@ -239,13 +272,17 @@ function LoginPhone({ history, appSettings }) {
239
272
  */
240
273
 
241
274
  const verifyOtp = () => {
275
+ // If OTP expired, block verification
276
+ if (otpExpired) {
277
+ return;
278
+ }
279
+
242
280
  setLoading(true);
243
- let formBody;
244
- // set formBody
245
- formBody = {
281
+
282
+ const formBody = {
246
283
  username: user.username,
247
284
  password: user.password,
248
- mode: user.mode,
285
+ mode: communicationMode,
249
286
  mobile: user.mobile,
250
287
  otp: otpValue,
251
288
  };
@@ -254,35 +291,46 @@ function LoginPhone({ history, appSettings }) {
254
291
  url: 'auth/verify-authentication-otp',
255
292
  formBody,
256
293
  headers,
257
- }).then((result) => {
258
- if (result.success) {
259
- setLoading(false);
260
- let d = result.user;
261
-
262
- // Setting access_token
263
- if (result?.access_token) localStorage.access_token = result.access_token;
264
- // Setting refresh_token
265
- if (result.refresh_token) localStorage.setItem('refresh_token', result.refresh_token);
266
-
267
- // set user info
268
- let userInfo = {
269
- id: result.user.id,
270
- locations: [],
271
- ...d,
272
- ...{
294
+ hideError: true, // removing unwanted error msg
295
+ })
296
+ .then((result) => {
297
+ if (result.success) {
298
+ // OTP success
299
+ setOtpSuccess(true);
300
+ setOtpError(false);
301
+ setLoading(false);
302
+
303
+ const userInfo = {
304
+ id: result.user.id,
305
+ locations: [],
306
+ ...result.user,
273
307
  loggedCheckDone: true,
274
- },
275
- };
308
+ };
309
+ // Setting access_token
310
+ if (result?.access_token) localStorage.access_token = result.access_token;
311
+ // Setting refresh_token
312
+ if (result.refresh_token) localStorage.setItem('refresh_token', result.refresh_token);
313
+
314
+ dispatch({ type: 'user', payload: userInfo });
315
+ // set user info into local storage
316
+ localStorage.setItem('userInfo', JSON.stringify(userInfo));
317
+
318
+ setTimeout(() => history.push('/'), 500);
319
+ } else {
320
+ // OTP FAILED (wrong OTP)
321
+ setOtpSuccess(false);
322
+ setOtpError(true);
323
+ setOtpValue(''); // Clear OTP
324
+ setLoading(false);
325
+ }
326
+ })
327
+ .catch((error) => {
328
+ // Server error, timeout, network fail → treat as failed OTP
329
+ setOtpSuccess(false);
330
+ setOtpError(true);
276
331
 
277
- dispatch({ type: 'user', payload: userInfo });
278
- // set user info into local storage
279
- localStorage.setItem('userInfo', JSON.stringify(userInfo));
280
- history.push('/');
281
- } else {
282
332
  setLoading(false);
283
- message.error(result.message);
284
- }
285
- });
333
+ });
286
334
  };
287
335
 
288
336
  const onFinishFailed = () => {
@@ -297,18 +345,21 @@ function LoginPhone({ history, appSettings }) {
297
345
  */
298
346
  const handleOtpChange = (value) => {
299
347
  setOtpValue(value);
348
+ // Reset states so correct OTP will work after wrong OTP
349
+ setOtpError(false);
350
+ setOtpSuccess(false);
300
351
  };
301
352
 
302
353
  /**
303
354
  * Otp resend Logic
304
355
  */
305
356
  const handleResendOTP = () => {
306
- // Resend OTP logic
307
- setOtpVisible(true);
308
- setotpVerification(false);
309
-
310
- // when resend the otp . otpvalue reset
311
357
  setOtpValue('');
358
+ setModeError(false);
359
+ setOtpError(false);
360
+ setOtpSuccess(false);
361
+ // resend the OTP automatically
362
+ sendAuthenticationOtp();
312
363
  };
313
364
 
314
365
  /**
@@ -317,10 +368,15 @@ function LoginPhone({ history, appSettings }) {
317
368
  const handlecancel = () => {
318
369
  setOtpVisible(false);
319
370
  setotpVerification(false);
371
+ setOtpValue('');
372
+ setModeError(false);
373
+ setOtpError(false);
320
374
  };
321
375
 
322
376
  // Redirect Home Page, When token is exist
323
377
  useEffect(() => {
378
+ // Do NOT redirect when OTP is visible or verification is happening
379
+ if (otpVerification) return;
324
380
  if (isAuthenticated && isRefreshTokenExist && path === '/login') {
325
381
  Location.navigate({ url: '/' });
326
382
  } else {
@@ -336,6 +392,14 @@ function LoginPhone({ history, appSettings }) {
336
392
  // document.documentElement.style.setProperty('--custom-input-bg-color', state.theme.colors.inputBgColor);
337
393
  // document.documentElement.style.setProperty('--custom-input-color', state.theme.colors.inputColor);
338
394
  }, [state.theme]);
395
+
396
+ // when otp is expired
397
+ useEffect(() => {
398
+ if (otpExpired) {
399
+ setOtpValue(''); // Clear OTP box
400
+ setOtpError(false);
401
+ }
402
+ }, [otpExpired]);
339
403
  /**
340
404
  * Function to get Ldap users
341
405
  * @returns
@@ -445,6 +509,13 @@ function LoginPhone({ history, appSettings }) {
445
509
  });
446
510
  };
447
511
 
512
+ // helper to show contact in OTP verification screen
513
+ const getOtpDestinationText = () => {
514
+ if (communicationMode === 'mobile') return user.mobile;
515
+ // if (communicationMode === 'whatsapp') return user.mobile;
516
+ return user.username;
517
+ };
518
+
448
519
  const { globalCustomerHeader = () => {} } = appSettings;
449
520
 
450
521
  const themeName = process.env.REACT_APP_THEME; // e.g., 'purple'
@@ -469,20 +540,7 @@ function LoginPhone({ history, appSettings }) {
469
540
  };
470
541
 
471
542
  return (
472
- <section
473
- className="full-page"
474
- style={
475
- sectionStyle
476
- // {
477
- // width: '100%',
478
- // height: '100vh',
479
- // backgroundImage: `url(${backgroundImage}), ${state.theme.colors.loginPageBackground}`,
480
- // backgroundPosition: 'center bottom, center',
481
- // backgroundRepeat: 'no-repeat, no-repeat',
482
- // backgroundSize: 'cover, cover',
483
- // }
484
- }
485
- >
543
+ <section className="full-page" style={sectionStyle}>
486
544
  <section className="user-authentication-section">
487
545
  {/* Background Section */}
488
546
  <motion.div
@@ -491,7 +549,7 @@ function LoginPhone({ history, appSettings }) {
491
549
  animate={{ y: 0, opacity: 1 }}
492
550
  transition={{ duration: 1, ease: 'anticipate' }}
493
551
  exit={{ opacity: 0 }}
494
- ></motion.div>
552
+ />
495
553
  {/* Background Section Ends */}
496
554
 
497
555
  <motion.div
@@ -502,112 +560,170 @@ function LoginPhone({ history, appSettings }) {
502
560
  exit={{ opacity: 0 }}
503
561
  >
504
562
  <div
505
- className="card card-shadow login-form-container"
506
- style={{ background: state.theme.colors.tableBg, borderStyle: state.theme.colors.tableBorderStyle }}
563
+ className=" login-form-container"
564
+ style={{
565
+ background: state.theme.colors.tableBg,
566
+ borderStyle: state.theme.colors.tableBorderStyle,
567
+ }}
507
568
  >
508
569
  <div className="brand-logo">
509
570
  <img className="logo-welcome" src={brandLogo} alt="Logo" />
510
571
  </div>
511
572
 
512
573
  <div className="otp-form">
513
- {/* Two factor authentication section start */}
514
- {otpVerification ? (
574
+ {/* OTP Verification Section */}
575
+
576
+ {/* OTP Method Selection Section */}
577
+ {otpVisible && (
515
578
  <div className="otp-input-container">
516
579
  <div className="form-title">
517
- <h3>Verify your account</h3>
518
- <p>
519
- An OTP has been sent to your <span className="otp-mode-text">{user.mode ? user.mode : 'Email'}</span>
520
- <br />
521
- </p>
522
- </div>
523
- <p className="otp-title">ENTER OTP</p>
524
- <div className="otp-container">
525
- <OTPInput value={otpValue} onChange={handleOtpChange} autoFocus OTPLength={6} />
580
+ {otpVerification ? (
581
+ <>
582
+ {/* <h3>OTP Verification</h3> */}
583
+ {/* <p>
584
+ Enter the verification code we sent to{' '}
585
+ <span className="otp-mode-text">
586
+ <strong>{getOtpDestinationText()}</strong>
587
+ </span>
588
+ </p> */}
589
+ </>
590
+ ) : (
591
+ <>
592
+ <Text type="primary">Two Factor Authentication</Text>
593
+ <p>
594
+ Two-Factor Authentication is enabled for your account. Please verify your account before logging in to enhance security.
595
+ </p>
596
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
597
+ </>
598
+ )}
526
599
  </div>
527
- <Button type="primary" className="SubmitBtn" onClick={verifyOtp}>
528
- <i className="fa-regular fa-circle-check"></i> Verify OTP
529
- </Button>
530
- <Link className="resend-otp-link" onClick={handleResendOTP}>
531
- <i className="fa-solid fa-arrows-rotate"></i> Resend OTP
532
- </Link>
533
- {/* Two factor authentication section end */}
600
+
601
+ {/* Show Communication Method ONLY IF:
602
+ - OTP not sent OR
603
+ - OTP expired
604
+ */}
605
+
606
+ {(!otpVerification || otpExpired) && (
607
+ <>
608
+ <Divider />
609
+ <div className="otp-method-section">
610
+ <Text type="primary">Select Preferred OTP Verification Method</Text>
611
+ <div className="otp-method-group">
612
+ <Radio checked={communicationMode === 'email'} onChange={() => setCommunicationMode('email')}>
613
+ Email <MailOutlined style={{ marginLeft: 6 }} />
614
+ </Radio>
615
+ <Radio checked={communicationMode === 'mobile'} onChange={() => setCommunicationMode('mobile')}>
616
+ SMS <MessageOutlined style={{ marginLeft: 6 }} />
617
+ </Radio>
618
+ </div>
619
+ {modeError && <p className="otp-mode-error">Please select a communication mode.</p>}
620
+ </div>
621
+ </>
622
+ )}
623
+
624
+ {!otpVerification ? (
625
+ <div className="otp-container">
626
+ <Button type="primary" onClick={sendAuthenticationOtp} style={{ marginTop: '6px' }}>
627
+ Send OTP
628
+ </Button>
629
+ </div>
630
+ ) : (
631
+ <>
632
+ {/* <Divider /> */}
633
+ <div className="otp-container">
634
+ <p>
635
+ Enter the verification code we sent to{' '}
636
+ <span className="otp-mode-text">
637
+ <strong>{getOtpDestinationText()}</strong>
638
+ </span>
639
+ </p>
640
+ <OTPInput
641
+ value={otpValue}
642
+ onChange={handleOtpChange}
643
+ autoFocus
644
+ OTPLength={6}
645
+ disabled={loading || otpExpired}
646
+ inputStyles={{
647
+ border: otpSuccess
648
+ ? '2px solid #52c41a' // green
649
+ : otpError
650
+ ? '2px solid #FF5C5C' // red
651
+ : '1px solid #d9d9d9', // default
652
+ borderRadius: 4,
653
+ width: isMobile ? 28 : 45, // MOBILE FIX
654
+ height: isMobile ? 36 : 40, // MOBILE FIX
655
+ margin: isMobile ? '2px' : '0 4px',
656
+ fontSize: isMobile ? 16 : 18,
657
+ textAlign: 'center',
658
+ }}
659
+ />
660
+ {/* Timer below OTP */}
661
+ <div className="otp-timer">
662
+ {/* {!otpExpired ? */}
663
+ {otpSuccess ? null : <span>{formatted}</span>}
664
+ {/* {otpError && (
665
+ <p style={{ color: 'red', marginTop: 8 }}>Invalid OTP, please try again</p>
666
+ )} */}
667
+
668
+ {otpSuccess && <p style={{ color: 'green', marginTop: 8 }}>Your OTP has been verified successfully.</p>}
669
+ </div>
670
+ </div>
671
+ {!otpSuccess && (
672
+ <Button type="primary" disabled={otpExpired} onClick={verifyOtp} style={{ marginTop: 16 }}>
673
+ Verify OTP
674
+ </Button>
675
+ )}
676
+ </>
677
+ )}
534
678
  </div>
535
- ) : otpVisible ? (
536
- <div className="otp-input-container">
537
- {/* Two factor authentication otp verification section start */}
679
+ )}
680
+
681
+ {/* Login Form Section */}
682
+ {!otpVerification && !otpVisible && (
683
+ <Form {...layout} layout="vertical" name="basic" onFinish={onFinish} onFinishFailed={onFinishFailed}>
538
684
  <div className="form-title">
539
- <h4>Two Factor Authentication</h4>
540
- <p>
541
- Two-Factor Authentication is enabled for your account . Please verify your account before your login to enhance the security of
542
- your account.
543
- <br></br>
544
- </p>
545
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
685
+ <h4></h4>
546
686
  </div>
547
687
 
548
- <div className="otp-container">
549
- <Button type="primary" className="SubmitBtn" onClick={sendAuthenticationOtp} loading={loading}>
550
- Send OTP
551
- </Button>
552
- <Button type="secondary" onClick={handlecancel}>
553
- Cancel
688
+ {!process.env.REACT_APP_SHOW_BRANCH_SWITCHER && <div className="branch-switcher">{globalCustomerHeader()}</div>}
689
+
690
+ {process.env.REACT_APP_ENABLE_LDAP === 'true' && (
691
+ <Button loading={ldaploading} type="secondary" className="SubmitBtn" onClick={loginWithLdap}>
692
+ Sign in using AD
554
693
  </Button>
555
- </div>
556
- {/* Two factor authentication otp verification section end */}
557
- </div>
558
- ) : (
559
- <>
560
- <Form {...layout} layout="vertical" name="basic" onFinish={onFinish} onFinishFailed={onFinishFailed}>
561
- <div className="form-title">
562
- <h4></h4>
563
- {/* <p>Your work assistant for the day!</p> */}
564
- </div>
694
+ )}
565
695
 
566
- {/** branchswitcher Option */}
567
- {!process.env.REACT_APP_SHOW_BRANCH_SWITCHER ? <div className="branch-switcher">{globalCustomerHeader()}</div> : null}
696
+ <Form.Item label="Email" name="email" rules={[{ required: true, message: 'Please input your email!' }]}>
697
+ <Input autoComplete="off" autoFocus />
698
+ </Form.Item>
568
699
 
569
- {/* {heroImage && (
570
- <img className="customers" src={heroImage} alt="Logo" />
571
- )} */}
700
+ <Form.Item label="Password" name="password" rules={[{ required: true, message: 'Please input your password!' }]}>
701
+ <Input.Password autoComplete="off" />
702
+ </Form.Item>
572
703
 
573
- <br></br>
574
- <div>
575
- {process.env.REACT_APP_ENABLE_LDAP == 'true' && (
576
- <Button loading={ldaploading} type="secondary" className="SubmitBtn" onClick={loginWithLdap}>
577
- Sign in using AD
578
- </Button>
579
- )}
580
- </div>
581
- <Form.Item label="Email" name="email" rules={[{ required: true, message: 'Please input your email!' }]}>
582
- <Input autoComplete="off" />
583
- </Form.Item>
584
-
585
- <Form.Item label="Password" name="password" rules={[{ required: true, message: 'Please input your password!' }]}>
586
- <Input.Password autoComplete="off" />
587
- </Form.Item>
588
-
589
- <Form.Item {...tailLayout}>
590
- <Button loading={loading} type="primary" htmlType="submit" className="SubmitBtn">
591
- {/* <FontAwesomeIcon icon={faCheck} /> */}
592
- &nbsp;&nbsp;
593
- {/* <span>
594
- <img src="../assets/tick-icon.png" alt="Tick Icon" />
595
- </span> */}
596
- Submit
597
- </Button>
598
- </Form.Item>
599
- </Form>
600
- </>
704
+ <Form.Item {...tailLayout}>
705
+ <Button loading={loading} type="primary" htmlType="submit" className="SubmitBtn">
706
+ &nbsp;&nbsp; Submit
707
+ </Button>
708
+ </Form.Item>
709
+ </Form>
601
710
  )}
602
711
  </div>
603
712
  </div>
604
-
605
- {/* <div className="center-line"> */}
606
- {/* <Link to="/register">Create Account</Link> */}
607
- {/* <img className="footer-logo" src={footerLogo} alt={'user photograph'} /> */}
608
- {/* </div> */}
609
-
610
- {/* {heroImage && <img className="customers2" src={heroImage} alt="Logo" />} */}
713
+ {!otpSuccess && otpVerification && (
714
+ <div className="otp-actions">
715
+ <div className="resend-action">
716
+ <Text disabled>Didn't receive OTP?</Text>
717
+ <Link className="resend-otp-link" disabled={!otpExpired} onClick={handleResendOTP}>
718
+ Resend OTP
719
+ </Link>
720
+ </div>
721
+
722
+ <Button size="small" type="default" onClick={handlecancel}>
723
+ Cancel
724
+ </Button>
725
+ </div>
726
+ )}
611
727
  </motion.div>
612
728
  </section>
613
729
  </section>