/** * Normalize asset URL for display. * This is the SINGLE source of truth for building static asset URLs. * * Handles various URL formats from the backend: * - /api/static/uploads/... - already correct, return as-is * - /assets/... - static assets in public folder, return as-is * - /static/uploads/... - needs /api prefix * - https://... or http://... - external URL, return as-is * - uploads/... or static/uploads/... - relative path, needs /api/static prefix * * IMPORTANT: Do NOT manually concatenate /api/static anywhere in the codebase. * Always use this function instead. */ export function normalizeAssetUrl(url: string | null | undefined): string | null { if (!url) return null; // External URLs - return as-is if (url.startsWith('http://') || url.startsWith('https://')) { return url; } // Already correct format with /api/static if (url.startsWith('/api/static')) { return url; } // Static assets in public folder (/assets/...) if (url.startsWith('/assets/')) { return url; } // Old format with /static/ prefix - add /api if (url.startsWith('/static/')) { return `/api${url}`; } // Relative path without leading slash (uploads/..., static/...) // Remove any duplicate prefixes and normalize let cleaned = url; // Remove leading /api/static if somehow duplicated cleaned = cleaned.replace(/^\/api\/static\//, ''); // Remove leading /static/ cleaned = cleaned.replace(/^\/static\//, ''); // Remove leading static/ (no slash) cleaned = cleaned.replace(/^static\//, ''); // Remove leading slash cleaned = cleaned.replace(/^\/+/, ''); // If it looks like a relative upload path, prefix with /api/static/ if (cleaned.startsWith('uploads/') || cleaned.includes('/')) { return `/api/static/${cleaned}`; } // Unknown format - return as-is (might be a simple filename) return url; }