chore(env): update application version to 2.2.7 in production environment

feat(image-upload): introduce ImageUploadField component for enhanced image uploads

- Added ImageUploadField component to streamline image uploads with improved UI and functionality.
- Integrated file upload handling with a new file service for better management of image uploads.
- Updated SectionStep component to utilize ImageUploadField for icon uploads, enhancing user experience.
- Refactored TenderListFilters to use isDateRangeActive utility for improved date range filtering logic.
This commit is contained in:
AmirReza Jamali
2026-06-06 09:51:32 +03:30
parent a99f86b2f9
commit 5d01d66ea3
11 changed files with 436 additions and 56 deletions
+59
View File
@@ -0,0 +1,59 @@
import api from "../axios";
import { buildFileDownloadUrl } from "../config";
import { API_ENDPOINTS } from "../endpoints";
interface IUploadFileParams {
file: File;
category?: string;
tags?: string[];
description?: string;
onProgress?: (percent: number) => void;
}
export const fileService = {
/**
* Uploads a binary file to the shared filestore endpoint and returns the
* absolute, directly-loadable download URL for the stored file.
*
* Mirrors the two-step flow documented for CMS/marketing media: upload the
* file, then persist the resulting URL on the entity (e.g. `card.icon`).
*/
uploadFile: async ({
file,
category,
tags = [],
description,
onProgress,
}: IUploadFileParams): Promise<string> => {
try {
const formData = new FormData();
formData.append("file", file);
if (category) formData.append("category", category);
tags.forEach((tag) => formData.append("tags", tag));
if (description) formData.append("description", description);
const response = await api.post(API_ENDPOINTS.FILES.UPLOAD, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress: (progressEvent) => {
if (!progressEvent.total) return;
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total,
);
onProgress?.(percent);
},
});
const fileId = response?.data?.file_id as string | undefined;
if (!fileId) {
throw new Error("No file id returned from upload");
}
return buildFileDownloadUrl(fileId);
} catch (error) {
console.error("ERROR caught in File Service Upload File", error);
throw error;
}
},
};
+1
View File
@@ -1,5 +1,6 @@
export * from "./companies-service";
export * from "./dashboard-service";
export * from "./file-service";
export * from "./inquiries-service";
export * from "./profile-service";
export * from "./tenders-service";
+9 -27
View File
@@ -1,6 +1,7 @@
import api from "../axios";
import { buildFileDownloadUrl, extractFileId } from "../config";
import { extractFileId } from "../config";
import { API_ENDPOINTS } from "../endpoints";
import { fileService } from "./file-service";
import {
ApiResponse,
IAdminListResponse,
@@ -128,34 +129,15 @@ export const userService = {
onProgress?: (percent: number) => void;
}) => {
try {
const formData = new FormData();
formData.append("file", file);
formData.append("category", "profile_images");
formData.append("tags", "user");
formData.append("tags", "profile");
formData.append("description", "Admin profile image");
const response = await api.post(API_ENDPOINTS.FILES.UPLOAD, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress: (progressEvent) => {
if (!progressEvent.total) return;
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total,
);
onProgress?.(percent);
},
});
const fileId = response?.data?.file_id as string | undefined;
if (!fileId) {
throw new Error("No file id returned from upload");
}
// Persisted on the backend as the profile image, so it must be an
// absolute, directly-loadable URL — not the frontend `/api/proxy` path.
return buildFileDownloadUrl(fileId);
return fileService.uploadFile({
file,
category: "profile_images",
tags: ["user", "profile"],
description: "Admin profile image",
onProgress,
});
} catch (error) {
console.error("ERROR caught in User Service Upload Profile Image", error);
throw error;