@visns-studio/visns-components 5.4.6 → 5.4.7

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/package.json CHANGED
@@ -78,7 +78,7 @@
78
78
  "react-dom": "^17.0.0 || ^18.0.0"
79
79
  },
80
80
  "name": "@visns-studio/visns-components",
81
- "version": "5.4.6",
81
+ "version": "5.4.7",
82
82
  "description": "Various packages to assist in the development of our Custom Applications.",
83
83
  "main": "src/index.js",
84
84
  "files": [
@@ -1,7 +1,7 @@
1
1
  import '../../styles/global.css';
2
2
 
3
3
  import React, { useEffect, useState } from 'react';
4
- import { Link, useLocation } from 'react-router-dom';
4
+ import { Link, useLocation, useNavigate } from 'react-router-dom';
5
5
  import Reveal from 'react-reveal/Reveal';
6
6
  import { toast } from 'react-toastify';
7
7
  import { Person, LockOff } from 'akar-icons';
@@ -12,6 +12,8 @@ import styles from './styles/Login.module.scss';
12
12
 
13
13
  const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
14
14
  const location = useLocation();
15
+ const navigate = useNavigate();
16
+ const [isLoading, setIsLoading] = useState(false);
15
17
 
16
18
  const [auth, setAuth] = useState({
17
19
  email: '',
@@ -92,6 +94,9 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
92
94
  if (e) {
93
95
  e.preventDefault();
94
96
 
97
+ setIsLoading(true);
98
+ toast.info('Logging in...', { autoClose: 2000 });
99
+
95
100
  CustomFetch(
96
101
  '/login/authenticate',
97
102
  'POST',
@@ -103,13 +108,27 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
103
108
  : '',
104
109
  },
105
110
  (result) => {
111
+ setIsLoading(false);
106
112
  if (result.error === '') {
107
- setUserProfile(result.user);
108
-
109
- if (result.previous && result.previous !== '') {
110
- window.location.href = result.previous;
113
+ // Check if 2FA is required
114
+ if (result.requires_2fa) {
115
+ // Redirect to 2FA page with user data
116
+ navigate('/2fa', {
117
+ state: {
118
+ userData: result.user,
119
+ previousUrl:
120
+ location.state?.previousUrl || '',
121
+ },
122
+ });
111
123
  } else {
112
- setSystemAuth(true);
124
+ // Normal login flow
125
+ setUserProfile(result.user);
126
+
127
+ if (result.previous && result.previous !== '') {
128
+ window.location.href = result.previous;
129
+ } else {
130
+ setSystemAuth(true);
131
+ }
113
132
  }
114
133
  } else {
115
134
  setSystemAuth(false);
@@ -120,11 +139,13 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
120
139
  }));
121
140
 
122
141
  toast.error(
123
- 'Could not log in with the supplied credentials, please try again.'
142
+ result.error ||
143
+ 'Could not log in with the supplied credentials, please try again.'
124
144
  );
125
145
  }
126
146
  },
127
147
  (error) => {
148
+ setIsLoading(false);
128
149
  setAuthClass((prevState) => ({
129
150
  ...prevState,
130
151
  username: 'inputError',
@@ -206,8 +227,9 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
206
227
  <button
207
228
  className={styles.btn}
208
229
  type="submit"
230
+ disabled={isLoading}
209
231
  >
210
- Login
232
+ {isLoading ? 'Logging in...' : 'Login'}
211
233
  </button>
212
234
  </div>
213
235
  <div
@@ -0,0 +1,184 @@
1
+ import '../../styles/global.css';
2
+
3
+ import React, { useState, useEffect } from 'react';
4
+ import { useNavigate, useLocation } from 'react-router-dom';
5
+ import Reveal from 'react-reveal/Reveal';
6
+ import { toast } from 'react-toastify';
7
+ import { LockOff } from 'akar-icons';
8
+
9
+ import CustomFetch from '../Fetch';
10
+
11
+ import styles from './styles/TwoFactorAuth.module.scss';
12
+
13
+ const TwoFactorAuth = ({ logo, setSystemAuth, setUserProfile }) => {
14
+ const navigate = useNavigate();
15
+ const location = useLocation();
16
+ const [isLoading, setIsLoading] = useState(false);
17
+
18
+ // Get user data from location state
19
+ const userData = location.state?.userData || null;
20
+ const previousUrl = location.state?.previousUrl || '';
21
+
22
+ // If no userData, redirect to login
23
+ useEffect(() => {
24
+ if (!userData) {
25
+ navigate('/login');
26
+ }
27
+ }, [userData, navigate]);
28
+
29
+ const [verificationCode, setVerificationCode] = useState('');
30
+ const [codeClass, setCodeClass] = useState('');
31
+
32
+ const handleChange = (e) => {
33
+ if (e) {
34
+ setVerificationCode(e.target.value);
35
+ setCodeClass('');
36
+ }
37
+ };
38
+
39
+ const handleSubmit = (e) => {
40
+ if (e) {
41
+ e.preventDefault();
42
+
43
+ if (!verificationCode.trim()) {
44
+ setCodeClass('inputError');
45
+ toast.error('Please enter the verification code');
46
+ return;
47
+ }
48
+
49
+ setIsLoading(true);
50
+ toast.info('Verifying code...', { autoClose: 2000 });
51
+
52
+ CustomFetch(
53
+ '/login/verify-2fa',
54
+ 'POST',
55
+ {
56
+ user_id: userData?.id,
57
+ verification_code: verificationCode,
58
+ previous_url: previousUrl
59
+ },
60
+ (result) => {
61
+ setIsLoading(false);
62
+ if (result.error === '') {
63
+ toast.success('Two-factor authentication successful');
64
+ setUserProfile(result.user || userData);
65
+
66
+ if (result.previous && result.previous !== '') {
67
+ window.location.href = result.previous;
68
+ } else {
69
+ setSystemAuth(true);
70
+ navigate('/');
71
+ }
72
+ } else {
73
+ setCodeClass('inputError');
74
+ toast.error(result.error || 'Invalid verification code');
75
+ }
76
+ },
77
+ (error) => {
78
+ setIsLoading(false);
79
+ setCodeClass('inputError');
80
+ toast.error(String(error) || 'Failed to verify code');
81
+ }
82
+ );
83
+ }
84
+ };
85
+
86
+ const handleResendCode = () => {
87
+ if (!userData?.id) {
88
+ toast.error('User information is missing. Please try logging in again.');
89
+ navigate('/login');
90
+ return;
91
+ }
92
+
93
+ toast.info('Requesting new verification code...', { autoClose: 2000 });
94
+
95
+ CustomFetch(
96
+ '/login/resend-2fa',
97
+ 'POST',
98
+ { user_id: userData.id },
99
+ (result) => {
100
+ if (result.error === '') {
101
+ toast.success('A new verification code has been sent');
102
+ } else {
103
+ toast.error(result.error || 'Failed to send new code');
104
+ }
105
+ },
106
+ (error) => {
107
+ toast.error(String(error) || 'Failed to send new code');
108
+ }
109
+ );
110
+ };
111
+
112
+ return (
113
+ <div className={styles.lcontainer}>
114
+ <div className={styles.lwrap}>
115
+ <div className={styles.logincontainer}>
116
+ <form onSubmit={handleSubmit}>
117
+ <Reveal effect="fadeInUp">
118
+ <div className={styles.login}>
119
+ <img
120
+ src={logo}
121
+ alt="CRM"
122
+ className={styles.loginlogo}
123
+ />
124
+ <div className={`${styles.formItem} ${styles.fwItem}`}>
125
+ <h2>Two-Factor Authentication</h2>
126
+ <p className={styles.instructions}>
127
+ Please enter the verification code that was sent to your device.
128
+ </p>
129
+ </div>
130
+ <div className={`${styles.formItem} ${styles.fwItem}`}>
131
+ <label className={styles.fi__label}>
132
+ <input
133
+ type="text"
134
+ name="verificationCode"
135
+ value={verificationCode}
136
+ onChange={handleChange}
137
+ tabIndex="1"
138
+ className={codeClass}
139
+ placeholder=" "
140
+ autoComplete="one-time-code"
141
+ maxLength="6"
142
+ />
143
+ <span className={styles.fi__span}>
144
+ Verification Code
145
+ </span>
146
+ <small>
147
+ <LockOff strokeWidth={2} size={18} />
148
+ </small>
149
+ </label>
150
+ </div>
151
+ <div className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem}`}>
152
+ <button
153
+ className={styles.btn}
154
+ type="submit"
155
+ disabled={isLoading}
156
+ >
157
+ {isLoading ? 'Verifying...' : 'Verify'}
158
+ </button>
159
+ </div>
160
+ <div className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem} ${styles.forgotten}`}>
161
+ <span>
162
+ Didn't receive a code?{' '}
163
+ <a href="#" onClick={(e) => { e.preventDefault(); handleResendCode(); }}>
164
+ Resend Code
165
+ </a>
166
+ </span>
167
+ </div>
168
+ <div className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem} ${styles.forgotten}`}>
169
+ <span>
170
+ <a href="#" onClick={(e) => { e.preventDefault(); navigate('/login'); }}>
171
+ Back to Login
172
+ </a>
173
+ </span>
174
+ </div>
175
+ </div>
176
+ </Reveal>
177
+ </form>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ );
182
+ };
183
+
184
+ export default TwoFactorAuth;
@@ -0,0 +1,29 @@
1
+ // Import the Login styles to maintain consistency
2
+ @import './Login.module.scss';
3
+
4
+ // Additional styles specific to the 2FA component
5
+ .instructions {
6
+ text-align: center;
7
+ margin: 0.5rem 0 1.5rem;
8
+ color: var(--paragraph-color);
9
+ font-size: 0.95rem;
10
+ line-height: 1.4;
11
+ }
12
+
13
+ .login {
14
+ h2 {
15
+ text-align: center;
16
+ margin: 0;
17
+ color: var(--heading-color);
18
+ font-weight: 500;
19
+ }
20
+ }
21
+
22
+ // Style for the verification code input
23
+ .formItem {
24
+ input[name="verificationCode"] {
25
+ letter-spacing: 0.25rem;
26
+ font-size: 1.2rem;
27
+ text-align: center;
28
+ }
29
+ }
@@ -17,6 +17,7 @@ import GenericMain from './GenericMain';
17
17
  import Login from '../auth/Login';
18
18
  import Reset from '../auth/Reset';
19
19
  import Verify from '../auth/Verify';
20
+ import TwoFactorAuth from '../auth/TwoFactorAuth';
20
21
 
21
22
  const ProtectedRoute = ({ user, loading, redirectPath = '/login' }) => {
22
23
  const location = useLocation();
@@ -111,6 +112,17 @@ const GenericAuth = ({
111
112
  />
112
113
  }
113
114
  />
115
+ <Route
116
+ path="2fa"
117
+ element={
118
+ <TwoFactorAuthComponent
119
+ loginBg={loginBg}
120
+ logo={logo}
121
+ setIsAuthenticated={setIsAuthenticated}
122
+ setUserProfile={setUserProfile}
123
+ />
124
+ }
125
+ />
114
126
  <Route
115
127
  element={
116
128
  <ProtectedRoute
@@ -174,4 +186,15 @@ const VerifyComponent = React.memo(({ loginBg, logo, setIsAuthenticated }) => (
174
186
  <Verify loginBg={loginBg} logo={logo} setSystemAuth={setIsAuthenticated} />
175
187
  ));
176
188
 
189
+ const TwoFactorAuthComponent = React.memo(
190
+ ({ loginBg, logo, setIsAuthenticated, setUserProfile }) => (
191
+ <TwoFactorAuth
192
+ loginBg={loginBg}
193
+ logo={logo}
194
+ setSystemAuth={setIsAuthenticated}
195
+ setUserProfile={setUserProfile}
196
+ />
197
+ )
198
+ );
199
+
177
200
  export default GenericAuth;