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:
cesnimda
2026-07-31 16:54:16 +02:00
parent a23c3dfc97
commit ce76046a29
1634 changed files with 6889 additions and 135429 deletions
@@ -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>
);
}