From cf6b5da4fc000d657c9621f18a7cb306d6283022 Mon Sep 17 00:00:00 2001 From: Kirill Date: Sun, 17 May 2026 18:03:32 +0500 Subject: [PATCH] feat(client): add isResized type, uploadGalleryImages, resizeGalleryImage API --- .../src/entities/gallery/api/gallery-api.ts | 41 +++++++++++++++++++ client/src/entities/gallery/index.ts | 2 +- client/src/entities/gallery/model/types.ts | 1 + shared/constants/upload-limits.d.ts | 4 ++ shared/constants/upload-limits.js | 6 +++ 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/client/src/entities/gallery/api/gallery-api.ts b/client/src/entities/gallery/api/gallery-api.ts index 8838fe8..4d4b15d 100644 --- a/client/src/entities/gallery/api/gallery-api.ts +++ b/client/src/entities/gallery/api/gallery-api.ts @@ -1,5 +1,7 @@ import type { GalleryImageItem } from '@/entities/gallery/model/types' import { apiClient } from '@/shared/api/client' +import { apiBaseURL } from '@/shared/config' +import { ADMIN_UPLOAD_IMAGE_MAX_BYTES, formatAdminImageMaxSizeHint } from '@/shared/constants/upload-limits' export async function fetchAdminGallery(): Promise<{ items: GalleryImageItem[] }> { const { data } = await apiClient.get<{ items: GalleryImageItem[] }>('admin/gallery') @@ -9,3 +11,42 @@ export async function fetchAdminGallery(): Promise<{ items: GalleryImageItem[] } export async function deleteGalleryImage(id: string): Promise { await apiClient.delete(`admin/gallery/${id}`) } + +export async function uploadGalleryImages(files: File[]): Promise { + for (const f of files) { + if (f.size > ADMIN_UPLOAD_IMAGE_MAX_BYTES) { + throw new Error( + `Файл «${f.name}» слишком большой (максимум ${formatAdminImageMaxSizeHint()} на одно изображение).`, + ) + } + } + const fd = new FormData() + for (const f of files) { + fd.append('files', f, f.name) + } + const token = localStorage.getItem('craftshop_auth_token') + const base = apiBaseURL.replace(/\/$/, '') + const res = await fetch(`${base}/admin/gallery/upload`, { + method: 'POST', + headers: token ? { Authorization: `Bearer ${token}` } : {}, + body: fd, + }) + const payload = (await res.json().catch(() => ({}))) as { urls?: string[]; error?: string } + if (!res.ok) { + if (res.status === 413) { + throw new Error( + 'Сервер отклонил файл как слишком большой (413). На проде часто лимит nginx: добавьте client_max_body_size для /api/ (см. docs/nginx-upload-limit.md). Проверьте также MAX_UPLOAD_BODY_BYTES в .env на сервере.', + ) + } + throw new Error(typeof payload.error === 'string' ? payload.error : `Ошибка загрузки (${res.status})`) + } + if (!Array.isArray(payload.urls)) { + throw new Error('Некорректный ответ сервера') + } + return payload.urls +} + +export async function resizeGalleryImage(id: string): Promise<{ url: string }> { + const { data } = await apiClient.post<{ url: string }>(`admin/gallery/${id}/resize`) + return data +} diff --git a/client/src/entities/gallery/index.ts b/client/src/entities/gallery/index.ts index 7fa845f..9f9114c 100644 --- a/client/src/entities/gallery/index.ts +++ b/client/src/entities/gallery/index.ts @@ -1,3 +1,3 @@ -export { fetchAdminGallery, deleteGalleryImage } from './api/gallery-api' +export { fetchAdminGallery, deleteGalleryImage, uploadGalleryImages, resizeGalleryImage } from './api/gallery-api' export type { GalleryImageItem } from './model/types' export { GalleryGrid } from './ui/GalleryGrid' diff --git a/client/src/entities/gallery/model/types.ts b/client/src/entities/gallery/model/types.ts index 7fe0189..922ce43 100644 --- a/client/src/entities/gallery/model/types.ts +++ b/client/src/entities/gallery/model/types.ts @@ -1,5 +1,6 @@ export type GalleryImageItem = { id: string url: string + isResized: boolean createdAt: string } diff --git a/shared/constants/upload-limits.d.ts b/shared/constants/upload-limits.d.ts index 8f9caf1..88f7e54 100644 --- a/shared/constants/upload-limits.d.ts +++ b/shared/constants/upload-limits.d.ts @@ -1 +1,5 @@ export declare const ADMIN_UPLOAD_IMAGE_MAX_FILE_BYTES_DEFAULT: 20971520 + +export declare const ADMIN_UPLOAD_IMAGE_MAX_BYTES: 20971520 + +export declare function formatAdminImageMaxSizeHint(): string diff --git a/shared/constants/upload-limits.js b/shared/constants/upload-limits.js index 5c020a3..ffea1d9 100644 --- a/shared/constants/upload-limits.js +++ b/shared/constants/upload-limits.js @@ -1,3 +1,9 @@ const MB = 1024 * 1024 export const ADMIN_UPLOAD_IMAGE_MAX_FILE_BYTES_DEFAULT = 20 * MB + +export const ADMIN_UPLOAD_IMAGE_MAX_BYTES = 20 * MB + +export function formatAdminImageMaxSizeHint() { + return '20 МБ' +}