Complete super admin panel with CRUD, bookings, profile, and dev API proxy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-21 14:20:52 +03:30
co-authored by Cursor
parent 03f3994121
commit 4bc6c003f3
31 changed files with 1508 additions and 43 deletions
+5 -9
View File
@@ -1,12 +1,8 @@
import { normalizeIranMobile } from '@/utils/mobile';
import { api } from './client';
import type { User } from './types';
export type User = {
id: string;
mobile: string;
fullName: string | null;
role: string;
isActive: boolean;
};
export type { User };
export type RequestOtpResponse = {
expiresInSeconds: number;
@@ -21,14 +17,14 @@ export type VerifyOtpResponse = {
export function requestOtp(mobile: string) {
return api<RequestOtpResponse>('/auth/request-otp', {
method: 'POST',
body: JSON.stringify({ mobile }),
body: JSON.stringify({ mobile: normalizeIranMobile(mobile) }),
});
}
export function verifyOtp(mobile: string, code: string) {
return api<VerifyOtpResponse>('/auth/verify-otp', {
method: 'POST',
body: JSON.stringify({ mobile, code }),
body: JSON.stringify({ mobile: normalizeIranMobile(mobile), code }),
});
}
+17
View File
@@ -0,0 +1,17 @@
import { api } from './client';
import type { Booking, BookingStatus } from './types';
export function fetchBookings(params?: {
businessId?: string;
status?: BookingStatus;
}) {
const search = new URLSearchParams();
if (params?.businessId) search.set('businessId', params.businessId);
if (params?.status) search.set('status', params.status);
const query = search.toString();
return api<Booking[]>(`/bookings${query ? `?${query}` : ''}`);
}
export function deleteBooking(id: string) {
return api<{ success: true }>(`/bookings/${id}`, { method: 'DELETE' });
}
+34
View File
@@ -0,0 +1,34 @@
import { api } from './client';
import type {
BusinessCategory,
CreateCategoryPayload,
UpdateCategoryPayload,
} from './types';
export function fetchCategories() {
return api<BusinessCategory[]>('/business-categories');
}
export function fetchCategory(id: string) {
return api<BusinessCategory>(`/business-categories/${id}`);
}
export function createCategory(payload: CreateCategoryPayload) {
return api<BusinessCategory>('/business-categories', {
method: 'POST',
body: JSON.stringify(payload),
});
}
export function updateCategory(id: string, payload: UpdateCategoryPayload) {
return api<BusinessCategory>(`/business-categories/${id}`, {
method: 'PATCH',
body: JSON.stringify(payload),
});
}
export function deleteCategory(id: string) {
return api<{ success: true }>(`/business-categories/${id}`, {
method: 'DELETE',
});
}
+47
View File
@@ -0,0 +1,47 @@
import { api } from './client';
import type {
AssignOwnerPayload,
Business,
CreateBusinessPayload,
UpdateBusinessPayload,
} from './types';
export function fetchBusinesses(params?: {
categoryId?: string;
isActive?: boolean;
}) {
const search = new URLSearchParams();
if (params?.categoryId) search.set('categoryId', params.categoryId);
if (params?.isActive !== undefined) search.set('isActive', String(params.isActive));
const query = search.toString();
return api<Business[]>(`/businesses${query ? `?${query}` : ''}`);
}
export function fetchBusiness(id: string) {
return api<Business>(`/businesses/${id}`);
}
export function createBusiness(payload: CreateBusinessPayload) {
return api<Business>('/businesses', {
method: 'POST',
body: JSON.stringify(payload),
});
}
export function updateBusiness(id: string, payload: UpdateBusinessPayload) {
return api<Business>(`/businesses/${id}`, {
method: 'PATCH',
body: JSON.stringify(payload),
});
}
export function deleteBusiness(id: string) {
return api<{ success: true }>(`/businesses/${id}`, { method: 'DELETE' });
}
export function assignBusinessOwner(id: string, payload: AssignOwnerPayload) {
return api<Business>(`/businesses/${id}/assign-owner`, {
method: 'POST',
body: JSON.stringify(payload),
});
}
+99
View File
@@ -0,0 +1,99 @@
export type UserRole = 'customer' | 'business_owner' | 'admin';
export type User = {
id: string;
mobile: string;
fullName: string | null;
role: UserRole;
isActive: boolean;
createdAt?: string;
updatedAt?: string;
};
export type BusinessCategory = {
id: string;
slug: string;
title: string;
isActive: boolean;
createdAt?: string;
updatedAt?: string;
};
export type Business = {
id: string;
title: string;
slug: string;
description: string | null;
contactPhone: string | null;
address: string | null;
latitude: number | null;
longitude: number | null;
isActive: boolean;
categoryId: string;
ownerId: string | null;
category?: BusinessCategory;
owner?: User | null;
createdAt?: string;
updatedAt?: string;
};
export type CreateBusinessPayload = {
title: string;
slug: string;
categoryId: string;
description?: string;
contactPhone?: string;
address?: string;
latitude?: number;
longitude?: number;
isActive?: boolean;
};
export type UpdateBusinessPayload = Partial<CreateBusinessPayload> & {
description?: string | null;
contactPhone?: string | null;
address?: string | null;
latitude?: number | null;
longitude?: number | null;
ownerId?: string | null;
};
export type CreateCategoryPayload = {
title: string;
slug: string;
isActive?: boolean;
};
export type UpdateCategoryPayload = Partial<CreateCategoryPayload>;
export type UpdateUserPayload = {
fullName?: string | null;
role?: UserRole;
isActive?: boolean;
};
export type AssignOwnerPayload = {
mobile: string;
fullName?: string | null;
};
export type BookingStatus =
| 'pending'
| 'confirmed'
| 'cancelled'
| 'completed';
export type Booking = {
id: string;
customerId: string;
businessId: string;
businessServiceId: string;
scheduledAt: string;
status: BookingStatus;
note: string | null;
customer?: User;
business?: Business;
businessService?: {
serviceTemplate?: { title: string };
};
};
+17
View File
@@ -0,0 +1,17 @@
import { api } from './client';
import type { UpdateUserPayload, User } from './types';
export function fetchUsers() {
return api<User[]>('/users');
}
export function fetchUser(id: string) {
return api<User>(`/users/${id}`);
}
export function updateUser(id: string, payload: UpdateUserPayload) {
return api<User>(`/users/${id}`, {
method: 'PATCH',
body: JSON.stringify(payload),
});
}