feat(frontend): add async admin form foundations

This commit is contained in:
2026-06-14 00:04:22 +03:30
parent 25bd46ea2a
commit 9080b0caea
9 changed files with 668 additions and 80 deletions

View File

@@ -397,6 +397,10 @@ class ApiClient {
return this.request<Types.UserListSchema[]>(`/api/auth/users${query.toString() ? `?${query.toString()}` : ''}`);
}
async getUserDetail(userId: number) {
return this.request<Types.UserProfileSchema>(`/api/auth/users/${userId}`);
}
async listAuthorizationRoles() {
return this.request<Types.AuthorizationRoleSchema[]>('/api/auth/roles');
}
@@ -856,6 +860,68 @@ class ApiClient {
});
}
async createEvent(data: Types.EventCreateSchema) {
return this.request<Types.EventDetailSchema>('/api/events/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
}
async uploadEventFeaturedImage(eventId: number, file: File) {
const formData = new FormData();
formData.append('file', file);
const token = this.getStorageValue('access_token');
const response = await fetch(`${this.baseUrl}/api/events/${eventId}/featured-image`, {
method: 'POST',
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: formData,
});
if (!response.ok) {
const body = (await response.json().catch(() => ({}))) as ApiErrorBody;
throw new Error(body.error || body.detail || 'Event image upload failed');
}
return response.json() as Promise<Types.EventDetailSchema>;
}
async deleteEventFeaturedImage(eventId: number) {
return this.request<Types.EventDetailSchema>(`/api/events/${eventId}/featured-image`, {
method: 'DELETE',
});
}
async listEventGallery(eventId: number) {
return this.request<Types.EventGalleryItem[]>(`/api/events/${eventId}/gallery`);
}
async uploadEventGalleryImage(eventId: number, file: File, data: { title?: string; alt_text?: string } = {}) {
const formData = new FormData();
formData.append('file', file);
if (data.title) formData.append('title', data.title);
if (data.alt_text) formData.append('alt_text', data.alt_text);
const token = this.getStorageValue('access_token');
const response = await fetch(`${this.baseUrl}/api/events/${eventId}/gallery`, {
method: 'POST',
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: formData,
});
if (!response.ok) {
const body = (await response.json().catch(() => ({}))) as ApiErrorBody;
throw new Error(body.error || body.detail || 'Event gallery upload failed');
}
return response.json() as Promise<Types.EventGalleryItem>;
}
async deleteEventGalleryImage(eventId: number, imageId: number) {
return this.request<Types.MessageSchema>(`/api/events/${eventId}/gallery/${imageId}`, {
method: 'DELETE',
});
}
async deleteEvent(eventId: number) {
return this.request<Types.MessageSchema>(`/api/events/${eventId}`, {
method: 'DELETE',
@@ -971,6 +1037,44 @@ class ApiClient {
);
}
async listDiscountCodes(params?: {
search?: string;
is_active?: boolean;
type?: 'percent' | 'fixed';
limit?: number;
offset?: number;
}) {
const query = new URLSearchParams();
if (params?.search) query.set('search', params.search);
if (params?.is_active != null) query.set('is_active', String(params.is_active));
if (params?.type) query.set('type', params.type);
if (params?.limit != null) query.set('limit', String(params.limit));
if (params?.offset != null) query.set('offset', String(params.offset));
return this.request<Types.PagedDiscountCodeSchema>(
`/api/payments/admin/discount-codes${query.toString() ? `?${query.toString()}` : ''}`,
);
}
async createDiscountCode(data: Types.DiscountCodeWriteSchema) {
return this.request<Types.DiscountCodeSchema>('/api/payments/admin/discount-codes', {
method: 'POST',
body: JSON.stringify(data),
});
}
async updateDiscountCode(codeId: number, data: Types.DiscountCodeWriteSchema) {
return this.request<Types.DiscountCodeSchema>(`/api/payments/admin/discount-codes/${codeId}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
async deleteDiscountCode(codeId: number) {
return this.request<Types.MessageSchema>(`/api/payments/admin/discount-codes/${codeId}`, {
method: 'DELETE',
});
}
// ============= Gallery Endpoints =============
async getGalleryImages(params?: {
@@ -1019,12 +1123,86 @@ class ApiClient {
});
}
async getMajors(): Promise<Types.MajorOption[]> {
return this.request('/api/meta/majors', { method: 'GET' });
async getMajors(params?: { search?: string; limit?: number; offset?: number }): Promise<Types.MajorOption[]> {
const data = await this.getMajorsPaged({ limit: 100, offset: 0, ...params });
return data.results.map((item) => ({ code: item.code, label: item.label, id: item.id, name: item.name, user_count: item.user_count }));
}
async getUniversities(): Promise<Types.MajorOption[]> {
return this.request('/api/meta/universities', { method: 'GET' });
async getUniversities(params?: { search?: string; limit?: number; offset?: number }): Promise<Types.MajorOption[]> {
const data = await this.getUniversitiesPaged({ limit: 100, offset: 0, ...params });
return data.results.map((item) => ({ code: item.code, label: item.label, id: item.id, name: item.name, user_count: item.user_count }));
}
async getMajorsPaged(params?: { search?: string; limit?: number; offset?: number }) {
const query = new URLSearchParams();
if (params?.search) query.set('search', params.search);
if (params?.limit != null) query.set('limit', String(params.limit));
if (params?.offset != null) query.set('offset', String(params.offset));
return this.request<Types.PagedMetaOptionSchema>(`/api/meta/majors${query.toString() ? `?${query.toString()}` : ''}`);
}
async getUniversitiesPaged(params?: { search?: string; limit?: number; offset?: number }) {
const query = new URLSearchParams();
if (params?.search) query.set('search', params.search);
if (params?.limit != null) query.set('limit', String(params.limit));
if (params?.offset != null) query.set('offset', String(params.offset));
return this.request<Types.PagedMetaOptionSchema>(`/api/meta/universities${query.toString() ? `?${query.toString()}` : ''}`);
}
async listAdminMajors(params?: { search?: string; limit?: number; offset?: number }) {
const query = new URLSearchParams();
if (params?.search) query.set('search', params.search);
if (params?.limit != null) query.set('limit', String(params.limit));
if (params?.offset != null) query.set('offset', String(params.offset));
return this.request<Types.PagedMetaOptionSchema>(`/api/meta/admin/majors${query.toString() ? `?${query.toString()}` : ''}`);
}
async createMajor(data: Types.MetaOptionWriteSchema) {
return this.request<Types.MetaOptionSchema>('/api/meta/admin/majors', {
method: 'POST',
body: JSON.stringify(data),
});
}
async updateMajor(id: number, data: Types.MetaOptionWriteSchema) {
return this.request<Types.MetaOptionSchema>(`/api/meta/admin/majors/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
async deleteMajor(id: number) {
return this.request<Types.MessageSchema>(`/api/meta/admin/majors/${id}`, {
method: 'DELETE',
});
}
async listAdminUniversities(params?: { search?: string; limit?: number; offset?: number }) {
const query = new URLSearchParams();
if (params?.search) query.set('search', params.search);
if (params?.limit != null) query.set('limit', String(params.limit));
if (params?.offset != null) query.set('offset', String(params.offset));
return this.request<Types.PagedMetaOptionSchema>(`/api/meta/admin/universities${query.toString() ? `?${query.toString()}` : ''}`);
}
async createUniversity(data: Types.MetaOptionWriteSchema) {
return this.request<Types.MetaOptionSchema>('/api/meta/admin/universities', {
method: 'POST',
body: JSON.stringify(data),
});
}
async updateUniversity(id: number, data: Types.MetaOptionWriteSchema) {
return this.request<Types.MetaOptionSchema>(`/api/meta/admin/universities/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
async deleteUniversity(id: number) {
return this.request<Types.MessageSchema>(`/api/meta/admin/universities/${id}`, {
method: 'DELETE',
});
}
async subscribeNewsletter(email: string) {