Added some mobile view work

This commit is contained in:
James Ketr 2025-07-16 18:41:01 -07:00
parent ed69096ef0
commit a5bf96437e
3 changed files with 30 additions and 45 deletions

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Box, Typography, SxProps, Theme } from '@mui/material'; import { Box, Typography, SxProps, Theme, useMediaQuery, useTheme } from '@mui/material';
import { import {
Email as EmailIcon, Email as EmailIcon,
Phone as PhoneIcon, Phone as PhoneIcon,
@ -305,9 +305,10 @@ export const resumeStyles: Record<string, ResumeStyle> = generateResumeStyles();
interface StyledHeaderProps { interface StyledHeaderProps {
candidate: Types.Candidate; candidate: Types.Candidate;
style: ResumeStyle; style: ResumeStyle;
isMobile: boolean;
} }
const StyledHeader: React.FC<StyledHeaderProps> = ({ candidate, style }) => { const StyledHeader: React.FC<StyledHeaderProps> = ({ candidate, style, isMobile }) => {
const phone = parsePhoneNumberFromString(candidate.phone || '', 'US'); const phone = parsePhoneNumberFromString(candidate.phone || '', 'US');
return ( return (
@ -329,7 +330,7 @@ const StyledHeader: React.FC<StyledHeaderProps> = ({ candidate, style }) => {
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: isMobile ? 'column' : 'row',
alignItems: 'flex-start', alignItems: 'flex-start',
gap: 1, gap: 1,
}} }}
@ -426,12 +427,12 @@ const StyledHeader: React.FC<StyledHeaderProps> = ({ candidate, style }) => {
// Styled Footer Component // Styled Footer Component
interface StyledFooterProps { interface StyledFooterProps {
candidate: Types.Candidate; resume: Types.Resume;
job?: Types.Job;
style: ResumeStyle; style: ResumeStyle;
isMobile: boolean;
} }
const StyledFooter: React.FC<StyledFooterProps> = ({ candidate, job, style }) => { const StyledFooter: React.FC<StyledFooterProps> = ({ resume, style, isMobile }) => {
return ( return (
<> <>
<Box <Box
@ -439,19 +440,18 @@ const StyledFooter: React.FC<StyledFooterProps> = ({ candidate, job, style }) =>
sx={{ sx={{
...style.footerStyle, ...style.footerStyle,
color: style.color.secondary, color: style.color.secondary,
fontSize: isMobile ? '0.6rem' : '1rem',
}} }}
> >
Dive deeper into my qualifications at Backstory... Dive deeper into my qualifications at Backstory...
<Box <Box
component="img" component="img"
src={`/api/1.0/candidates/qr-code/${candidate.id || ''}/${(job && job.id) || ''}`} src={`/api/1.0/candidates/qr-code/${resume.id}`}
alt="QR Code" alt="QR Code"
className="qr-code" className="qr-code"
sx={{ display: 'flex', mt: 1, mb: 1 }} sx={{ display: 'flex', mt: 1, mb: 1 }}
/> />
{candidate?.username {window.location.protocol}://{window.location.host}/chat/{resume.id}
? `${window.location.protocol}://${window.location.host}/u/${candidate?.username}`
: 'backstory'}
</Box> </Box>
<Box sx={{ pb: 2 }}>&nbsp;</Box> <Box sx={{ pb: 2 }}>&nbsp;</Box>
</> </>
@ -470,6 +470,8 @@ export const ResumePreview: React.FC<ResumePreviewProps> = (props: ResumePreview
const currentStyle = resumeStyles[selectedStyle] || resumeStyles.corporate; const currentStyle = resumeStyles[selectedStyle] || resumeStyles.corporate;
const job: Types.Job | null = resume.job || null; const job: Types.Job | null = resume.job || null;
const candidate: Types.Candidate | null = resume.candidate || null; const candidate: Types.Candidate | null = resume.candidate || null;
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
if (!resume || !candidate || !job) { if (!resume || !candidate || !job) {
return ( return (
@ -505,7 +507,7 @@ export const ResumePreview: React.FC<ResumePreviewProps> = (props: ResumePreview
}} }}
> >
{/* Custom Header */} {/* Custom Header */}
<StyledHeader candidate={candidate} style={currentStyle} /> <StyledHeader isMobile={isMobile} candidate={candidate} style={currentStyle} />
{/* Styled Markdown Content */} {/* Styled Markdown Content */}
<Box sx={currentStyle.markdownStyle}> <Box sx={currentStyle.markdownStyle}>
@ -523,7 +525,7 @@ export const ResumePreview: React.FC<ResumePreviewProps> = (props: ResumePreview
</Box> </Box>
{/* QR Code Footer */} {/* QR Code Footer */}
{job && <StyledFooter candidate={candidate} job={job} style={currentStyle} />} {job && <StyledFooter isMobile={isMobile} resume={resume} style={currentStyle} />}
</Box> </Box>
</Box> </Box>
); );

View File

@ -341,6 +341,7 @@ const CandidateChatPage = forwardRef<ConversationHandle, CandidateChatPageProps>
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
p: 1, p: 1,
fontSize: isMobile ? '0.8rem' : '1rem',
}} }}
> >
<strong>{resume.job?.title}</strong>&nbsp;at&nbsp; <strong>{resume.job?.title}</strong>&nbsp;at&nbsp;

View File

@ -58,6 +58,7 @@ from models import (
RAGDocumentRequest, RAGDocumentRequest,
RagContentMetadata, RagContentMetadata,
RagContentResponse, RagContentResponse,
Resume,
SkillAssessment, SkillAssessment,
SkillMatchRequest, SkillMatchRequest,
SkillStrength, SkillStrength,
@ -716,55 +717,36 @@ async def get_candidate_profile_image(
status_code=500, content=create_error_response("FETCH_ERROR", "Failed to retrieve profile image") status_code=500, content=create_error_response("FETCH_ERROR", "Failed to retrieve profile image")
) )
@router.get("/qr-code/{candidate_id}/{job_id}") @router.get("/qr-code/{resume_id}")
async def get_candidate_qr_code( async def get_candidate_qr_code(
candidate_id: str = Path(..., description="ID of the candidate"), resume_id: str = Path(..., description="ID of the resume"),
job_id: Optional[str] = Path(..., description="ID of the candidate"),
# current_user = Depends(get_current_user), # current_user = Depends(get_current_user),
database: RedisDatabase = Depends(get_database), database: RedisDatabase = Depends(get_database),
): ):
"""Get profile image of a candidate by username""" """Get profile image of a candidate by username"""
try: try:
all_candidates_data = await database.get_all_candidates() resume_data = await database.get_resume(resume_id=resume_id)
candidates_list = [Candidate.model_validate(data) for data in all_candidates_data.values()] if not resume_data:
logger.warning(f"⚠️ Resume not found for ID: {resume_id}")
# Normalize username to lowercase for case-insensitive search return JSONResponse(status_code=404, content=create_error_response("NOT_FOUND", "Resume not found"))
query_lower = candidate_id.lower() resume: Resume = Resume.model_validate(resume_data)
candidate_data = await database.get_candidate(resume.candidate_id)
# Filter by search query if not candidate_data:
candidates_list = [c for c in candidates_list if query_lower == c.id.lower()] logger.warning(f"⚠️ Candidate not found for resume ID: {resume_id}")
if not len(candidates_list):
return JSONResponse(status_code=404, content=create_error_response("NOT_FOUND", "Candidate not found")) return JSONResponse(status_code=404, content=create_error_response("NOT_FOUND", "Candidate not found"))
candidate: Candidate = Candidate.model_validate(candidate_data)
candidate = Candidate.model_validate(candidates_list[0]) file_name = f"resume-{resume_id}.png"
job = None
if job_id:
job_data = await database.get_job(job_id)
if not job_data:
logger.warning(f"⚠️ Job not found for ID: {job_id}")
return JSONResponse(
status_code=404,
content=create_error_response("JOB_NOT_FOUND", f"Job with id '{job_id}' not found"),
)
job = Job.model_validate(job_data)
file_name = f"{job.id}.png" if job else "qrcode.png"
file_path = pathlib.Path(defines.user_dir) / candidate.username / file_name file_path = pathlib.Path(defines.user_dir) / candidate.username / file_name
if not file_path.exists(): if not file_path.exists():
import pyqrcode import pyqrcode
if job: qrobj = pyqrcode.create(f"{defines.frontend_url}/chat/{resume.id}")
qrobj = pyqrcode.create(f"{defines.frontend_url}/job-analysis/{candidate.id}/{job.id}/2")
else:
qrobj = pyqrcode.create(f"{defines.frontend_url}/u/{candidate.id}")
with open(file_path, "wb") as f: with open(file_path, "wb") as f:
qrobj.png(f, scale=2) qrobj.png(f, scale=2)
return FileResponse( return FileResponse(
file_path, file_path,
media_type=f"image/{file_path.suffix[1:]}", # Get extension without dot media_type=f"image/{file_path.suffix[1:]}", # Get extension without dot
filename="qrcode.png", filename=file_name,
) )
except Exception as e: except Exception as e:
logger.error(backstory_traceback.format_exc()) logger.error(backstory_traceback.format_exc())