Spaces:
Sleeping
Sleeping
Download frontend/src/components/DomainValidationModal.jsx from chowdary1/ai-interviewer-version-2: direct link, hf CLI and curl.
- Browser
- Download file 20.3 kB
-
https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/components/DomainValidationModal.jsx
- Command line
-
hf download hf://spaces/chowdary1/ai-interviewer-version-2/frontend/src/components/DomainValidationModal.jsx
-
curl -L -o DomainValidationModal.jsx https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/components/DomainValidationModal.jsx
20.3 kB
| /** | |
| * Domain Validation Modal | |
| * | |
| * CRITICAL UI COMPONENT for resume-first architecture. | |
| * | |
| * This modal: | |
| * 1. Shows when user-selected domain doesn't match resume domain | |
| * 2. BLOCKS interview flow until user confirms or switches domain | |
| * 3. Shows clear warning with confidence scores | |
| * 4. Provides options to switch domain or continue anyway | |
| */ | |
| import React from 'react'; | |
| import { | |
| AlertTriangle, CheckCircle, XCircle, ArrowRight, | |
| RefreshCw, Shield, Code, Cpu, Zap, Building2, Palette, Cog | |
| } from 'lucide-react'; | |
| // Domain icon mapping | |
| const DOMAIN_ICONS = { | |
| computer_science: Code, | |
| electronics: Cpu, | |
| electrical: Zap, | |
| mechanical: Cog, | |
| civil: Building2, | |
| architecture: Palette, | |
| }; | |
| // Domain color mapping | |
| const DOMAIN_COLORS = { | |
| computer_science: '#8b5cf6', | |
| electronics: '#06b6d4', | |
| electrical: '#f59e0b', | |
| mechanical: '#ef4444', | |
| civil: '#10b981', | |
| architecture: '#ec4899', | |
| }; | |
| function DomainValidationModal({ | |
| validationResult, | |
| selectedBranch, | |
| selectedRole, | |
| onSwitchDomain, | |
| onContinueAnyway, | |
| onClose, | |
| isLoading = false, | |
| }) { | |
| if (!validationResult) return null; | |
| const { | |
| is_match, | |
| warning_level, | |
| message, | |
| recommendation, | |
| can_proceed, | |
| requires_confirmation, | |
| resume_domain, | |
| resume_domain_name, | |
| resume_confidence, | |
| allowed_rounds, | |
| // NEW: Role-level mismatch fields | |
| mismatch_type, | |
| resume_specialization, | |
| resume_specialization_name, | |
| selected_role_specialization, | |
| selected_role_specialization_name, | |
| resume_skills, | |
| match_confidence, | |
| } = validationResult; | |
| // Check if this is a role-level mismatch (within CS domain) | |
| const isRoleMismatch = mismatch_type === 'role'; | |
| // Get icons for domains | |
| const ResumeDomainIcon = DOMAIN_ICONS[resume_domain] || Code; | |
| const SelectedDomainIcon = DOMAIN_ICONS[selectedBranch] || Code; | |
| const resumeDomainColor = DOMAIN_COLORS[resume_domain] || '#8b5cf6'; | |
| const selectedDomainColor = DOMAIN_COLORS[selectedBranch] || '#8b5cf6'; | |
| // Determine modal severity | |
| const isHardWarning = warning_level === 'hard'; | |
| const isSoftWarning = warning_level === 'soft'; | |
| // Modal style based on severity | |
| const severityStyles = { | |
| hard: { | |
| borderColor: '#ef4444', | |
| iconBg: 'rgba(239, 68, 68, 0.1)', | |
| iconColor: '#ef4444', | |
| }, | |
| soft: { | |
| borderColor: '#f59e0b', | |
| iconBg: 'rgba(245, 158, 11, 0.1)', | |
| iconColor: '#f59e0b', | |
| }, | |
| none: { | |
| borderColor: '#10b981', | |
| iconBg: 'rgba(16, 185, 129, 0.1)', | |
| iconColor: '#10b981', | |
| }, | |
| }; | |
| const styles = severityStyles[warning_level] || severityStyles.none; | |
| // If it's a match, don't show modal (auto-proceed) | |
| if (is_match && can_proceed && !requires_confirmation) { | |
| return null; | |
| } | |
| return ( | |
| <div className="domain-modal-overlay"> | |
| <div | |
| className="domain-modal" | |
| style={{ borderColor: styles.borderColor }} | |
| > | |
| {/* Header Icon */} | |
| <div | |
| className="domain-modal-icon" | |
| style={{ backgroundColor: styles.iconBg }} | |
| > | |
| {isHardWarning ? ( | |
| <XCircle size={48} color={styles.iconColor} /> | |
| ) : isSoftWarning ? ( | |
| <AlertTriangle size={48} color={styles.iconColor} /> | |
| ) : ( | |
| <CheckCircle size={48} color={styles.iconColor} /> | |
| )} | |
| </div> | |
| {/* Title */} | |
| <h2 className="domain-modal-title"> | |
| {isHardWarning ? (isRoleMismatch ? '🚫 Role Mismatch Detected' : '🚫 Domain Mismatch Detected') : | |
| isSoftWarning ? (isRoleMismatch ? '⚠️ Role Mismatch' : '⚠️ Domain Mismatch') : | |
| '✅ Validated'} | |
| </h2> | |
| {/* Message */} | |
| <div className="domain-modal-message"> | |
| <p>{message}</p> | |
| </div> | |
| {/* Domain Comparison */} | |
| <div className="domain-comparison"> | |
| {/* Resume Domain */} | |
| <div className="domain-card resume-domain"> | |
| <div | |
| className="domain-card-icon" | |
| style={{ backgroundColor: resumeDomainColor + '20' }} | |
| > | |
| <ResumeDomainIcon size={24} color={resumeDomainColor} /> | |
| </div> | |
| <div className="domain-card-content"> | |
| <span className="domain-card-label">Your Resume</span> | |
| <span className="domain-card-name"> | |
| {isRoleMismatch ? resume_specialization_name : resume_domain_name} | |
| </span> | |
| <span className="domain-card-confidence"> | |
| {Math.round((match_confidence || resume_confidence || 0) * 100)}% confidence | |
| </span> | |
| {isRoleMismatch && resume_skills && resume_skills.length > 0 && ( | |
| <span className="domain-card-skills"> | |
| Skills: {resume_skills.slice(0, 3).join(', ')} | |
| </span> | |
| )} | |
| </div> | |
| </div> | |
| {/* Arrow */} | |
| <div className="domain-comparison-arrow"> | |
| <ArrowRight size={24} /> | |
| </div> | |
| {/* Selected Domain */} | |
| <div className="domain-card selected-domain"> | |
| <div | |
| className="domain-card-icon" | |
| style={{ backgroundColor: selectedDomainColor + '20' }} | |
| > | |
| <SelectedDomainIcon size={24} color={selectedDomainColor} /> | |
| </div> | |
| <div className="domain-card-content"> | |
| <span className="domain-card-label">You Selected</span> | |
| <span className="domain-card-name"> | |
| {isRoleMismatch ? selectedRole : selectedBranch.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())} | |
| </span> | |
| {!isRoleMismatch && <span className="domain-card-role">{selectedRole}</span>} | |
| {isRoleMismatch && selected_role_specialization_name && ( | |
| <span className="domain-card-role">({selected_role_specialization_name})</span> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| {/* Role Mismatch Explanation */} | |
| {isRoleMismatch && ( | |
| <div className="role-mismatch-explanation"> | |
| <Code size={16} /> | |
| <span> | |
| Both roles are in <strong>Computer Science</strong>, but they require different skill sets. | |
| Interview questions will be tailored to your selected role. | |
| </span> | |
| </div> | |
| )} | |
| {/* Recommendation */} | |
| {recommendation && ( | |
| <div className="domain-recommendation"> | |
| <RefreshCw size={16} /> | |
| <span>{recommendation}</span> | |
| </div> | |
| )} | |
| {/* Blocked Rounds Warning */} | |
| {allowed_rounds && allowed_rounds.blocked_rounds && ( | |
| <div className="blocked-rounds-warning"> | |
| <Shield size={16} /> | |
| <span> | |
| <strong>Note:</strong> The following rounds are not applicable for non-CS domains:{' '} | |
| {allowed_rounds.blocked_rounds.join(', ')} | |
| </span> | |
| </div> | |
| )} | |
| {/* Action Buttons */} | |
| <div className="domain-modal-actions"> | |
| {/* Switch Domain Button - Always shown for mismatch */} | |
| {!is_match && ( | |
| <button | |
| className="btn-switch-domain" | |
| onClick={() => onSwitchDomain(resume_domain, resume_domain_name)} | |
| disabled={isLoading} | |
| style={{ backgroundColor: resumeDomainColor }} | |
| > | |
| <ResumeDomainIcon size={18} /> | |
| Switch to {resume_domain_name} | |
| </button> | |
| )} | |
| {/* Continue Anyway Button */} | |
| {(can_proceed || requires_confirmation) && ( | |
| <button | |
| className={`btn-continue-anyway ${isHardWarning ? 'warning' : ''}`} | |
| onClick={onContinueAnyway} | |
| disabled={isLoading} | |
| > | |
| {isLoading ? ( | |
| <span className="loading-spinner" /> | |
| ) : ( | |
| <>Continue Anyway (Advanced)</> | |
| )} | |
| </button> | |
| )} | |
| {/* Cancel/Close Button */} | |
| {!can_proceed && !requires_confirmation && ( | |
| <button | |
| className="btn-close-modal" | |
| onClick={onClose} | |
| > | |
| Go Back | |
| </button> | |
| )} | |
| </div> | |
| {/* Warning Footer for Hard Mismatch */} | |
| {isHardWarning && ( | |
| <div className="domain-modal-footer-warning"> | |
| ⚠️ Continuing with mismatched domain may result in irrelevant questions and lower scores. | |
| </div> | |
| )} | |
| </div> | |
| <style jsx>{` | |
| .domain-modal-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: rgba(0, 0, 0, 0.8); | |
| backdrop-filter: blur(8px); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 9999; | |
| animation: fadeIn 0.2s ease-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .domain-modal { | |
| background: linear-gradient(145deg, #1a1a2e 0%, #0d0d1a 100%); | |
| border: 2px solid; | |
| border-radius: 20px; | |
| padding: 32px; | |
| max-width: 520px; | |
| width: 90%; | |
| animation: slideUp 0.3s ease-out; | |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); | |
| } | |
| @keyframes slideUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .domain-modal-icon { | |
| width: 80px; | |
| height: 80px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin: 0 auto 20px; | |
| } | |
| .domain-modal-title { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| text-align: center; | |
| margin-bottom: 16px; | |
| color: #fff; | |
| } | |
| .domain-modal-message { | |
| text-align: center; | |
| color: rgba(255, 255, 255, 0.8); | |
| line-height: 1.6; | |
| margin-bottom: 24px; | |
| white-space: pre-line; | |
| } | |
| .domain-comparison { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 16px; | |
| margin-bottom: 24px; | |
| flex-wrap: wrap; | |
| } | |
| .domain-card { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 12px; | |
| padding: 16px; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| min-width: 180px; | |
| } | |
| .domain-card-icon { | |
| width: 48px; | |
| height: 48px; | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .domain-card-content { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| } | |
| .domain-card-label { | |
| font-size: 0.75rem; | |
| color: rgba(255, 255, 255, 0.5); | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| } | |
| .domain-card-name { | |
| font-size: 1rem; | |
| font-weight: 600; | |
| color: #fff; | |
| } | |
| .domain-card-confidence, | |
| .domain-card-role { | |
| font-size: 0.8rem; | |
| color: rgba(255, 255, 255, 0.6); | |
| } | |
| .domain-comparison-arrow { | |
| color: rgba(255, 255, 255, 0.3); | |
| } | |
| .domain-recommendation { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 12px 16px; | |
| background: rgba(139, 92, 246, 0.1); | |
| border: 1px solid rgba(139, 92, 246, 0.3); | |
| border-radius: 8px; | |
| color: #a78bfa; | |
| font-size: 0.9rem; | |
| margin-bottom: 16px; | |
| } | |
| .blocked-rounds-warning { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 8px; | |
| padding: 12px 16px; | |
| background: rgba(245, 158, 11, 0.1); | |
| border: 1px solid rgba(245, 158, 11, 0.3); | |
| border-radius: 8px; | |
| color: #fbbf24; | |
| font-size: 0.85rem; | |
| margin-bottom: 16px; | |
| } | |
| .role-mismatch-explanation { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 8px; | |
| padding: 12px 16px; | |
| background: rgba(139, 92, 246, 0.1); | |
| border: 1px solid rgba(139, 92, 246, 0.3); | |
| border-radius: 8px; | |
| color: #c4b5fd; | |
| font-size: 0.85rem; | |
| margin-bottom: 16px; | |
| } | |
| .role-mismatch-explanation strong { | |
| color: #a78bfa; | |
| } | |
| .domain-card-skills { | |
| font-size: 0.75rem; | |
| color: rgba(255, 255, 255, 0.5); | |
| margin-top: 4px; | |
| font-style: italic; | |
| } | |
| .domain-modal-actions { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| margin-top: 24px; | |
| } | |
| .btn-switch-domain { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| padding: 14px 24px; | |
| border: none; | |
| border-radius: 10px; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| color: #fff; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| } | |
| .btn-switch-domain:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3); | |
| } | |
| .btn-continue-anyway { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 12px 24px; | |
| background: transparent; | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| border-radius: 10px; | |
| font-size: 0.9rem; | |
| color: rgba(255, 255, 255, 0.7); | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| } | |
| .btn-continue-anyway:hover { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-color: rgba(255, 255, 255, 0.3); | |
| } | |
| .btn-continue-anyway.warning { | |
| border-color: rgba(239, 68, 68, 0.3); | |
| color: rgba(239, 68, 68, 0.8); | |
| } | |
| .btn-continue-anyway.warning:hover { | |
| background: rgba(239, 68, 68, 0.1); | |
| } | |
| .btn-close-modal { | |
| padding: 12px 24px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border: none; | |
| border-radius: 10px; | |
| font-size: 0.9rem; | |
| color: #fff; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| } | |
| .btn-close-modal:hover { | |
| background: rgba(255, 255, 255, 0.15); | |
| } | |
| .loading-spinner { | |
| width: 20px; | |
| height: 20px; | |
| border: 2px solid rgba(255, 255, 255, 0.3); | |
| border-top-color: #fff; | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .domain-modal-footer-warning { | |
| margin-top: 16px; | |
| padding: 12px; | |
| text-align: center; | |
| font-size: 0.8rem; | |
| color: #fca5a5; | |
| background: rgba(239, 68, 68, 0.1); | |
| border-radius: 8px; | |
| } | |
| @media (max-width: 640px) { | |
| .domain-modal { | |
| padding: 24px; | |
| } | |
| .domain-comparison { | |
| flex-direction: column; | |
| } | |
| .domain-comparison-arrow { | |
| transform: rotate(90deg); | |
| } | |
| .domain-card { | |
| width: 100%; | |
| } | |
| } | |
| `}</style> | |
| </div> | |
| ); | |
| } | |
| export default DomainValidationModal; | |