types and api client

This commit is contained in:
2025-04-29 19:57:31 +01:00
parent 4689d69a37
commit 861f0f6d51
3 changed files with 215 additions and 0 deletions

79
frontend/src/lib/api.ts Normal file
View File

@@ -0,0 +1,79 @@
import type {
CategoryListResponse,
Pattern,
PatternListResponse,
QuestionDetail,
QuestionListResponse,
Stats,
} from "@/types";
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
async function fetchApi<T>(
endpoint: string,
options?: RequestInit
): Promise<T> {
const url = `${API_BASE_URL}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return response.json();
}
export interface QuestionFilters {
page?: number;
limit?: number;
difficulty?: string;
category?: string;
pattern?: string;
search?: string;
}
export async function getQuestions(
filters: QuestionFilters = {}
): Promise<QuestionListResponse> {
const params = new URLSearchParams();
if (filters.page) params.set("page", filters.page.toString());
if (filters.limit) params.set("limit", filters.limit.toString());
if (filters.difficulty) params.set("difficulty", filters.difficulty);
if (filters.category) params.set("category", filters.category);
if (filters.pattern) params.set("pattern", filters.pattern);
if (filters.search) params.set("search", filters.search);
const queryString = params.toString();
const endpoint = queryString
? `/api/questions?${queryString}`
: "/api/questions";
return fetchApi<QuestionListResponse>(endpoint);
}
export async function getQuestion(slug: string): Promise<QuestionDetail> {
return fetchApi<QuestionDetail>(`/api/questions/${slug}`);
}
export async function getCategories(): Promise<CategoryListResponse> {
return fetchApi<CategoryListResponse>("/api/categories");
}
export async function getPatterns(): Promise<PatternListResponse> {
return fetchApi<PatternListResponse>("/api/patterns");
}
export async function getPattern(slug: string): Promise<Pattern> {
return fetchApi<Pattern>(`/api/patterns/${slug}`);
}
export async function getStats(): Promise<Stats> {
return fetchApi<Stats>("/api/stats");
}

20
frontend/src/lib/utils.ts Normal file
View File

@@ -0,0 +1,20 @@
import type { Difficulty } from "@/types";
export function cn(...classes: (string | undefined | false)[]): string {
return classes.filter(Boolean).join(" ");
}
export function getDifficultyColor(difficulty: Difficulty): string {
switch (difficulty) {
case "easy":
return "text-green-600 bg-green-100 dark:text-green-400 dark:bg-green-900/30";
case "medium":
return "text-yellow-600 bg-yellow-100 dark:text-yellow-400 dark:bg-yellow-900/30";
case "hard":
return "text-red-600 bg-red-100 dark:text-red-400 dark:bg-red-900/30";
}
}
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}

116
frontend/src/types/index.ts Normal file
View File

@@ -0,0 +1,116 @@
export type Difficulty = "easy" | "medium" | "hard";
export interface CategoryBrief {
id: string;
name: string;
slug: string;
}
export interface PatternBrief {
id: string;
name: string;
slug: string;
}
export interface Category extends CategoryBrief {
description: string | null;
question_count: number;
}
export interface Pattern extends PatternBrief {
description: string | null;
when_to_use: string | null;
question_count: number;
}
export interface QuestionListItem {
id: string;
title: string;
slug: string;
difficulty: Difficulty;
leetcode_id: number | null;
leetcode_url: string | null;
categories: CategoryBrief[];
patterns: PatternBrief[];
created_at: string;
}
export interface CommonPitfall {
title: string;
description: string;
wrong_approach?: string;
correct_approach?: string;
}
export interface Explanation {
approach: string;
intuition: string;
common_pitfalls: CommonPitfall[] | null;
key_takeaways: string[] | null;
time_complexity: string;
space_complexity: string;
complexity_explanation: string | null;
}
export interface Solution {
id: string;
approach_name: string;
code: string;
language: string;
is_optimal: boolean;
explanation: string | null;
}
export interface QuestionDetail extends QuestionListItem {
description: string;
constraints: string | null;
examples: Array<{
input: string;
output: string;
explanation?: string;
}> | null;
explanation: Explanation | null;
solutions: Solution[];
updated_at: string;
}
export interface QuestionListResponse {
items: QuestionListItem[];
total: number;
page: number;
limit: number;
pages: number;
}
export interface CategoryListResponse {
items: Category[];
}
export interface PatternListResponse {
items: Pattern[];
}
export interface DifficultyCount {
easy: number;
medium: number;
hard: number;
}
export interface CategoryCount {
name: string;
slug: string;
count: number;
}
export interface PatternCount {
name: string;
slug: string;
count: number;
}
export interface Stats {
total_questions: number;
by_difficulty: DifficultyCount;
by_category: CategoryCount[];
by_pattern: PatternCount[];
}