1308 lines
43 KiB
TypeScript
1308 lines
43 KiB
TypeScript
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<Record<string, string>>({});
|
|
const [showSuccess, setShowSuccess] = useState(false);
|
|
const [registrationResult, setRegistrationResult] = useState<any>(null);
|
|
|
|
const validateForm = () => {
|
|
const newErrors: Record<string, string> = {};
|
|
|
|
// 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.createCandidateWithVerification({
|
|
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 (
|
|
<div style={{
|
|
maxWidth: '500px',
|
|
margin: '0 auto',
|
|
padding: '20px',
|
|
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
}}>
|
|
<div style={{
|
|
backgroundColor: 'white',
|
|
borderRadius: '12px',
|
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
|
padding: '40px'
|
|
}}>
|
|
<div style={{ textAlign: 'center', marginBottom: '30px' }}>
|
|
<h1 style={{ margin: '0 0 8px 0', color: '#333' }}>
|
|
Join as a Candidate
|
|
</h1>
|
|
<p style={{ margin: 0, color: '#666' }}>
|
|
Create your account to start finding your dream job
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
{/* Email Field */}
|
|
<div style={{ marginBottom: '20px' }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Email Address *
|
|
</label>
|
|
<input
|
|
type="email"
|
|
value={formData.email}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.email}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Username Field */}
|
|
<div style={{ marginBottom: '20px' }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Username *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.username}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.username}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Name Fields */}
|
|
<div style={{ display: 'flex', gap: '12px', marginBottom: '20px' }}>
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
First Name *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.firstName}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.firstName}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Last Name *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.lastName}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.lastName}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Phone Field */}
|
|
<div style={{ marginBottom: '20px' }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Phone Number (Optional)
|
|
</label>
|
|
<input
|
|
type="tel"
|
|
value={formData.phone}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.phone}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Password Field */}
|
|
<div style={{ marginBottom: '20px' }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Password *
|
|
</label>
|
|
<input
|
|
type="password"
|
|
value={formData.password}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ marginTop: '8px' }}>
|
|
<div style={{
|
|
width: '100%',
|
|
height: '4px',
|
|
backgroundColor: '#f0f0f0',
|
|
borderRadius: '2px',
|
|
overflow: 'hidden'
|
|
}}>
|
|
<div style={{
|
|
width: passwordStrength.width,
|
|
height: '100%',
|
|
backgroundColor: passwordStrength.color,
|
|
transition: 'all 0.3s ease'
|
|
}} />
|
|
</div>
|
|
<div style={{
|
|
fontSize: '12px',
|
|
marginTop: '4px',
|
|
color: passwordStrength.color,
|
|
textTransform: 'capitalize'
|
|
}}>
|
|
Password strength: {passwordStrength.level}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{errors.password && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.password}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Confirm Password Field */}
|
|
<div style={{ marginBottom: '24px' }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Confirm Password *
|
|
</label>
|
|
<input
|
|
type="password"
|
|
value={formData.confirmPassword}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.confirmPassword}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* General Error */}
|
|
{errors.general && (
|
|
<div style={{
|
|
padding: '12px 16px',
|
|
backgroundColor: '#ffebee',
|
|
border: '1px solid #f44336',
|
|
borderRadius: '8px',
|
|
marginBottom: '20px',
|
|
color: '#c62828'
|
|
}}>
|
|
{errors.general}
|
|
</div>
|
|
)}
|
|
|
|
{/* Submit Button */}
|
|
<button
|
|
onClick={handleSubmit}
|
|
disabled={loading}
|
|
style={{
|
|
width: '100%',
|
|
padding: '16px',
|
|
backgroundColor: loading ? '#ccc' : '#1976d2',
|
|
color: 'white',
|
|
border: 'none',
|
|
borderRadius: '8px',
|
|
fontSize: '16px',
|
|
fontWeight: '600',
|
|
cursor: loading ? 'not-allowed' : 'pointer',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
transition: 'background-color 0.3s ease'
|
|
}}
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<div style={{
|
|
width: '20px',
|
|
height: '20px',
|
|
border: '2px solid transparent',
|
|
borderTop: '2px solid white',
|
|
borderRadius: '50%',
|
|
animation: 'spin 1s linear infinite',
|
|
marginRight: '8px'
|
|
}} />
|
|
Creating Account...
|
|
</>
|
|
) : (
|
|
'Create Account'
|
|
)}
|
|
</button>
|
|
|
|
{/* Login Link */}
|
|
<div style={{ textAlign: 'center', marginTop: '20px' }}>
|
|
<span style={{ color: '#666', fontSize: '14px' }}>
|
|
Already have an account?{' '}
|
|
<a
|
|
onClick={(e) => { e.preventDefault(); navigate('/login'); }}
|
|
style={{
|
|
color: '#1976d2',
|
|
textDecoration: 'none',
|
|
fontWeight: '600'
|
|
}}
|
|
>
|
|
Sign in here
|
|
</a>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Success Dialog */}
|
|
{showSuccess && registrationResult && (
|
|
<RegistrationSuccessDialog
|
|
open={showSuccess}
|
|
onClose={() => setShowSuccess(false)}
|
|
email={registrationResult.email}
|
|
userType="candidate"
|
|
/>
|
|
)}
|
|
|
|
<style>
|
|
{`
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
`}
|
|
</style>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 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<Record<string, string>>({});
|
|
const [showSuccess, setShowSuccess] = useState(false);
|
|
const [registrationResult, setRegistrationResult] = useState<any>(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<string, string> = {};
|
|
|
|
// 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 (
|
|
<div style={{
|
|
maxWidth: '600px',
|
|
margin: '0 auto',
|
|
padding: '20px',
|
|
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
}}>
|
|
<div style={{
|
|
backgroundColor: 'white',
|
|
borderRadius: '12px',
|
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
|
padding: '40px'
|
|
}}>
|
|
<div style={{ textAlign: 'center', marginBottom: '30px' }}>
|
|
<h1 style={{ margin: '0 0 8px 0', color: '#333' }}>
|
|
Join as an Employer
|
|
</h1>
|
|
<p style={{ margin: 0, color: '#666' }}>
|
|
Create your company account to start hiring top talent
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
{/* Account Information Section */}
|
|
<div style={{
|
|
backgroundColor: '#f8f9fa',
|
|
padding: '20px',
|
|
borderRadius: '8px',
|
|
marginBottom: '24px'
|
|
}}>
|
|
<h3 style={{ margin: '0 0 16px 0', color: '#333' }}>Account Information</h3>
|
|
|
|
{/* Email and Username */}
|
|
<div style={{ display: 'flex', gap: '12px', marginBottom: '20px' }}>
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Email Address *
|
|
</label>
|
|
<input
|
|
type="email"
|
|
value={formData.email}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.email}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Username *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.username}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.username}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Password Fields */}
|
|
<div style={{ display: 'flex', gap: '12px' }}>
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Password *
|
|
</label>
|
|
<input
|
|
type="password"
|
|
value={formData.password}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.password}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Confirm Password *
|
|
</label>
|
|
<input
|
|
type="password"
|
|
value={formData.confirmPassword}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.confirmPassword}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Company Information Section */}
|
|
<div style={{
|
|
backgroundColor: '#f0f7ff',
|
|
padding: '20px',
|
|
borderRadius: '8px',
|
|
marginBottom: '24px'
|
|
}}>
|
|
<h3 style={{ margin: '0 0 16px 0', color: '#333' }}>Company Information</h3>
|
|
|
|
{/* Company Name */}
|
|
<div style={{ marginBottom: '20px' }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Company Name *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.companyName}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.companyName}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Industry and Company Size */}
|
|
<div style={{ display: 'flex', gap: '12px', marginBottom: '20px' }}>
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Industry *
|
|
</label>
|
|
<select
|
|
value={formData.industry}
|
|
onChange={(e) => handleInputChange('industry', e.target.value)}
|
|
style={{
|
|
width: '100%',
|
|
padding: '12px',
|
|
border: errors.industry ? '2px solid #f44336' : '2px solid #ddd',
|
|
borderRadius: '8px',
|
|
fontSize: '16px',
|
|
outline: 'none',
|
|
boxSizing: 'border-box',
|
|
backgroundColor: 'white'
|
|
}}
|
|
>
|
|
<option value="">Select Industry</option>
|
|
{industryOptions.map(industry => (
|
|
<option key={industry} value={industry}>{industry}</option>
|
|
))}
|
|
</select>
|
|
{errors.industry && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.industry}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Company Size *
|
|
</label>
|
|
<select
|
|
value={formData.companySize}
|
|
onChange={(e) => handleInputChange('companySize', e.target.value)}
|
|
style={{
|
|
width: '100%',
|
|
padding: '12px',
|
|
border: errors.companySize ? '2px solid #f44336' : '2px solid #ddd',
|
|
borderRadius: '8px',
|
|
fontSize: '16px',
|
|
outline: 'none',
|
|
boxSizing: 'border-box',
|
|
backgroundColor: 'white'
|
|
}}
|
|
>
|
|
<option value="">Select Size</option>
|
|
{companySizeOptions.map(size => (
|
|
<option key={size} value={size}>{size}</option>
|
|
))}
|
|
</select>
|
|
{errors.companySize && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.companySize}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Company Description */}
|
|
<div style={{ marginBottom: '20px' }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Company Description * (minimum 50 characters)
|
|
</label>
|
|
<textarea
|
|
value={formData.companyDescription}
|
|
onChange={(e) => handleInputChange('companyDescription', e.target.value)}
|
|
placeholder="Tell us about your company, what you do, your mission, and what makes you unique..."
|
|
rows={4}
|
|
style={{
|
|
width: '100%',
|
|
padding: '12px',
|
|
border: errors.companyDescription ? '2px solid #f44336' : '2px solid #ddd',
|
|
borderRadius: '8px',
|
|
fontSize: '16px',
|
|
outline: 'none',
|
|
boxSizing: 'border-box',
|
|
resize: 'vertical',
|
|
fontFamily: 'inherit'
|
|
}}
|
|
/>
|
|
<div style={{
|
|
fontSize: '12px',
|
|
color: '#666',
|
|
marginTop: '4px',
|
|
textAlign: 'right'
|
|
}}>
|
|
{formData.companyDescription.length}/50 characters minimum
|
|
</div>
|
|
{errors.companyDescription && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.companyDescription}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Website and Phone */}
|
|
<div style={{ display: 'flex', gap: '12px' }}>
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Website URL (Optional)
|
|
</label>
|
|
<input
|
|
type="url"
|
|
value={formData.websiteUrl}
|
|
onChange={(e) => handleInputChange('websiteUrl', e.target.value)}
|
|
placeholder="https://www.yourcompany.com"
|
|
style={{
|
|
width: '100%',
|
|
padding: '12px',
|
|
border: errors.websiteUrl ? '2px solid #f44336' : '2px solid #ddd',
|
|
borderRadius: '8px',
|
|
fontSize: '16px',
|
|
outline: 'none',
|
|
boxSizing: 'border-box'
|
|
}}
|
|
/>
|
|
{errors.websiteUrl && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.websiteUrl}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div style={{ flex: 1 }}>
|
|
<label style={{
|
|
display: 'block',
|
|
marginBottom: '6px',
|
|
fontWeight: '600',
|
|
color: '#333'
|
|
}}>
|
|
Phone Number (Optional)
|
|
</label>
|
|
<input
|
|
type="tel"
|
|
value={formData.phone}
|
|
onChange={(e) => 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 && (
|
|
<div style={{ color: '#f44336', fontSize: '14px', marginTop: '4px' }}>
|
|
{errors.phone}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* General Error */}
|
|
{errors.general && (
|
|
<div style={{
|
|
padding: '12px 16px',
|
|
backgroundColor: '#ffebee',
|
|
border: '1px solid #f44336',
|
|
borderRadius: '8px',
|
|
marginBottom: '20px',
|
|
color: '#c62828'
|
|
}}>
|
|
{errors.general}
|
|
</div>
|
|
)}
|
|
|
|
{/* Submit Button */}
|
|
<button
|
|
onClick={handleSubmit}
|
|
disabled={loading}
|
|
style={{
|
|
width: '100%',
|
|
padding: '16px',
|
|
backgroundColor: loading ? '#ccc' : '#1976d2',
|
|
color: 'white',
|
|
border: 'none',
|
|
borderRadius: '8px',
|
|
fontSize: '16px',
|
|
fontWeight: '600',
|
|
cursor: loading ? 'not-allowed' : 'pointer',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
transition: 'background-color 0.3s ease'
|
|
}}
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<div style={{
|
|
width: '20px',
|
|
height: '20px',
|
|
border: '2px solid transparent',
|
|
borderTop: '2px solid white',
|
|
borderRadius: '50%',
|
|
animation: 'spin 1s linear infinite',
|
|
marginRight: '8px'
|
|
}} />
|
|
Creating Company Account...
|
|
</>
|
|
) : (
|
|
'Create Company Account'
|
|
)}
|
|
</button>
|
|
|
|
{/* Login Link */}
|
|
<div style={{ textAlign: 'center', marginTop: '20px' }}>
|
|
<span style={{ color: '#666', fontSize: '14px' }}>
|
|
Already have an account?{' '}
|
|
<a
|
|
href="/login"
|
|
style={{
|
|
color: '#1976d2',
|
|
textDecoration: 'none',
|
|
fontWeight: '600'
|
|
}}
|
|
>
|
|
Sign in here
|
|
</a>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Success Dialog */}
|
|
{showSuccess && registrationResult && (
|
|
<RegistrationSuccessDialog
|
|
open={showSuccess}
|
|
onClose={() => setShowSuccess(false)}
|
|
email={registrationResult.email}
|
|
userType="employer"
|
|
/>
|
|
)}
|
|
|
|
<style>
|
|
{`
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
`}
|
|
</style>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Registration Type Selector Component
|
|
export function RegistrationTypeSelector() {
|
|
return (
|
|
<div style={{
|
|
maxWidth: '600px',
|
|
margin: '60px auto',
|
|
padding: '20px',
|
|
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
}}>
|
|
<div style={{
|
|
textAlign: 'center',
|
|
marginBottom: '40px'
|
|
}}>
|
|
<h1 style={{ margin: '0 0 16px 0', fontSize: '32px', color: '#333' }}>
|
|
Join Backstory
|
|
</h1>
|
|
<p style={{ margin: 0, fontSize: '18px', color: '#666' }}>
|
|
Choose how you'd like to get started
|
|
</p>
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', gap: '20px' }}>
|
|
{/* Candidate Option */}
|
|
<div style={{
|
|
flex: 1,
|
|
padding: '30px',
|
|
backgroundColor: 'white',
|
|
borderRadius: '12px',
|
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
|
textAlign: 'center',
|
|
cursor: 'pointer',
|
|
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
|
|
border: '2px solid transparent'
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.transform = 'translateY(-4px)';
|
|
e.currentTarget.style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.15)';
|
|
e.currentTarget.style.borderColor = '#1976d2';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.transform = 'translateY(0)';
|
|
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.1)';
|
|
e.currentTarget.style.borderColor = 'transparent';
|
|
}}
|
|
onClick={() => window.location.href = '/register/candidate'}
|
|
>
|
|
<div style={{ fontSize: '48px', marginBottom: '16px' }}>👤</div>
|
|
<h3 style={{ margin: '0 0 12px 0', color: '#333' }}>I'm looking for work</h3>
|
|
<p style={{ margin: '0 0 20px 0', color: '#666', fontSize: '14px' }}>
|
|
Create a candidate profile to find your next opportunity
|
|
</p>
|
|
<div style={{
|
|
display: 'inline-block',
|
|
padding: '10px 20px',
|
|
backgroundColor: '#1976d2',
|
|
color: 'white',
|
|
borderRadius: '6px',
|
|
fontSize: '14px',
|
|
fontWeight: '600'
|
|
}}>
|
|
Join as Candidate
|
|
</div>
|
|
</div>
|
|
|
|
{/* Employer Option */}
|
|
<div style={{
|
|
flex: 1,
|
|
padding: '30px',
|
|
backgroundColor: 'white',
|
|
borderRadius: '12px',
|
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
|
textAlign: 'center',
|
|
cursor: 'pointer',
|
|
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
|
|
border: '2px solid transparent'
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.transform = 'translateY(-4px)';
|
|
e.currentTarget.style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.15)';
|
|
e.currentTarget.style.borderColor = '#1976d2';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.transform = 'translateY(0)';
|
|
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.1)';
|
|
e.currentTarget.style.borderColor = 'transparent';
|
|
}}
|
|
onClick={() => window.location.href = '/register/employer'}
|
|
>
|
|
<div style={{ fontSize: '48px', marginBottom: '16px' }}>🏢</div>
|
|
<h3 style={{ margin: '0 0 12px 0', color: '#333' }}>I'm hiring</h3>
|
|
<p style={{ margin: '0 0 20px 0', color: '#666', fontSize: '14px' }}>
|
|
Create a company account to find and hire talent
|
|
</p>
|
|
<div style={{
|
|
display: 'inline-block',
|
|
padding: '10px 20px',
|
|
backgroundColor: '#1976d2',
|
|
color: 'white',
|
|
borderRadius: '6px',
|
|
fontSize: '14px',
|
|
fontWeight: '600'
|
|
}}>
|
|
Join as Employer
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{ textAlign: 'center', marginTop: '30px' }}>
|
|
<span style={{ color: '#666', fontSize: '14px' }}>
|
|
Already have an account?{' '}
|
|
<a
|
|
href="/login"
|
|
style={{
|
|
color: '#1976d2',
|
|
textDecoration: 'none',
|
|
fontWeight: '600'
|
|
}}
|
|
>
|
|
Sign in here
|
|
</a>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export { CandidateRegistrationForm, EmployerRegistrationForm }; |