diff --git a/frontend/public/eliza.png b/frontend/public/eliza.png deleted file mode 100755 index df80e92..0000000 Binary files a/frontend/public/eliza.png and /dev/null differ diff --git a/frontend/public/profile.png b/frontend/public/profile.png deleted file mode 100755 index a633b87..0000000 Binary files a/frontend/public/profile.png and /dev/null differ diff --git a/frontend/public/select-job-analysis.png b/frontend/public/select-job-analysis.png deleted file mode 100755 index 3ee2971..0000000 Binary files a/frontend/public/select-job-analysis.png and /dev/null differ diff --git a/frontend/src/pages/Conversation.png b/frontend/src/assets/Conversation.png similarity index 100% rename from frontend/src/pages/Conversation.png rename to frontend/src/assets/Conversation.png diff --git a/frontend/public/final-resume.png b/frontend/src/assets/final-resume.png similarity index 100% rename from frontend/public/final-resume.png rename to frontend/src/assets/final-resume.png diff --git a/frontend/public/select-a-candidate.png b/frontend/src/assets/select-a-candidate.png similarity index 100% rename from frontend/public/select-a-candidate.png rename to frontend/src/assets/select-a-candidate.png diff --git a/frontend/public/select-a-job.png b/frontend/src/assets/select-a-job.png similarity index 100% rename from frontend/public/select-a-job.png rename to frontend/src/assets/select-a-job.png diff --git a/frontend/src/assets/select-job-analysis.png b/frontend/src/assets/select-job-analysis.png new file mode 100755 index 0000000..92c71a1 Binary files /dev/null and b/frontend/src/assets/select-job-analysis.png differ diff --git a/frontend/public/select-start-analysis.png b/frontend/src/assets/select-start-analysis.png similarity index 100% rename from frontend/public/select-start-analysis.png rename to frontend/src/assets/select-start-analysis.png diff --git a/frontend/public/wait.png b/frontend/src/assets/wait.png similarity index 100% rename from frontend/public/wait.png rename to frontend/src/assets/wait.png diff --git a/frontend/src/components/EmailVerificationComponents.tsx b/frontend/src/components/EmailVerificationComponents.tsx index d2b4f46..602a65b 100644 --- a/frontend/src/components/EmailVerificationComponents.tsx +++ b/frontend/src/components/EmailVerificationComponents.tsx @@ -17,7 +17,8 @@ import { DialogActions, Checkbox, FormControlLabel, - Grid + Grid, + IconButton } from '@mui/material'; import { Email as EmailIcon, @@ -25,7 +26,9 @@ import { CheckCircle as CheckCircleIcon, ErrorOutline as ErrorIcon, Refresh as RefreshIcon, - DevicesOther as DevicesIcon + DevicesOther as DevicesIcon, + VisibilityOff, + Visibility } from '@mui/icons-material'; import { useAuth } from 'hooks/AuthContext'; import { BackstoryPageProps } from './BackstoryTab'; @@ -493,6 +496,7 @@ const LoginForm = () => { const [password, setPassword] = useState(''); const [errorMessage, setErrorMessage] = useState(null); const navigate = useNavigate(); + const [showPassword, setShowPassword] = useState(false); useEffect(() => { if (!error) { @@ -547,14 +551,28 @@ const LoginForm = () => { autoFocus /> setPassword(e.target.value)} autoComplete="current-password" + placeholder="Create a strong password" + required + InputProps={{ + endAdornment: ( + + setShowPassword(!showPassword)} + onMouseDown={(e) => e.preventDefault()} + edge="end" + > + {showPassword ? : } + + + ), + }} /> {errorMessage && ( diff --git a/frontend/src/components/RegistrationForms.tsx b/frontend/src/components/RegistrationForms.tsx deleted file mode 100644 index f803531..0000000 --- a/frontend/src/components/RegistrationForms.tsx +++ /dev/null @@ -1,1308 +0,0 @@ -import React, { useState } from 'react'; -import { ApiClient } from 'services/api-client'; -import { RegistrationSuccessDialog } from 'components/EmailVerificationComponents'; -import { useAuth } from 'hooks/AuthContext'; -import { useNavigate } from 'react-router-dom'; - -// Candidate Registration Form -const CandidateRegistrationForm = () => { - const { apiClient } = useAuth(); - const navigate = useNavigate(); - const [formData, setFormData] = useState({ - email: '', - username: '', - password: '', - confirmPassword: '', - firstName: '', - lastName: '', - phone: '' - }); - - const [loading, setLoading] = useState(false); - const [errors, setErrors] = useState>({}); - const [showSuccess, setShowSuccess] = useState(false); - const [registrationResult, setRegistrationResult] = useState(null); - - const validateForm = () => { - const newErrors: Record = {}; - - // Email validation - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!formData.email) { - newErrors.email = 'Email is required'; - } else if (!emailRegex.test(formData.email)) { - newErrors.email = 'Please enter a valid email address'; - } - - // Username validation - if (!formData.username) { - newErrors.username = 'Username is required'; - } else if (formData.username.length < 3) { - newErrors.username = 'Username must be at least 3 characters'; - } else if (!/^[a-zA-Z0-9_]+$/.test(formData.username)) { - newErrors.username = 'Username can only contain letters, numbers, and underscores'; - } - - // Password validation - if (!formData.password) { - newErrors.password = 'Password is required'; - } else { - const passwordErrors = validatePassword(formData.password); - if (passwordErrors.length > 0) { - newErrors.password = passwordErrors.join(', '); - } - } - - // Confirm password - if (formData.password !== formData.confirmPassword) { - newErrors.confirmPassword = 'Passwords do not match'; - } - - // Name validation - if (!formData.firstName.trim()) { - newErrors.firstName = 'First name is required'; - } - if (!formData.lastName.trim()) { - newErrors.lastName = 'Last name is required'; - } - - // Phone validation (optional but must be valid if provided) - if (formData.phone && !/^[\+]?[1-9][\d]{0,15}$/.test(formData.phone.replace(/\s/g, ''))) { - newErrors.phone = 'Please enter a valid phone number'; - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - const validatePassword = (password: string): string[] => { - const errors: string[] = []; - - if (password.length < 8) { - errors.push('at least 8 characters'); - } - if (!/[a-z]/.test(password)) { - errors.push('one lowercase letter'); - } - if (!/[A-Z]/.test(password)) { - errors.push('one uppercase letter'); - } - if (!/\d/.test(password)) { - errors.push('one number'); - } - if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { - errors.push('one special character'); - } - - return errors.length > 0 ? [`Password must contain ${errors.join(', ')}`] : []; - }; - - const handleInputChange = (field: string, value: string) => { - setFormData(prev => ({ ...prev, [field]: value })); - - // Clear error when user starts typing - if (errors[field]) { - setErrors(prev => ({ ...prev, [field]: '' })); - } - }; - - const handleSubmit = async () => { - - if (!validateForm()) { - return; - } - - setLoading(true); - try { - const result = await apiClient.createCandidate({ - email: formData.email, - username: formData.username, - password: formData.password, - firstName: formData.firstName, - lastName: formData.lastName, - phone: formData.phone || undefined - }); - - // Set pending verification - apiClient.setPendingEmailVerification(formData.email); - - setRegistrationResult(result); - setShowSuccess(true); - - } catch (error: any) { - if (error.message.includes('already exists')) { - if (error.message.includes('email')) { - setErrors({ email: 'An account with this email already exists' }); - } else if (error.message.includes('username')) { - setErrors({ username: 'This username is already taken' }); - } - } else { - setErrors({ general: error.message || 'Registration failed. Please try again.' }); - } - } finally { - setLoading(false); - } - }; - - const getPasswordStrength = (password: string) => { - const validations = [ - password.length >= 8, - /[a-z]/.test(password), - /[A-Z]/.test(password), - /\d/.test(password), - /[!@#$%^&*(),.?":{}|<>]/.test(password) - ]; - - const strength = validations.filter(Boolean).length; - - if (strength < 2) return { level: 'weak', color: '#f44336', width: '20%' }; - if (strength < 4) return { level: 'medium', color: '#ff9800', width: '60%' }; - return { level: 'strong', color: '#4caf50', width: '100%' }; - }; - - const passwordStrength = formData.password ? getPasswordStrength(formData.password) : null; - - return ( -
-
-
-

- Join as a Candidate -

-

- Create your account to start finding your dream job -

-
- -
- {/* Email Field */} -
- - handleInputChange('email', e.target.value)} - placeholder="your.email@example.com" - style={{ - width: '100%', - padding: '12px', - border: errors.email ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box', - transition: 'border-color 0.3s ease' - }} - onFocus={(e) => e.target.style.borderColor = '#1976d2'} - onBlur={(e) => e.target.style.borderColor = errors.email ? '#f44336' : '#ddd'} - /> - {errors.email && ( -
- {errors.email} -
- )} -
- - {/* Username Field */} -
- - handleInputChange('username', e.target.value.toLowerCase())} - placeholder="johndoe123" - style={{ - width: '100%', - padding: '12px', - border: errors.username ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box' - }} - /> - {errors.username && ( -
- {errors.username} -
- )} -
- - {/* Name Fields */} -
-
- - handleInputChange('firstName', e.target.value)} - placeholder="John" - style={{ - width: '100%', - padding: '12px', - border: errors.firstName ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box' - }} - /> - {errors.firstName && ( -
- {errors.firstName} -
- )} -
- -
- - handleInputChange('lastName', e.target.value)} - placeholder="Doe" - style={{ - width: '100%', - padding: '12px', - border: errors.lastName ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box' - }} - /> - {errors.lastName && ( -
- {errors.lastName} -
- )} -
-
- - {/* Phone Field */} -
- - handleInputChange('phone', e.target.value)} - placeholder="+1 (555) 123-4567" - style={{ - width: '100%', - padding: '12px', - border: errors.phone ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box' - }} - /> - {errors.phone && ( -
- {errors.phone} -
- )} -
- - {/* Password Field */} -
- - handleInputChange('password', e.target.value)} - placeholder="Create a strong password" - style={{ - width: '100%', - padding: '12px', - border: errors.password ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box' - }} - /> - - {/* Password Strength Indicator */} - {formData.password && passwordStrength && ( -
-
-
-
-
- Password strength: {passwordStrength.level} -
-
- )} - - {errors.password && ( -
- {errors.password} -
- )} -
- - {/* Confirm Password Field */} -
- - handleInputChange('confirmPassword', e.target.value)} - placeholder="Confirm your password" - style={{ - width: '100%', - padding: '12px', - border: errors.confirmPassword ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box' - }} - /> - {errors.confirmPassword && ( -
- {errors.confirmPassword} -
- )} -
- - {/* General Error */} - {errors.general && ( -
- {errors.general} -
- )} - - {/* Submit Button */} - - - {/* Login Link */} - -
-
- - {/* Success Dialog */} - {showSuccess && registrationResult && ( - setShowSuccess(false)} - email={registrationResult.email} - userType="candidate" - /> - )} - - -
- ); -} - -// Employer Registration Form -const EmployerRegistrationForm = () => { - const [formData, setFormData] = useState({ - email: '', - username: '', - password: '', - confirmPassword: '', - companyName: '', - industry: '', - companySize: '', - companyDescription: '', - websiteUrl: '', - phone: '' - }); - - const [loading, setLoading] = useState(false); - const [errors, setErrors] = useState>({}); - const [showSuccess, setShowSuccess] = useState(false); - const [registrationResult, setRegistrationResult] = useState(null); - - const apiClient = new ApiClient(); - - const industryOptions = [ - 'Technology', 'Healthcare', 'Finance', 'Education', 'Manufacturing', - 'Retail', 'Consulting', 'Media', 'Non-profit', 'Government', 'Other' - ]; - - const companySizeOptions = [ - '1-10 employees', '11-50 employees', '51-200 employees', - '201-500 employees', '501-1000 employees', '1000+ employees' - ]; - - const validateForm = () => { - const newErrors: Record = {}; - - // Email validation - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!formData.email) { - newErrors.email = 'Email is required'; - } else if (!emailRegex.test(formData.email)) { - newErrors.email = 'Please enter a valid email address'; - } - - // Username validation - if (!formData.username) { - newErrors.username = 'Username is required'; - } else if (formData.username.length < 3) { - newErrors.username = 'Username must be at least 3 characters'; - } - - // Password validation - if (!formData.password) { - newErrors.password = 'Password is required'; - } else { - const passwordErrors = validatePassword(formData.password); - if (passwordErrors.length > 0) { - newErrors.password = passwordErrors.join(', '); - } - } - - // Confirm password - if (formData.password !== formData.confirmPassword) { - newErrors.confirmPassword = 'Passwords do not match'; - } - - // Company validation - if (!formData.companyName.trim()) { - newErrors.companyName = 'Company name is required'; - } - if (!formData.industry) { - newErrors.industry = 'Industry is required'; - } - if (!formData.companySize) { - newErrors.companySize = 'Company size is required'; - } - if (!formData.companyDescription.trim()) { - newErrors.companyDescription = 'Company description is required'; - } else if (formData.companyDescription.length < 50) { - newErrors.companyDescription = 'Company description must be at least 50 characters'; - } - - // Website URL validation (optional but must be valid if provided) - if (formData.websiteUrl) { - try { - new URL(formData.websiteUrl); - } catch { - newErrors.websiteUrl = 'Please enter a valid website URL'; - } - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - const validatePassword = (password: string): string[] => { - const errors: string[] = []; - - if (password.length < 8) { - errors.push('at least 8 characters'); - } - if (!/[a-z]/.test(password)) { - errors.push('one lowercase letter'); - } - if (!/[A-Z]/.test(password)) { - errors.push('one uppercase letter'); - } - if (!/\d/.test(password)) { - errors.push('one number'); - } - if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { - errors.push('one special character'); - } - - return errors.length > 0 ? [`Password must contain ${errors.join(', ')}`] : []; - }; - - const handleInputChange = (field: string, value: string) => { - setFormData(prev => ({ ...prev, [field]: value })); - - // Clear error when user starts typing - if (errors[field]) { - setErrors(prev => ({ ...prev, [field]: '' })); - } - }; - - const handleSubmit = async () => { - - if (!validateForm()) { - return; - } - - setLoading(true); - try { - const result = await apiClient.createEmployerWithVerification({ - email: formData.email, - username: formData.username, - password: formData.password, - companyName: formData.companyName, - industry: formData.industry, - companySize: formData.companySize, - companyDescription: formData.companyDescription, - websiteUrl: formData.websiteUrl || undefined, - phone: formData.phone || undefined - }); - - // Set pending verification - apiClient.setPendingEmailVerification(formData.email); - - setRegistrationResult(result); - setShowSuccess(true); - - } catch (error: any) { - if (error.message.includes('already exists')) { - if (error.message.includes('email')) { - setErrors({ email: 'An account with this email already exists' }); - } else if (error.message.includes('username')) { - setErrors({ username: 'This username is already taken' }); - } - } else { - setErrors({ general: error.message || 'Registration failed. Please try again.' }); - } - } finally { - setLoading(false); - } - }; - - return ( -
-
-
-

