feat(docs): migrate and reorganize frontend guides for BP panel and file uploads

- Deleted outdated documentation files for BP panel list sorting and Filestore frontend guide.
- Created new documentation files for BP panel list sorting and Filestore frontend guide in the client directory.
- Updated the structure and content to enhance clarity and accessibility for frontend integration with file upload and profile image APIs.
- Ensured consistency in documentation style and improved examples for better developer experience.
This commit is contained in:
AmirReza Jamali
2026-05-31 15:18:37 +03:30
parent 46ddc018a6
commit eb41f9b7b2
10 changed files with 172 additions and 31 deletions
+22
View File
@@ -0,0 +1,22 @@
import { API_ENDPOINTS } from "./endpoints";
/**
* Absolute base URL of the backend admin API.
*
* Mirrors the rewrite target in `next.config.mjs` (`/api/proxy/* ->
* https://admin.opplenz.com/admin/v1/*`). Axios calls use the relative
* `/api/proxy/` base so the browser attaches auth headers same-origin, but
* values that are persisted on the backend (e.g. a `profile_image` download
* URL) must be absolute so they resolve from anywhere — not the proxy path.
*/
export const BACKEND_API_BASE_URL =
process.env.NEXT_PUBLIC_BACKEND_API_BASE_URL ??
"https://admin.opplenz.com/admin/v1";
/** Absolute, directly-loadable URL for a stored GridFS file. */
export const buildFileDownloadUrl = (fileId: string) =>
`${BACKEND_API_BASE_URL}/${API_ENDPOINTS.FILES.DOWNLOAD(fileId)}`;
/** Pull the GridFS file id out of a stored download URL, if it is one. */
export const extractFileId = (url: string): string | undefined =>
url.match(/\/files\/([^/]+)\/download/)?.[1];
+22 -2
View File
@@ -1,4 +1,5 @@
import api from "../axios";
import { buildFileDownloadUrl, extractFileId } from "../config";
import { API_ENDPOINTS } from "../endpoints";
import {
ApiResponse,
@@ -152,11 +153,30 @@ export const userService = {
throw new Error("No file id returned from upload");
}
// /api/proxy/* is rewritten to /admin/v1/* in next.config.
return `/api/proxy/${API_ENDPOINTS.FILES.DOWNLOAD(fileId)}`;
// 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);
} catch (error) {
console.error("ERROR caught in User Service Upload Profile Image", error);
throw error;
}
},
/**
* Fetch an auth-protected file download through the proxy (so the axios
* interceptor attaches the bearer token) and return a local object URL that
* can be used directly as an `<img>` source.
*/
fetchFileObjectUrl: async (url: string): Promise<string> => {
try {
const fileId = extractFileId(url);
// Route via the relative proxy endpoint when this is a backend file URL;
// otherwise fetch the URL as-is.
const path = fileId ? API_ENDPOINTS.FILES.DOWNLOAD(fileId) : url;
const response = await api.get(path, { responseType: "blob" });
return URL.createObjectURL(response.data as Blob);
} catch (error) {
console.error("ERROR caught in User Service Fetch File Object Url", error);
throw error;
}
},
};
+26
View File
@@ -1,6 +1,7 @@
import {
parsePhoneNumberFromString,
isValidPhoneNumber,
getCountryCallingCode,
type CountryCode,
type PhoneNumber,
} from "libphonenumber-js";
@@ -50,6 +51,31 @@ export function normalizeToE164(
return parsed.format("E.164");
}
/**
* Best-effort E.164 for the value the user is typing.
*
* Returns the strictly-valid E.164 when the number is complete and valid;
* otherwise returns `+<callingCode><digits>` so partial/invalid input is
* preserved in form state (and surfaced by validation) instead of being
* silently reset to an empty string. Returns `""` only when no digits exist.
*/
export function toBestEffortE164(
value: string,
defaultCountry: CountryCode,
): string {
const strict = normalizeToE164(value, defaultCountry);
if (strict) return strict;
const digits = value.replace(/\D/g, "");
if (!digits) return "";
try {
return `+${getCountryCallingCode(defaultCountry)}${digits}`;
} catch {
return `+${digits}`;
}
}
export function isValidE164Phone(value: string): boolean {
const sanitized = sanitizePhoneInput(value);
if (!sanitized) return false;