939 lines
29 KiB
TypeScript
939 lines
29 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
Paper,
|
|
Box,
|
|
Typography,
|
|
TextField,
|
|
Button,
|
|
Stack,
|
|
Alert,
|
|
Select,
|
|
MenuItem,
|
|
FormControl,
|
|
InputLabel,
|
|
FormHelperText,
|
|
LinearProgress,
|
|
CircularProgress,
|
|
Link,
|
|
Card,
|
|
CardContent,
|
|
CardActions,
|
|
useMediaQuery,
|
|
useTheme,
|
|
IconButton,
|
|
InputAdornment,
|
|
} from "@mui/material";
|
|
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
|
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 theme = useTheme();
|
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
const [showSuccess, setShowSuccess] = useState(false);
|
|
const [registrationResult, setRegistrationResult] = useState<any>(null);
|
|
|
|
// Password visibility states
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
|
|
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.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: "error", value: 20 };
|
|
if (strength < 4) return { level: "medium", color: "warning", value: 60 };
|
|
return { level: "strong", color: "success", value: 100 };
|
|
};
|
|
|
|
const passwordStrength = formData.password
|
|
? getPasswordStrength(formData.password)
|
|
: null;
|
|
|
|
return (
|
|
<Box sx={{ p: isMobile ? 1 : 5 }}>
|
|
<Box sx={{ textAlign: "center", mb: 4 }}>
|
|
<Typography variant="h4" component="h1" sx={{ mb: 1 }}>
|
|
Join as a Candidate
|
|
</Typography>
|
|
<Typography variant="body1" color="text.secondary">
|
|
Create your account to start finding your dream job
|
|
</Typography>
|
|
</Box>
|
|
|
|
<Stack spacing={3}>
|
|
<TextField
|
|
fullWidth
|
|
label="Email Address"
|
|
type="email"
|
|
value={formData.email}
|
|
onChange={(e) => handleInputChange("email", e.target.value)}
|
|
placeholder="your.email@example.com"
|
|
error={!!errors.email}
|
|
helperText={errors.email}
|
|
required
|
|
/>
|
|
|
|
<TextField
|
|
fullWidth
|
|
label="Username"
|
|
value={formData.username}
|
|
onChange={(e) =>
|
|
handleInputChange("username", e.target.value.toLowerCase())
|
|
}
|
|
placeholder="johndoe123"
|
|
error={!!errors.username}
|
|
helperText={errors.username}
|
|
required
|
|
/>
|
|
|
|
<Stack direction="row" spacing={2}>
|
|
<TextField
|
|
fullWidth
|
|
label="First Name"
|
|
value={formData.firstName}
|
|
onChange={(e) => handleInputChange("firstName", e.target.value)}
|
|
placeholder="John"
|
|
error={!!errors.firstName}
|
|
helperText={errors.firstName}
|
|
required
|
|
/>
|
|
<TextField
|
|
fullWidth
|
|
label="Last Name"
|
|
value={formData.lastName}
|
|
onChange={(e) => handleInputChange("lastName", e.target.value)}
|
|
placeholder="Doe"
|
|
error={!!errors.lastName}
|
|
helperText={errors.lastName}
|
|
required
|
|
/>
|
|
</Stack>
|
|
|
|
<TextField
|
|
fullWidth
|
|
label="Phone Number"
|
|
type="tel"
|
|
value={formData.phone}
|
|
onChange={(e) => handleInputChange("phone", e.target.value)}
|
|
placeholder="+1 (555) 123-4567"
|
|
error={!!errors.phone}
|
|
helperText={errors.phone || "Optional"}
|
|
/>
|
|
|
|
<Box>
|
|
<TextField
|
|
fullWidth
|
|
label="Password"
|
|
type={showPassword ? "text" : "password"}
|
|
value={formData.password}
|
|
onChange={(e) => handleInputChange("password", e.target.value)}
|
|
placeholder="Create a strong password"
|
|
error={!!errors.password}
|
|
helperText={errors.password}
|
|
required
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton
|
|
aria-label="toggle password visibility"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
onMouseDown={(e) => e.preventDefault()}
|
|
edge="end"
|
|
>
|
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
{formData.password && passwordStrength && (
|
|
<Box sx={{ mt: 1 }}>
|
|
<LinearProgress
|
|
variant="determinate"
|
|
value={passwordStrength.value}
|
|
color={passwordStrength.color as any}
|
|
sx={{ height: 6, borderRadius: 3 }}
|
|
/>
|
|
<Typography
|
|
variant="caption"
|
|
color={`${passwordStrength.color}.main`}
|
|
sx={{ mt: 0.5, display: "block", textTransform: "capitalize" }}
|
|
>
|
|
Password strength: {passwordStrength.level}
|
|
</Typography>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
|
|
<TextField
|
|
fullWidth
|
|
label="Confirm Password"
|
|
type={showConfirmPassword ? "text" : "password"}
|
|
value={formData.confirmPassword}
|
|
onChange={(e) => handleInputChange("confirmPassword", e.target.value)}
|
|
placeholder="Confirm your password"
|
|
error={!!errors.confirmPassword}
|
|
helperText={errors.confirmPassword}
|
|
required
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton
|
|
aria-label="toggle confirm password visibility"
|
|
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
|
onMouseDown={(e) => e.preventDefault()}
|
|
edge="end"
|
|
>
|
|
{showConfirmPassword ? <VisibilityOff /> : <Visibility />}
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
|
|
{errors.general && <Alert severity="error">{errors.general}</Alert>}
|
|
|
|
<Button
|
|
fullWidth
|
|
variant="contained"
|
|
size="large"
|
|
onClick={handleSubmit}
|
|
disabled={loading}
|
|
sx={{ py: 2 }}
|
|
>
|
|
{loading ? (
|
|
<Stack direction="row" alignItems="center" spacing={1}>
|
|
<CircularProgress size={20} color="inherit" />
|
|
<Typography>Creating Account...</Typography>
|
|
</Stack>
|
|
) : (
|
|
"Create Account"
|
|
)}
|
|
</Button>
|
|
|
|
<Box sx={{ textAlign: "center" }}>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Already have an account?{" "}
|
|
<Link
|
|
component="button"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/login");
|
|
}}
|
|
sx={{ fontWeight: 600 }}
|
|
>
|
|
Sign in here
|
|
</Link>
|
|
</Typography>
|
|
</Box>
|
|
</Stack>
|
|
{showSuccess && registrationResult && (
|
|
<RegistrationSuccessDialog
|
|
open={showSuccess}
|
|
onClose={() => setShowSuccess(false)}
|
|
email={registrationResult.email}
|
|
userType="candidate"
|
|
/>
|
|
)}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
// 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);
|
|
|
|
// Password visibility states
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
|
|
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 (
|
|
<Paper elevation={3}>
|
|
<Box sx={{ p: 5 }}>
|
|
<Box sx={{ textAlign: "center", mb: 4 }}>
|
|
<Typography variant="h4" component="h1" sx={{ mb: 1 }}>
|
|
Join as an Employer
|
|
</Typography>
|
|
<Typography variant="body1" color="text.secondary">
|
|
Create your company account to start hiring top talent
|
|
</Typography>
|
|
</Box>
|
|
|
|
<Stack spacing={4}>
|
|
{/* Account Information Section */}
|
|
<Box sx={{ bgcolor: "grey.50", p: 3, borderRadius: 2 }}>
|
|
<Typography variant="h6" sx={{ mb: 2 }}>
|
|
Account Information
|
|
</Typography>
|
|
|
|
<Stack spacing={3}>
|
|
<Stack direction="row" spacing={2}>
|
|
<TextField
|
|
fullWidth
|
|
label="Email Address"
|
|
type="email"
|
|
value={formData.email}
|
|
onChange={(e) => handleInputChange("email", e.target.value)}
|
|
placeholder="company@example.com"
|
|
error={!!errors.email}
|
|
helperText={errors.email}
|
|
required
|
|
/>
|
|
<TextField
|
|
fullWidth
|
|
label="Username"
|
|
value={formData.username}
|
|
onChange={(e) =>
|
|
handleInputChange("username", e.target.value.toLowerCase())
|
|
}
|
|
placeholder="company123"
|
|
error={!!errors.username}
|
|
helperText={errors.username}
|
|
required
|
|
/>
|
|
</Stack>
|
|
|
|
<Stack direction="row" spacing={2}>
|
|
<TextField
|
|
fullWidth
|
|
label="Password"
|
|
type={showPassword ? "text" : "password"}
|
|
value={formData.password}
|
|
onChange={(e) =>
|
|
handleInputChange("password", e.target.value)
|
|
}
|
|
placeholder="Create a strong password"
|
|
error={!!errors.password}
|
|
helperText={errors.password}
|
|
required
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton
|
|
aria-label="toggle password visibility"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
onMouseDown={(e) => e.preventDefault()}
|
|
edge="end"
|
|
>
|
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
<TextField
|
|
fullWidth
|
|
label="Confirm Password"
|
|
type={showConfirmPassword ? "text" : "password"}
|
|
value={formData.confirmPassword}
|
|
onChange={(e) =>
|
|
handleInputChange("confirmPassword", e.target.value)
|
|
}
|
|
placeholder="Confirm your password"
|
|
error={!!errors.confirmPassword}
|
|
helperText={errors.confirmPassword}
|
|
required
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton
|
|
aria-label="toggle confirm password visibility"
|
|
onClick={() =>
|
|
setShowConfirmPassword(!showConfirmPassword)
|
|
}
|
|
onMouseDown={(e) => e.preventDefault()}
|
|
edge="end"
|
|
>
|
|
{showConfirmPassword ? (
|
|
<VisibilityOff />
|
|
) : (
|
|
<Visibility />
|
|
)}
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
</Stack>
|
|
</Stack>
|
|
</Box>
|
|
|
|
{/* Company Information Section */}
|
|
<Box sx={{ bgcolor: "primary.50", p: 3, borderRadius: 2 }}>
|
|
<Typography variant="h6" sx={{ mb: 2 }}>
|
|
Company Information
|
|
</Typography>
|
|
|
|
<Stack spacing={3}>
|
|
<TextField
|
|
fullWidth
|
|
label="Company Name"
|
|
value={formData.companyName}
|
|
onChange={(e) =>
|
|
handleInputChange("companyName", e.target.value)
|
|
}
|
|
placeholder="Your Company Inc."
|
|
error={!!errors.companyName}
|
|
helperText={errors.companyName}
|
|
required
|
|
/>
|
|
|
|
<Stack direction="row" spacing={2}>
|
|
<FormControl fullWidth error={!!errors.industry} required>
|
|
<InputLabel>Industry</InputLabel>
|
|
<Select
|
|
value={formData.industry}
|
|
onChange={(e) =>
|
|
handleInputChange("industry", e.target.value)
|
|
}
|
|
label="Industry"
|
|
>
|
|
{industryOptions.map((industry) => (
|
|
<MenuItem key={industry} value={industry}>
|
|
{industry}
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.industry && (
|
|
<FormHelperText>{errors.industry}</FormHelperText>
|
|
)}
|
|
</FormControl>
|
|
|
|
<FormControl fullWidth error={!!errors.companySize} required>
|
|
<InputLabel>Company Size</InputLabel>
|
|
<Select
|
|
value={formData.companySize}
|
|
onChange={(e) =>
|
|
handleInputChange("companySize", e.target.value)
|
|
}
|
|
label="Company Size"
|
|
>
|
|
{companySizeOptions.map((size) => (
|
|
<MenuItem key={size} value={size}>
|
|
{size}
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.companySize && (
|
|
<FormHelperText>{errors.companySize}</FormHelperText>
|
|
)}
|
|
</FormControl>
|
|
</Stack>
|
|
|
|
<Box>
|
|
<TextField
|
|
fullWidth
|
|
label="Company Description"
|
|
multiline
|
|
rows={4}
|
|
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..."
|
|
error={!!errors.companyDescription}
|
|
helperText={
|
|
errors.companyDescription ||
|
|
`${formData.companyDescription.length}/50 characters minimum`
|
|
}
|
|
required
|
|
/>
|
|
</Box>
|
|
|
|
<Stack direction="row" spacing={2}>
|
|
<TextField
|
|
fullWidth
|
|
label="Website URL"
|
|
type="url"
|
|
value={formData.websiteUrl}
|
|
onChange={(e) =>
|
|
handleInputChange("websiteUrl", e.target.value)
|
|
}
|
|
placeholder="https://www.yourcompany.com"
|
|
error={!!errors.websiteUrl}
|
|
helperText={errors.websiteUrl || "Optional"}
|
|
/>
|
|
<TextField
|
|
fullWidth
|
|
label="Phone Number"
|
|
type="tel"
|
|
value={formData.phone}
|
|
onChange={(e) => handleInputChange("phone", e.target.value)}
|
|
placeholder="+1 (555) 123-4567"
|
|
error={!!errors.phone}
|
|
helperText={errors.phone || "Optional"}
|
|
/>
|
|
</Stack>
|
|
</Stack>
|
|
</Box>
|
|
|
|
{errors.general && <Alert severity="error">{errors.general}</Alert>}
|
|
|
|
<Button
|
|
fullWidth
|
|
variant="contained"
|
|
size="large"
|
|
onClick={handleSubmit}
|
|
disabled={loading}
|
|
sx={{ py: 2 }}
|
|
>
|
|
{loading ? (
|
|
<Stack direction="row" alignItems="center" spacing={1}>
|
|
<CircularProgress size={20} color="inherit" />
|
|
<Typography>Creating Company Account...</Typography>
|
|
</Stack>
|
|
) : (
|
|
"Create Company Account"
|
|
)}
|
|
</Button>
|
|
|
|
<Box sx={{ textAlign: "center" }}>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Already have an account?{" "}
|
|
<Link href="/login" sx={{ fontWeight: 600 }}>
|
|
Sign in here
|
|
</Link>
|
|
</Typography>
|
|
</Box>
|
|
</Stack>
|
|
</Box>
|
|
|
|
{showSuccess && registrationResult && (
|
|
<RegistrationSuccessDialog
|
|
open={showSuccess}
|
|
onClose={() => setShowSuccess(false)}
|
|
email={registrationResult.email}
|
|
userType="employer"
|
|
/>
|
|
)}
|
|
</Paper>
|
|
);
|
|
};
|
|
|
|
// Registration Type Selector Component
|
|
export function RegistrationTypeSelector() {
|
|
return (
|
|
<Paper elevation={3}>
|
|
<Box sx={{ p: 5 }}>
|
|
<Box sx={{ textAlign: "center", mb: 5 }}>
|
|
<Typography variant="h3" component="h1" sx={{ mb: 2 }}>
|
|
Join Backstory
|
|
</Typography>
|
|
<Typography variant="h6" color="text.secondary">
|
|
Choose how you'd like to get started
|
|
</Typography>
|
|
</Box>
|
|
|
|
<Stack direction="row" spacing={3}>
|
|
{/* Candidate Option */}
|
|
<Card
|
|
sx={{
|
|
flex: 1,
|
|
cursor: "pointer",
|
|
transition: "all 0.3s ease",
|
|
border: "2px solid transparent",
|
|
"&:hover": {
|
|
transform: "translateY(-4px)",
|
|
boxShadow: 6,
|
|
borderColor: "primary.main",
|
|
},
|
|
}}
|
|
onClick={() => (window.location.href = "/register/candidate")}
|
|
>
|
|
<CardContent sx={{ textAlign: "center", py: 4 }}>
|
|
<Typography variant="h1" sx={{ mb: 2 }}>
|
|
👤
|
|
</Typography>
|
|
<Typography variant="h5" component="h3" sx={{ mb: 1.5 }}>
|
|
I'm looking for work
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
|
Create a candidate profile to find your next opportunity
|
|
</Typography>
|
|
</CardContent>
|
|
<CardActions sx={{ justifyContent: "center", pb: 3 }}>
|
|
<Button variant="contained" size="large">
|
|
Join as Candidate
|
|
</Button>
|
|
</CardActions>
|
|
</Card>
|
|
|
|
{/* Employer Option */}
|
|
<Card
|
|
sx={{
|
|
flex: 1,
|
|
cursor: "pointer",
|
|
transition: "all 0.3s ease",
|
|
border: "2px solid transparent",
|
|
"&:hover": {
|
|
transform: "translateY(-4px)",
|
|
boxShadow: 6,
|
|
borderColor: "primary.main",
|
|
},
|
|
}}
|
|
onClick={() => (window.location.href = "/register/employer")}
|
|
>
|
|
<CardContent sx={{ textAlign: "center", py: 4 }}>
|
|
<Typography variant="h1" sx={{ mb: 2 }}>
|
|
🏢
|
|
</Typography>
|
|
<Typography variant="h5" component="h3" sx={{ mb: 1.5 }}>
|
|
I'm hiring
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
|
Create a company account to find and hire talent
|
|
</Typography>
|
|
</CardContent>
|
|
<CardActions sx={{ justifyContent: "center", pb: 3 }}>
|
|
<Button variant="contained" size="large">
|
|
Join as Employer
|
|
</Button>
|
|
</CardActions>
|
|
</Card>
|
|
</Stack>
|
|
|
|
<Box sx={{ textAlign: "center", mt: 4 }}>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Already have an account?{" "}
|
|
<Link href="/login" sx={{ fontWeight: 600 }}>
|
|
Sign in here
|
|
</Link>
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
</Paper>
|
|
);
|
|
}
|
|
|
|
export { CandidateRegistrationForm, EmployerRegistrationForm };
|