/** * Thin fetch wrapper for the Zino backend. * Base URL comes from VITE_API_BASE_URL (see .env.example); falls back to dev. */ /** Auth host (login). */ export const API_BASE_URL = ( import.meta.env.VITE_API_BASE_URL || 'https://dev-apps.getzino.in' ).replace(/\/+$/, '') /** App/workflow host (activities such as "start"). */ export const APP_API_BASE_URL = ( import.meta.env.VITE_APP_API_BASE_URL || 'https://dev-apps.getzino.in' ).replace(/\/+$/, '') export const TOKEN_KEY = 'oam.auth.token' export const USER_KEY = 'oam.auth.user' export class ApiError extends Error { constructor(message, { status, data } = {}) { super(message) this.name = 'ApiError' this.status = status this.data = data } } export function getStoredToken() { try { return localStorage.getItem(TOKEN_KEY) } catch { return null } } /** * JSON request helper. Attaches the bearer token when one is stored, * parses JSON responses and throws ApiError on non-2xx. * A FormData body is sent as multipart; the browser sets its boundary. */ export async function apiFetch(path, { method = 'GET', body, headers, auth = true, ...rest } = {}) { const url = path.startsWith('http') ? path : `${API_BASE_URL}${path}` const token = auth ? getStoredToken() : null const isForm = body instanceof FormData let response try { response = await fetch(url, { method, headers: { accept: 'application/json, text/plain, */*', ...(body !== undefined && !isForm && { 'content-type': 'application/json' }), ...(token && { authorization: `Bearer ${token}` }), ...headers, }, body: isForm ? body : body !== undefined ? JSON.stringify(body) : undefined, ...rest, }) } catch (err) { throw new ApiError('Unable to reach the server. Check your connection and try again.', { status: 0, data: err, }) } const text = await response.text() let data = null if (text) { try { data = JSON.parse(text) } catch { data = text } } if (!response.ok) { const message = (data && typeof data === 'object' && (data.error || data.message)) || (typeof data === 'string' && data) || `Request failed (${response.status})` throw new ApiError(message, { status: response.status, data }) } return data }