@visns-studio/visns-components 5.4.6 → 5.4.8

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,347 @@
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
+ const remember = location.state?.remember || false;
22
+
23
+ // If no userData, redirect to login
24
+ useEffect(() => {
25
+ if (!userData) {
26
+ navigate('/login');
27
+ }
28
+ }, [userData, navigate]);
29
+
30
+ const [verificationCode, setVerificationCode] = useState('');
31
+ const [codeClass, setCodeClass] = useState('');
32
+
33
+ const handleChange = (e) => {
34
+ if (e) {
35
+ const value = e.target.value;
36
+ // Only allow digits and limit to 6 characters
37
+ if (/^\d*$/.test(value) && value.length <= 6) {
38
+ setVerificationCode(value);
39
+ setCodeClass('');
40
+ }
41
+ }
42
+ };
43
+
44
+ const handleSubmit = (e) => {
45
+ if (e) {
46
+ e.preventDefault();
47
+
48
+ if (!verificationCode.trim() || verificationCode.length !== 6) {
49
+ setCodeClass('inputError');
50
+ toast.error('Please enter a valid 6-digit code');
51
+ return;
52
+ }
53
+
54
+ setIsLoading(true);
55
+ toast.info('Verifying code...', { autoClose: 2000 });
56
+
57
+ CustomFetch(
58
+ '/login/two-factor-challenge',
59
+ 'POST',
60
+ {
61
+ code: verificationCode,
62
+ previous_url: previousUrl,
63
+ remember: remember,
64
+ },
65
+ (result) => {
66
+ setIsLoading(false);
67
+
68
+ // Log the result to help with debugging
69
+ console.log('2FA Result:', result);
70
+
71
+ // Check if the result contains an error message
72
+ if (result && result.error) {
73
+ setCodeClass('inputError');
74
+ console.log('Found error in result:', result.error);
75
+
76
+ // Check if it's a session timeout error
77
+ if (
78
+ result.error ===
79
+ 'Invalid two-factor authentication session.'
80
+ ) {
81
+ console.log(
82
+ 'Session timeout detected, redirecting to login'
83
+ );
84
+ toast.error(
85
+ 'Your session has expired. Please log in again.'
86
+ );
87
+
88
+ // Redirect to login page
89
+ setTimeout(() => {
90
+ navigate('/login');
91
+ }, 1500); // Short delay to allow the toast to be seen
92
+ } else {
93
+ // Display the error message
94
+ toast.error(result.error);
95
+ }
96
+ return;
97
+ }
98
+
99
+ // Additional check for error in different format
100
+ if (
101
+ typeof result === 'string' &&
102
+ result.includes(
103
+ 'Invalid two-factor authentication session'
104
+ )
105
+ ) {
106
+ console.log(
107
+ 'Session timeout detected in string result, redirecting to login'
108
+ );
109
+ setCodeClass('inputError');
110
+ toast.error(
111
+ 'Your session has expired. Please log in again.'
112
+ );
113
+
114
+ // Redirect to login page
115
+ setTimeout(() => {
116
+ navigate('/login');
117
+ }, 1500);
118
+ return;
119
+ }
120
+
121
+ // If we get here, it means we received a 200 response with the user object
122
+ console.log(
123
+ 'Authentication successful, updating user profile'
124
+ );
125
+ toast.success('Two-factor authentication successful');
126
+
127
+ // Update the user profile with the received data
128
+ setUserProfile(result);
129
+
130
+ // Set authentication state to true
131
+ setSystemAuth(true);
132
+
133
+ // Use a small delay to ensure state updates are processed
134
+ setTimeout(() => {
135
+ // Navigate based on previous URL if available
136
+ if (previousUrl && previousUrl !== '') {
137
+ window.location.href = previousUrl;
138
+ } else {
139
+ // Use window.location for a full page refresh to ensure session is recognized
140
+ window.location.href = '/';
141
+ }
142
+ }, 500);
143
+ },
144
+ (error) => {
145
+ setIsLoading(false);
146
+ setCodeClass('inputError');
147
+
148
+ // Log the error to help with debugging
149
+ console.log('2FA Error:', error);
150
+
151
+ // Log more detailed information about the error response
152
+ if (error.response) {
153
+ console.log('Error Response:', error.response);
154
+ console.log(
155
+ 'Error Response Data:',
156
+ error.response.data
157
+ );
158
+ }
159
+
160
+ // Extract the error message from the response
161
+ let errorMessage =
162
+ 'Invalid verification code. Please try again.';
163
+
164
+ // Direct check for the specific error we're looking for
165
+ // This is a temporary solution to force the redirect
166
+ if (
167
+ error &&
168
+ error.message === 'Request failed with status code 401'
169
+ ) {
170
+ // Force redirect to login for 401 errors
171
+ console.log('Detected 401 error, redirecting to login');
172
+ toast.error(
173
+ 'Your session has expired. Please log in again.'
174
+ );
175
+ setTimeout(() => {
176
+ navigate('/login');
177
+ }, 1500);
178
+ return;
179
+ }
180
+
181
+ try {
182
+ // Try to parse the error response
183
+ if (error.response && error.response.data) {
184
+ console.log(
185
+ 'Response data type:',
186
+ typeof error.response.data
187
+ );
188
+ console.log('Response data:', error.response.data);
189
+
190
+ // If data is a string, use it directly
191
+ if (typeof error.response.data === 'string') {
192
+ errorMessage = error.response.data;
193
+ }
194
+ // If data is an object with an error property
195
+ else if (typeof error.response.data === 'object') {
196
+ if (error.response.data.error) {
197
+ errorMessage = error.response.data.error;
198
+ console.log(
199
+ 'Found error in object:',
200
+ errorMessage
201
+ );
202
+ } else if (error.response.data.message) {
203
+ errorMessage = error.response.data.message;
204
+ console.log(
205
+ 'Found message in object:',
206
+ errorMessage
207
+ );
208
+ }
209
+ }
210
+ // If data is JSON string, try to parse it
211
+ else if (
212
+ typeof error.response.data === 'string' &&
213
+ error.response.data.includes('{')
214
+ ) {
215
+ const parsedData = JSON.parse(
216
+ error.response.data
217
+ );
218
+ if (parsedData.error) {
219
+ errorMessage = parsedData.error;
220
+ console.log(
221
+ 'Found error in parsed JSON:',
222
+ errorMessage
223
+ );
224
+ }
225
+ }
226
+ }
227
+ } catch (e) {
228
+ console.error('Error parsing error response:', e);
229
+ }
230
+
231
+ console.log('Extracted error message:', errorMessage);
232
+
233
+ // Check if it's a session timeout error
234
+ if (
235
+ errorMessage ===
236
+ 'Invalid two-factor authentication session.' ||
237
+ errorMessage.includes(
238
+ 'Invalid two-factor authentication session'
239
+ )
240
+ ) {
241
+ toast.error(
242
+ 'Your session has expired. Please log in again.'
243
+ );
244
+ // Redirect to login page
245
+ setTimeout(() => {
246
+ navigate('/login');
247
+ }, 1500); // Short delay to allow the toast to be seen
248
+ } else {
249
+ toast.error(errorMessage);
250
+ }
251
+ }
252
+ );
253
+ }
254
+ };
255
+
256
+ return (
257
+ <div className={styles.lcontainer}>
258
+ <div className={styles.lwrap}>
259
+ <div className={styles.logincontainer}>
260
+ <form onSubmit={handleSubmit}>
261
+ <Reveal effect="fadeInUp">
262
+ <div className={styles.login}>
263
+ <img
264
+ src={logo}
265
+ alt="CRM"
266
+ className={styles.loginlogo}
267
+ />
268
+ <div
269
+ className={`${styles.formItem} ${styles.fwItem}`}
270
+ >
271
+ <h2>Two-Factor Authentication</h2>
272
+ <p className={styles.instructions}>
273
+ Please enter the 6-digit code from your
274
+ authenticator app.
275
+ </p>
276
+ </div>
277
+ <div
278
+ className={`${styles.formItem} ${styles.fwItem}`}
279
+ >
280
+ <label className={styles.fi__label}>
281
+ <input
282
+ type="text"
283
+ name="verificationCode"
284
+ value={verificationCode}
285
+ onChange={handleChange}
286
+ tabIndex="1"
287
+ className={codeClass}
288
+ placeholder=" "
289
+ autoComplete="one-time-code"
290
+ inputMode="numeric"
291
+ maxLength="6"
292
+ />
293
+ <span className={styles.fi__span}>
294
+ Verification Code
295
+ </span>
296
+ <small>
297
+ <LockOff
298
+ strokeWidth={2}
299
+ size={18}
300
+ />
301
+ </small>
302
+ </label>
303
+ </div>
304
+ <div
305
+ className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem}`}
306
+ >
307
+ <button
308
+ className={styles.btn}
309
+ type="submit"
310
+ disabled={isLoading}
311
+ >
312
+ {isLoading ? 'Verifying...' : 'Verify'}
313
+ </button>
314
+ </div>
315
+ <div
316
+ className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem} ${styles.forgotten}`}
317
+ >
318
+ <span>
319
+ Having trouble? Contact your
320
+ administrator for assistance.
321
+ </span>
322
+ </div>
323
+ <div
324
+ className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem} ${styles.forgotten}`}
325
+ >
326
+ <span>
327
+ <a
328
+ href="#"
329
+ onClick={(e) => {
330
+ e.preventDefault();
331
+ navigate('/login');
332
+ }}
333
+ >
334
+ Back to Login
335
+ </a>
336
+ </span>
337
+ </div>
338
+ </div>
339
+ </Reveal>
340
+ </form>
341
+ </div>
342
+ </div>
343
+ </div>
344
+ );
345
+ };
346
+
347
+ export default TwoFactorAuth;
@@ -2,6 +2,7 @@
2
2
  width: 50%;
3
3
  position: relative;
4
4
  box-sizing: border-box;
5
+ margin-bottom: 0.5rem;
5
6
 
6
7
  .fi__label {
7
8
  position: relative;
@@ -118,6 +119,41 @@
118
119
  }
119
120
  }
120
121
 
122
+ .rememberMeContainer {
123
+ display: flex;
124
+ align-items: center;
125
+ justify-content: flex-start;
126
+ width: 100%;
127
+ padding: 0.5rem 1rem;
128
+ margin-bottom: 0.75rem;
129
+ box-sizing: border-box;
130
+ margin-top: -0.25rem;
131
+ }
132
+
133
+ .rememberMeCheckboxWrapper {
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ margin-right: 0.5rem;
138
+ }
139
+
140
+ .rememberMeLabel {
141
+ cursor: pointer;
142
+ user-select: none;
143
+ color: var(--paragraph-color);
144
+ font-size: 0.85rem;
145
+ white-space: nowrap;
146
+ }
147
+
148
+ .rememberMeCheckbox {
149
+ /* Use the browser's default checkbox but with some styling */
150
+ width: 16px;
151
+ height: 16px;
152
+ cursor: pointer;
153
+ position: relative;
154
+ accent-color: var(--primary-color); /* Modern browsers support this */
155
+ }
156
+
121
157
  .lcontainer {
122
158
  min-height: 100vh;
123
159
  display: flex;