import { BaseResumeSelection } from "@client/pages/settings/components/BaseResumeSelection"; import { SettingsInput } from "@client/pages/settings/components/SettingsInput"; import { toggleAiSelectable, toggleMustInclude, } from "@client/pages/settings/resume-projects-state"; import type { ResumeProjectsSettingsInput } from "@shared/settings-schema.js"; import type { ResumeProjectCatalogItem, RxResumeMode } from "@shared/types.js"; import type React from "react"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { Separator } from "@/components/ui/separator"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { clampInt } from "@/lib/utils"; import { StatusIndicator } from "./StatusIndicator"; type VersionValidationState = { checked: boolean; valid: boolean; message?: string | null; }; type ProjectSelectionConfig = { baseResumeId: string | null; onBaseResumeIdChange: (value: string | null) => void; projects: ResumeProjectCatalogItem[]; value: ResumeProjectsSettingsInput | null | undefined; onChange: (next: ResumeProjectsSettingsInput) => void; lockedCount: number; maxProjectsTotal: number; isProjectsLoading: boolean; disabled: boolean; maxProjectsError?: string; }; type ReactiveResumeConfigPanelProps = { mode: RxResumeMode; onModeChange: (mode: RxResumeMode) => void; disabled?: boolean; hasRxResumeAccess?: boolean; showValidationStatus?: boolean; validationStatuses?: { v4: VersionValidationState; v5: VersionValidationState; }; intro?: { title: string; description?: string; }; v5: { apiKey: string; onApiKeyChange: (value: string) => void; error?: string; helper?: string; placeholder?: string; }; v4: { email: string; onEmailChange: (value: string) => void; emailError?: string; password: string; onPasswordChange: (value: string) => void; passwordError?: string; emailPlaceholder?: string; passwordPlaceholder?: string; }; projectSelection?: ProjectSelectionConfig; }; function renderStatusPill(label: string, state: VersionValidationState) { const statusLabel = state.checked ? state.valid ? "Connected" : "Failed" : "Not tested"; const dotColor = state.checked ? state.valid ? "bg-emerald-500" : "bg-destructive" : "bg-muted-foreground"; return ( ); } export const ReactiveResumeConfigPanel: React.FC< ReactiveResumeConfigPanelProps > = ({ mode, onModeChange, disabled = false, hasRxResumeAccess = false, showValidationStatus = false, validationStatuses, intro, v5, v4, projectSelection, }) => { const canShowProjectSelection = Boolean( projectSelection && hasRxResumeAccess, ); const selectedValidationStatus = validationStatuses?.[mode]; const handleModeChange = (value: string) => onModeChange(value === "v4" ? "v4" : "v5"); return (
{intro ? (

{intro.title}

{intro.description ? (

{intro.description}

) : null}
) : null} v5 (API key) v4 (Email + Password) {showValidationStatus && selectedValidationStatus ? (
{renderStatusPill(`${mode} status`, selectedValidationStatus)}
) : null} {mode === "v5" ? (
v5.onApiKeyChange(event.currentTarget.value), }} type="password" placeholder={v5.placeholder ?? "Enter v5 API key"} helper={v5.helper} disabled={disabled} error={v5.error} />
) : (
v4.onEmailChange(event.currentTarget.value), }} placeholder={v4.emailPlaceholder ?? "you@example.com"} disabled={disabled} error={v4.emailError} /> v4.onPasswordChange(event.currentTarget.value), }} type="password" placeholder={v4.passwordPlaceholder ?? "Enter v4 password"} disabled={disabled} error={v4.passwordError} />
)} {projectSelection ? ( <> {!canShowProjectSelection ? (
Connect Reactive Resume and choose a template resume to configure resume projects.
) : (
{!projectSelection.baseResumeId ? (
Choose a PDF to configure resume projects.
) : ( <>
Max projects to choose
{ if (!projectSelection.value) return; const next = Number(event.target.value); const clamped = clampInt( next, projectSelection.lockedCount, projectSelection.maxProjectsTotal, ); projectSelection.onChange({ ...projectSelection.value, maxProjects: clamped, }); }} disabled={ projectSelection.disabled || projectSelection.isProjectsLoading || !projectSelection.value } /> {projectSelection.maxProjectsError ? (

{projectSelection.maxProjectsError}

) : null}
Project Visible in template Must Include AI selectable {projectSelection.projects.map((project) => { const value = projectSelection.value; const locked = Boolean( value?.lockedProjectIds.includes(project.id), ); const aiSelectable = Boolean( value?.aiSelectableProjectIds.includes(project.id), ); const projectMeta = mode === "v5" ? project.date : [project.description, project.date] .filter(Boolean) .join(" - "); return (
{project.name}
{projectMeta ? (
{projectMeta}
) : null}
{project.isVisibleInBase ? "Yes" : "No"} { if (!value) return; projectSelection.onChange( toggleMustInclude({ settings: value, projectId: project.id, checked: !locked, maxProjectsTotal: projectSelection.maxProjectsTotal, }), ); }} disabled={ projectSelection.disabled || projectSelection.isProjectsLoading || !value } /> { if (!value) return; projectSelection.onChange( toggleAiSelectable({ settings: value, projectId: project.id, checked: !aiSelectable, }), ); }} disabled={ projectSelection.disabled || projectSelection.isProjectsLoading || locked || !value } />
); })}
)}
)} ) : null}
); };