use person name from base.json for file downloads

This commit is contained in:
DaKheera47
2026-01-21 14:48:17 +00:00
parent e5c99d54bf
commit 6a3a25578a
10 changed files with 177 additions and 20 deletions
+9 -1
View File
@@ -171,7 +171,15 @@ export async function getSettings(): Promise<AppSettings> {
}
export async function getProfileProjects(): Promise<ResumeProjectCatalogItem[]> {
return fetchApi<ResumeProjectCatalogItem[]>('/profile/projects');
return fetchApi<ResumeProjectCatalogItem[]>('/profile/projects', {
method: 'POST',
});
}
export async function getProfile(): Promise<any> {
return fetchApi<any>('/profile', {
method: 'POST',
});
}
@@ -45,6 +45,7 @@ import { cn, copyTextToClipboard, formatJobForWebhook } from "@/lib/utils";
import * as api from "../api";
import { FitAssessment, JobHeader, TailoredSummary } from ".";
import { TailorMode } from "./discovered-panel/TailorMode";
import { useProfile } from "../hooks/useProfile";
import type { Job, ResumeProjectCatalogItem } from "../../shared/types";
type PanelMode = "ready" | "tailor";
@@ -74,6 +75,8 @@ export const ReadyPanel: React.FC<ReadyPanelProps> = ({
timeoutId: ReturnType<typeof setTimeout>;
} | null>(null);
const { personName } = useProfile();
// Load project catalog once
useEffect(() => {
api.getProfileProjects().then(setCatalog).catch(console.error);
@@ -279,7 +282,7 @@ export const ReadyPanel: React.FC<ReadyPanelProps> = ({
<Button asChild variant="outline" className="h-9 w-full gap-1 px-2 text-xs">
<a
href={pdfHref}
download={`Shaheer_Sarfaraz_${safeFilenamePart(job.employer)}.pdf`}
download={`${personName.replace(/\s+/g, '_')}_${safeFilenamePart(job.employer)}.pdf`}
>
<Download className="h-3.5 w-3.5 shrink-0" />
<span className="truncate">Download PDF</span>
@@ -0,0 +1,90 @@
import { useEffect, useState } from 'react';
import * as api from '../api';
let profileCache: any = null;
let profileError: Error | null = null;
let subscribers: Set<(profile: any | null, error: Error | null) => void> = new Set();
let isFetching = false;
/**
* Hook to get the full profile data from base.json.
* Caches the result to avoid re-fetching.
*/
export function useProfile() {
const [profile, setProfile] = useState<any | null>(profileCache);
const [error, setError] = useState<Error | null>(profileError);
useEffect(() => {
if (profileCache) {
setProfile(profileCache);
}
if (profileError) {
setError(profileError);
}
const handleUpdate = (newProfile: any | null, newError: Error | null) => {
setProfile(newProfile);
setError(newError);
};
subscribers.add(handleUpdate);
if (!profileCache && !isFetching) {
isFetching = true;
profileError = null;
api.getProfile()
.then((data) => {
profileCache = data;
profileError = null;
subscribers.forEach(sub => sub(data, null));
})
.catch((err) => {
profileError = err instanceof Error ? err : new Error(String(err));
subscribers.forEach(sub => sub(profileCache, profileError));
})
.finally(() => {
isFetching = false;
});
}
return () => {
subscribers.delete(handleUpdate);
};
}, []);
const refreshProfile = async () => {
isFetching = true;
profileError = null;
subscribers.forEach(sub => sub(profileCache, null));
try {
const data = await api.getProfile();
profileCache = data;
profileError = null;
subscribers.forEach(sub => sub(data, null));
return data;
} catch (err) {
profileError = err instanceof Error ? err : new Error(String(err));
subscribers.forEach(sub => sub(profileCache, profileError));
throw profileError;
} finally {
isFetching = false;
}
};
return {
profile,
error,
isLoading: !profile && isFetching && !error,
personName: profile?.basics?.name || 'Resume',
refreshProfile,
};
}
/** @internal For testing only */
export function _resetProfileCache() {
profileCache = null;
profileError = null;
isFetching = false;
subscribers.clear();
}
@@ -30,6 +30,7 @@ import { copyTextToClipboard, formatJobForWebhook, safeFilenamePart, stripHtml }
import { DiscoveredPanel, FitAssessment, JobHeader, TailoredSummary } from "../../components";
import { ReadyPanel } from "../../components/ReadyPanel";
import { TailoringEditor } from "../../components/TailoringEditor";
import { useProfile } from "../../hooks/useProfile";
import * as api from "../../api";
import type { Job } from "../../../shared/types";
import type { FilterTab } from "./constants";
@@ -59,6 +60,8 @@ export const JobDetailPanel: React.FC<JobDetailPanelProps> = ({
const [processingJobId, setProcessingJobId] = useState<string | null>(null);
const saveTailoringRef = useRef<null | (() => Promise<void>)>(null);
const { personName } = useProfile();
useEffect(() => {
setHasUnsavedTailoring(false);
saveTailoringRef.current = null;
@@ -363,7 +366,7 @@ export const JobDetailPanel: React.FC<JobDetailPanelProps> = ({
<DropdownMenuItem asChild>
<a
href={selectedPdfHref}
download={`Shaheer_Sarfaraz_${safeFilenamePart(selectedJob.employer)}.pdf`}
download={`${personName.replace(/\s+/g, '_')}_${safeFilenamePart(selectedJob.employer)}.pdf`}
>
<FileText className="mr-2 h-4 w-4" />
Download PDF