@visns-studio/visns-components 5.6.6 → 5.6.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.
- package/README.md +86 -1
- package/package.json +1 -1
- package/src/components/crm/DataGrid.jsx +114 -14
- package/src/components/crm/Field.jsx +1 -0
- package/src/components/crm/MultiSelect.jsx +2 -1
- package/src/components/crm/auth/Login.jsx +51 -39
- package/src/components/crm/auth/Reset.jsx +11 -5
- package/src/components/crm/auth/Verify.jsx +11 -5
- package/src/components/crm/auth/styles/Login.module.scss +110 -39
- package/src/components/crm/auth/styles/Reset.module.scss +51 -46
- package/src/components/crm/auth/styles/Verify.module.scss +51 -46
- package/src/components/crm/generic/GenericDetail.jsx +35 -16
- package/src/components/crm/generic/styles/GenericDetail.module.scss +102 -34
- package/src/components/crm/generic/styles/GenericMain.module.scss +2 -2
- package/src/components/crm/styles/DataGrid.module.scss +48 -1
- package/src/components/crm/styles/Navigation.module.scss +9 -0
package/README.md
CHANGED
|
@@ -59,6 +59,7 @@ VISNS Components is a React-based UI component library that provides a set of re
|
|
|
59
59
|
- **Notification Pages** - User notification center
|
|
60
60
|
- **Sorting Pages** - Content ordering interfaces
|
|
61
61
|
- **Report Pages** - Custom report building and execution
|
|
62
|
+
- **Audit Logs** - System activity tracking and history
|
|
62
63
|
|
|
63
64
|
## Installation
|
|
64
65
|
|
|
@@ -224,6 +225,34 @@ A powerful data table component with sorting, filtering, and pagination.
|
|
|
224
225
|
| setConfig | Callback for configuration changes | Function | - |
|
|
225
226
|
| style | Custom styles | Object | - |
|
|
226
227
|
|
|
228
|
+
#### Auto-Refresh Functionality
|
|
229
|
+
|
|
230
|
+
The DataGrid component includes an auto-refresh feature that automatically reloads data at specified intervals. This is useful for displaying real-time or frequently updated data.
|
|
231
|
+
|
|
232
|
+
To enable auto-refresh:
|
|
233
|
+
|
|
234
|
+
```jsx
|
|
235
|
+
<DataGrid
|
|
236
|
+
ajaxSetting={{
|
|
237
|
+
url: '/api/data',
|
|
238
|
+
method: 'GET',
|
|
239
|
+
params: {},
|
|
240
|
+
autoRefresh: 30, // Refresh every 30 seconds
|
|
241
|
+
// or
|
|
242
|
+
autoRefresh: true, // Use default 30-second interval
|
|
243
|
+
}}
|
|
244
|
+
// Other props...
|
|
245
|
+
/>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
When auto-refresh is configured:
|
|
249
|
+
|
|
250
|
+
- A checkbox appears in the DataGrid header to enable/disable auto-refresh
|
|
251
|
+
- Auto-refresh is enabled by default when configured
|
|
252
|
+
- The interval can be customized (in seconds) by setting a numeric value
|
|
253
|
+
- Setting `autoRefresh: true` uses the default 30-second interval
|
|
254
|
+
- The component silently refreshes data without showing notifications
|
|
255
|
+
|
|
227
256
|
### Form Components
|
|
228
257
|
|
|
229
258
|
#### Form
|
|
@@ -336,7 +365,7 @@ File upload component with drag and drop support.
|
|
|
336
365
|
|
|
337
366
|
#### CustomFetch
|
|
338
367
|
|
|
339
|
-
Data fetching utility with error handling.
|
|
368
|
+
Data fetching utility with error handling and payload size validation.
|
|
340
369
|
|
|
341
370
|
```jsx
|
|
342
371
|
// Using async/await
|
|
@@ -358,6 +387,15 @@ CustomFetch(
|
|
|
358
387
|
);
|
|
359
388
|
```
|
|
360
389
|
|
|
390
|
+
The CustomFetch component includes these features:
|
|
391
|
+
|
|
392
|
+
- **Payload Size Validation**: Automatically checks if the request payload exceeds the maximum allowed size (default: 4.5MB, configurable via `VITE_MAX_PAYLOAD_SIZE_MB` environment variable)
|
|
393
|
+
- **Toast Notifications**: Displays error messages using toast notifications
|
|
394
|
+
- **Promise Tracking**: Integrates with react-promise-tracker for loading indicators
|
|
395
|
+
- **HTML Error Parsing**: Properly renders HTML content in error messages
|
|
396
|
+
- **CSRF Protection**: Redirects to login page on CSRF token mismatch
|
|
397
|
+
- **Authentication Handling**: Silently handles unauthenticated errors without showing toast notifications
|
|
398
|
+
|
|
361
399
|
#### Download
|
|
362
400
|
|
|
363
401
|
File download utility.
|
|
@@ -404,6 +442,53 @@ The QrCode component fetches QR code data from the specified URL and displays it
|
|
|
404
442
|
}
|
|
405
443
|
```
|
|
406
444
|
|
|
445
|
+
#### AuditLogs and AuditLog
|
|
446
|
+
|
|
447
|
+
The AuditLogs component displays a table of system activity logs, showing who made changes, what was changed, and when the changes occurred.
|
|
448
|
+
|
|
449
|
+
```jsx
|
|
450
|
+
<AuditLogs layout="full" />
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
The AuditLog component displays detailed information about a specific audit log entry.
|
|
454
|
+
|
|
455
|
+
```jsx
|
|
456
|
+
<AuditLog userProfile={userProfile} />
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
The AuditLogs component features:
|
|
460
|
+
|
|
461
|
+
- User tracking - Shows which user performed the action
|
|
462
|
+
- Event tracking - Displays the type of action (create, update, delete)
|
|
463
|
+
- Value comparison - Shows old and new values for changed fields
|
|
464
|
+
- IP address logging - Records the IP address of the user
|
|
465
|
+
- Timestamp recording - Shows when the action occurred
|
|
466
|
+
- Formatted display - Presents JSON data in a readable format
|
|
467
|
+
|
|
468
|
+
The component expects audit log data from the API with the following structure:
|
|
469
|
+
|
|
470
|
+
```json
|
|
471
|
+
{
|
|
472
|
+
"id": 123,
|
|
473
|
+
"user": {
|
|
474
|
+
"id": 1,
|
|
475
|
+
"name": "John Doe"
|
|
476
|
+
},
|
|
477
|
+
"old_values": {
|
|
478
|
+
"field1": "old value",
|
|
479
|
+
"field2": "old value"
|
|
480
|
+
},
|
|
481
|
+
"new_values": {
|
|
482
|
+
"field1": "new value",
|
|
483
|
+
"field2": "new value"
|
|
484
|
+
},
|
|
485
|
+
"formatted_event": "Updating",
|
|
486
|
+
"ip_address": "192.168.1.1",
|
|
487
|
+
"url": "/api/resource/123",
|
|
488
|
+
"created_at": "2023-01-01T12:00:00Z"
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
407
492
|
## Two-Factor Authentication (2FA)
|
|
408
493
|
|
|
409
494
|
## Client Portal
|
package/package.json
CHANGED
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
85
85
|
},
|
|
86
86
|
"name": "@visns-studio/visns-components",
|
|
87
|
-
"version": "5.6.
|
|
87
|
+
"version": "5.6.8",
|
|
88
88
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
89
89
|
"main": "src/index.js",
|
|
90
90
|
"files": [
|
|
@@ -17,7 +17,6 @@ import numeral from 'numeral';
|
|
|
17
17
|
import Popup from 'reactjs-popup';
|
|
18
18
|
import 'reactjs-popup/dist/index.css';
|
|
19
19
|
import parse from 'html-react-parser';
|
|
20
|
-
import 'react-toggle/style.css';
|
|
21
20
|
import { saveAs } from 'file-saver';
|
|
22
21
|
import NumberFilter from '@visns-studio/visns-datagrid-enterprise/NumberFilter';
|
|
23
22
|
import SelectFilter from '@visns-studio/visns-datagrid-enterprise/SelectFilter';
|
|
@@ -386,10 +385,51 @@ const DataGrid = forwardRef(
|
|
|
386
385
|
const [localInputValues, setLocalInputValues] = useState({});
|
|
387
386
|
const localInputValuesRef = useRef({}); // Add this ref to track values
|
|
388
387
|
|
|
388
|
+
/** Auto Refresh States */
|
|
389
|
+
const [autoRefreshEnabled, setAutoRefreshEnabled] = useState(false);
|
|
390
|
+
const [refreshInterval, setRefreshInterval] = useState(30); // Default 30 seconds interval
|
|
391
|
+
const refreshTimerRef = useRef(null);
|
|
392
|
+
const autoRefreshInitialized = useRef(false); // Track if we've initialized auto-refresh
|
|
393
|
+
|
|
394
|
+
/** Responsive Modal States */
|
|
395
|
+
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
|
396
|
+
|
|
389
397
|
useEffect(() => {
|
|
390
398
|
localInputValuesRef.current = localInputValues;
|
|
391
399
|
}, [localInputValues]);
|
|
392
400
|
|
|
401
|
+
// Effect to handle window resize for responsive modals
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
const handleResize = () => {
|
|
404
|
+
setWindowWidth(window.innerWidth);
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
window.addEventListener('resize', handleResize);
|
|
408
|
+
|
|
409
|
+
// Clean up event listener on unmount
|
|
410
|
+
return () => {
|
|
411
|
+
window.removeEventListener('resize', handleResize);
|
|
412
|
+
};
|
|
413
|
+
}, []);
|
|
414
|
+
|
|
415
|
+
// Effect to handle autoRefresh setting
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
if (ajaxSetting && ajaxSetting.autoRefresh !== undefined) {
|
|
418
|
+
// Enable auto-refresh by default when the setting is present
|
|
419
|
+
if (!autoRefreshInitialized.current) {
|
|
420
|
+
setAutoRefreshEnabled(true);
|
|
421
|
+
autoRefreshInitialized.current = true;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Set the refresh interval based on the provided value
|
|
425
|
+
if (typeof ajaxSetting.autoRefresh === 'number') {
|
|
426
|
+
setRefreshInterval(ajaxSetting.autoRefresh);
|
|
427
|
+
} else if (ajaxSetting.autoRefresh === true) {
|
|
428
|
+
setRefreshInterval(30); // Default 30 seconds
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}, [ajaxSetting]);
|
|
432
|
+
|
|
393
433
|
/** Table Functions */
|
|
394
434
|
useImperativeHandle(ref, () => ({
|
|
395
435
|
reload: () => {
|
|
@@ -754,8 +794,6 @@ const DataGrid = forwardRef(
|
|
|
754
794
|
// Extracting nested value using predefined keys from 's.relation'
|
|
755
795
|
const nestedValue = getNestedValue(d, s.relation);
|
|
756
796
|
|
|
757
|
-
console.info(d, s.relation);
|
|
758
|
-
|
|
759
797
|
// Check if nestedValue is defined and is an array before mapping
|
|
760
798
|
if (!nestedValue || !Array.isArray(nestedValue)) {
|
|
761
799
|
console.warn(
|
|
@@ -1243,11 +1281,33 @@ const DataGrid = forwardRef(
|
|
|
1243
1281
|
className={styles.searchInput}
|
|
1244
1282
|
/>
|
|
1245
1283
|
</div>
|
|
1246
|
-
{
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1284
|
+
<div className={styles.actionButtonsWrapper}>
|
|
1285
|
+
{!isCreateDisabled && renderCreateButton()}
|
|
1286
|
+
</div>
|
|
1287
|
+
</div>
|
|
1288
|
+
);
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
const renderAutoRefreshControls = () => {
|
|
1292
|
+
// Only show auto-refresh if the setting is present in ajaxSetting
|
|
1293
|
+
if (!ajaxSetting || ajaxSetting.autoRefresh === undefined) {
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
return (
|
|
1298
|
+
<div className={styles.autoRefreshContainer}>
|
|
1299
|
+
<label className={styles.autoRefreshLabel}>
|
|
1300
|
+
<input
|
|
1301
|
+
type="checkbox"
|
|
1302
|
+
checked={autoRefreshEnabled}
|
|
1303
|
+
onChange={() =>
|
|
1304
|
+
setAutoRefreshEnabled(!autoRefreshEnabled)
|
|
1305
|
+
}
|
|
1306
|
+
className={styles.autoRefreshCheckbox}
|
|
1307
|
+
/>
|
|
1308
|
+
Auto Refresh{' '}
|
|
1309
|
+
{refreshInterval && `(${refreshInterval}s)`}
|
|
1310
|
+
</label>
|
|
1251
1311
|
</div>
|
|
1252
1312
|
);
|
|
1253
1313
|
};
|
|
@@ -1259,7 +1319,7 @@ const DataGrid = forwardRef(
|
|
|
1259
1319
|
if (linkUrl) {
|
|
1260
1320
|
const baseUrl = window.location.origin;
|
|
1261
1321
|
const fullUrl = `${baseUrl}${linkUrl}${
|
|
1262
|
-
rowProps.data[form.primaryKey]
|
|
1322
|
+
rowProps.data[cellProps?.link?.key || form.primaryKey]
|
|
1263
1323
|
}`;
|
|
1264
1324
|
|
|
1265
1325
|
menuProps.autoDismiss = true;
|
|
@@ -3146,6 +3206,34 @@ const DataGrid = forwardRef(
|
|
|
3146
3206
|
};
|
|
3147
3207
|
}, []);
|
|
3148
3208
|
|
|
3209
|
+
// Auto-refresh effect
|
|
3210
|
+
useEffect(() => {
|
|
3211
|
+
// Clear any existing timer
|
|
3212
|
+
if (refreshTimerRef.current) {
|
|
3213
|
+
clearInterval(refreshTimerRef.current);
|
|
3214
|
+
refreshTimerRef.current = null;
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
// Set up new timer if auto-refresh is enabled and the setting is present in ajaxSetting
|
|
3218
|
+
if (
|
|
3219
|
+
autoRefreshEnabled &&
|
|
3220
|
+
ajaxSetting &&
|
|
3221
|
+
ajaxSetting.autoRefresh !== undefined
|
|
3222
|
+
) {
|
|
3223
|
+
refreshTimerRef.current = setInterval(() => {
|
|
3224
|
+
handleReload();
|
|
3225
|
+
// No toast notification on refresh
|
|
3226
|
+
}, refreshInterval * 1000);
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
// Clean up on unmount
|
|
3230
|
+
return () => {
|
|
3231
|
+
if (refreshTimerRef.current) {
|
|
3232
|
+
clearInterval(refreshTimerRef.current);
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
}, [autoRefreshEnabled, refreshInterval, ajaxSetting]);
|
|
3236
|
+
|
|
3149
3237
|
return (
|
|
3150
3238
|
<div
|
|
3151
3239
|
style={{
|
|
@@ -3156,12 +3244,15 @@ const DataGrid = forwardRef(
|
|
|
3156
3244
|
overflow: 'hidden',
|
|
3157
3245
|
}}
|
|
3158
3246
|
>
|
|
3159
|
-
{/*
|
|
3160
|
-
{
|
|
3161
|
-
<div className={styles.
|
|
3247
|
+
{/* Top container with search, action buttons, and auto-refresh */}
|
|
3248
|
+
<div className={styles.dataGridTopContainer}>
|
|
3249
|
+
<div className={styles.dataGridTopLeftContainer}>
|
|
3162
3250
|
{renderSearch()}
|
|
3163
3251
|
</div>
|
|
3164
|
-
|
|
3252
|
+
<div className={styles.dataGridTopRightContainer}>
|
|
3253
|
+
{renderAutoRefreshControls()}
|
|
3254
|
+
</div>
|
|
3255
|
+
</div>
|
|
3165
3256
|
|
|
3166
3257
|
{/* DataGrid container with its own border */}
|
|
3167
3258
|
<div className={styles.dataGridContainer}>
|
|
@@ -3221,6 +3312,11 @@ const DataGrid = forwardRef(
|
|
|
3221
3312
|
open={modalShow}
|
|
3222
3313
|
onClose={modalClose}
|
|
3223
3314
|
closeOnDocumentClick={false}
|
|
3315
|
+
contentStyle={{
|
|
3316
|
+
width: windowWidth < 768 ? '95vw' : '80vw',
|
|
3317
|
+
minWidth: windowWidth < 768 ? '95vw' : '600px',
|
|
3318
|
+
maxWidth: '1200px',
|
|
3319
|
+
}}
|
|
3224
3320
|
nested
|
|
3225
3321
|
>
|
|
3226
3322
|
<Form
|
|
@@ -3244,7 +3340,11 @@ const DataGrid = forwardRef(
|
|
|
3244
3340
|
open={modalGalleryShow}
|
|
3245
3341
|
onClose={modalGalleryClose}
|
|
3246
3342
|
closeOnDocumentClick={false}
|
|
3247
|
-
contentStyle={{
|
|
3343
|
+
contentStyle={{
|
|
3344
|
+
width: windowWidth < 768 ? '95vw' : '80vw',
|
|
3345
|
+
minWidth: windowWidth < 768 ? '95vw' : '600px',
|
|
3346
|
+
maxWidth: '1200px',
|
|
3347
|
+
}}
|
|
3248
3348
|
overlayStyle={{ background: 'rgba(0, 0, 0, 0.7)' }}
|
|
3249
3349
|
>
|
|
3250
3350
|
<div
|
|
@@ -27,6 +27,7 @@ function MultiSelect({
|
|
|
27
27
|
settings,
|
|
28
28
|
style,
|
|
29
29
|
isCreatable = false,
|
|
30
|
+
isSearchable = true,
|
|
30
31
|
creatableConfig = {},
|
|
31
32
|
}) {
|
|
32
33
|
const [selectOptions, setSelectOptions] = useState([]);
|
|
@@ -142,7 +143,7 @@ function MultiSelect({
|
|
|
142
143
|
<SelectComponent
|
|
143
144
|
closeMenuOnSelect={!multi || settings.limit === 1}
|
|
144
145
|
isClearable
|
|
145
|
-
isSearchable
|
|
146
|
+
isSearchable={isSearchable}
|
|
146
147
|
isMulti={multi}
|
|
147
148
|
filterOption={createFilter({ ignoreAccents: false })}
|
|
148
149
|
components={{ SelectList }}
|
|
@@ -28,20 +28,11 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
|
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
const renderSsoButton = () => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
display: 'block',
|
|
37
|
-
textAlign: 'center',
|
|
38
|
-
width: '100%',
|
|
39
|
-
}}
|
|
40
|
-
>
|
|
41
|
-
OR
|
|
42
|
-
</span>
|
|
43
|
-
</div>
|
|
44
|
-
) : null;
|
|
31
|
+
if (!providers || providers.length === 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let content = <div className={styles.divider}>OR</div>;
|
|
45
36
|
|
|
46
37
|
if (providers) {
|
|
47
38
|
providers.forEach((provider) => {
|
|
@@ -51,13 +42,12 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
|
|
|
51
42
|
<>
|
|
52
43
|
{content}
|
|
53
44
|
<div
|
|
54
|
-
className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem}
|
|
45
|
+
className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem}`}
|
|
55
46
|
>
|
|
56
47
|
<button
|
|
57
|
-
className={styles.
|
|
48
|
+
className={styles.ssoButton}
|
|
58
49
|
onClick={(e) => {
|
|
59
50
|
e.preventDefault();
|
|
60
|
-
|
|
61
51
|
window.location.href =
|
|
62
52
|
'/auth/azure';
|
|
63
53
|
}}
|
|
@@ -66,10 +56,11 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
|
|
|
66
56
|
src={
|
|
67
57
|
'https://d16lktya8ojp5z.cloudfront.net/generic/images/microsoft.png'
|
|
68
58
|
}
|
|
69
|
-
width="
|
|
70
|
-
height="
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
width="18px"
|
|
60
|
+
height="18px"
|
|
61
|
+
alt="Microsoft logo"
|
|
62
|
+
/>
|
|
63
|
+
Sign in with Microsoft
|
|
73
64
|
</button>
|
|
74
65
|
</div>
|
|
75
66
|
</>
|
|
@@ -182,6 +173,17 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
|
|
|
182
173
|
alt="CRM"
|
|
183
174
|
className={styles.loginlogo}
|
|
184
175
|
/>
|
|
176
|
+
<h2
|
|
177
|
+
style={{
|
|
178
|
+
textAlign: 'center',
|
|
179
|
+
marginBottom: '1.5rem',
|
|
180
|
+
color: '#111827',
|
|
181
|
+
fontWeight: '600',
|
|
182
|
+
fontSize: '1.5rem',
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
Sign in to your account
|
|
186
|
+
</h2>
|
|
185
187
|
<div
|
|
186
188
|
className={`${styles.formItem} ${styles.fwItem}`}
|
|
187
189
|
>
|
|
@@ -232,9 +234,10 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
|
|
|
232
234
|
>
|
|
233
235
|
<div className={styles.rememberMeContainer}>
|
|
234
236
|
<div
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
237
|
+
style={{
|
|
238
|
+
display: 'flex',
|
|
239
|
+
alignItems: 'center',
|
|
240
|
+
}}
|
|
238
241
|
>
|
|
239
242
|
<input
|
|
240
243
|
type="checkbox"
|
|
@@ -246,13 +249,28 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
|
|
|
246
249
|
styles.rememberMeCheckbox
|
|
247
250
|
}
|
|
248
251
|
/>
|
|
252
|
+
<label
|
|
253
|
+
htmlFor="rememberMe"
|
|
254
|
+
className={
|
|
255
|
+
styles.rememberMeLabel
|
|
256
|
+
}
|
|
257
|
+
>
|
|
258
|
+
Remember me
|
|
259
|
+
</label>
|
|
260
|
+
</div>
|
|
261
|
+
<div>
|
|
262
|
+
<Link
|
|
263
|
+
to="/reset"
|
|
264
|
+
style={{
|
|
265
|
+
color: 'var(--primary-color, #4f46e5)',
|
|
266
|
+
textDecoration: 'none',
|
|
267
|
+
fontWeight: '500',
|
|
268
|
+
fontSize: '0.875rem',
|
|
269
|
+
}}
|
|
270
|
+
>
|
|
271
|
+
Forgot password?
|
|
272
|
+
</Link>
|
|
249
273
|
</div>
|
|
250
|
-
<label
|
|
251
|
-
htmlFor="rememberMe"
|
|
252
|
-
className={styles.rememberMeLabel}
|
|
253
|
-
>
|
|
254
|
-
Remember me
|
|
255
|
-
</label>
|
|
256
274
|
</div>
|
|
257
275
|
</div>
|
|
258
276
|
<div
|
|
@@ -263,17 +281,11 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
|
|
|
263
281
|
type="submit"
|
|
264
282
|
disabled={isLoading}
|
|
265
283
|
>
|
|
266
|
-
{isLoading
|
|
284
|
+
{isLoading
|
|
285
|
+
? 'Signing in...'
|
|
286
|
+
: 'Sign in'}
|
|
267
287
|
</button>
|
|
268
288
|
</div>
|
|
269
|
-
<div
|
|
270
|
-
className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem} ${styles.forgotten}`}
|
|
271
|
-
>
|
|
272
|
-
<span>
|
|
273
|
-
Forgot your password?{' '}
|
|
274
|
-
<Link to="/reset">Click Here</Link>
|
|
275
|
-
</span>
|
|
276
|
-
</div>
|
|
277
289
|
{renderSsoButton()}
|
|
278
290
|
</div>
|
|
279
291
|
</Reveal>
|
|
@@ -53,11 +53,17 @@ const Reset = ({ logo }) => {
|
|
|
53
53
|
alt="CRM"
|
|
54
54
|
className={styles.loginlogo}
|
|
55
55
|
/>
|
|
56
|
-
<
|
|
57
|
-
|
|
56
|
+
<h2
|
|
57
|
+
style={{
|
|
58
|
+
textAlign: 'center',
|
|
59
|
+
marginBottom: '1.5rem',
|
|
60
|
+
color: '#111827',
|
|
61
|
+
fontWeight: '600',
|
|
62
|
+
fontSize: '1.5rem',
|
|
63
|
+
}}
|
|
58
64
|
>
|
|
59
|
-
|
|
60
|
-
</
|
|
65
|
+
Forgot your password?
|
|
66
|
+
</h2>
|
|
61
67
|
<div
|
|
62
68
|
className={`${styles.formItem} ${styles.fwItem}`}
|
|
63
69
|
>
|
|
@@ -89,7 +95,7 @@ const Reset = ({ logo }) => {
|
|
|
89
95
|
type="submit"
|
|
90
96
|
tabIndex="2"
|
|
91
97
|
>
|
|
92
|
-
Reset
|
|
98
|
+
Send Reset Link
|
|
93
99
|
</button>
|
|
94
100
|
</div>
|
|
95
101
|
</div>
|
|
@@ -107,11 +107,17 @@ const Verify = ({ logo }) => {
|
|
|
107
107
|
alt="CRM"
|
|
108
108
|
className={styles.loginlogo}
|
|
109
109
|
/>
|
|
110
|
-
<
|
|
111
|
-
|
|
110
|
+
<h2
|
|
111
|
+
style={{
|
|
112
|
+
textAlign: 'center',
|
|
113
|
+
marginBottom: '1.5rem',
|
|
114
|
+
color: '#111827',
|
|
115
|
+
fontWeight: '600',
|
|
116
|
+
fontSize: '1.5rem',
|
|
117
|
+
}}
|
|
112
118
|
>
|
|
113
|
-
|
|
114
|
-
</
|
|
119
|
+
Reset your password
|
|
120
|
+
</h2>
|
|
115
121
|
<div
|
|
116
122
|
className={`${styles.formItem} ${styles.fwItem}`}
|
|
117
123
|
>
|
|
@@ -154,7 +160,7 @@ const Verify = ({ logo }) => {
|
|
|
154
160
|
type="submit"
|
|
155
161
|
tabIndex="3"
|
|
156
162
|
>
|
|
157
|
-
|
|
163
|
+
Set New Password
|
|
158
164
|
</button>
|
|
159
165
|
</div>
|
|
160
166
|
</div>
|