Update May 12 by Elvis

This commit is contained in:
belviskhoremk
2026-05-12 00:28:37 +00:00
parent b32a70cd0e
commit c4450c993b
37 changed files with 3749 additions and 600 deletions

18
lib/api/admin.ts Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 });
}