const API_URL = 'http://localhost:8080/api'; class ApiError extends Error { constructor(message, status, data) { super(message); this.name = 'ApiError'; this.status = status; this.data = data; } } async function fetchAPI(endpoint, options = {}) { const url = `${API_URL}${endpoint}`; const config = { ...options, headers: { 'Content-Type': 'application/json', ...options.headers, }, }; // Add auth token if available const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = `Bearer ${token}`; } try { const response = await fetch(url, config); // Check if response is JSON before parsing const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { throw new ApiError( 'Server returned non-JSON response', response.status, { message: 'The server is not responding correctly. Please try again later.' } ); } const data = await response.json(); if (!response.ok) { throw new ApiError( data.error || 'API request failed', response.status, data ); } return data; } catch (error) { if (error instanceof ApiError) { throw error; } // Handle JSON parsing errors if (error instanceof SyntaxError) { throw new ApiError( 'Invalid server response', 0, { message: 'The server returned an invalid response. Please check if the server is running.' } ); } throw new ApiError('Network error', 0, { message: error.message }); } } // Auth API export const authAPI = { async register(username, email, password, firstName = null, lastName = null, wsdcId = null) { const data = await fetchAPI('/auth/register', { method: 'POST', body: JSON.stringify({ username, email, password, firstName, lastName, wsdcId }), }); // Save token if (data.data.token) { localStorage.setItem('token', data.data.token); } return data.data; }, async login(email, password) { const data = await fetchAPI('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }), }); // Save token if (data.data.token) { localStorage.setItem('token', data.data.token); } return data.data; }, async getCurrentUser() { const data = await fetchAPI('/users/me'); return data.data; }, async verifyEmailByToken(token) { const data = await fetchAPI(`/auth/verify-email?token=${token}`); return data; }, async verifyEmailByCode(email, code) { const data = await fetchAPI('/auth/verify-code', { method: 'POST', body: JSON.stringify({ email, code }), }); return data; }, async resendVerification(email) { const data = await fetchAPI('/auth/resend-verification', { method: 'POST', body: JSON.stringify({ email }), }); return data; }, async requestPasswordReset(email) { const data = await fetchAPI('/auth/request-password-reset', { method: 'POST', body: JSON.stringify({ email }), }); return data; }, async resetPassword(token, newPassword) { const data = await fetchAPI('/auth/reset-password', { method: 'POST', body: JSON.stringify({ token, newPassword }), }); return data; }, async updateProfile(profileData) { const data = await fetchAPI('/users/me', { method: 'PATCH', body: JSON.stringify(profileData), }); // Update token if it was returned (email changed) if (data.data?.token) { localStorage.setItem('token', data.data.token); } return data; }, async changePassword(currentPassword, newPassword) { const data = await fetchAPI('/users/me/password', { method: 'PATCH', body: JSON.stringify({ currentPassword, newPassword }), }); return data; }, async getUserByUsername(username) { const data = await fetchAPI(`/users/${username}`); return data.data; }, logout() { localStorage.removeItem('token'); localStorage.removeItem('user'); }, }; // WSDC API (Phase 1.5) export const wsdcAPI = { async lookupDancer(wsdcId) { const data = await fetchAPI(`/wsdc/lookup?id=${wsdcId}`); return data; }, }; // Events API export const eventsAPI = { async getAll() { const data = await fetchAPI('/events'); return data.data; }, async getBySlug(slug) { const data = await fetchAPI(`/events/${slug}`); return data.data; }, async getMessages(slug, before = null, limit = 20) { const params = new URLSearchParams({ limit: limit.toString() }); if (before) { params.append('before', before.toString()); } const data = await fetchAPI(`/events/${slug}/messages?${params}`); return data; }, async getDetails(slug) { const data = await fetchAPI(`/events/${slug}/details`); return data; }, async checkin(token) { const data = await fetchAPI(`/events/checkin/${token}`, { method: 'POST', }); return data; }, async leave(slug) { const data = await fetchAPI(`/events/${slug}/leave`, { method: 'DELETE', }); return data; }, }; // Divisions API (Phase 1.6) export const divisionsAPI = { async getAll() { const data = await fetchAPI('/divisions'); return data.data; }, }; // Competition Types API (Phase 1.6) export const competitionTypesAPI = { async getAll() { const data = await fetchAPI('/competition-types'); return data.data; }, }; // Heats API (Phase 1.6) export const heatsAPI = { async saveHeats(slug, heats) { const data = await fetchAPI(`/events/${slug}/heats`, { method: 'POST', body: JSON.stringify({ heats }), }); return data; }, async getMyHeats(slug) { const data = await fetchAPI(`/events/${slug}/heats/me`); return data.data; }, async getAllHeats(slug) { const data = await fetchAPI(`/events/${slug}/heats/all`); return data.data; }, async deleteHeat(slug, heatId) { const data = await fetchAPI(`/events/${slug}/heats/${heatId}`, { method: 'DELETE', }); return data; }, }; export { ApiError };