Files
spotlightcam/frontend/src/services/api.js
Radosław Gierwiało 897d6e61b3 feat: add public user profiles
- Add GET /api/users/:username endpoint for public profiles
- Create PublicProfilePage component with user stats and info
- Add getUserByUsername function to API service
- Add /:username route to App.jsx
- Display user info: name, location, stats, WSDC ID, social links
- Only show public data (no email or sensitive information)
- Accessible only to authenticated users

Users can now view public profiles of other users by visiting
/<username>. The profile displays stats, location, WSDC ID, and
social media links.
2025-11-13 21:03:37 +01:00

205 lines
4.8 KiB
JavaScript

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 getById(id) {
const data = await fetchAPI(`/events/${id}`);
return data.data;
},
async getMessages(eventId, before = null, limit = 20) {
const params = new URLSearchParams({ limit: limit.toString() });
if (before) {
params.append('before', before.toString());
}
const data = await fetchAPI(`/events/${eventId}/messages?${params}`);
return data;
},
};
export { ApiError };