Shortlist / frontend /src /lib /api.ts
Eren-Sama
Initial commit — full-stack AI portfolio architect
53e1531
/** Typed fetch wrapper for the FastAPI backend. All requests include the Supabase JWT. */
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
interface ApiError {
detail: string;
errors?: Array<{ field: string; message: string }>;
}
class ApiClient {
private token: string | null = null;
setToken(token: string | null) {
this.token = token;
}
private async request<T>(
method: string,
path: string,
body?: unknown
): Promise<T> {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (this.token) {
headers["Authorization"] = `Bearer ${this.token}`;
}
const res = await fetch(`${API_BASE}${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
const error: ApiError = await res.json().catch(() => ({
detail: `Request failed with status ${res.status}`,
}));
throw new Error(error.detail || "An unknown error occurred");
}
return res.json();
}
async analyzeJD(data: {
jd_text: string;
role: string;
company_type: string;
geography?: string;
}) {
return this.request<JDAnalysisResponse>("POST", "/api/v1/jd/analyze", data);
}
async getAnalysis(analysisId: string) {
return this.request<JDAnalysisResponse>("GET", `/api/v1/jd/${analysisId}`);
}
async listAnalyses(limit = 20, offset = 0) {
return this.request<AnalysisListResponse>(
"GET",
`/api/v1/jd/?limit=${limit}&offset=${offset}`
);
}
async deleteAnalysis(analysisId: string) {
return this.request<{ deleted: boolean; analysis_id: string }>(
"DELETE",
`/api/v1/jd/${analysisId}`
);
}
async generateCapstone(data: {
analysis_id: string;
num_projects?: number;
preferred_stack?: string[];
}) {
return this.request<CapstoneResponse>(
"POST",
"/api/v1/capstone/generate",
data
);
}
async getCapstoneProjects(analysisId: string) {
return this.request<{ analysis_id: string; projects: CapstoneProject[] }>(
"GET",
`/api/v1/capstone/${analysisId}`
);
}
async toggleProjectSelection(projectId: string, selected: boolean) {
return this.request<{ id: string; is_selected: boolean }>(
"PUT",
`/api/v1/capstone/${projectId}/select?selected=${selected}`
);
}
async analyzeRepo(data: { github_url: string }) {
return this.request<RepoAnalysisResponse>(
"POST",
"/api/v1/repo/analyze",
data
);
}
async getRepoAnalysis(analysisId: string) {
return this.request<RepoAnalysisResponse>(
"GET",
`/api/v1/repo/${analysisId}`
);
}
async listRepoAnalyses(limit = 20, offset = 0) {
return this.request<RepoAnalysisListResponse>(
"GET",
`/api/v1/repo/?limit=${limit}&offset=${offset}`
);
}
async generateScaffold(data: {
project_title: string;
project_description: string;
tech_stack: string[];
include_docker?: boolean;
include_ci?: boolean;
include_tests?: boolean;
analysis_id?: string;
project_id?: string;
}) {
return this.request<ScaffoldResponse>(
"POST",
"/api/v1/scaffold/generate",
data
);
}
async getScaffold(scaffoldId: string) {
return this.request<ScaffoldResponse>(
"GET",
`/api/v1/scaffold/${scaffoldId}`
);
}
async listScaffolds(limit = 20, offset = 0) {
return this.request<ScaffoldListResponse>(
"GET",
`/api/v1/scaffold/?limit=${limit}&offset=${offset}`
);
}
async optimizePortfolio(data: {
project_title: string;
project_description: string;
tech_stack: string[];
key_features?: string[];
repo_score?: number;
target_role?: string;
analysis_id?: string;
}) {
return this.request<PortfolioResponse>(
"POST",
"/api/v1/portfolio/optimize",
data
);
}
async getPortfolio(portfolioId: string) {
return this.request<PortfolioRecord>(
"GET",
`/api/v1/portfolio/${portfolioId}`
);
}
async listPortfolios(limit = 20, offset = 0) {
return this.request<PortfolioListResponse>(
"GET",
`/api/v1/portfolio/?limit=${limit}&offset=${offset}`
);
}
async scoreFitness(data: { analysis_id: string; resume_text: string }) {
return this.request<FitnessScoreResponse>(
"POST",
"/api/v1/fitness/score",
data
);
}
async getFitness(fitnessId: string) {
return this.request<FitnessScoreResponse>(
"GET",
`/api/v1/fitness/${fitnessId}`
);
}
async listFitnessScores(limit = 20, offset = 0) {
return this.request<FitnessListResponse>(
"GET",
`/api/v1/fitness/?limit=${limit}&offset=${offset}`
);
}
}
export interface Skill {
name: string;
category: string;
weight: number;
source: string;
}
export interface EngineeringExpectation {
dimension: string;
importance: number;
description: string;
}
export interface SkillProfile {
skills: Skill[];
experience_level: string;
domain: string;
engineering_expectations: EngineeringExpectation[];
key_responsibilities: string[];
summary: string;
}
export interface CompanyModifiers {
company_type: string;
emphasis_areas: string[];
weight_adjustments: Record<string, number>;
portfolio_focus: string;
}
export interface JDAnalysisResponse {
analysis_id: string;
skill_profile: SkillProfile;
company_modifiers: CompanyModifiers;
raw_role: string;
raw_company_type: string;
raw_geography?: string;
}
export interface AnalysisListItem {
id: string;
role: string;
company_type: string;
geography?: string;
status: string;
processing_time_ms?: number;
created_at: string;
updated_at: string;
}
export interface AnalysisListResponse {
analyses: AnalysisListItem[];
total: number;
limit: number;
offset: number;
}
export interface ArchitectureOverview {
description: string;
components: string[];
data_flow: string;
diagram_mermaid?: string;
}
export interface CapstoneProject {
title: string;
problem_statement: string;
recruiter_match_reasoning: string;
architecture: ArchitectureOverview;
tech_stack: string[];
complexity_level: number;
estimated_days: number;
resume_bullet: string;
key_features: string[];
differentiator: string;
}
export interface CapstoneResponse {
analysis_id: string;
projects: CapstoneProject[];
generation_metadata: {
processing_time_ms: number;
projects_generated: number;
projects_persisted: number;
};
}
export interface ScoreDimension {
name: string;
score: number;
details: string;
suggestions: string[];
}
export interface RepoScoreCard {
repo_url: string;
repo_name: string;
primary_language?: string;
total_files: number;
total_lines: number;
code_quality: ScoreDimension;
test_coverage: ScoreDimension;
complexity: ScoreDimension;
structure: ScoreDimension;
deployment_readiness: ScoreDimension;
overall_score: number;
summary: string;
top_improvements: string[];
}
export interface RepoAnalysisResponse {
analysis_id: string;
scorecard: RepoScoreCard;
analysis_metadata: Record<string, unknown>;
}
export interface RepoAnalysisListItem {
id: string;
github_url: string;
repo_name?: string;
primary_language?: string;
overall_score?: number;
status: string;
processing_time_ms?: number;
created_at: string;
updated_at: string;
}
export interface RepoAnalysisListResponse {
analyses: RepoAnalysisListItem[];
total: number;
limit: number;
offset: number;
}
export interface GeneratedFile {
path: string;
content: string;
language: string;
description: string;
}
export interface ScaffoldResponse {
project_name: string;
files: GeneratedFile[];
file_tree: string;
download_url?: string;
generation_metadata: Record<string, unknown>;
}
export interface ScaffoldListItem {
id: string;
project_id?: string;
project_title: string;
tech_stack: string[];
status: string;
processing_time_ms?: number;
created_at: string;
updated_at: string;
}
export interface ScaffoldListResponse {
scaffolds: ScaffoldListItem[];
total: number;
limit: number;
offset: number;
}
export interface ResumeBullet {
bullet: string;
keywords: string[];
impact_type: "quantitative" | "qualitative" | "technical";
}
export interface DemoStep {
timestamp: string;
action: string;
narration: string;
}
export interface DemoScript {
total_duration_seconds: number;
opening_hook: string;
steps: DemoStep[];
closing_cta: string;
}
export interface LinkedInPost {
hook: string;
body: string;
hashtags: string[];
call_to_action: string;
}
export interface PortfolioResponse {
readme_markdown: string;
resume_bullets: ResumeBullet[];
demo_script: DemoScript;
linkedin_post: LinkedInPost;
generation_metadata: {
portfolio_id: string;
processing_time_ms: number;
model: string;
};
}
export interface PortfolioRecord {
id: string;
project_title: string;
project_description: string;
tech_stack: string[];
target_role?: string;
readme_markdown?: string;
resume_bullets?: ResumeBullet[];
demo_script?: DemoScript;
linkedin_post?: LinkedInPost;
status: string;
processing_time_ms?: number;
created_at: string;
updated_at: string;
}
export interface PortfolioListItem {
id: string;
project_title: string;
tech_stack: string[];
target_role?: string;
status: string;
processing_time_ms?: number;
created_at: string;
updated_at: string;
}
export interface PortfolioListResponse {
items: PortfolioListItem[];
total: number;
limit: number;
offset: number;
}
export interface MatchedSkill {
skill: string;
evidence: string;
strength: "strong" | "moderate" | "weak";
}
export interface MissingSkill {
skill: string;
importance: "critical" | "important" | "nice_to_have";
suggestion: string;
}
export interface Improvement {
area: string;
current_state: string;
recommended_action: string;
impact: "high" | "medium" | "low";
}
export interface FitnessScoreResponse {
fitness_id: string;
analysis_id: string;
fitness_score: number;
verdict: "strong_fit" | "good_fit" | "partial_fit" | "weak_fit";
matched_skills: MatchedSkill[];
missing_skills: MissingSkill[];
strengths: string[];
improvements: Improvement[];
detailed_feedback: string;
processing_time_ms?: number;
}
export interface FitnessListItem {
id: string;
analysis_id: string;
fitness_score: number;
verdict: string;
status: string;
processing_time_ms?: number;
created_at: string;
updated_at: string;
}
export interface FitnessListResponse {
scores: FitnessListItem[];
total: number;
limit: number;
offset: number;
}
export const api = new ApiClient();