import type { Leaderboard, PredictedScore } from "../types/leaderboard"; import type { Lesson, LessonsResponse } from "../types/lesson"; import type { ClaimedRewardResponse, QuestArc, UserInventory, UserTitle, } from "../types/quest"; import type { SessionAnswerResponse, SessionQuestionsResponse, SessionRequest, SessionResponse, SubmitAnswer, } from "../types/session"; import type { PracticeSheet } from "../types/sheet"; import type { Topic } from "../types/topic"; const API_URL = "https://ed-dev-api.omukk.dev"; export interface LoginRequest { email: string; password: string; } export interface RegistrationRequest { email: string; name: string; avatar_url: string; password: string; } export interface User { email: string; name: string; role: "STUDENT" | "TEACHER" | "ADMIN"; avatar_url: string; id: string; status: "ACTIVE" | "INACTIVE"; joined_at: string; last_active: string; total_xp: number; current_level: number; next_level_threshold: number; current_level_start: number; } export interface LoginResponse { token: string; token_type: string; user: User; } export interface ApiError { detail?: string; message?: string; } class ApiClient { private baseURL: string; constructor(baseURL: string) { this.baseURL = baseURL; } private async request( endpoint: string, options: RequestInit = {}, ): Promise { const url = `${this.baseURL}${endpoint}`; const config: RequestInit = { ...options, headers: { "Content-Type": "application/json", ...options.headers, }, }; try { const response = await fetch(url, config); if (!response.ok) { const error: ApiError = await response.json().catch(() => ({ message: "An error occurred", })); throw new Error(error.detail || error.message || "Request failed"); } return await response.json(); } catch (error) { if (error instanceof Error) { throw error; } throw new Error("Network error occurred"); } } async fetchUser(token: string): Promise { return this.authenticatedRequest("/auth/me/", token); } // Auth endpoints async login(credentials: LoginRequest): Promise { return this.request("/auth/login/", { method: "POST", body: JSON.stringify(credentials), }); } async register( credentials: RegistrationRequest, ): Promise<{ message: string }> { return this.request<{ message: string }>("/auth/register/", { method: "POST", body: JSON.stringify(credentials), }); } // Authenticated request helper async authenticatedRequest( endpoint: string, token: string, options: RequestInit = {}, ): Promise { return this.request(endpoint, { ...options, headers: { ...options.headers, Authorization: `Bearer ${token}`, }, }); } // Example: Get user profile (authenticated endpoint) async getUserProfile(token: string): Promise { return this.authenticatedRequest("/auth/me/", token); } async getPracticeSheets( token: string, page: number, limit: number, ): Promise { const queryParams = new URLSearchParams({ page: page.toString(), limit: limit.toString(), }).toString(); return this.authenticatedRequest( `/practice-sheets/?${queryParams}`, token, ); } async getPracticeSheetById( token: string, sheetId: string, ): Promise { return this.authenticatedRequest( `/practice-sheets/${sheetId}`, token, ); } async startSession( token: string, sessionData: SessionRequest, ): Promise { return this.authenticatedRequest(`/sessions/`, token, { method: "POST", body: JSON.stringify(sessionData), }); } async fetchSessionQuestions( token: string, sessionId: string, ): Promise { return this.authenticatedRequest( `/sessions/${sessionId}/questions/`, token, ); } async submitAnswer( token: string, sessionId: string, answerSubmissionData: SubmitAnswer, ): Promise { return this.authenticatedRequest( `/sessions/${sessionId}/answer/`, token, { method: "POST", body: JSON.stringify(answerSubmissionData), }, ); } async fetchNextModule(token: string, sessionId: string): Promise { return this.authenticatedRequest( `/sessions/${sessionId}/next-module/`, token, { method: "POST", }, ); } async fetchSessionStateById( token: string, sessionId: string, ): Promise { return this.authenticatedRequest( `/sessions/${sessionId}`, token, ); } async fetchLessonVideos(token: string): Promise { return this.authenticatedRequest(`/lessons/`, token); } async fetchLessonById(token: string, lessonId: string): Promise { return this.authenticatedRequest(`/lessons/${lessonId}`, token); } async fetchAllTopics(token: string): Promise { return this.authenticatedRequest(`/topics/`, token); } async fetchTopicById(token: string, topicId: string): Promise { return this.authenticatedRequest(`/topics/${topicId}`, token); } async fetchLeaderboard( token: string, metric: string, timeframe: string, ): Promise { return this.authenticatedRequest( `/leaderboard/?metric=${metric}&timeframe=${timeframe}`, token, ); } async fetchPredictedScore(token: string): Promise { return this.authenticatedRequest(`/prediction/`, token); } /*------------QUEST JOURNEY-------------- */ async fetchUserJourney(token: string): Promise { return this.authenticatedRequest(`/journey/`, token); } async claimReward( token: string, node_id: string, ): Promise { return this.authenticatedRequest( `/journey/claim/${node_id}`, token, { method: "POST", }, ); } /*------------INVENTORY-------------- */ async fetchUserInventory(token: string): Promise { return this.authenticatedRequest(`/inventory/`, token); } async activateItem(token: string, itemId: string): Promise { return this.authenticatedRequest( `/inventory/use/${itemId}`, token, ); } /*------------TITLES-------------- */ async fetchUserTitles(token: string): Promise { return this.authenticatedRequest(`/inventory/titles/`, token); } async equipTitle( token: string, titleData: { title_id: string }, ): Promise { return this.authenticatedRequest(`/inventory/titles/equip`, token, { method: "POST", body: JSON.stringify(titleData), }); } /*------------UPLOADS-------------- */ // token is optional — the /uploads/ endpoint appears to be public // (no Authorization header in the curl example). Pass a token if needed. async uploadAvatar(file: File, token?: string): Promise { const formData = new FormData(); formData.append("file", file); const headers: HeadersInit = { accept: "application/json" }; if (token) headers["Authorization"] = `Bearer ${token}`; // Note: Do NOT set Content-Type manually — fetch sets it automatically // with the correct multipart/form-data boundary when the body is FormData. const url = `${this.baseURL}/uploads/`; const response = await fetch(url, { method: "POST", headers, body: formData, }); if (!response.ok) { const error: ApiError = await response.json().catch(() => ({ message: "Upload failed", })); throw new Error(error.detail || error.message || "Upload failed"); } const data = await response.json(); // Adjust the field name below to match your API's response shape, // e.g. data.url, data.file_url, data.avatar_url, etc. return data.url as string; } } export const api = new ApiClient(API_URL);