- Join as an Employer -

-

- Create your company account to start hiring top talent -

-
- -
- {/* Account Information Section */} -
-

Account Information

- - {/* Email and Username */} -
-
- - handleInputChange('email', e.target.value)} - placeholder="company@example.com" - style={{ - width: '100%', - padding: '12px', - border: errors.email ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box' - }} - /> - {errors.email && ( -
- {errors.email} -
- )} -
- -
- - handleInputChange('username', e.target.value.toLowerCase())} - placeholder="company123" - style={{ - width: '100%', - padding: '12px', - border: errors.username ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box' - }} - /> - {errors.username && ( -
- {errors.username} -
- )} -
-
- - {/* Password Fields */} -
-
- - handleInputChange('password', e.target.value)} - placeholder="Create a strong password" - style={{ - width: '100%', - padding: '12px', - border: errors.password ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box' - }} - /> - {errors.password && ( -
- {errors.password} -
- )} -
- -
- - handleInputChange('confirmPassword', e.target.value)} - placeholder="Confirm your password" - style={{ - width: '100%', - padding: '12px', - border: errors.confirmPassword ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box' - }} - /> - {errors.confirmPassword && ( -
- {errors.confirmPassword} -
- )} -
-
-
- - {/* Company Information Section */} -
-

Company Information

- - {/* Company Name */} -
- - handleInputChange('companyName', e.target.value)} - placeholder="Your Company Inc." - style={{ - width: '100%', - padding: '12px', - border: errors.companyName ? '2px solid #f44336' : '2px solid #ddd', - borderRadius: '8px', - fontSize: '16px', - outline: 'none', - boxSizing: 'border-box' - }} - /> - {errors.companyName && ( -
- {errors.companyName} -
- )} -
- - {/* Industry and Company Size */} -
-
- - - {errors.industry && ( -
- {errors.industry} -
- )} -
- -
- - - {errors.companySize && ( -
- {errors.companySize} -
- )} -
-
- - {/* Company Description */} -
- -