readysite / website / frontend / hooks / useAPI.js
1.5 KB
useAPI.js
// Generic fetch wrapper with error handling
async function fetchJSON(url, options) {
  const res = await fetch(url, {
    ...options,
    credentials: 'same-origin', // Ensure cookies are sent
    headers: {
      'Content-Type': 'application/json',
      ...options?.headers,
    },
  });

  if (!res.ok) {
    const text = await res.text();
    throw new Error(text || `HTTP ${res.status}`);
  }

  if (res.status === 204) {
    return undefined;
  }

  // Check content-type to avoid parsing HTML as JSON
  const contentType = res.headers.get('content-type');
  if (!contentType || !contentType.includes('application/json')) {
    throw new Error(`Expected JSON but got ${contentType || 'unknown'}`);
  }

  return res.json();
}

// API mutation functions
export async function api(method, url, body) {
  return fetchJSON(url, {
    method,
    body: body ? JSON.stringify(body) : undefined,
  });
}

export const apiGet = (url) => api('GET', url);
export const apiPost = (url, body) => api('POST', url, body);
export const apiPut = (url, body) => api('PUT', url, body);
export const apiPatch = (url, body) => api('PATCH', url, body);
export const apiDelete = (url) => api('DELETE', url);

// File upload
export async function uploadFile(file) {
  const formData = new FormData();
  formData.append('file', file);

  const res = await fetch('/api/files', {
    method: 'POST',
    body: formData,
  });

  if (!res.ok) {
    const text = await res.text();
    throw new Error(text || `HTTP ${res.status}`);
  }

  return res.json();
}
← Back