Files
radarsargasses/frontend/src/api/client.js
Gwadaking fd476f50c2 feat: système de signalement bug/idée + modal changelog avec limitations
- AppReport entity + migration (type, message, fingerprint SHA-256, created_at)
- POST /api/report avec rate-limit 5/heure par fingerprint IP
- ReportModal : formulaire type radio (bug/idée) + textarea 1 000 chars max
- ChangelogModal : fonctionnalités en prod, roadmap, 5 limitations honnêtes
- Deux boutons flottants bas-gauche (? info + ✉ signalement)
- Légende remontée pour éviter le chevauchement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:52:32 -04:00

62 lines
1.9 KiB
JavaScript

const BASE_URL = import.meta.env.VITE_API_URL ?? '/api';
async function get(path, params = {}) {
const url = new URL(BASE_URL + path, window.location.origin);
Object.entries(params).forEach(([k, v]) => v !== undefined && url.searchParams.set(k, v));
const res = await fetch(url.toString(), {
headers: { Accept: 'application/json' },
});
if (!res.ok) throw new Error(`API error ${res.status}: ${path}`);
return res.json();
}
async function post(path, body) {
const res = await fetch(BASE_URL + path, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`API error ${res.status}: ${path}`);
return res.json();
}
export const api = {
spots: {
overview: () => get('/spots/overview'), // tous les spots + score en 1 requête cachée
get: (id) => get(`/spots/${id}`),
score: (id, at) => get(`/spots/${id}/score`, { at }),
},
observations: {
list: (bbox, date, source) => get('/observations', { bbox, date, source }),
get: (id) => get(`/observations/${id}`),
},
forecasts: {
list: (bbox, horizon) => get('/forecasts', { bbox, horizon }),
get: (id) => get(`/forecasts/${id}`),
},
feedback: {
create: (data) => post('/feedback', data),
},
status: {
get: () => get('/status'),
},
report: {
create: (data) => post('/report', data),
},
push: {
vapidKey: () => get('/push/vapid-public-key'),
subscribe: (data) => post('/push/subscribe', data),
unsubscribe: async (endpoint) => {
const res = await fetch(BASE_URL + '/push/subscribe', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ endpoint }),
});
if (!res.ok && res.status !== 204) throw new Error(`API error ${res.status}`);
},
},
};