157 lines
6.0 KiB
TypeScript
157 lines
6.0 KiB
TypeScript
import { api } from "./api";
|
|
|
|
// Mirrors the backend CvVariantSettings (the lens over the master career profile).
|
|
export type CvSectionSetting = { key: string; hidden?: boolean; title?: string; itemOrder?: string[] };
|
|
export type CvItemOverride = { hidden?: boolean; title?: string; subtitle?: string; bullets?: string[] };
|
|
export type CvCustomSectionSetting = { key: string; title?: string; items: string[]; hidden?: boolean };
|
|
|
|
export type CvVariantSettings = {
|
|
themeId: string;
|
|
accentColor?: string | null;
|
|
headingFont?: string | null;
|
|
bodyFont?: string | null;
|
|
density?: string | null;
|
|
pageSize?: string | null;
|
|
dateFormat?: string | null;
|
|
language?: string | null;
|
|
headline?: string | null;
|
|
showPhoto: boolean;
|
|
showPageNumbers: boolean;
|
|
showIcons: boolean;
|
|
sections: CvSectionSetting[];
|
|
overrides: Record<string, CvItemOverride>;
|
|
customSections: CvCustomSectionSetting[];
|
|
};
|
|
|
|
export type CvTheme = {
|
|
id: string;
|
|
name: string;
|
|
category: string;
|
|
description: string;
|
|
layout: string;
|
|
accent: string;
|
|
photoShape: string;
|
|
supportsIcons: boolean;
|
|
atsFriendly: boolean;
|
|
premium: boolean;
|
|
available: boolean;
|
|
swatches: string[];
|
|
};
|
|
|
|
// Master profile resolved to sections+entries (GET /api/cv/outline) — what the Content tab edits.
|
|
export type CvOutlineEntry = { key?: string; title?: string; subtitle?: string; meta?: string; bullets: string[]; tags: string[] };
|
|
export type CvOutlineSection = { key: string; title: string; kind: "bullets" | "tags" | "entries"; bullets: string[]; tags: string[]; entries: CvOutlineEntry[] };
|
|
export type CvOutline = { fullName: string; headline?: string; sections: CvOutlineSection[] };
|
|
|
|
export type CvVariantSummary = {
|
|
id: number;
|
|
name: string;
|
|
themeId: string;
|
|
publicSlug: string;
|
|
isPublic: boolean;
|
|
version: number;
|
|
jobApplicationId: number | null;
|
|
updatedAtUtc: string;
|
|
};
|
|
|
|
export type CvVariant = {
|
|
id: number;
|
|
name: string;
|
|
settings: CvVariantSettings;
|
|
isPublic: boolean;
|
|
publicSlug: string;
|
|
version: number;
|
|
jobApplicationId: number | null;
|
|
updatedAtUtc: string;
|
|
};
|
|
|
|
export type CvVariantVersionInfo = { version: number; source: string; createdAtUtc: string; isCurrent: boolean };
|
|
export type CvRender = { themeId: string; html: string; suggestedFileName: string };
|
|
|
|
export const AI_ACTIONS: { key: string; label: string }[] = [
|
|
{ key: "improve", label: "Improve writing" },
|
|
{ key: "professional", label: "Professional tone" },
|
|
{ key: "shorten", label: "Shorten" },
|
|
{ key: "expand", label: "Expand" },
|
|
{ key: "grammar", label: "Fix grammar" },
|
|
{ key: "ats", label: "ATS optimise" },
|
|
{ key: "bullets", label: "Generate bullets" },
|
|
{ key: "summary", label: "Generate summary" },
|
|
{ key: "rewrite", label: "Rewrite" },
|
|
{ key: "tailor", label: "Tailor to role" },
|
|
];
|
|
|
|
export const DEFAULT_SECTION_ORDER = [
|
|
"summary", "experience", "education", "projects", "skills", "certifications", "languages", "interests",
|
|
];
|
|
|
|
export const SECTION_LABELS: Record<string, string> = {
|
|
summary: "Professional Summary",
|
|
experience: "Experience",
|
|
education: "Education",
|
|
projects: "Projects",
|
|
skills: "Skills",
|
|
certifications: "Certifications",
|
|
languages: "Languages",
|
|
interests: "Interests",
|
|
};
|
|
|
|
export function emptyCvVariantSettings(themeId = "modern"): CvVariantSettings {
|
|
return {
|
|
themeId,
|
|
showPhoto: false,
|
|
showPageNumbers: false,
|
|
showIcons: true,
|
|
sections: [],
|
|
overrides: {},
|
|
customSections: [],
|
|
};
|
|
}
|
|
|
|
// --- API ---
|
|
// --- Pure helpers (unit-tested) ---
|
|
|
|
// Move an array item from one index to another, returning a new array.
|
|
export function moveItem<T>(arr: T[], from: number, to: number): T[] {
|
|
if (from === to || from < 0 || to < 0 || from >= arr.length || to >= arr.length) return arr;
|
|
const next = [...arr];
|
|
const [item] = next.splice(from, 1);
|
|
next.splice(to, 0, item);
|
|
return next;
|
|
}
|
|
|
|
// Wrap the [start,end) selection of `text` with markdown markers, for the rich-text toolbar.
|
|
// Returns the new text and the selection to restore. If nothing is selected, inserts a placeholder.
|
|
export function wrapSelection(
|
|
text: string,
|
|
start: number,
|
|
end: number,
|
|
before: string,
|
|
after: string,
|
|
placeholder = "text",
|
|
): { text: string; selStart: number; selEnd: number } {
|
|
const selected = text.slice(start, end) || placeholder;
|
|
const next = text.slice(0, start) + before + selected + after + text.slice(end);
|
|
return { text: next, selStart: start + before.length, selEnd: start + before.length + selected.length };
|
|
}
|
|
|
|
export const cvBuilderApi = {
|
|
themes: () => api.get<CvTheme[]>("/cv/themes").then((r) => r.data),
|
|
outline: () => api.get<CvOutline>("/cv/outline").then((r) => r.data),
|
|
list: () => api.get<CvVariantSummary[]>("/cv/variants").then((r) => r.data),
|
|
create: (body: { name?: string; jobApplicationId?: number | null; settings?: CvVariantSettings }) =>
|
|
api.post<CvVariant>("/cv/variants", body).then((r) => r.data),
|
|
get: (id: number) => api.get<CvVariant>(`/cv/variants/${id}`).then((r) => r.data),
|
|
save: (id: number, body: { name?: string; settings: CvVariantSettings; source?: string }) =>
|
|
api.put<CvVariant>(`/cv/variants/${id}`, body).then((r) => r.data),
|
|
remove: (id: number) => api.delete(`/cv/variants/${id}`),
|
|
duplicate: (id: number, name?: string) => api.post<CvVariant>(`/cv/variants/${id}/duplicate`, { name }).then((r) => r.data),
|
|
setPublic: (id: number, isPublic: boolean) => api.put<CvVariant>(`/cv/variants/${id}/public`, { isPublic }).then((r) => r.data),
|
|
versions: (id: number) => api.get<CvVariantVersionInfo[]>(`/cv/variants/${id}/versions`).then((r) => r.data),
|
|
restore: (id: number, version: number) => api.post<CvVariant>(`/cv/variants/${id}/versions/${version}/restore`).then((r) => r.data),
|
|
previewSettings: (settings: CvVariantSettings) => api.post<CvRender>("/cv/preview", { settings }).then((r) => r.data),
|
|
aiAssist: (body: { action: string; text: string; role?: string; language?: string; context?: string }) =>
|
|
api.post<{ original: string; result: string }>("/cv/ai/assist", body).then((r) => r.data),
|
|
exportPdfUrl: (id: number) => `/cv/variants/${id}/export-pdf`,
|
|
};
|