163 lines
5.2 KiB
TypeScript
163 lines
5.2 KiB
TypeScript
import React from 'react';
|
|
import { Box, Link, Typography, Avatar, Grid, SxProps } from '@mui/material';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
Divider,
|
|
useTheme,
|
|
} from '@mui/material';
|
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
import { useMediaQuery } from '@mui/material';
|
|
import { Candidate, CandidateAI } from 'types/types';
|
|
import { CopyBubble } from "components/CopyBubble";
|
|
import { rest } from 'lodash';
|
|
import { AIBanner } from 'components/ui/AIBanner';
|
|
import { useAuth } from 'hooks/AuthContext';
|
|
import { DeleteConfirmation } from '../DeleteConfirmation';
|
|
|
|
interface CandidateInfoProps {
|
|
candidate: Candidate;
|
|
sx?: SxProps;
|
|
action?: string;
|
|
elevation?: number;
|
|
variant?: "small" | "normal" | null
|
|
};
|
|
|
|
const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps) => {
|
|
const { candidate } = props;
|
|
const { user, apiClient } = useAuth();
|
|
const {
|
|
sx,
|
|
action = '',
|
|
elevation = 1,
|
|
variant = "normal"
|
|
} = props;
|
|
const theme = useTheme();
|
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
|
const ai: CandidateAI | null = ('isAI' in candidate) ? candidate as CandidateAI : null;
|
|
const isAdmin = user?.isAdmin;
|
|
|
|
const deleteCandidate = async (candidateId: string | undefined) => {
|
|
if (candidateId) {
|
|
await apiClient.deleteCandidate(candidateId);
|
|
}
|
|
}
|
|
|
|
if (!candidate) {
|
|
return <Box>No user loaded.</Box>;
|
|
}
|
|
|
|
return (
|
|
<Card
|
|
elevation={elevation}
|
|
sx={{
|
|
display: "flex",
|
|
borderColor: 'transparent',
|
|
borderWidth: 2,
|
|
borderStyle: 'solid',
|
|
transition: 'all 0.3s ease',
|
|
...sx
|
|
}}
|
|
{...rest}
|
|
>
|
|
<CardContent sx={{ display: "flex", flexGrow: 1, p: 3, height: '100%', flexDirection: 'column', alignItems: 'stretch', position: "relative" }}>
|
|
{ai && <AIBanner />}
|
|
|
|
<Grid container spacing={2}>
|
|
<Grid
|
|
size={{ xs: 12, sm: 2 }}
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
minWidth: "80px",
|
|
maxWidth: "80px"
|
|
}}>
|
|
<Avatar
|
|
src={candidate.profileImage ? `/api/1.0/candidates/profile/${candidate.username}?timestamp=${Date.now()}` : ''}
|
|
alt={`${candidate.fullName}'s profile`}
|
|
sx={{
|
|
alignSelf: "flex-start",
|
|
width: 80,
|
|
height: 80,
|
|
border: '2px solid #e0e0e0',
|
|
}}
|
|
/>
|
|
</Grid>
|
|
{isAdmin && ai &&
|
|
<DeleteConfirmation
|
|
onDelete={() => { deleteCandidate(candidate.id); }}
|
|
sx={{ minWidth: 'auto', px: 2, maxHeight: "min-content", color: "red" }}
|
|
action="delete"
|
|
label="user"
|
|
title="Delete AI user"
|
|
icon=<DeleteIcon />
|
|
message={`Are you sure you want to delete ${candidate.username}? This action cannot be undone.`}
|
|
/>}
|
|
|
|
<Grid size={{ xs: 12, sm: 10 }}>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'flex-start',
|
|
mb: 1 }}>
|
|
<Box>
|
|
<Box sx={{
|
|
display: "flex",
|
|
flexDirection: isMobile ? "column" : "row",
|
|
alignItems: "left",
|
|
gap: 1, "& > .MuiTypography-root": { m: 0 }
|
|
}}>
|
|
{
|
|
action !== '' &&
|
|
<Typography variant="body1">{action}</Typography>
|
|
}
|
|
<Typography variant="h5" component="h1"
|
|
sx={{
|
|
fontWeight: 'bold',
|
|
whiteSpace: 'nowrap'
|
|
}}>
|
|
{candidate.fullName}
|
|
</Typography>
|
|
</Box>
|
|
<Box sx={{ fontSize: "0.75rem", alignItems: "center" }} >
|
|
<Link href={`/u/${candidate.username}`}>{`/u/${candidate.username}`}</Link>
|
|
<CopyBubble
|
|
onClick={(event: any) => { event.stopPropagation() }}
|
|
tooltip="Copy link" content={`${window.location.origin}/u/{candidate.username}`} />
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
|
|
<Typography variant="body1" color="text.secondary">
|
|
{candidate.description}
|
|
</Typography>
|
|
|
|
{variant !== "small" && <>
|
|
<Divider sx={{ my: 2 }} />
|
|
|
|
{candidate.location &&
|
|
<Typography variant="body2" sx={{ mb: 1 }}>
|
|
<strong>Location:</strong> {candidate.location.city}, {candidate.location.state || candidate.location.country}
|
|
</Typography>
|
|
}
|
|
{candidate.email &&
|
|
<Typography variant="body2" sx={{ mb: 1 }}>
|
|
<strong>Email:</strong> {candidate.email}
|
|
</Typography>
|
|
}
|
|
{candidate.phone && <Typography variant="body2">
|
|
<strong>Phone:</strong> {candidate.phone}
|
|
</Typography>
|
|
}
|
|
</>}
|
|
</Grid>
|
|
</Grid>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export { CandidateInfo };
|