This commit is contained in:
2026-06-14 13:19:39 +03:00
parent 4a3db42971
commit 19fff3a92f
152 changed files with 10908 additions and 8276 deletions
+13 -13
View File
@@ -1,25 +1,25 @@
import {apiRequest, clearRequestSecurity} from "#/api/client";
import type {AuthUser, LoginRequest, RegisterRequest} from "#/types/auth";
import { apiRequest, clearRequestSecurity } from "#/api/client";
import type { AuthUser, LoginRequest, RegisterRequest } from "#/types/auth";
export function getCurrentUser(signal?: AbortSignal) {
return apiRequest<AuthUser>("/auth/me", {signal});
return apiRequest<AuthUser>("/auth/me", { signal });
}
export function login(request: LoginRequest) {
return apiRequest<AuthUser>("/auth/login", {
method: "POST",
body: request,
});
return apiRequest<AuthUser>("/auth/login", {
method: "POST",
body: request,
});
}
export function register(request: RegisterRequest) {
return apiRequest<AuthUser>("/auth/register", {
method: "POST",
body: request,
});
return apiRequest<AuthUser>("/auth/register", {
method: "POST",
body: request,
});
}
export async function logout() {
await apiRequest<void>("/auth/logout", {method: "POST"});
clearRequestSecurity();
await apiRequest<void>("/auth/logout", { method: "POST" });
clearRequestSecurity();
}
+71 -69
View File
@@ -1,110 +1,112 @@
import type {ApiErrorResponse} from "#/types/api";
import type { ApiErrorResponse } from "#/types/api";
const API_BASE_URL =
import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080/api";
import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080/api";
let csrfToken: string | null = null;
let csrfHeaderName = "X-XSRF-TOKEN";
export class ApiError extends Error {
status: number;
code: string;
details: unknown;
status: number;
code: string;
details: unknown;
constructor(status: number, error: ApiErrorResponse) {
super(error.message || "Request failed");
this.name = "ApiError";
this.status = status;
this.code = error.error;
this.details = error.details;
}
constructor(status: number, error: ApiErrorResponse) {
super(error.message || "Request failed");
this.name = "ApiError";
this.status = status;
this.code = error.error;
this.details = error.details;
}
}
type RequestOptions = {
method?: "GET" | "POST" | "DELETE" | "PUT" | "PATCH";
body?: unknown;
signal?: AbortSignal;
method?: "GET" | "POST" | "DELETE" | "PUT" | "PATCH";
body?: unknown;
signal?: AbortSignal;
};
type CsrfResponse = {
headerName: string;
parameterName: string;
token: string;
headerName: string;
parameterName: string;
token: string;
};
async function loadCsrfToken() {
const response = await fetch(`${API_BASE_URL}/auth/csrf`, {
credentials: "include",
});
const response = await fetch(`${API_BASE_URL}/auth/csrf`, {
credentials: "include",
});
if (!response.ok) {
throw new Error("Could not initialize request security");
}
if (!response.ok) {
throw new Error("Could not initialize request security");
}
const data = (await response.json()) as CsrfResponse;
csrfToken = data.token;
csrfHeaderName = data.headerName;
const data = (await response.json()) as CsrfResponse;
csrfToken = data.token;
csrfHeaderName = data.headerName;
}
function isMutating(method: string) {
return method !== "GET";
return method !== "GET";
}
export async function apiRequest<T>(
path: string,
options: RequestOptions = {},
path: string,
options: RequestOptions = {},
): Promise<T> {
const method = options.method ?? "GET";
const method = options.method ?? "GET";
if (isMutating(method) && !csrfToken) {
await loadCsrfToken();
}
if (isMutating(method) && !csrfToken) {
await loadCsrfToken();
}
const response = await fetch(`${API_BASE_URL}${path}`, {
method,
credentials: "include",
headers: {
"Content-Type": "application/json",
...(isMutating(method) && csrfToken ? {[csrfHeaderName]: csrfToken} : {}),
},
body: options.body ? JSON.stringify(options.body) : undefined,
signal: options.signal,
});
const response = await fetch(`${API_BASE_URL}${path}`, {
method,
credentials: "include",
headers: {
"Content-Type": "application/json",
...(isMutating(method) && csrfToken
? { [csrfHeaderName]: csrfToken }
: {}),
},
body: options.body ? JSON.stringify(options.body) : undefined,
signal: options.signal,
});
if (!response.ok) {
let errorBody: ApiErrorResponse;
if (!response.ok) {
let errorBody: ApiErrorResponse;
try {
errorBody = await response.json();
} catch {
errorBody = {
error: "REQUEST_FAILED",
message: `Request failed with status ${response.status}`,
details: null,
timestamp: new Date().toISOString(),
path,
};
}
try {
errorBody = await response.json();
} catch {
errorBody = {
error: "REQUEST_FAILED",
message: `Request failed with status ${response.status}`,
details: null,
timestamp: new Date().toISOString(),
path,
};
}
throw new ApiError(response.status, errorBody);
}
throw new ApiError(response.status, errorBody);
}
if (response.status === 204) {
return undefined as T;
}
if (response.status === 204) {
return undefined as T;
}
const text = await response.text();
if (!text) {
return undefined as T;
}
const text = await response.text();
if (!text) {
return undefined as T;
}
return JSON.parse(text) as T;
return JSON.parse(text) as T;
}
export function clearRequestSecurity() {
csrfToken = null;
csrfToken = null;
}
export async function initializeRequestSecurity() {
await loadCsrfToken();
await loadCsrfToken();
}
+15 -15
View File
@@ -1,20 +1,20 @@
import {apiRequest} from "#/api/client";
import type {FareCalculationResponse} from "#/types/fare";
import { apiRequest } from "#/api/client";
import type { FareCalculationResponse } from "#/types/fare";
export function calculateFare(
distanceKm: number,
surgeMultiplier?: number,
signal?: AbortSignal,
distanceKm: number,
surgeMultiplier?: number,
signal?: AbortSignal,
) {
const params = new URLSearchParams();
params.set("distanceKm", String(distanceKm));
const params = new URLSearchParams();
params.set("distanceKm", String(distanceKm));
if (surgeMultiplier !== undefined) {
params.set("surgeMultiplier", String(surgeMultiplier));
}
if (surgeMultiplier !== undefined) {
params.set("surgeMultiplier", String(surgeMultiplier));
}
return apiRequest<FareCalculationResponse>(
`/fare/calculate?${params.toString()}`,
{signal},
);
}
return apiRequest<FareCalculationResponse>(
`/fare/calculate?${params.toString()}`,
{ signal },
);
}
+17 -17
View File
@@ -1,24 +1,24 @@
import {apiRequest} from "#/api/client";
import type {InventoryResponse, LowStockInventoryResponse,} from "#/types/inventory";
import { apiRequest } from "#/api/client";
import type {
InventoryResponse,
LowStockInventoryResponse,
} from "#/types/inventory";
export function getInventory(signal?: AbortSignal) {
return apiRequest<InventoryResponse>("/inventory", {signal});
return apiRequest<InventoryResponse>("/inventory", { signal });
}
export function getLowStockInventory(
threshold?: number,
signal?: AbortSignal,
) {
const params = new URLSearchParams();
export function getLowStockInventory(threshold?: number, signal?: AbortSignal) {
const params = new URLSearchParams();
if (threshold !== undefined) {
params.set("threshold", String(threshold));
}
if (threshold !== undefined) {
params.set("threshold", String(threshold));
}
const query = params.toString();
const query = params.toString();
return apiRequest<LowStockInventoryResponse>(
`/inventory/low-stock${query ? `?${query}` : ""}`,
{signal},
);
}
return apiRequest<LowStockInventoryResponse>(
`/inventory/low-stock${query ? `?${query}` : ""}`,
{ signal },
);
}
+42 -39
View File
@@ -1,62 +1,65 @@
import {apiRequest} from "#/api/client";
import type {PageResponse} from "#/types/api";
import { apiRequest } from "#/api/client";
import type { PageResponse } from "#/types/api";
import type {
CancelOrderResponse,
CreateOrderRequest,
Order,
OrderFilters,
OrderStatus,
OrderSummary,
CancelOrderResponse,
CreateOrderRequest,
Order,
OrderFilters,
OrderStatus,
OrderSummary,
} from "#/types/order";
export function getOrders(
filters: OrderFilters,
page = 0,
size = 20,
signal?: AbortSignal,
filters: OrderFilters,
page = 0,
size = 20,
signal?: AbortSignal,
) {
const params = new URLSearchParams();
const params = new URLSearchParams();
if (filters.status) {
params.set("status", filters.status);
}
if (filters.status) {
params.set("status", filters.status);
}
if (filters.from) {
params.set("from", filters.from);
}
if (filters.from) {
params.set("from", filters.from);
}
if (filters.to) {
params.set("to", filters.to);
}
if (filters.to) {
params.set("to", filters.to);
}
params.set("page", String(page));
params.set("size", String(size));
params.set("page", String(page));
params.set("size", String(size));
return apiRequest<PageResponse<OrderSummary>>(`/orders?${params.toString()}`, {
signal,
});
return apiRequest<PageResponse<OrderSummary>>(
`/orders?${params.toString()}`,
{
signal,
},
);
}
export function getOrder(id: string, signal?: AbortSignal) {
return apiRequest<Order>(`/orders/${id}`, {signal});
return apiRequest<Order>(`/orders/${id}`, { signal });
}
export function createOrder(request: CreateOrderRequest) {
return apiRequest<Order>("/orders", {
method: "POST",
body: request,
});
return apiRequest<Order>("/orders", {
method: "POST",
body: request,
});
}
export function cancelOrder(id: string) {
return apiRequest<CancelOrderResponse>(`/orders/${id}`, {
method: "DELETE",
});
return apiRequest<CancelOrderResponse>(`/orders/${id}`, {
method: "DELETE",
});
}
export function updateOrderStatus(id: string, status: OrderStatus) {
return apiRequest<Order>(`/orders/${id}/status`, {
method: "PATCH",
body: {status},
});
return apiRequest<Order>(`/orders/${id}/status`, {
method: "PATCH",
body: { status },
});
}
+38
View File
@@ -0,0 +1,38 @@
import { apiRequest } from "#/api/client";
import type {
CheckoutQuote,
CheckoutQuoteRequest,
PaystackInitializeRequest,
PaystackInitializeResponse,
PaystackVerifyResponse,
} from "#/types/payment";
export function getCheckoutQuote(
request: CheckoutQuoteRequest,
signal?: AbortSignal,
) {
return apiRequest<CheckoutQuote>("/checkout/quote", {
method: "POST",
body: request,
signal,
});
}
export function initializePaystackPayment(request: PaystackInitializeRequest) {
return apiRequest<PaystackInitializeResponse>(
"/payments/paystack/initialize",
{
method: "POST",
body: request,
},
);
}
export function verifyPaystackPayment(reference: string) {
return apiRequest<PaystackVerifyResponse>(
`/payments/paystack/verify/${encodeURIComponent(reference)}`,
{
method: "POST",
},
);
}
+13 -13
View File
@@ -1,28 +1,28 @@
import {apiRequest} from "#/api/client";
import type {Product, ProductRequest} from "#/types/product";
import { apiRequest } from "#/api/client";
import type { Product, ProductRequest } from "#/types/product";
export function getProducts(signal?: AbortSignal) {
return apiRequest<Product[]>("/products", {signal});
return apiRequest<Product[]>("/products", { signal });
}
export function getProduct(id: string, signal?: AbortSignal) {
return apiRequest<Product>(`/products/${id}`, {signal});
return apiRequest<Product>(`/products/${id}`, { signal });
}
export function createProduct(request: ProductRequest) {
return apiRequest<Product>("/products", {
method: "POST",
body: request,
});
return apiRequest<Product>("/products", {
method: "POST",
body: request,
});
}
export function updateProduct(id: string, request: ProductRequest) {
return apiRequest<Product>(`/products/${id}`, {
method: "PUT",
body: request,
});
return apiRequest<Product>(`/products/${id}`, {
method: "PUT",
body: request,
});
}
export function deactivateProduct(id: string) {
return apiRequest<Product>(`/products/${id}`, {method: "DELETE"});
return apiRequest<Product>(`/products/${id}`, { method: "DELETE" });
}
+14 -14
View File
@@ -1,32 +1,32 @@
import {apiRequest} from "#/api/client";
import type {CreateRideRequest, Ride, RideStatus} from "#/types/ride";
import { apiRequest } from "#/api/client";
import type { CreateRideRequest, Ride, RideStatus } from "#/types/ride";
export function getRides(signal?: AbortSignal) {
return apiRequest<Ride[]>("/rides", {signal});
return apiRequest<Ride[]>("/rides", { signal });
}
export function requestRide(request: CreateRideRequest) {
return apiRequest<Ride>("/rides", {
method: "POST",
body: request,
});
return apiRequest<Ride>("/rides", {
method: "POST",
body: request,
});
}
export function acceptRide(id: string) {
return apiRequest<Ride>(`/rides/${id}/accept`, {method: "POST"});
return apiRequest<Ride>(`/rides/${id}/accept`, { method: "POST" });
}
export function completeRide(id: string) {
return apiRequest<Ride>(`/rides/${id}/complete`, {method: "POST"});
return apiRequest<Ride>(`/rides/${id}/complete`, { method: "POST" });
}
export function cancelRide(id: string) {
return apiRequest<Ride>(`/rides/${id}`, {method: "DELETE"});
return apiRequest<Ride>(`/rides/${id}`, { method: "DELETE" });
}
export function updateRideStatus(id: string, status: RideStatus) {
return apiRequest<Ride>(`/rides/${id}/status`, {
method: "PATCH",
body: {status},
});
return apiRequest<Ride>(`/rides/${id}/status`, {
method: "PATCH",
body: { status },
});
}
+14 -14
View File
@@ -1,32 +1,32 @@
import {apiRequest} from "#/api/client";
import type {AdminUserRequest, UserSummary} from "#/types/user";
import { apiRequest } from "#/api/client";
import type { AdminUserRequest, UserSummary } from "#/types/user";
export function getUsers(signal?: AbortSignal) {
return apiRequest<UserSummary[]>("/users", {signal});
return apiRequest<UserSummary[]>("/users", { signal });
}
export function getCustomers(signal?: AbortSignal) {
return apiRequest<UserSummary[]>("/users/customers", {signal});
return apiRequest<UserSummary[]>("/users/customers", { signal });
}
export function getDrivers(signal?: AbortSignal) {
return apiRequest<UserSummary[]>("/users/drivers", {signal});
return apiRequest<UserSummary[]>("/users/drivers", { signal });
}
export function createUser(request: AdminUserRequest) {
return apiRequest<UserSummary>("/users", {
method: "POST",
body: request,
});
return apiRequest<UserSummary>("/users", {
method: "POST",
body: request,
});
}
export function updateUser(id: string, request: AdminUserRequest) {
return apiRequest<UserSummary>(`/users/${id}`, {
method: "PUT",
body: request,
});
return apiRequest<UserSummary>(`/users/${id}`, {
method: "PUT",
body: request,
});
}
export function disableUser(id: string) {
return apiRequest<UserSummary>(`/users/${id}`, {method: "DELETE"});
return apiRequest<UserSummary>(`/users/${id}`, { method: "DELETE" });
}