192 lines
4.6 KiB
TypeScript
192 lines
4.6 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 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 listCustomers() {
|
|
return request<any[]>("/customers")
|
|
},
|
|
|
|
async createCustomer(data: Partial<CustomerInsert>) {
|
|
return request("/customers", {
|
|
method: "POST",
|
|
body: JSON.stringify(data)
|
|
})
|
|
},
|
|
|
|
async importCustomersCsv(file: File) {
|
|
const formData = new FormData()
|
|
formData.append("file", file)
|
|
return request("/customers/import", {
|
|
method: "POST",
|
|
body: formData
|
|
})
|
|
},
|
|
|
|
async deleteCustomer(id: string) {
|
|
return request(`/customers/${id}`, {
|
|
method: "DELETE"
|
|
})
|
|
},
|
|
|
|
async getCustomerProjects(customerId: string) {
|
|
return request<any[]>(`/customers/${customerId}/projects`)
|
|
},
|
|
|
|
async getCustomerTimeEntries(customerId: string) {
|
|
return request<any[]>(`/customers/${customerId}/time-entries`)
|
|
},
|
|
|
|
async listProjects(opts?: Record<string, string>) {
|
|
const query = opts ? `?${new URLSearchParams(opts).toString()}` : ""
|
|
return request<any[]>(`/projects${query}`)
|
|
},
|
|
|
|
async getProject(id: string) {
|
|
return request<any>(`/projects/${id}`)
|
|
},
|
|
|
|
async getProjectStats(id: string) {
|
|
return request<any>(`/projects/${id}/stats`)
|
|
},
|
|
|
|
async createProject(data: Partial<ProjectInsert>) {
|
|
return request("/projects", {
|
|
method: "POST",
|
|
body: JSON.stringify(data)
|
|
})
|
|
},
|
|
|
|
async deleteProject(id: string) {
|
|
return request(`/projects/${id}`, {
|
|
method: "DELETE"
|
|
})
|
|
},
|
|
|
|
async listUsers() {
|
|
return request<any[]>("/users")
|
|
},
|
|
|
|
async createUser(data: { email: string; name: string; role: "admin" | "user"; password: string }) {
|
|
return request("/users", {
|
|
method: "POST",
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
} |