feat(phase-2): separate /profile (identity) from /career (master profile)
Phase 2 — Career/Profile separation. The master career profile is the source of
truth; identity and career data are now saved independently so neither wipes the
other. CV Builder deliberately not built yet.
Backend — PUT /auth/profile is now a partial update:
- null/omitted field -> unchanged; "" -> cleared; value -> set (trimmed).
- Email/UserName never cleared to empty (login identifiers).
This lets /profile save identity fields and /career save the master-profile
fields through the same endpoint without one nulling the other. 4 new tests
cover the data-integrity guarantees (identity save keeps the CV, career save
keeps identity, empty clears, null leaves).
Frontend:
- ProfilePage save payload is now scoped by careerOnly: /career sends only
{ profileCvText, profileCvStructureJson }, /profile sends only identity.
- CareerWorkspacePage: removed the inert "CV Builder" tab (careerView) — Phase 2
establishes the master profile only; the builder is Phase 4.
- Dropped the dead careerView prop.
- Updated the CV-save test to render career mode and assert identity is excluded.
Source-of-truth flip (CareerProfileService authoritative) stays deferred to F5
per the branch design; CareerProfileService keeps mirroring via its dual-write.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
|
||||
import { Alert, Box, Paper, Tab, Tabs, Typography } from "@mui/material";
|
||||
import { Alert, Box, Paper, Typography } from "@mui/material";
|
||||
|
||||
import ProfilePage from "./ProfilePage";
|
||||
|
||||
// Phase 2: /career is the Career Workspace — the master career profile, which is the single source
|
||||
// of truth for all generated documents. The CV Builder is deliberately NOT here yet (Phase 4);
|
||||
// Phase 2 only establishes the master profile. The previously-inert "CV Builder" tab was removed.
|
||||
export default function CareerWorkspacePage() {
|
||||
const [tab, setTab] = useState<"master" | "builder">("master");
|
||||
const [hasMasterCv, setHasMasterCv] = useState(false);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "grid", gap: 2 }}>
|
||||
<Paper sx={{ p: 2.5, borderRadius: 4, boxShadow: "0px 8px 24px -12px rgba(15,23,42,0.12)" }}>
|
||||
@@ -19,17 +19,8 @@ export default function CareerWorkspacePage() {
|
||||
<Alert severity="info" sx={{ borderRadius: 3 }}>
|
||||
Your master profile is the source of truth. Job-specific CV drafts remain separate and never overwrite it.
|
||||
</Alert>
|
||||
<Paper sx={{ borderRadius: 4, overflow: "hidden", boxShadow: "0px 8px 24px -12px rgba(15,23,42,0.12)" }}>
|
||||
<Tabs value={tab} onChange={(_, value) => setTab(value)} variant="scrollable" allowScrollButtonsMobile sx={{ px: 1.5, pt: 1 }}>
|
||||
<Tab value="master" label="Master CV" />
|
||||
<Tab value="builder" label="CV Builder" disabled={!hasMasterCv} />
|
||||
</Tabs>
|
||||
{!hasMasterCv ? <Alert severity="info" sx={{ mx: 2.5, mb: 0, borderRadius: 3 }}>
|
||||
Create your Master CV first. Upload an existing CV or add your career history manually; the builder will unlock when the profile has content.
|
||||
</Alert> : null}
|
||||
<Box sx={{ p: { xs: 1.5, md: 2.5 } }}>
|
||||
<ProfilePage careerOnly careerView={tab} onMasterCvAvailabilityChange={setHasMasterCv} />
|
||||
</Box>
|
||||
<Paper sx={{ borderRadius: 4, p: { xs: 1.5, md: 2.5 }, boxShadow: "0px 8px 24px -12px rgba(15,23,42,0.12)" }}>
|
||||
<ProfilePage careerOnly />
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user