Add guided CV builder controls
CI and Deploy / test (push) Failing after 0s
CI and Deploy / deploy (push) Has been skipped

This commit is contained in:
2026-04-20 21:22:31 +02:00
parent eea327e1f6
commit 657cb95a48
3 changed files with 59 additions and 6 deletions
+42 -1
View File
@@ -28,6 +28,9 @@ import { JobApplication } from "../types";
type CvSectionOption = "" | "Professional Summary" | "Core Skills" | "Experience Highlights" | "Selected Achievements" | "Projects";
type CvSectionStyle = "ats-minimal" | "harvard" | "auckland" | "edinburgh" | "monarch" | "fjord";
type CvBuilderTone = "Concise and direct" | "Executive and polished" | "Technical and detailed" | "Warm and people-focused";
type CvBuilderLanguage = "English" | "Norwegian" | "Spanish" | "French" | "German";
type ExtractionRun = {
id: number;
trigger: string;
@@ -94,6 +97,9 @@ type RewriteRequestPayload = {
targetRole: string | null;
jobApplicationId: number | null;
sourceText: string | null;
promptBackground: string | null;
tone: string | null;
language: string | null;
};
type MeResponse = {
@@ -242,6 +248,9 @@ export default function ProfilePage() {
const [cvSection, setCvSection] = useState<CvSectionOption>("");
const [cvSectionStyle, setCvSectionStyle] = useState<CvSectionStyle>("ats-minimal");
const [cvSectionTargetRole, setCvSectionTargetRole] = useState("");
const [cvPromptBackground, setCvPromptBackground] = useState("");
const [cvTone, setCvTone] = useState<CvBuilderTone>("Concise and direct");
const [cvLanguage, setCvLanguage] = useState<CvBuilderLanguage>("English");
const [selectedRewriteJobId, setSelectedRewriteJobId] = useState<string>("");
const [rewritePreview, setRewritePreview] = useState<CvBuilderPreview | null>(null);
const [rewritePreviewTemplate, setRewritePreviewTemplate] = useState<RewriteTemplateOption | null>(null);
@@ -361,7 +370,10 @@ export default function ProfilePage() {
targetRole: cvSectionTargetRole.trim() || null,
jobApplicationId: selectedRewriteJob ? selectedRewriteJob.id : null,
sourceText: profileCvText.trim() || null,
}), [cvSection, cvSectionTargetRole, profileCvText, selectedRewriteJob]);
promptBackground: cvPromptBackground.trim() || null,
tone: cvTone,
language: cvLanguage,
}), [cvLanguage, cvPromptBackground, cvSection, cvSectionTargetRole, cvTone, profileCvText, selectedRewriteJob]);
const resetPdfCarousel = useCallback(() => {
setPdfCarousel((current) => {
@@ -1030,6 +1042,16 @@ export default function ProfilePage() {
</Box>
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 1.5, mb: 1.75 }}>
<TextField
label="Prompt-based CV brief"
value={cvPromptBackground}
onChange={(e) => setCvPromptBackground(e.target.value)}
fullWidth
multiline
minRows={4}
helperText="Describe your strengths, preferred emphasis, industry background, or the angle you want the AI to lean into."
sx={{ gridColumn: { xs: "1 / -1", md: "1 / -1" } }}
/>
<FormControl fullWidth size="small">
<InputLabel>{t("profileCvSectionLabel")}</InputLabel>
<Select value={cvSection} label={t("profileCvSectionLabel")} onChange={(e) => setCvSection(e.target.value as CvSectionOption)}>
@@ -1048,6 +1070,25 @@ export default function ProfilePage() {
fullWidth
helperText={selectedRewriteJob ? `Using saved job context: ${selectedRewriteJob.jobTitle}` : "Leave empty to let the selected job drive tailoring."}
/>
<FormControl fullWidth size="small">
<InputLabel>Language</InputLabel>
<Select value={cvLanguage} label="Language" onChange={(e) => setCvLanguage(e.target.value as CvBuilderLanguage)}>
<MenuItem value="English">English</MenuItem>
<MenuItem value="Norwegian">Norwegian</MenuItem>
<MenuItem value="Spanish">Spanish</MenuItem>
<MenuItem value="French">French</MenuItem>
<MenuItem value="German">German</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth size="small">
<InputLabel>Tone</InputLabel>
<Select value={cvTone} label="Tone" onChange={(e) => setCvTone(e.target.value as CvBuilderTone)}>
<MenuItem value="Concise and direct">Concise and direct</MenuItem>
<MenuItem value="Executive and polished">Executive and polished</MenuItem>
<MenuItem value="Technical and detailed">Technical and detailed</MenuItem>
<MenuItem value="Warm and people-focused">Warm and people-focused</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth size="small" sx={{ gridColumn: { xs: "1 / -1", md: "1 / -1" } }}>
<InputLabel>Saved job context</InputLabel>
<Select value={selectedRewriteJobId} label="Saved job context" onChange={(e) => setSelectedRewriteJobId(String(e.target.value))}>
+7 -4
View File
@@ -248,9 +248,8 @@ test('profile page rewrite tools use selected template and saved job context', a
expect(await screen.findByText(/template-driven cv builder/i)).toBeInTheDocument();
fireEvent.click(screen.getByText(/harvard/i));
fireEvent.mouseDown(screen.getAllByRole('combobox')[1]);
fireEvent.click(await screen.findByText(/senior backend engineer · acme systems/i));
fireEvent.change(screen.getByLabelText(/prompt-based cv brief/i), { target: { value: 'Highlight backend platform ownership, distributed systems, and cross-team delivery.' } });
fireEvent.change(screen.getByLabelText(/target role/i), { target: { value: 'Senior Platform Engineer' } });
const rewriteButton = screen.getByRole('button', { name: /build preview/i });
fireEvent.click(rewriteButton);
@@ -259,7 +258,11 @@ test('profile page rewrite tools use selected template and saved job context', a
sectionName: null,
style: 'harvard',
templateId: 'harvard',
jobApplicationId: 42,
jobApplicationId: null,
promptBackground: 'Highlight backend platform ownership, distributed systems, and cross-team delivery.',
targetRole: 'Senior Platform Engineer',
language: 'English',
tone: 'Concise and direct',
}));
});