|
152 | 152 | } |
153 | 153 | |
154 | 154 | .pending-user-email { |
155 | | - font-size: 1.1rem; |
| 155 | + font-size: 1.2rem; |
156 | 156 | color: #f7fafc; |
157 | 157 | } |
158 | 158 | |
|
176 | 176 | |
177 | 177 | .info-label { |
178 | 178 | color: #a0aec0; |
179 | | - font-size: 0.9rem; |
| 179 | + font-size: 1rem; |
180 | 180 | font-weight: 500; |
181 | 181 | } |
182 | 182 | |
183 | 183 | .info-value { |
184 | 184 | color: #f7fafc; |
185 | | - font-size: 0.9rem; |
| 185 | + font-size: 1rem; |
186 | 186 | } |
187 | 187 | |
188 | 188 | .pending-user-divider { |
|
191 | 191 | margin: 1.5rem 0; |
192 | 192 | } |
193 | 193 | |
| 194 | + /* Email validation warning styles */ |
| 195 | + .email-warning { |
| 196 | + padding: 0.75rem; |
| 197 | + margin: 0.5rem 0; |
| 198 | + border-radius: 8px; |
| 199 | + display: flex; |
| 200 | + align-items: center; |
| 201 | + font-size: 0.95rem; |
| 202 | + font-weight: 500; |
| 203 | + } |
| 204 | + |
| 205 | + .email-warning.invalid-warning { |
| 206 | + background: linear-gradient(135deg, rgba(229, 62, 62, 0.1) 0%, rgba(229, 62, 62, 0.05) 100%); |
| 207 | + border: 1px solid rgba(229, 62, 62, 0.3); |
| 208 | + color: #fed7d7; |
| 209 | + } |
| 210 | + |
| 211 | + .email-warning.invalid-warning i { |
| 212 | + color: #e53e3e; |
| 213 | + margin-right: 0.5rem; |
| 214 | + font-size: 1rem; |
| 215 | + } |
| 216 | + |
| 217 | + .email-warning.suspicious-warning { |
| 218 | + background: linear-gradient(135deg, rgba(214, 158, 46, 0.1) 0%, rgba(214, 158, 46, 0.05) 100%); |
| 219 | + border: 1px solid rgba(214, 158, 46, 0.3); |
| 220 | + color: #faf089; |
| 221 | + } |
| 222 | + |
| 223 | + .email-warning.suspicious-warning i { |
| 224 | + color: #d69e2e; |
| 225 | + margin-right: 0.5rem; |
| 226 | + font-size: 1rem; |
| 227 | + } |
| 228 | + |
194 | 229 | .recent-search-item { |
195 | 230 | margin-bottom: 0.75rem; |
| 231 | + font-size: 0.95rem; |
| 232 | + } |
| 233 | + |
| 234 | + .recent-searches h6 { |
| 235 | + font-size: 1rem; |
| 236 | + color: #e2e8f0; |
| 237 | + font-weight: 600; |
196 | 238 | } |
197 | 239 | |
198 | 240 | .no-users-found { |
|
297 | 339 | .recent-searches-title { |
298 | 340 | color: #e2e8f0; |
299 | 341 | font-weight: 600; |
300 | | - font-size: 0.875rem; |
| 342 | + font-size: 1rem; |
301 | 343 | margin: 0; |
302 | 344 | display: flex; |
303 | 345 | align-items: center; |
|
2338 | 2380 | displayPendingUserResultsPage(pendingUsers, pagination.currentPage, pagination.totalRecords, pagination.totalPages); |
2339 | 2381 | } |
2340 | 2382 | |
| 2383 | + // Email validation functions |
| 2384 | + function isValidEmail(email) { |
| 2385 | + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
| 2386 | + return emailRegex.test(email); |
| 2387 | + } |
| 2388 | + |
| 2389 | + function getEmailValidationLevel(email) { |
| 2390 | + if (!email || typeof email !== 'string') return 'invalid'; |
| 2391 | + |
| 2392 | + // Basic email structure check |
| 2393 | + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
| 2394 | + if (!emailRegex.test(email)) return 'invalid'; |
| 2395 | + |
| 2396 | + // Check for common typos in domain extensions |
| 2397 | + const domain = email.split('@')[1]; |
| 2398 | + const validExtensions = [ |
| 2399 | + 'com', 'org', 'net', 'edu', 'gov', 'mil', 'int', |
| 2400 | + 'co.uk', 'co.jp', 'co.in', 'com.au', 'com.br', 'com.mx', |
| 2401 | + 'de', 'fr', 'it', 'es', 'ca', 'au', 'jp', 'in', 'br', 'mx' |
| 2402 | + ]; |
| 2403 | + |
| 2404 | + // Check if domain ends with a valid extension |
| 2405 | + const hasValidExtension = validExtensions.some(ext => domain.endsWith('.' + ext) || domain === ext); |
| 2406 | + |
| 2407 | + // Check for suspicious patterns |
| 2408 | + const suspiciousPatterns = [ |
| 2409 | + /[^a-zA-Z0-9@._-]/, // Contains invalid characters |
| 2410 | + /\.{2,}/, // Multiple consecutive dots |
| 2411 | + /^\.|\.$/, // Starts or ends with dot |
| 2412 | + /@.*@/, // Multiple @ symbols |
| 2413 | + /\.{2,}@/, // Dots before @ |
| 2414 | + /@\.{2,}/, // Dots after @ |
| 2415 | + ]; |
| 2416 | + |
| 2417 | + for (const pattern of suspiciousPatterns) { |
| 2418 | + if (pattern.test(email)) return 'invalid'; |
| 2419 | + } |
| 2420 | + |
| 2421 | + // Check for common typos in popular domains |
| 2422 | + const commonDomains = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'aol.com']; |
| 2423 | + const emailDomain = domain.toLowerCase(); |
| 2424 | + |
| 2425 | + for (const commonDomain of commonDomains) { |
| 2426 | + if (emailDomain.includes(commonDomain) && emailDomain !== commonDomain) { |
| 2427 | + // Check if it's just a typo (similar but not exact) |
| 2428 | + if (emailDomain.length <= commonDomain.length + 3) { |
| 2429 | + return 'suspicious'; |
| 2430 | + } |
| 2431 | + } |
| 2432 | + } |
| 2433 | + |
| 2434 | + // Check for domains that end with extra characters (like .comq) |
| 2435 | + if (domain.includes('.') && !hasValidExtension) { |
| 2436 | + const parts = domain.split('.'); |
| 2437 | + const lastPart = parts[parts.length - 1]; |
| 2438 | + if (lastPart.length > 4 || !/^[a-zA-Z]+$/.test(lastPart)) { |
| 2439 | + return 'suspicious'; |
| 2440 | + } |
| 2441 | + } |
| 2442 | + |
| 2443 | + return hasValidExtension ? 'valid' : 'suspicious'; |
| 2444 | + } |
| 2445 | + |
| 2446 | + function getEmailWarningIcon(validationLevel) { |
| 2447 | + switch (validationLevel) { |
| 2448 | + case 'invalid': |
| 2449 | + return '<i class="fas fa-exclamation-triangle" style="color: #e53e3e; margin-left: 8px;" title="Invalid email address"></i>'; |
| 2450 | + case 'suspicious': |
| 2451 | + return '<i class="fas fa-exclamation-circle" style="color: #d69e2e; margin-left: 8px;" title="Potentially invalid email address"></i>'; |
| 2452 | + default: |
| 2453 | + return '<i class="fas fa-check-circle" style="color: #38a169; margin-left: 8px;" title="Valid email address"></i>'; |
| 2454 | + } |
| 2455 | + } |
| 2456 | + |
| 2457 | + function getEmailWarningMessage(validationLevel, email) { |
| 2458 | + switch (validationLevel) { |
| 2459 | + case 'invalid': |
| 2460 | + return `<div class="email-warning invalid-warning"> |
| 2461 | + <i class="fas fa-exclamation-triangle"></i> |
| 2462 | + <span>Invalid email format - user may have entered incorrect email</span> |
| 2463 | + </div>`; |
| 2464 | + case 'suspicious': |
| 2465 | + const domain = email.split('@')[1]; |
| 2466 | + return `<div class="email-warning suspicious-warning"> |
| 2467 | + <i class="fas fa-exclamation-circle"></i> |
| 2468 | + <span>Potentially invalid email (${domain}) - verify with user</span> |
| 2469 | + </div>`; |
| 2470 | + default: |
| 2471 | + return ''; |
| 2472 | + } |
| 2473 | + } |
| 2474 | + |
2341 | 2475 | function displayPendingUserResultsPage(pendingUsers, currentPage, totalRecords, totalPages) { |
2342 | 2476 | let html = ''; |
2343 | 2477 | |
2344 | 2478 | pendingUsers.forEach((pendingUser, index) => { |
| 2479 | + // Validate email |
| 2480 | + const emailValidation = getEmailValidationLevel(pendingUser.email); |
| 2481 | + const warningIcon = getEmailWarningIcon(emailValidation); |
| 2482 | + const warningMessage = getEmailWarningMessage(emailValidation, pendingUser.email); |
| 2483 | + |
2345 | 2484 | const createdAt = new Date(pendingUser.createdAt).toLocaleString(); |
2346 | 2485 | html += ` |
2347 | 2486 | <div class="pending-user-card"> |
2348 | 2487 | <div class="pending-user-header"> |
2349 | 2488 | <div class="pending-user-email"> |
2350 | 2489 | <i class="fas fa-envelope" style="margin-right: 8px; color: #667eea;"></i> |
2351 | 2490 | <strong>${pendingUser.email}</strong> |
| 2491 | + ${warningIcon} |
2352 | 2492 | </div> |
2353 | 2493 | <div class="pending-user-actions"> |
2354 | 2494 | <button class="btn btn-sm copy-btn" |
|
2358 | 2498 | </button> |
2359 | 2499 | </div> |
2360 | 2500 | </div> |
| 2501 | + ${warningMessage} |
2361 | 2502 | <div class="pending-user-details"> |
2362 | 2503 | <div class="pending-user-info"> |
2363 | 2504 | <div class="info-item"> |
|
0 commit comments