EmberClone/apps/web/src/lib/api.ts

185 lines
4.4 KiB
TypeScript

import type { TimeEntryInsert, CustomerInsert, ProjectInsert } from "@emberclone/shared"
const API_BASE = "/api"
async function request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const token = localStorage.getItem("auth_token")
const headers = new Headers(options.headers)
if (token) {
headers.set("Authorization", `Bearer ${token}`)
}
if (!(options.body instanceof FormData)) {
headers.set("Content-Type", "application/json")
}
const response = await fetch(`${API_BASE}${endpoint}`, {
...options,
headers,
credentials: "include"
})
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
const err: any = new Error(errorData.message || `API Error: ${response.status}`)
err.status = response.status
throw err
}
return response.json()
}
export const api = {
async login(email: string, password: string) {
const data = await request<{ token: string }>("/auth/login", {
method: "POST",
body: JSON.stringify({ email, password })
})
if (data?.token) {
localStorage.setItem("auth_token", data.token)
}
return data
},
async logout() {
localStorage.removeItem("auth_token")
return request("/auth/logout", { method: "POST" }).catch(() => {})
},
async getMe() {
return request<{ id: string; email: string; name: string; role: "admin" | "user" }>("/auth/me")
},
async changePassword(data: { oldPassword: string; newPassword: string }) {
return request("/auth/change-password", {
method: "POST",
body: JSON.stringify(data)
})
},
async deleteAccount(password: string) {
return request("/auth/delete-account", {
method: "DELETE",
body: JSON.stringify({ password })
})
},
async forgotPassword(email: string) {
return request("/auth/forgot-password", {
method: "POST",
body: JSON.stringify({ email })
})
},
async resetPassword(token: string, password: string) {
return request("/auth/reset-password", {
method: "POST",
body: JSON.stringify({ token, password })
})
},
async updateProfile(data: { name: string }) {
return request("/users/me", {
method: "PATCH",
body: JSON.stringify(data)
})
},
async listTimeEntries(opts?: Record<string, string>) {
const query = opts ? `?${new URLSearchParams(opts).toString()}` : ""
return request<any[]>(`/time-entries${query}`)
},
async createTimeEntry(data: Partial<TimeEntryInsert>) {
return request("/time-entries", {
method: "POST",
body: JSON.stringify(data)
})
},
async deleteTimeEntry(id: string) {
return request(`/time-entries/${id}`, {
method: "DELETE"
})
},
async bulkDeleteTimeEntries(ids: string[]) {
return request("/time-entries/bulk-delete", {
method: "POST",
body: JSON.stringify({ ids })
})
},
async getRunningTimeEntry() {
try {
return await request<any>("/time-entries/running")
} catch (err: any) {
if (err.status === 404) return null
throw err
}
},
async startTimeEntry(data: { description: string; projectId?: string }) {
return request("/time-entries/start", {
method: "POST",
body: JSON.stringify(data)
})
},
async stopTimeEntry(id: string) {
return request(`/time-entries/${id}/stop`, {
method: "POST"
})
},
async importTimeEntriesCsv(file: File) {
const formData = new FormData()
formData.append("file", file)
return request("/time-entries/import", {
method: "POST",
body: formData
})
},
async listTimeEntryTemplates() {
return request<any[]>("/time-entry-templates")
},
async createTimeEntryTemplate(data: any) {
return request("/time-entry-templates", {
method: "POST",
body: JSON.stringify(data)
})
},
async updateTimeEntryTemplate(id: string, data: any) {
return request(`/time-entry-templates/${id}`, {
method: "PATCH",
body: JSON.stringify(data)
})
},
async deleteTimeEntryTemplate(id: string) {
return request(`/time-entry-templates/${id}`, {
method: "DELETE"
})
},
async listHolidays() {
return request<any[]>("/holidays")
},
async createHoliday(data: { date: string; name: string }) {
return request("/holidays", {
method: "POST",
body: JSON.stringify(data)
})
},
async deleteHoliday(id: string) {
return request(`/holidays/${id}`, {
method: "DELETE"
})
}
}