feat: complete release readiness work
- consolidate API ownership and remove dead vendor code - add Stripe billing, learning paths, and public CV hardening - add migration, recovery, security, audit, and browser gates
This commit is contained in:
@@ -8,7 +8,11 @@ function copyLines(items: string[]) {
|
||||
void navigator.clipboard.writeText(items.join("\n"));
|
||||
}
|
||||
|
||||
export function MatchScoreCard({ score, loading }: { score: MatchScore | null; loading: boolean }) {
|
||||
export function MatchScoreCard({ score, loading, onUpdateLearningRecommendation }: {
|
||||
score: MatchScore | null;
|
||||
loading: boolean;
|
||||
onUpdateLearningRecommendation?: (id: number, status: "done" | "dismissed") => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
|
||||
if (loading && !score) {
|
||||
@@ -25,6 +29,9 @@ export function MatchScoreCard({ score, loading }: { score: MatchScore | null; l
|
||||
const color: "success" | "warning" | "error" | "inherit" =
|
||||
!score.hasEnoughSignal ? "inherit" : score.score >= 75 ? "success" : score.score >= 50 ? "warning" : "error";
|
||||
const bandLabel = t(`matchScoreBand_${score.band}` as any) || score.band;
|
||||
const learningRecommendations = score.learningRecommendations ?? [];
|
||||
const visibleRecommendations = learningRecommendations.filter(item => item.status !== "dismissed");
|
||||
const dismissedCount = learningRecommendations.length - visibleRecommendations.length;
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 1.75, mb: 2, borderRadius: 3, boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)", backgroundColor: "background.default" }}>
|
||||
@@ -80,6 +87,28 @@ export function MatchScoreCard({ score, loading }: { score: MatchScore | null; l
|
||||
</Box>
|
||||
</Box>
|
||||
) : null}
|
||||
{learningRecommendations.length ? (
|
||||
<Box sx={{ mt: 1.5 }}>
|
||||
<Typography variant="overline">{t("matchScoreLearningPath")}</Typography>
|
||||
<Typography variant="caption" sx={{ color: "text.secondary", display: "block", mb: 1 }}>
|
||||
{t("matchScoreLearningPathHint")}
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 0.75 }}>
|
||||
{visibleRecommendations.map(item => (
|
||||
<Box key={item.id} sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 1, flexWrap: "wrap" }}>
|
||||
<Chip size="small" label={item.keyword} color={item.status === "done" ? "success" : "warning"} variant={item.status === "done" ? "filled" : "outlined"} />
|
||||
{item.status === "done" ? <Typography variant="caption" color="success.main">{t("matchScoreLearned")}</Typography> : (
|
||||
<Box sx={{ display: "flex", gap: 0.75 }}>
|
||||
<Button size="small" onClick={() => onUpdateLearningRecommendation?.(item.id, "done")}>{t("matchScoreMarkLearned")}</Button>
|
||||
<Button size="small" color="inherit" onClick={() => onUpdateLearningRecommendation?.(item.id, "dismissed")}>{t("matchScoreDismiss")}</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
{dismissedCount ? <Typography variant="caption" sx={{ color: "text.secondary" }}>{t("matchScoreDismissedCount", { count: dismissedCount })}</Typography> : null}
|
||||
</Box>
|
||||
</Box>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user