@visns-studio/visns-components 5.14.16 → 5.14.18

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
@@ -12,8 +12,8 @@
12
12
  "@nivo/line": "^0.99.0",
13
13
  "@nivo/pie": "^0.99.0",
14
14
  "@tinymce/miniature": "^6.0.0",
15
- "@tinymce/tinymce-react": "^6.2.1",
16
- "@uiw/react-color": "^2.7.1",
15
+ "@tinymce/tinymce-react": "^6.3.0",
16
+ "@uiw/react-color": "^2.7.3",
17
17
  "@visns-studio/visns-datagrid-community": "1.1.0",
18
18
  "@visns-studio/visns-datagrid-enterprise": "1.1.0",
19
19
  "@vitejs/plugin-react": "^4.7.0",
@@ -24,13 +24,13 @@
24
24
  "dayjs": "^1.11.13",
25
25
  "fabric": "^6.7.1",
26
26
  "file-saver": "^2.0.5",
27
- "framer-motion": "^12.23.11",
27
+ "framer-motion": "^12.23.12",
28
28
  "html-react-parser": "^5.2.6",
29
29
  "lodash": "^4.17.21",
30
30
  "lodash.debounce": "^4.0.8",
31
31
  "lucide-react": "^0.532.0",
32
32
  "moment": "^2.30.1",
33
- "motion": "^12.23.11",
33
+ "motion": "^12.23.12",
34
34
  "numeral": "^2.0.6",
35
35
  "pluralize": "^8.0.0",
36
36
  "qrcode.react": "^4.2.0",
@@ -89,7 +89,7 @@
89
89
  "react-dom": "^17.0.0 || ^18.0.0"
90
90
  },
91
91
  "name": "@visns-studio/visns-components",
92
- "version": "5.14.16",
92
+ "version": "5.14.18",
93
93
  "description": "Various packages to assist in the development of our Custom Applications.",
94
94
  "main": "src/index.js",
