export type TCreateResourcePayload = Record; /** * Transforms create payload to convert string numbers to actual numbers * for fields that should be numeric (e.g., rate, limit, etc.) */ function transformCreatePayload( payload: TCreateResourcePayload ): TCreateResourcePayload { const transformed: TCreateResourcePayload = { ...payload }; for (const [key, value] of Object.entries(transformed)) { // Convert string numbers to actual numbers for common numeric fields if (typeof value === "string" && value.trim() !== "") { const numericValue = Number(value); // Only convert if it's a valid number and the key suggests it should be numeric if ( !Number.isNaN(numericValue) && (key.toLowerCase().includes("rate") || key.toLowerCase().includes("price") || key.toLowerCase().includes("amount") || key.toLowerCase().includes("limit") || key.toLowerCase().includes("count")) ) { transformed[key] = numericValue; } } } return transformed; } export async function createResourceApi( endpointBase: string, payload: TCreateResourcePayload, resourceName: string ) { // Transform the payload to convert string numbers to actual numbers const transformedPayload = transformCreatePayload(payload); const response = await fetch(`${endpointBase}/create`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(transformedPayload), }); if (!response.ok) { const errorData = await response .json() .catch(() => ({ message: `Failed to create ${resourceName}` })); throw new Error(errorData?.message || `Failed to create ${resourceName}`); } return response.json(); } export async function deleteResourceApi( endpointBase: string, id: number | string, resourceName: string ) { const response = await fetch(`${endpointBase}/${id}`, { method: "DELETE", }); if (!response.ok) { const errorData = await response .json() .catch(() => ({ message: `Failed to delete ${resourceName}` })); throw new Error(errorData?.message || `Failed to delete ${resourceName}`); } try { return await response.json(); } catch { return { success: true }; } } export type TUpdateResourcePayload = Record; /** * Converts a key to PascalCase (e.g., "enabled" -> "Enabled", "first_name" -> "FirstName") */ function toPascalCase(key: string): string { return key .split("_") .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(""); } /** * Transforms frontend data to backend format * - data object uses lowercase keys (matching API response) * - fields array uses PascalCase (required by backend) */ function transformResourceUpdateData(updates: Record): { data: Record; fields: string[]; } { const data: Record = {}; const fields: string[] = []; for (const [key, value] of Object.entries(updates)) { // Skip undefined/null values if (value === undefined || value === null) { continue; } // Use the key as-is for data (matching API response casing) data[key] = value; // Convert to PascalCase for fields array (required by backend) fields.push(toPascalCase(key)); } return { data, fields }; } export async function updateResourceApi( endpointBase: string, id: number | string, payload: TUpdateResourcePayload, resourceName: string ) { // Transform the payload to match backend format const transformedPayload = transformResourceUpdateData(payload); const response = await fetch(`${endpointBase}/${id}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(transformedPayload), }); if (!response.ok) { const errorData = await response .json() .catch(() => ({ message: `Failed to update ${resourceName}` })); throw new Error(errorData?.message || `Failed to update ${resourceName}`); } return response.json(); }