backstory/frontend/src/pages/CreateProfilePage.tsx

382 lines
11 KiB
TypeScript

import React, { useState } from 'react';
import {
Box,
Button,
Container,
Grid,
Paper,
TextField,
Typography,
Avatar,
IconButton,
Stepper,
Step,
StepLabel,
useMediaQuery,
CircularProgress,
Snackbar,
Alert
} from '@mui/material';
import { styled } from '@mui/material/styles';
import { CloudUpload, PhotoCamera } from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
// import { Beta } from '../components/Beta';
// Interfaces
interface ProfileFormData {
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
jobTitle: string;
location: string;
bio: string;
}
// Styled components
const VisuallyHiddenInput = styled('input')({
clip: 'rect(0 0 0 0)',
clipPath: 'inset(50%)',
height: 1,
overflow: 'hidden',
position: 'absolute',
bottom: 0,
left: 0,
whiteSpace: 'nowrap',
width: 1,
});
const CreateProfilePage: React.FC = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
// State management
const [activeStep, setActiveStep] = useState<number>(0);
const [profileImage, setProfileImage] = useState<string | null>(null);
const [resumeFile, setResumeFile] = useState<File | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [snackbar, setSnackbar] = useState<{open: boolean, message: string, severity: "success" | "error"}>({
open: false,
message: '',
severity: 'success'
});
const [formData, setFormData] = useState<ProfileFormData>({
firstName: '',
lastName: '',
email: '',
phoneNumber: '',
jobTitle: '',
location: '',
bio: '',
});
// Steps for the profile creation process
const steps = ['Personal Information', 'Professional Details', 'Resume Upload'];
// Handle form input changes
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value,
});
};
// Handle profile image upload
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
const reader = new FileReader();
reader.onload = (event) => {
if (event.target?.result) {
setProfileImage(event.target.result.toString());
}
};
reader.readAsDataURL(e.target.files[0]);
}
};
// Handle resume file upload
const handleResumeUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
setResumeFile(e.target.files[0]);
setSnackbar({
open: true,
message: `Resume uploaded: ${e.target.files[0].name}`,
severity: 'success'
});
}
};
// Navigation functions
const handleNext = () => {
if (activeStep === steps.length - 1) {
handleSubmit();
} else {
setActiveStep((prevStep) => prevStep + 1);
}
};
const handleBack = () => {
setActiveStep((prevStep) => prevStep - 1);
};
// Form submission
const handleSubmit = async () => {
setLoading(true);
// Simulate API call with timeout
setTimeout(() => {
setLoading(false);
setSnackbar({
open: true,
message: 'Profile created successfully! Redirecting to dashboard...',
severity: 'success'
});
// Redirect would happen here in a real application
// history.push('/dashboard');
}, 2000);
};
// Form validation
const isStepValid = () => {
switch (activeStep) {
case 0:
return formData.firstName.trim() !== '' &&
formData.lastName.trim() !== '' &&
formData.email.trim() !== '';
case 1:
return formData.jobTitle.trim() !== '';
case 2:
return resumeFile !== null;
default:
return true;
}
};
// Stepper content based on active step
const getStepContent = (step: number) => {
switch (step) {
case 0:
return (
<Grid container spacing={3}>
<Grid size={{xs: 12}} sx={{ textAlign: 'center', mb: 2 }}>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<Avatar
src={profileImage || ''}
sx={{
width: 120,
height: 120,
mb: 2,
border: `2px solid ${theme.palette.primary.main}`
}}
/>
<IconButton
color="primary"
aria-label="upload picture"
component="label"
>
<PhotoCamera />
<VisuallyHiddenInput
type="file"
accept="image/*"
onChange={handleImageUpload}
/>
</IconButton>
<Typography variant="caption" color="textSecondary">
Add profile photo
</Typography>
</Box>
</Grid>
<Grid size={{xs: 12, sm: 6}}>
<TextField
required
fullWidth
label="First Name"
name="firstName"
value={formData.firstName}
onChange={handleInputChange}
variant="outlined"
/>
</Grid>
<Grid size={{xs: 12, sm: 6}}>
<TextField
required
fullWidth
label="Last Name"
name="lastName"
value={formData.lastName}
onChange={handleInputChange}
variant="outlined"
/>
</Grid>
<Grid size={{xs: 12}}>
<TextField
required
fullWidth
label="Email Address"
name="email"
type="email"
value={formData.email}
onChange={handleInputChange}
variant="outlined"
/>
</Grid>
<Grid size={{xs:12}}>
<TextField
fullWidth
label="Phone Number"
name="phoneNumber"
value={formData.phoneNumber}
onChange={handleInputChange}
variant="outlined"
/>
</Grid>
</Grid>
);
case 1:
return (
<Grid container spacing={3}>
<Grid size={{xs:12}}>
<TextField
required
fullWidth
label="Job Title"
name="jobTitle"
value={formData.jobTitle}
onChange={handleInputChange}
variant="outlined"
/>
</Grid>
<Grid size={{xs: 12}}>
<TextField
fullWidth
label="Location"
name="location"
placeholder="City, State, Country"
value={formData.location}
onChange={handleInputChange}
variant="outlined"
/>
</Grid>
<Grid size={{xs:12}}>
<TextField
fullWidth
multiline
rows={4}
label="Professional Bio"
name="bio"
placeholder="Tell us about yourself and your professional experience"
value={formData.bio}
onChange={handleInputChange}
variant="outlined"
/>
</Grid>
</Grid>
);
case 2:
return (
<Grid container spacing={3}>
<Grid size={{xs: 12}}>
<Typography variant="body1" component="p">
Upload your resume to complete your profile. We'll analyze it to better understand your skills and experience.
(Supported formats: .pdf, .docx, .md, and .txt)
</Typography>
<Box sx={{ textAlign: 'center', mt: 2 }}>
<Button
component="label"
variant="contained"
startIcon={<CloudUpload />}
sx={{ mb: 2 }}
>
Upload Resume
<VisuallyHiddenInput
type="file"
accept=".pdf,.docx,.txt,.md"
onChange={handleResumeUpload}
/>
</Button>
{resumeFile && (
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
File uploaded: {resumeFile.name}
</Typography>
)}
</Box>
</Grid>
</Grid>
);
default:
return 'Unknown step';
}
};
return (
<Container component="main">
<Paper
elevation={3}
sx={{
p: { xs: 2, sm: 4 },
mt: { xs: 2, sm: 4 },
mb: { xs: 2, sm: 4 },
}}
>
<Typography component="h1" variant="h4" align="center" gutterBottom>
Create Your Profile
</Typography>
<Stepper
activeStep={activeStep}
alternativeLabel={!isMobile}
orientation={isMobile ? 'vertical' : 'horizontal'}
sx={{ mt: 3, mb: 5 }}
>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<Box sx={{ mt: 2, mb: 4 }}>
{getStepContent(activeStep)}
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 4 }}>
<Button
disabled={activeStep === 0}
onClick={handleBack}
variant="outlined"
>
Back
</Button>
<Button
variant="contained"
onClick={handleNext}
disabled={!isStepValid() || loading}
startIcon={loading ? <CircularProgress size={20} color="inherit" /> : null}
>
{activeStep === steps.length - 1 ? 'Create Profile' : 'Next'}
</Button>
</Box>
</Paper>
<Snackbar
open={snackbar.open}
autoHideDuration={6000}
onClose={() => setSnackbar({ ...snackbar, open: false })}
>
<Alert
onClose={() => setSnackbar({ ...snackbar, open: false })}
severity={snackbar.severity}
sx={{ width: '100%' }}
>
{snackbar.message}
</Alert>
</Snackbar>
</Container>
);
};
export { CreateProfilePage };