mirror of
http://88.130.71.182:3000/BlitTech/badoHair_fe.git
synced 2026-06-13 10:17:09 +00:00
Update May 12 by Elvis
This commit is contained in:
18
lib/api/admin.ts
Normal file
18
lib/api/admin.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { api } from "@/lib/api";
|
||||
|
||||
export interface DashboardStats {
|
||||
revenue_today: number;
|
||||
revenue_week: number;
|
||||
revenue_month: number;
|
||||
orders_pending: number;
|
||||
bookings_pending: number;
|
||||
bookings_confirmed: number;
|
||||
products_count: number;
|
||||
catalog_value: number;
|
||||
low_stock_count: number;
|
||||
new_customers_month: number;
|
||||
}
|
||||
|
||||
export async function getDashboardStats(): Promise<DashboardStats> {
|
||||
return api.get<DashboardStats>("/admin/stats/overview");
|
||||
}
|
||||
53
lib/api/auth.ts
Normal file
53
lib/api/auth.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { api, setTokens, clearTokens } from "@/lib/api";
|
||||
|
||||
export interface UserProfile {
|
||||
id: string;
|
||||
email: string;
|
||||
full_name: string | null;
|
||||
phone: string | null;
|
||||
role: "client" | "admin";
|
||||
}
|
||||
|
||||
interface AuthResponse {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
}
|
||||
|
||||
export async function login(email: string, password: string): Promise<UserProfile> {
|
||||
const tokens = await api.post<AuthResponse>("/auth/login", { email, password });
|
||||
setTokens(tokens.access_token, tokens.refresh_token);
|
||||
return api.get<UserProfile>("/auth/me");
|
||||
}
|
||||
|
||||
export async function register(
|
||||
email: string,
|
||||
password: string,
|
||||
name: string
|
||||
): Promise<UserProfile> {
|
||||
const tokens = await api.post<AuthResponse>("/auth/register", { email, password, name });
|
||||
if ("access_token" in (tokens as object)) {
|
||||
setTokens((tokens as AuthResponse).access_token, (tokens as AuthResponse).refresh_token);
|
||||
}
|
||||
return api.get<UserProfile>("/auth/me");
|
||||
}
|
||||
|
||||
export async function getMe(): Promise<UserProfile> {
|
||||
return api.get<UserProfile>("/auth/me");
|
||||
}
|
||||
|
||||
export async function updateProfile(
|
||||
full_name: string,
|
||||
phone: string | null
|
||||
): Promise<UserProfile> {
|
||||
return api.patch<UserProfile>("/auth/me", { full_name, phone });
|
||||
}
|
||||
|
||||
export async function forgotPassword(email: string): Promise<void> {
|
||||
await api.post("/auth/forgot-password", { email });
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
clearTokens();
|
||||
}
|
||||
143
lib/api/bookings.ts
Normal file
143
lib/api/bookings.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { api, PaginatedResult } from "@/lib/api";
|
||||
|
||||
export interface TimeSlotApi {
|
||||
id: string;
|
||||
date: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
is_blocked: boolean;
|
||||
is_booked: boolean;
|
||||
}
|
||||
|
||||
export interface BookingApi {
|
||||
id: string;
|
||||
user_id: string | null;
|
||||
slot_id: string;
|
||||
slot_date: string;
|
||||
slot_start: string;
|
||||
slot_end: string;
|
||||
service_note: string | null;
|
||||
client_name: string | null;
|
||||
client_email: string | null;
|
||||
client_phone: string | null;
|
||||
status: "pending" | "confirmed" | "cancelled" | "completed" | "no_show";
|
||||
amount_paid: number | null;
|
||||
stripe_payment_intent_id: string | null;
|
||||
admin_notes: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreateBookingPayload {
|
||||
slot_id: string;
|
||||
service_note?: string;
|
||||
guest_name?: string;
|
||||
guest_email?: string;
|
||||
guest_phone?: string;
|
||||
}
|
||||
|
||||
export interface BookingResult {
|
||||
booking_id: string;
|
||||
}
|
||||
|
||||
export async function getAvailableSlots(
|
||||
from_date: string,
|
||||
to_date: string
|
||||
): Promise<TimeSlotApi[]> {
|
||||
return api.get<TimeSlotApi[]>(
|
||||
`/bookings/slots?from_date=${from_date}&to_date=${to_date}`
|
||||
);
|
||||
}
|
||||
|
||||
export async function createBooking(payload: CreateBookingPayload): Promise<BookingResult> {
|
||||
return api.post<BookingResult>("/bookings", payload);
|
||||
}
|
||||
|
||||
export async function listMyBookings(): Promise<PaginatedResult<BookingApi>> {
|
||||
return api.get<PaginatedResult<BookingApi>>("/bookings");
|
||||
}
|
||||
|
||||
export async function cancelBooking(id: string): Promise<void> {
|
||||
await api.del(`/bookings/${id}`);
|
||||
}
|
||||
|
||||
// ── Admin — Schedule ──────────────────────────────────────────────────────────
|
||||
|
||||
export interface WeeklySchedule {
|
||||
id: string;
|
||||
day_of_week: number; // 0=Monday, 6=Sunday
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
slot_duration_minutes: number;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
export interface BlockedDate {
|
||||
id: string;
|
||||
date: string;
|
||||
reason: string | null;
|
||||
}
|
||||
|
||||
export async function adminGetSchedule(): Promise<WeeklySchedule[]> {
|
||||
return api.get<WeeklySchedule[]>("/admin/schedule");
|
||||
}
|
||||
|
||||
export async function adminCreateSchedule(payload: {
|
||||
day_of_week: number;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
slot_duration_minutes: number;
|
||||
}): Promise<WeeklySchedule> {
|
||||
return api.post<WeeklySchedule>("/admin/schedule", payload);
|
||||
}
|
||||
|
||||
export async function adminDeleteSchedule(id: string): Promise<void> {
|
||||
await api.del(`/admin/schedule/${id}`);
|
||||
}
|
||||
|
||||
export async function adminListSlots(from_date: string, to_date: string): Promise<TimeSlotApi[]> {
|
||||
return api.get<TimeSlotApi[]>(`/admin/slots?from_date=${from_date}&to_date=${to_date}`);
|
||||
}
|
||||
|
||||
export async function adminGenerateSlots(from_date: string, to_date: string): Promise<{ created: number }> {
|
||||
return api.post<{ created: number }>("/admin/slots/generate", { from_date, to_date });
|
||||
}
|
||||
|
||||
export async function adminUpdateSlot(id: string, is_blocked: boolean, block_reason?: string): Promise<TimeSlotApi> {
|
||||
return api.patch<TimeSlotApi>(`/admin/slots/${id}`, { is_blocked, block_reason });
|
||||
}
|
||||
|
||||
export async function adminDeleteSlot(id: string): Promise<void> {
|
||||
await api.del(`/admin/slots/${id}`);
|
||||
}
|
||||
|
||||
export async function adminGetBlockedDates(): Promise<BlockedDate[]> {
|
||||
return api.get<BlockedDate[]>("/admin/blocked-dates");
|
||||
}
|
||||
|
||||
export async function adminAddBlockedDate(date: string, reason?: string): Promise<BlockedDate> {
|
||||
return api.post<BlockedDate>("/admin/blocked-dates", { date, reason });
|
||||
}
|
||||
|
||||
export async function adminRemoveBlockedDate(id: string): Promise<void> {
|
||||
await api.del(`/admin/blocked-dates/${id}`);
|
||||
}
|
||||
|
||||
// ── Admin — Bookings ──────────────────────────────────────────────────────────
|
||||
|
||||
export async function adminListBookings(
|
||||
status?: string
|
||||
): Promise<PaginatedResult<BookingApi>> {
|
||||
const qs = status ? `?status=${status}&per_page=100` : "?per_page=100";
|
||||
return api.get<PaginatedResult<BookingApi>>(`/admin/bookings${qs}`);
|
||||
}
|
||||
|
||||
export async function adminUpdateBookingStatus(
|
||||
id: string,
|
||||
status: string
|
||||
): Promise<BookingApi> {
|
||||
return api.patch<BookingApi>(`/admin/bookings/${id}`, { status });
|
||||
}
|
||||
|
||||
export async function adminDeleteBooking(id: string): Promise<void> {
|
||||
await api.del(`/admin/bookings/${id}`);
|
||||
}
|
||||
9
lib/api/contact.ts
Normal file
9
lib/api/contact.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { api } from "@/lib/api";
|
||||
|
||||
export async function submitContact(
|
||||
name: string,
|
||||
email: string,
|
||||
message: string
|
||||
): Promise<void> {
|
||||
await api.post("/contact", { name, email, message });
|
||||
}
|
||||
22
lib/api/customers.ts
Normal file
22
lib/api/customers.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { api, PaginatedResult } from "@/lib/api";
|
||||
|
||||
export interface CustomerApi {
|
||||
id: string;
|
||||
email: string;
|
||||
full_name: string | null;
|
||||
phone: string | null;
|
||||
is_blocked: boolean;
|
||||
created_at: string;
|
||||
orders_count: number;
|
||||
bookings_count: number;
|
||||
total_spent: number;
|
||||
}
|
||||
|
||||
export async function adminListCustomers(search?: string): Promise<PaginatedResult<CustomerApi>> {
|
||||
const qs = search ? `?search=${encodeURIComponent(search)}&per_page=100` : "?per_page=100";
|
||||
return api.get<PaginatedResult<CustomerApi>>(`/admin/customers${qs}`);
|
||||
}
|
||||
|
||||
export async function adminBlockCustomer(id: string, is_blocked: boolean): Promise<CustomerApi> {
|
||||
return api.patch<CustomerApi>(`/admin/customers/${id}`, { is_blocked });
|
||||
}
|
||||
61
lib/api/orders.ts
Normal file
61
lib/api/orders.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { api, PaginatedResult } from "@/lib/api";
|
||||
|
||||
export interface OrderItem {
|
||||
product_id: string;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface CreateOrderPayload {
|
||||
items: OrderItem[];
|
||||
notes?: string;
|
||||
shipping_address?: string;
|
||||
}
|
||||
|
||||
export interface OrderResult {
|
||||
order_id: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface MyOrderApi {
|
||||
id: string;
|
||||
status: string;
|
||||
total_amount: number;
|
||||
shipping_address: string | null;
|
||||
notes: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface AdminOrderApi {
|
||||
id: string;
|
||||
user_id: string | null;
|
||||
status: string;
|
||||
total_amount: number;
|
||||
shipping_address: string | null;
|
||||
notes: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
client_name: string | null;
|
||||
client_email: string | null;
|
||||
client_phone: string | null;
|
||||
}
|
||||
|
||||
export async function createOrder(payload: CreateOrderPayload): Promise<OrderResult> {
|
||||
return api.post<OrderResult>("/orders", payload);
|
||||
}
|
||||
|
||||
export async function listMyOrders(): Promise<PaginatedResult<MyOrderApi>> {
|
||||
return api.get<PaginatedResult<MyOrderApi>>("/orders?per_page=50");
|
||||
}
|
||||
|
||||
// ── Admin ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function adminListOrders(status?: string): Promise<PaginatedResult<AdminOrderApi>> {
|
||||
const qs = status ? `?status=${status}&per_page=100` : "?per_page=100";
|
||||
return api.get<PaginatedResult<AdminOrderApi>>(`/admin/orders${qs}`);
|
||||
}
|
||||
|
||||
export async function adminUpdateOrderStatus(id: string, status: string): Promise<{ id: string; status: string }> {
|
||||
return api.patch<{ id: string; status: string }>(`/admin/orders/${id}/status`, { status });
|
||||
}
|
||||
|
||||
119
lib/api/products.ts
Normal file
119
lib/api/products.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { api, PaginatedResult } from "@/lib/api";
|
||||
import type { Product } from "@/data/products";
|
||||
|
||||
// Backend shape (snake_case)
|
||||
interface ApiProduct {
|
||||
id: string;
|
||||
name: string;
|
||||
category: "clip-in" | "tape-in" | "ponytail" | "keratin";
|
||||
price: number;
|
||||
original_price?: number;
|
||||
image: string;
|
||||
images: string[];
|
||||
colors: string[];
|
||||
lengths: string[];
|
||||
description: string;
|
||||
features: string[];
|
||||
is_new?: boolean;
|
||||
is_bestseller?: boolean;
|
||||
rating: number;
|
||||
review_count: number;
|
||||
stock_quantity: number;
|
||||
is_featured: boolean;
|
||||
is_hidden: boolean;
|
||||
}
|
||||
|
||||
function toProduct(p: ApiProduct): Product {
|
||||
return {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
category: p.category,
|
||||
price: p.price,
|
||||
originalPrice: p.original_price,
|
||||
image: p.image,
|
||||
images: p.images,
|
||||
colors: p.colors,
|
||||
lengths: p.lengths,
|
||||
description: p.description,
|
||||
features: p.features,
|
||||
isNew: p.is_new,
|
||||
isBestseller: p.is_bestseller,
|
||||
rating: p.rating,
|
||||
reviewCount: p.review_count,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ProductFilters {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
search?: string;
|
||||
category?: string;
|
||||
bestseller?: boolean;
|
||||
is_new?: boolean;
|
||||
exclude?: string;
|
||||
}
|
||||
|
||||
export async function listProducts(
|
||||
filters: ProductFilters = {}
|
||||
): Promise<PaginatedResult<Product>> {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.page) params.set("page", String(filters.page));
|
||||
if (filters.per_page) params.set("per_page", String(filters.per_page));
|
||||
if (filters.search) params.set("search", filters.search);
|
||||
if (filters.category) params.set("category", filters.category);
|
||||
if (filters.bestseller) params.set("bestseller", "true");
|
||||
if (filters.is_new) params.set("is_new", "true");
|
||||
if (filters.exclude) params.set("exclude", filters.exclude);
|
||||
|
||||
const qs = params.toString();
|
||||
const res = await api.get<PaginatedResult<ApiProduct>>(`/products${qs ? `?${qs}` : ""}`);
|
||||
return { data: res.data.map(toProduct), meta: res.meta };
|
||||
}
|
||||
|
||||
export async function getProduct(id: string): Promise<Product> {
|
||||
const p = await api.get<ApiProduct>(`/products/${id}`);
|
||||
return toProduct(p);
|
||||
}
|
||||
|
||||
// ── Admin ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ProductPayload {
|
||||
name: string;
|
||||
category: string;
|
||||
price: number;
|
||||
original_price?: number;
|
||||
description?: string;
|
||||
images?: string[];
|
||||
colors?: string[];
|
||||
lengths?: string[];
|
||||
features?: string[];
|
||||
is_new?: boolean;
|
||||
is_bestseller?: boolean;
|
||||
stock_quantity?: number;
|
||||
}
|
||||
|
||||
export async function adminListProducts(): Promise<PaginatedResult<Product>> {
|
||||
const res = await api.get<PaginatedResult<ApiProduct>>("/admin/products?per_page=100&include_hidden=true");
|
||||
return { data: res.data.map(toProduct), meta: res.meta };
|
||||
}
|
||||
|
||||
export async function adminCreateProduct(payload: ProductPayload): Promise<Product> {
|
||||
const p = await api.post<ApiProduct>("/admin/products", payload);
|
||||
return toProduct(p);
|
||||
}
|
||||
|
||||
export async function adminUpdateProduct(id: string, payload: Partial<ProductPayload>): Promise<Product> {
|
||||
const p = await api.put<ApiProduct>(`/admin/products/${id}`, payload);
|
||||
return toProduct(p);
|
||||
}
|
||||
|
||||
export async function adminDeleteProduct(id: string): Promise<void> {
|
||||
await api.del(`/admin/products/${id}`);
|
||||
}
|
||||
|
||||
export async function adminUploadProductImage(productId: string, file: File): Promise<Product> {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
const p = await api.upload<ApiProduct>(`/admin/products/${productId}/images`, form);
|
||||
return toProduct(p);
|
||||
}
|
||||
52
lib/api/services.ts
Normal file
52
lib/api/services.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { api } from "@/lib/api";
|
||||
|
||||
export interface ApiService {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
duration_minutes: number;
|
||||
price: number;
|
||||
}
|
||||
|
||||
export function formatDuration(minutes: number): string {
|
||||
if (minutes < 60) return `${minutes} min`;
|
||||
const h = Math.floor(minutes / 60);
|
||||
const m = minutes % 60;
|
||||
return m > 0 ? `${h}h ${m}min` : `${h}h`;
|
||||
}
|
||||
|
||||
export async function listServices(): Promise<ApiService[]> {
|
||||
return api.get<ApiService[]>("/services");
|
||||
}
|
||||
|
||||
// ── Admin ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AdminServiceApi extends ApiService {
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface ServicePayload {
|
||||
name: string;
|
||||
description?: string;
|
||||
duration_minutes: number;
|
||||
price: number;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
export async function adminListServices(): Promise<AdminServiceApi[]> {
|
||||
return api.get<AdminServiceApi[]>("/admin/services");
|
||||
}
|
||||
|
||||
export async function adminCreateService(payload: ServicePayload): Promise<AdminServiceApi> {
|
||||
return api.post<AdminServiceApi>("/admin/services", payload);
|
||||
}
|
||||
|
||||
export async function adminUpdateService(id: string, payload: Partial<ServicePayload>): Promise<AdminServiceApi> {
|
||||
return api.put<AdminServiceApi>(`/admin/services/${id}`, payload);
|
||||
}
|
||||
|
||||
export async function adminDeleteService(id: string): Promise<void> {
|
||||
await api.del(`/admin/services/${id}`);
|
||||
}
|
||||
15
lib/api/settings.ts
Normal file
15
lib/api/settings.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { api } from "@/lib/api";
|
||||
|
||||
export interface StoreSetting {
|
||||
key: string;
|
||||
value: unknown;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export async function adminGetSettings(): Promise<StoreSetting[]> {
|
||||
return api.get<StoreSetting[]>("/admin/settings");
|
||||
}
|
||||
|
||||
export async function adminUpdateSetting(key: string, value: unknown): Promise<StoreSetting> {
|
||||
return api.put<StoreSetting>(`/admin/settings/${key}`, { value });
|
||||
}
|
||||
Reference in New Issue
Block a user