95
95
  "files": [
@@ -25,6 +25,7 @@ import {
25
25
  X,
26
26
  } from 'lucide-react';
27
27
  import { toast } from 'react-toastify';
28
+ import validator from 'validator';
28
29
  import styles from './styles/Field.module.scss';
29
30
 
30
31
  import CustomFetch from './Fetch';
@@ -81,6 +82,9 @@ function Field({
81
82
  const [formItemStyle, setFormItemStyle] = useState({});
82
83
  const [formItemClass, setFormItemClass] = useState('');
83
84
  const [options, setOptions] = useState([]);
85
+ const [emailError, setEmailError] = useState('');
86
+ const [emailSuggestions, setEmailSuggestions] = useState([]);
87
+ const [showEmailSuggestions, setShowEmailSuggestions] = useState(false);
84
88
 
85
89
  /** Canvas States */
86
90
  const [canvasPopupOpen, setCanvasPopupOpen] = useState(false);
@@ -91,6 +95,91 @@ function Field({
91
95
  const [lineColor, setLineColor] = useState('rgba(0, 0, 0, 1)');
92
96
  const sketchRef = useRef(null);
93
97
 
98
+ /** Email Functions */
99
+ const validateEmail = (email) => {
100
+ if (!email) {
101
+ setEmailError('');
102
+ return true;
103
+ }
104
+
105
+ if (!validator.isEmail(email)) {
106
+ setEmailError('Please enter a valid email address');
107
+ return false;
108
+ }
109
+
110
+ setEmailError('');
111
+ return true;
112
+ };
113
+
114
+ const generateEmailSuggestions = (inputEmail) => {
115
+ if (!inputEmail || inputEmail.length < 3) {
116
+ setEmailSuggestions([]);
117
+ setShowEmailSuggestions(false);
118
+ return;
119
+ }
120
+
121
+ const commonDomains = [
122
+ 'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
123
+ 'icloud.com', 'live.com', 'msn.com', 'aol.com',
124
+ 'company.com', 'business.com', 'work.com'
125
+ ];
126
+
127
+ const suggestions = [];
128
+ const [localPart, domainPart] = inputEmail.split('@');
129
+
130
+ if (!localPart) return;
131
+
132
+ if (!domainPart || domainPart.length === 0) {
133
+ // If no @ symbol or no domain part, suggest common domains
134
+ commonDomains.forEach(domain => {
135
+ suggestions.push(`${localPart}@${domain}`);
136
+ });
137
+ } else if (domainPart.length > 0 && domainPart.length < 4) {
138
+ // If domain part is short, suggest completions
139
+ commonDomains.forEach(domain => {
140
+ if (domain.toLowerCase().startsWith(domainPart.toLowerCase())) {
141
+ suggestions.push(`${localPart}@${domain}`);
142
+ }
143
+ });
144
+ } else {
145
+ // Check for common typos and suggest corrections
146
+ const typoMap = {
147
+ 'gmial.com': 'gmail.com',
148
+ 'gmai.com': 'gmail.com',
149
+ 'yahooo.com': 'yahoo.com',
150
+ 'hotmial.com': 'hotmail.com',
151
+ 'outlok.com': 'outlook.com',
152
+ 'iclou.com': 'icloud.com'
153
+ };
154
+
155
+ if (typoMap[domainPart.toLowerCase()]) {
156
+ suggestions.push(`${localPart}@${typoMap[domainPart.toLowerCase()]}`);
157
+ }
158
+ }
159
+
160
+ setEmailSuggestions(suggestions.slice(0, 5)); // Limit to 5 suggestions
161
+ setShowEmailSuggestions(suggestions.length > 0);
162
+ };
163
+
164
+ const handleEmailChange = (e) => {
165
+ const email = e.target.value;
166
+ validateEmail(email);
167
+ generateEmailSuggestions(email);
168
+ onChange(e);
169
+ };
170
+
171
+ const selectEmailSuggestion = (suggestion) => {
172
+ const syntheticEvent = {
173
+ target: {
174
+ dataset: { name: settings.id },
175
+ value: suggestion
176
+ }
177
+ };
178
+ validateEmail(suggestion);
179
+ setShowEmailSuggestions(false);
180
+ onChange(syntheticEvent);
181
+ };
182
+
94
183
  /** Canvas Functions */
95
184
  const handleCanvasSave = async () => {
96
185
  // Save the current canvas state
@@ -469,6 +558,7 @@ function Field({
469
558
  'dropdown',
470
559
  'dropdown-ajax',
471
560
  'dynamicdata',
561
+ 'email',
472
562
  'file',
473
563
  'gallery',
474
564
  'image',
@@ -2073,6 +2163,49 @@ function Field({
2073
2163
  autoComplete="off"
2074
2164
  />
2075
2165
  );
2166
+ case 'email':
2167
+ return (
2168
+ <div className={styles.emailFieldContainer}>
2169
+ <input
2170
+ type="email"
2171
+ data-name={settings.id}
2172
+ className={`${inputClass[settings.id]} ${emailError ? styles.emailError : ''}`}
2173
+ placeholder="Enter email address"
2174
+ onChange={handleEmailChange}
2175
+ onBlur={() => setShowEmailSuggestions(false)}
2176
+ onFocus={() => generateEmailSuggestions(inputValue || '')}
2177
+ value={
2178
+ inputValue && inputValue !== 'null'
2179
+ ? inputValue
2180
+ : ''
2181
+ }
2182
+ readOnly={settings.readOnly ? settings.readOnly : false}
2183
+ autoComplete="email"
2184
+ spellCheck={false}
2185
+ />
2186
+ {emailError && (
2187
+ <div className={styles.emailErrorMessage}>
2188
+ {emailError}
2189
+ </div>
2190
+ )}
2191
+ {showEmailSuggestions && emailSuggestions.length > 0 && (
2192
+ <div className={styles.emailSuggestions}>
2193
+ {emailSuggestions.map((suggestion, index) => (
2194
+ <div
2195
+ key={index}
2196
+ className={styles.emailSuggestion}
2197
+ onMouseDown={(e) => {
2198
+ e.preventDefault(); // Prevent blur event
2199
+ selectEmailSuggestion(suggestion);
2200
+ }}
2201
+ >
2202
+ {suggestion}
2203
+ </div>
2204
+ ))}
2205
+ </div>
2206
+ )}
2207
+ </div>
2208
+ );
2076
2209
  case 'password':
2077
2210
  return (
2078
2211
  <input
@@ -2109,6 +2242,7 @@ function Field({
2109
2242
  'dropdown',
2110
2243
  'dropdown-ajax',
2111
2244
  'dynamicdata',
2245
+ 'email',
2112
2246
  'file',
2113
2247
  'gallery',
2114
2248
  'image',
@@ -10,7 +10,7 @@ import CustomFetch from '../Fetch';
10
10
 
11
11
  import styles from '../styles/Login.module.scss';
12
12
 
13
- const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
13
+ const Login = ({ logo, providers, setSystemAuth, setUserProfile, config = {} }) => {
14
14
  const location = useLocation();
15
15
  const navigate = useNavigate();
16
16
  const [isLoading, setIsLoading] = useState(false);
@@ -258,22 +258,24 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
258
258
  Remember me
259
259
  </label>
260
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
- className={
271
- styles.forgotPasswordLink
272
- }
273
- >
274
- Forgot password?
275
- </Link>
276
- </div>
261
+ {config.passwordReset !== false && (
262
+ <div>
263
+ <Link
264
+ to="/reset"
265
+ style={{
266
+ color: 'var(--primary-color, #4f46e5)',
267
+ textDecoration: 'none',
268
+ fontWeight: '500',
269
+ fontSize: '0.875rem',
270
+ }}
271
+ className={
272
+ styles.forgotPasswordLink
273
+ }
274
+ >
275
+ Forgot password?
276
+ </Link>
277
+ </div>
278
+ )}
277
279
  </div>
278
280
  </div>
279
281
  <div
@@ -65,6 +65,7 @@ const GenericAuth = ({
65
65
  themeType = 'primary',
66
66
  enforce2FA = false, // Default to not enforcing 2FA
67
67
  clientPortalConfig = null, // Configuration for client portal with component and URLs
68
+ config = {}, // Configuration object for auth features
68
69
  }) => {
69
70
  const navigate = useNavigate();
70
71
  const location = useLocation();
@@ -173,6 +174,7 @@ const GenericAuth = ({
173
174
  providers={providers}
174
175
  setIsAuthenticated={setIsAuthenticated}
175
176
  setUserProfile={setUserProfile}
177
+ config={config}
176
178
  />
177
179
  }
178
180
  />
@@ -320,13 +322,14 @@ const GenericAuth = ({
320
322
  };
321
323
 
322
324
  const LoginComponent = React.memo(
323
- ({ loginBg, logo, providers, setIsAuthenticated, setUserProfile }) => (
325
+ ({ loginBg, logo, providers, setIsAuthenticated, setUserProfile, config }) => (
324
326
  <Login
325
327
  loginBg={loginBg}
326
328
  logo={logo}
327
329
  providers={providers}
328
330
  setSystemAuth={setIsAuthenticated}
329
331
  setUserProfile={setUserProfile}
332
+ config={config}
330
333
  />
331
334
  )
332
335
  );
@@ -1303,3 +1303,102 @@ input[type='file']:hover {
1303
1303
  font-size: 0.85em;
1304
1304
  }
1305
1305
  }
1306
+
1307
+ /* Email Field Styles */
1308
+ .emailFieldContainer {
1309
+ position: relative;
1310
+ width: 100%;
1311
+ }
1312
+
1313
+ .emailError {
1314
+ border-color: var(--error-color, #dc3545) !important;
1315
+ box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25) !important;
1316
+ }
1317
+
1318
+ .emailErrorMessage {
1319
+ color: var(--error-color, #dc3545);
1320
+ font-size: 0.875rem;
1321
+ margin-top: 0.25rem;
1322
+ display: flex;
1323
+ align-items: center;
1324
+ gap: 0.25rem;
1325
+ }
1326
+
1327
+ .emailErrorMessage::before {
1328
+ content: "⚠";
1329
+ font-size: 1rem;
1330
+ }
1331
+
1332
+ .emailSuggestions {
1333
+ position: absolute;
1334
+ top: 100%;
1335
+ left: 0;
1336
+ right: 0;
1337
+ background: var(--item-color, #fff);
1338
+ border: 1px solid var(--border-color, #e9ecef);
1339
+ border-top: none;
1340
+ border-radius: 0 0 4px 4px; /* Match react-select border radius */
1341
+ box-shadow: none; /* Match react-select no-shadow pattern */
1342
+ z-index: 999; /* Match react-select z-index */
1343
+ max-height: 200px;
1344
+ overflow-y: auto;
1345
+ font-size: 14px; /* Match react-select font size */
1346
+ }
1347
+
1348
+ .emailSuggestion {
1349
+ padding: 8px 12px; /* Match react-select option padding */
1350
+ cursor: pointer;
1351
+ transition: background-color 0.15s ease; /* Match react-select transition */
1352
+ color: var(--text-color, #333);
1353
+ line-height: 1.4;
1354
+ }
1355
+
1356
+ .emailSuggestion:hover {
1357
+ background-color: #f5f5f5; /* Match react-select hover state */
1358
+ color: var(--text-color, #333);
1359
+ }
1360
+
1361
+ .emailSuggestion:active {
1362
+ background-color: var(--primary-color, #007bff);
1363
+ color: white;
1364
+ }
1365
+
1366
+ /* Enhanced email field input styles */
1367
+ .emailFieldContainer input[type="email"] {
1368
+ border-radius: var(--br, 6px);
1369
+ transition: border-color 0.15s ease; /* Match react-select transition */
1370
+ position: relative;
1371
+ }
1372
+
1373
+ .emailFieldContainer input[type="email"]:focus {
1374
+ outline: none;
1375
+ border-color: var(--primary-color, #007bff);
1376
+ box-shadow: none; /* Match react-select no-shadow pattern */
1377
+ }
1378
+
1379
+ .emailFieldContainer input[type="email"]:valid {
1380
+ border-color: var(--success-color, #28a745);
1381
+ }
1382
+
1383
+ .emailFieldContainer input[type="email"]:valid:focus {
1384
+ border-color: var(--success-color, #28a745);
1385
+ box-shadow: none; /* Match react-select no-shadow pattern */
1386
+ }
1387
+
1388
+ /* Dark mode support */
1389
+ @media (prefers-color-scheme: dark) {
1390
+ .emailSuggestions {
1391
+ background: var(--dark-item-color, #2d3748);
1392
+ border-color: var(--dark-border-color, #4a5568);
1393
+ }
1394
+
1395
+ .emailSuggestion {
1396
+ color: var(--dark-text-color, #e2e8f0);
1397
+ border-bottom-color: var(--dark-border-color, #4a5568);
1398
+ }
1399
+
1400
+ .emailSuggestion:hover {
1401
+ background: var(--dark-primary-color, #3182ce);
1402
+ color: var(--dark-primary-text-color, #fff);
1403
+ }
1404
+ }