1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Register - Smart Clinician</title> 7 <style> 8 * { 9 margin: 0; 10 padding: 0; 11 box-sizing: border-box; 12 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 13 } 14 15 body { 16 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 17 min-height: 100vh; 18 display: flex; 19 align-items: center; 20 justify-content: center; 21 padding: 20px; 22 } 23 24 .container { 25 background: white; 26 border-radius: 20px; 27 box-shadow: 0 20px 60px rgba(0,0,0,0.3); 28 overflow: hidden; 29 max-width: 900px; 30 width: 100%; 31 display: grid; 32 grid-template-columns: 1fr 1fr; 33 } 34 35 .left-panel { 36 background: linear-gradient(135deg, #34a853 0%, #4caf50 100%); 37 color: white; 38 padding: 60px 40px; 39 display: flex; 40 flex-direction: column; 41 justify-content: center; 42 } 43 44 .logo { 45 display: flex; 46 align-items: center; 47 gap: 10px; 48 margin-bottom: 30px; 49 } 50 51 .logo a { 52 color: white; 53 text-decoration: none; 54 } 55 56 .logo-icon { 57 font-size: 40px; 58 } 59 60 .logo h1 { 61 font-size: 28px; 62 font-weight: 600; 63 } 64 65 .left-panel h2 { 66 font-size: 32px; 67 margin-bottom: 20px; 68 line-height: 1.3; 69 } 70 71 .left-panel p { 72 font-size: 16px; 73 line-height: 1.6; 74 opacity: 0.9; 75 } 76 77 .benefits { 78 margin-top: 30px; 79 } 80 81 .benefit { 82 display: flex; 83 align-items: center; 84 gap: 10px; 85 margin-bottom: 15px; 86 } 87 88 .benefit-icon { 89 font-size: 24px; 90 } 91 92 .right-panel { 93 padding: 60px 40px; 94 } 95 96 .form-header { 97 margin-bottom: 30px; 98 } 99 100 .form-header h2 { 101 font-size: 28px; 102 color: #202124; 103 margin-bottom: 10px; 104 } 105 106 .form-header p { 107 color: #5f6368; 108 font-size: 14px; 109 } 110 111 .form-row { 112 display: grid; 113 grid-template-columns: 1fr 1fr; 114 gap: 15px; 115 } 116 117 .form-group { 118 margin-bottom: 20px; 119 } 120 121 label { 122 display: block; 123 margin-bottom: 8px; 124 font-weight: 600; 125 color: #202124; 126 font-size: 14px; 127 } 128 129 input { 130 width: 100%; 131 padding: 12px 15px; 132 border: 2px solid #dadce0; 133 border-radius: 8px; 134 font-size: 14px; 135 transition: all 0.3s; 136 } 137 138 input:focus { 139 outline: none; 140 border-color: #34a853; 141 box-shadow: 0 0 0 3px rgba(52, 168, 83, 0.1); 142 } 143 144 .password-strength { 145 height: 4px; 146 background: #dadce0; 147 border-radius: 2px; 148 margin-top: 8px; 149 overflow: hidden; 150 } 151 152 .password-strength-bar { 153 height: 100%; 154 width: 0%; 155 transition: all 0.3s; 156 } 157 158 .weak { width: 33%; background: #ea4335; } 159 .medium { width: 66%; background: #fbbc04; } 160 .strong { width: 100%; background: #34a853; } 161 162 .btn { 163 width: 100%; 164 padding: 14px; 165 background: linear-gradient(135deg, #34a853 0%, #4caf50 100%); 166 color: white; 167 border: none; 168 border-radius: 8px; 169 font-size: 16px; 170 font-weight: 600; 171 cursor: pointer; 172 transition: all 0.3s; 173 margin-top: 10px; 174 } 175 176 .btn:hover { 177 transform: translateY(-2px); 178 box-shadow: 0 10px 20px rgba(52, 168, 83, 0.3); 179 } 180 181 .btn:disabled { 182 opacity: 0.6;
183 cursor: not-allowed; 184 transform: none; 185 } 186 187 .divider { 188 text-align: center; 189 margin: 25px 0; 190 color: #5f6368; 191 font-size: 14px; 192 } 193 194 .login-link { 195 text-align: center; 196 margin-top: 20px; 197 color: #5f6368; 198 font-size: 14px; 199 } 200 201 .login-link a { 202 color: #34a853; 203 text-decoration: none; 204 font-weight: 600; 205 } 206 207 .login-link a:hover { 208 text-decoration: underline; 209 } 210 211 .alert { 212 padding: 12px 15px; 213 border-radius: 8px; 214 margin-bottom: 20px; 215 font-size: 14px; 216 } 217 218 .alert-error { 219 background: #fce8e6; 220 color: #d93025; 221 border: 1px solid #d93025; 222 } 223 224 .alert-success { 225 background: #e6f4ea; 226 color: #1e8e3e; 227 border: 1px solid #1e8e3e; 228 } 229 230 /* Verification Modal */ 231 .verification-modal { 232 display: none; 233 position: fixed; 234 top: 0; 235 left: 0; 236 width: 100%; 237 height: 100%; 238 background: rgba(0, 0, 0, 0.6); 239 z-index: 10000; 240 align-items: center; 241 justify-content: center; 242 animation: fadeIn 0.3s; 243 } 244 245 .verification-modal.show { 246 display: flex; 247 } 248 249 @keyframes fadeIn { 250 from { opacity: 0; } 251 to { opacity: 1; } 252 } 253 254 .verification-modal-content { 255 background: white; 256 border-radius: 20px; 257 padding: 40px; 258 max-width: 500px; 259 width: 90%; 260 box-shadow: 0 20px 60px rgba(0,0,0,0.3); 261 text-align: center; 262 animation: slideUp 0.3s; 263 } 264 265 @keyframes slideUp { 266 from { 267 transform: translateY(30px); 268 opacity: 0; 269 } 270 to { 271 transform: translateY(0); 272 opacity: 1; 273 } 274 } 275 276 .verification-icon { 277 font-size: 64px; 278 margin-bottom: 20px; 279 } 280 281 .verification-modal-content h2 { 282 color: #202124; 283 margin-bottom: 15px; 284 font-size: 28px; 285 } 286 287 .verification-modal-content p { 288 color: #5f6368; 289 line-height: 1.6; 290 margin-bottom: 10px; 291 } 292 293 .verification-email { 294 background: #f8f9fa; 295 padding: 15px; 296 border-radius: 8px; 297 margin: 20px 0; 298 font-weight: 600; 299 color: #1a73e8; 300 font-size: 16px; 301 } 302 303 .verification-steps { 304 text-align: left; 305 margin: 25px 0; 306 padding: 20px; 307 background: #f8f9fa; 308 border-radius: 8px; 309 } 310 311 .verification-steps li { 312 margin: 10px 0; 313 color: #5f6368; 314 line-height: 1.6; 315 } 316 317 .verification-modal-btn { 318 background: linear-gradient(135deg, #34a853 0%, #4caf50 100%); 319 color: white; 320 padding: 14px 30px; 321 border: none; 322 border-radius: 8px; 323 font-size: 16px; 324 font-weight: 600; 325 cursor: pointer; 326 margin-top: 20px; 327 transition: all 0.3s; 328 width: 100%; 329 } 330 331 .verification-modal-btn:hover { 332 transform: translateY(-2px); 333 box-shadow: 0 10px 20px rgba(52, 168, 83, 0.3); 334 } 335 336 .loading { 337 display: inline-block; 338 width: 14px; 339 height: 14px; 340 border: 2px solid white; 341 border-top-color: transparent; 342 border-radius: 50%; 343 animation: spin 0.8s linear infinite; 344 margin-right: 8px; 345 } 346 347 @keyframes spin { 348 to { transform: rotate(360deg); } 349 } 350 351 .disclaimer-box { 352 background: #fff3cd; 353 border: 2px solid #ffc107; 354 border-radius: 8px; 355 padding: 20px; 356 margin: 20px 0; 357 } 358 359 .disclaimer-content { 360 font-size: 13px; 361 color: #333; 362 } 363 364 .disclaimer-content h3 { 365 color: #f57c00; 366 font-size: 16px; 367 margin-bottom: 10px; 368 display: flex; 369 align-items: center; 370 gap: 8px; 371 } 372 373 .disclaimer-content p { 374 margin-bottom: 10px; 375 color: #666; 376 } 377 378 .disclaimer-content strong { 379 color: #333; 380 } 381 382 .checkbox-label { 383 display: flex; 384 gap: 12px; 385 align-items: flex-start; 386 cursor: pointer; 387 font-size: 13px; 388 line-height: 1.6; 389 color: #333; 390 } 391 392 .checkbox-label input[type="checkbox"] {
393 width: 20px; 394 height: 20px; 395 cursor: pointer; 396 flex-shrink: 0; 397 margin-top: 2px; 398 } 399 400 .checkbox-label span { 401 flex: 1; 402 } 403 404 @media (max-width: 768px) { 405 .container { 406 grid-template-columns: 1fr; 407 } 408 409 .left-panel { 410 display: none; 411 } 412 413 .right-panel { 414 padding: 40px 20px; 415 } 416 417 .form-row { 418 grid-template-columns: 1fr; 419 } 420 } 421 </style> 422</head> 423<body> 424 <div class="container"> 425 <div class="left-panel"> 426 <div class="logo"> 427 <div class="logo-icon">ð©º</div> 428 <h1><a href="index.html">Smart Clinician</a></h1> 429 </div> 430 <h2>Join thousands of healthcare professionals</h2> 431 <p>Start making smarter clinical decisions with AI-powered medical insights.</p> 432 <div class="benefits"> 433 <div class="benefit"> 434 <div class="benefit-icon">â </div> 435 <div>Free tier with 5 cases per month</div> 436 </div> 437 <div class="benefit"> 438 <div class="benefit-icon">ð</div> 439 <div>Instant access to AI diagnosis</div> 440 </div> 441 <div class="benefit"> 442 <div class="benefit-icon">ð</div> 443 <div>Evidence-based recommendations</div> 444 </div> 445 <div class="benefit"> 446 <div class="benefit-icon">ð¼</div> 447 <div>Upgrade anytime to Professional</div> 448 </div> 449 </div> 450 </div> 451 452 <div class="right-panel"> 453 <div class="form-header"> 454 <h2>Create Account</h2> 455 <p>Start your 30-day free trial today</p> 456 </div> 457 458 <div id="alert"></div> 459 460 <form id="registerForm"> 461 <div class="form-row"> 462 <div class="form-group"> 463 <label for="firstName">First Name</label> 464 <input type="text" id="firstName" name="firstName" placeholder="John" required> 465 </div> 466 467 <div class="form-group"> 468 <label for="lastName">Last Name</label> 469 <input type="text" id="lastName" name="lastName" placeholder="Doe" required> 470 </div> 471 </div> 472 473 <div class="form-group"> 474 <label for="email">Email Address</label> 475 <input type="email" id="email" name="email" placeholder="[email protected]" required> 476 </div> 477 478 <div class="form-group"> 479 <label for="username">Username</label> 480 <input type="text" id="username" name="username" placeholder="Choose a unique username" required minlength="3" maxlength="30" pattern="[a-zA-Z0-9_-]+"> 481 <small style="display: block; margin-top: 5px; color: #666; font-size: 12px;">3-30 characters, letters, numbers, - and _ only</small> 482 </div> 483 484 <div class="form-group"> 485 <label for="password">Password</label> 486 <input type="password" id="password" name="password" placeholder="Minimum 8 characters" required> 487 <div class="password-strength"> 488 <div class="password-strength-bar" id="strengthBar"></div> 489 </div> 490 </div> 491 492 <div class="form-group"> 493 <label for="confirmPassword">Confirm Password</label> 494 <input type="password" id="confirmPassword" name="confirmPassword" placeholder="Re-enter password" required> 495 </div> 496 497 <div class="disclaimer-box"> 498 <div class="disclaimer-content"> 499 <h3>â ï¸ Important Medical Disclaimer</h3> 500 <p style="margin-top: 10px; line-height: 1.6;"> 501 <strong>Smart Clinician AI is a clinical decision support tool intended for guidance only.</strong>
502 The information provided by this system, including AI-generated diagnoses, treatment recommendations, 503 and medical guidance, is for <strong>informational and educational purposes only</strong>. 504 </p> 505 <p style="margin-top: 10px; line-height: 1.6;"> 506 This tool <strong>does not replace professional medical judgment</strong>, clinical examination, 507 or direct patient care. All recommendations must be verified by qualified healthcare professionals. 508 Healthcare providers remain fully responsible for all clinical decisions and patient care. 509 </p> 510 <p style="margin-top: 10px; line-height: 1.6;"> 511 This tool uses AI models trained on publicly available medical literature and is <strong>not affiliated 512 with, endorsed by, or licensed from UpToDate, PubMed, Cochrane, AHA/ACC, NICE, IDSA, ESC, or any other 513 medical organization</strong>. 514 </p> 515 <p style="margin-top: 10px; line-height: 1.6; font-weight: 600;"> 516 By using this service, you acknowledge that AI-generated content may contain errors and should 517 always be independently verified against current evidence-based guidelines and standards of care. 518 </p> 519 </div> 520 <div class="form-group" style="margin-top: 15px;"> 521 <label class="checkbox-label"> 522 <input type="checkbox" id="disclaimerAcknowledgment" name="disclaimerAcknowledgment" required> 523 <span> 524 I acknowledge that I have read and understood the disclaimer above. I understand that this 525 tool is for guidance only and does not replace professional medical judgment. 526 </span> 527 </label> 528 </div> 529 </div> 530 531 <button type="submit" class="btn" id="submitBtn"> 532 Create Account 533 </button> 534 </form> 535 536 <div class="divider">OR</div> 537 538 <div class="login-link"> 539 Already have an account? <a href="login.html">Sign In</a> 540 </div> 541 </div> 542 </div> 543 544 <!-- Email Verification Modal --> 545 <div id="verificationModal" class="verification-modal"> 546 <div class="verification-modal-content"> 547 <div class="verification-icon">âï¸</div> 548 <h2>Check Your Email!</h2> 549 <p>We've sent a verification email to:</p> 550 <div class="verification-email" id="verificationEmail">[email protected]</div> 551 <p>Please click the verification link in the email to activate your account.</p> 552 553 <div class="verification-steps"> 554 <h3 style="margin-bottom: 15px; color: #202124; font-size: 16px;">ð Next Steps:</h3> 555 <ol style="padding-left: 20px;"> 556 <li>Check your email inbox (and spam folder)</li> 557 <li>Click the verification link in the email</li> 558 <li>Return to the login page to sign in</li> 559 </ol> 560 </div> 561 562 <p style="font-size: 13px; color: #999; margin-top: 20px;"> 563 Didn't receive the email? Check your spam folder or contact support. 564 </p> 565 566 <button class="verification-modal-btn" onclick="closeVerificationModal()"> 567 Got it! Take me to Login 568 </button> 569 </div> 570 </div> 571 572
572<script> 573 // Use global API_URL if available, otherwise set it 574 if (typeof window.API_URL === 'undefined') { 575 window.API_URL = 'https://smartclinician.ai/api'; 576 } 577 const form = document.getElementById('registerForm'); 578 const submitBtn = document.getElementById('submitBtn'); 579 const alertDiv = document.getElementById('alert'); 580 const passwordInput = document.getElementById('password'); 581 const strengthBar = document.getElementById('strengthBar'); 582 583 // Password strength checker 584 passwordInput.addEventListener('input', (e) => { 585 const password = e.target.value; 586 let strength = 0; 587 588 if (password.length >= 8) strength++; 589 if (password.length >= 12) strength++; 590 if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++; 591 if (/[0-9]/.test(password)) strength++; 592 if (/[^a-zA-Z0-9]/.test(password)) strength++; 593 594 strengthBar.className = 'password-strength-bar'; 595 if (strength <= 2) { 596 strengthBar.classList.add('weak'); 597 } else if (strength <= 4) { 598 strengthBar.classList.add('medium'); 599 } else { 600 strengthBar.classList.add('strong'); 601 } 602 }); 603 604 form.addEventListener('submit', async (e) => { 605 e.preventDefault(); 606 607 const firstName = document.getElementById('firstName').value; 608 const lastName = document.getElementById('lastName').value; 609 const email = document.getElementById('email').value; 610 const username = document.getElementById('username').value; 611 const password = document.getElementById('password').value; 612 const confirmPassword = document.getElementById('confirmPassword').value; 613 const disclaimerAcknowledgment = document.getElementById('disclaimerAcknowledgment').checked; 614 615 // Validation 616 if (password !== confirmPassword) { 617 showAlert('Passwords do not match', 'error'); 618 return; 619 } 620 621 if (password.length < 8) { 622 showAlert('Password must be at least 8 characters', 'error'); 623 return; 624 } 625 626 if (!disclaimerAcknowledgment) { 627 showAlert('You must acknowledge the disclaimer to create an account', 'error'); 628 return; 629 } 630 631 // Disable button and show loading 632 submitBtn.disabled = true; 633 submitBtn.innerHTML = '<span class="loading"></span>Creating account...'; 634 alertDiv.innerHTML = ''; 635 636 try { 637 const response = await fetch(`${API_URL}/auth/register`, { 638 method: 'POST', 639 headers: { 640 'Content-Type': 'application/json' 641 }, 642 body: JSON.stringify({ 643 firstName, 644 lastName, 645 email, 646 username, 647 password, 648 disclaimerAcknowledged: disclaimerAcknowledgment 649 }) 650 }); 651 652 const data = await response.json(); 653 654 if (response.ok) { 655 // Always reset button state on success 656 submitBtn.disabled = false; 657 submitBtn.innerHTML = 'Create Account'; 658 659 // CRITICAL: For new registrations, NEVER log in immediately 660 // Always require email verification - this is the only safe path 661 // Check for token first - if NO token exists, verification is required 662 const hasToken = data.token && typeof data.token === 'string' && data.token.length > 0; 663 const isVerified = data.user && data.user.emailVerified === true; 664 const requiresVerification = data.requiresVerification === true || !hasToken || !isVerified; 665 666 if (requiresVerification) { 667 // Email verification REQUIRED - show modal and DO NOT log in 668 // This is the expected and ONLY behavior for new registrations 669 if (data.user && data.user.email) { 670 showVerificationModal(data.user.email); 671 } else { 672 // Fallback if user data is missing 673 showVerificationModal(data.email || email || 'your email'); 674 } 675 } else if (hasToken && data.user && isVerified) { 676 // ONLY allow login if ALL conditions are met: 677 // 1. Token exists 678 // 2. User data exists 679 // 3. Email is verified 680 // This should only happen for pre-verified accounts (very rare) 681 // DISABLED: Even if token exists, require verification for new registrations 682 console.warn('â ï¸ Registration returned token but requiring verification anyway for security'); 683 // DO NOT store token - force verification 684 showVerificationModal(data.user?.email || data.email || email || 'your email'); 685 } else { 686 // Edge case - default to verification required 687 console.warn('â ï¸ Registration state unclear - defaulting to verification required'); 688 showVerificationModal(data.user?.email || data.email || email || 'your email'); 689 } 690 } else { 691 // Registration failed 692 showAlert(data.error || data.message || 'Registration failed. Please try again.', 'error'); 693 submitBtn.disabled = false;
694 submitBtn.innerHTML = 'Create Account'; 695 } 696 } catch (error) { 697 console.error('Registration error:', error); 698 showAlert('Network error. Please check your connection and try again.', 'error'); 699 submitBtn.disabled = false; 700 submitBtn.innerHTML = 'Create Account'; 701 } 702 }); 703 704 function showAlert(message, type) { 705 alertDiv.innerHTML = `<div class="alert alert-${type}">${message}</div>`; 706 } 707 708 // Show verification modal 709 function showVerificationModal(email) { 710 const modal = document.getElementById('verificationModal'); 711 const emailElement = document.getElementById('verificationEmail'); 712 713 if (emailElement) { 714 emailElement.textContent = email; 715 } 716 717 if (modal) { 718 modal.classList.add('show'); 719 // Prevent body scrolling when modal is open 720 document.body.style.overflow = 'hidden'; 721 } 722 } 723 724 // Close verification modal and redirect to login 725 function closeVerificationModal() { 726 const modal = document.getElementById('verificationModal'); 727 if (modal) { 728 modal.classList.remove('show'); 729 document.body.style.overflow = ''; 730 // Redirect to login page 731 window.location.href = 'login.html?message=' + encodeURIComponent('Please verify your email before logging in'); 732 } 733 } 734 735 // Close modal when clicking outside 736 document.addEventListener('click', function(e) { 737 const modal = document.getElementById('verificationModal'); 738 if (modal && e.target === modal) { 739 closeVerificationModal(); 740 } 741 }); 742 743 // Check if already logged in and verified 744 // IMPORTANT: Only redirect verified users - don't allow unverified users to skip verification 745 const existingToken = localStorage.getItem('token'); 746 if (existingToken) { 747 // Verify user is actually verified before redirecting 748 fetch(`${API_URL}/auth/me`, { 749 headers: { 750 'Authorization': `Bearer ${existingToken}` 751 } 752 }) 753 .then(response => response.json()) 754 .then(data => { 755 if (data.user && data.user.emailVerified === true) { 756 // User is verified - allow redirect 757 window.location.href = 'smartclinician.html'; 758 } else { 759 // User not verified - clear token and stay on registration page 760 localStorage.removeItem('token'); 761 localStorage.removeItem('user'); 762 } 763 }) 764 .catch(() => { 765 // API call failed - clear invalid token 766 localStorage.removeItem('token'); 767 localStorage.removeItem('user'); 768 }); 769 } 770 </script>
770 771</body> 772</html> 773
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.