feat(ui): separate career and connected accounts

This commit is contained in:
cesnimda
2026-07-15 10:37:02 +02:00
parent dab39bd570
commit d7d7e70d08
7 changed files with 221 additions and 28 deletions
@@ -0,0 +1,22 @@
import React from "react";
import { Alert, Box, Paper, Typography } from "@mui/material";
import ProfilePage from "./ProfilePage";
export default function CareerWorkspacePage() {
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)" }}>
<Typography variant="h5" sx={{ fontWeight: 900, mb: 0.5 }}>Career Workspace</Typography>
<Typography sx={{ color: "text.secondary" }}>
Maintain the master career profile that powers your CVs, tailored application material, and future portfolio outputs.
</Typography>
</Paper>
<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>
<ProfilePage careerOnly />
</Box>
);
}
@@ -0,0 +1,23 @@
import React from "react";
import { Box, Button, Paper, Typography } from "@mui/material";
import { useNavigate } from "react-router-dom";
import EmailProviderConnections from "../components/EmailProviderConnections";
export default function ConnectedAccountsPage() {
const navigate = useNavigate();
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)" }}>
<Typography variant="h5" sx={{ fontWeight: 900, mb: 0.5 }}>Connected accounts</Typography>
<Typography sx={{ color: "text.secondary", maxWidth: 720 }}>
Connect the inboxes Jobbjakt may use to identify recruiter correspondence. Connecting an inbox never sends a message on your behalf.
</Typography>
<Button sx={{ mt: 1 }} onClick={() => navigate("/settings")}>Back to settings</Button>
</Paper>
<EmailProviderConnections />
</Box>
);
}
+29 -27
View File
@@ -13,7 +13,6 @@ import MicrosoftAuthCard from "../components/MicrosoftAuthCard";
import AuthStatusCard from "../components/AuthStatusCard";
import TwoFactorSettingsCard from "../components/TwoFactorSettingsCard";
import SessionsSettingsCard from "../components/SessionsSettingsCard";
import EmailProviderConnections from "../components/EmailProviderConnections";
import CropImageDialog from "../components/CropImageDialog";
import { useToast } from "../toast";
import { useI18n } from "../i18n/I18nProvider";
@@ -227,7 +226,7 @@ function FieldReviewNote({ metadata }: { metadata?: StructuredCvFieldMetadata })
);
}
export default function ProfilePage() {
export default function ProfilePage({ careerOnly = false }: { careerOnly?: boolean }) {
const { toast } = useToast();
const { t } = useI18n();
const cvInputRef = useRef<HTMLInputElement | null>(null);
@@ -553,7 +552,7 @@ export default function ProfilePage() {
</Box>
<Box>
<Typography variant="h5" sx={{ fontWeight: 900 }}>
{t("profileTitle")}
{careerOnly ? "Master career profile" : t("profileTitle")}
</Typography>
<Typography sx={{ color: "text.secondary" }}>{me?.userName || me?.displayName || fullName || me?.email || "-"}</Typography>
<Typography variant="body2" sx={{ color: "text.secondary" }}>{headline || t("profileHeadlinePlaceholder")}</Typography>
@@ -566,37 +565,38 @@ export default function ProfilePage() {
</Box>
</Box>
<AuthStatusCard />
<GoogleAuthCard />
<MicrosoftAuthCard />
<Box sx={{ mt: 2 }}>
<EmailProviderConnections />
</Box>
{!careerOnly ? <>
<AuthStatusCard />
<GoogleAuthCard />
<MicrosoftAuthCard />
</> : null}
<Box sx={{ mt: 3, display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 2 }}>
<Box sx={{ gridColumn: "1 / -1" }}>
{!careerOnly ? <Box sx={{ gridColumn: "1 / -1" }}>
<Typography variant="h6">{t("profileAccountSection")}</Typography>
{!isLocal ? (
<Alert severity="info" sx={{ mt: 1 }}>
{t("profileReadOnlyInfo")}
</Alert>
) : null}
</Box>
</Box> : null}
<TextField label={t("profileDisplayName")} value={displayName} onChange={(e) => setDisplayName(e.target.value)} disabled={!isLocal} fullWidth />
<TextField label={t("profileUsername")} value={userName} onChange={(e) => setUserName(e.target.value)} disabled={!isLocal} fullWidth />
<TextField label={t("profileFirstName")} value={firstName} onChange={(e) => setFirstName(e.target.value)} disabled={!isLocal} fullWidth />
<TextField label={t("profileLastName")} value={lastName} onChange={(e) => setLastName(e.target.value)} disabled={!isLocal} fullWidth />
<TextField label={t("profileEmail")} value={email} onChange={(e) => setEmail(e.target.value)} disabled={!isLocal} fullWidth />
<TextField
{!careerOnly ? <>
<TextField label={t("profileDisplayName")} value={displayName} onChange={(e) => setDisplayName(e.target.value)} disabled={!isLocal} fullWidth />
<TextField label={t("profileUsername")} value={userName} onChange={(e) => setUserName(e.target.value)} disabled={!isLocal} fullWidth />
<TextField label={t("profileFirstName")} value={firstName} onChange={(e) => setFirstName(e.target.value)} disabled={!isLocal} fullWidth />
<TextField label={t("profileLastName")} value={lastName} onChange={(e) => setLastName(e.target.value)} disabled={!isLocal} fullWidth />
<TextField label={t("profileEmail")} value={email} onChange={(e) => setEmail(e.target.value)} disabled={!isLocal} fullWidth />
<TextField
label={t("profileHeadline")}
value={headline}
onChange={(e) => setHeadline(e.target.value)}
helperText={t("profileHeadlineHelp")}
fullWidth
/>
/>
</> : null}
<Box sx={{ gridColumn: "1 / -1", p: 2, borderRadius: 3, border: "1px solid", borderColor: "divider", backgroundColor: "background.default" }}>
<Box sx={{ gridColumn: "1 / -1", p: 2, borderRadius: 3, border: "1px solid", borderColor: "divider", backgroundColor: "background.default", display: careerOnly ? "block" : "none" }}>
<Box sx={{ display: "flex", justifyContent: "space-between", gap: 2, flexWrap: "wrap", alignItems: "center", mb: 1.5 }}>
<Box>
<Typography variant="h6">{t("profileMasterCv")}</Typography>
@@ -1318,16 +1318,17 @@ export default function ProfilePage() {
</Button>
</Box>
<Box sx={{ gridColumn: "1 / -1", mt: 1 }}>
{!careerOnly ? <Box sx={{ gridColumn: "1 / -1", mt: 1 }}>
<Divider sx={{ mb: 2 }} />
<Typography variant="h6">{t("profileChangePassword")}</Typography>
{!isLocal ? <Typography sx={{ color: "text.secondary" }}>{t("profilePasswordLocalOnly")}</Typography> : null}
</Box>
</Box> : null}
<TextField label={t("profileCurrentPassword")} type="password" value={currentPassword} onChange={(e) => setCurrentPassword(e.target.value)} disabled={!isLocal} fullWidth />
<TextField label={t("profileNewPassword")} type="password" value={newPassword} onChange={(e) => setNewPassword(e.target.value)} disabled={!isLocal} fullWidth />
{!careerOnly ? <>
<TextField label={t("profileCurrentPassword")} type="password" value={currentPassword} onChange={(e) => setCurrentPassword(e.target.value)} disabled={!isLocal} fullWidth />
<TextField label={t("profileNewPassword")} type="password" value={newPassword} onChange={(e) => setNewPassword(e.target.value)} disabled={!isLocal} fullWidth />
<Box sx={{ gridColumn: "1 / -1", display: "flex", justifyContent: "flex-end" }}>
<Box sx={{ gridColumn: "1 / -1", display: "flex", justifyContent: "flex-end" }}>
<Button
variant="outlined"
disabled={!isLocal || loading}
@@ -1348,11 +1349,12 @@ export default function ProfilePage() {
>
{t("profileUpdatePassword")}
</Button>
</Box>
</Box>
</> : null}
</Box>
{isLocal ? <TwoFactorSettingsCard /> : null}
{isLocal ? <SessionsSettingsCard /> : null}
{!careerOnly && isLocal ? <TwoFactorSettingsCard /> : null}
{!careerOnly && isLocal ? <SessionsSettingsCard /> : null}
</Paper>
);
}