Refactor frontend to Vue 3 + Vite + Pinia + Cypress E2E
- Replace single-file 8441-line HTML with Vue 3 SPA - Pinia stores: auth, oils, recipes, diary, ui - Composables: useApi, useDialog, useSmartPaste, useOilTranslation - 6 shared components: RecipeCard, RecipeDetailOverlay, TagPicker, etc. - 9 page views: RecipeSearch, RecipeManager, Inventory, OilReference, etc. - 14 Cypress E2E test specs (113 tests), all passing - Multi-stage Dockerfile (Node build + Python runtime) - Demo video generation scripts (TTS + subtitles + screen recording) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
44
frontend/src/composables/useApi.js
Normal file
44
frontend/src/composables/useApi.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const API_BASE = '' // same origin, uses vite proxy in dev
|
||||
|
||||
export function getToken() {
|
||||
return localStorage.getItem('oil_auth_token') || ''
|
||||
}
|
||||
|
||||
export function setToken(token) {
|
||||
if (token) localStorage.setItem('oil_auth_token', token)
|
||||
else localStorage.removeItem('oil_auth_token')
|
||||
}
|
||||
|
||||
function buildHeaders(extra = {}) {
|
||||
const headers = { 'Content-Type': 'application/json', ...extra }
|
||||
const token = getToken()
|
||||
if (token) headers['Authorization'] = 'Bearer ' + token
|
||||
return headers
|
||||
}
|
||||
|
||||
async function request(path, opts = {}) {
|
||||
const headers = buildHeaders(opts.headers)
|
||||
const res = await fetch(API_BASE + path, { ...opts, headers })
|
||||
return res
|
||||
}
|
||||
|
||||
async function requestJSON(path, opts = {}) {
|
||||
const res = await request(path, opts)
|
||||
if (!res.ok) throw res
|
||||
return res.json()
|
||||
}
|
||||
|
||||
// api is callable as api(path, opts) → raw Response
|
||||
// AND has convenience methods: api.get(), api.post(), api.put(), api.delete()
|
||||
function apiFn(path, opts = {}) {
|
||||
return request(path, opts)
|
||||
}
|
||||
|
||||
apiFn.raw = request
|
||||
apiFn.get = (path) => requestJSON(path)
|
||||
apiFn.post = (path, body) => requestJSON(path, { method: 'POST', body: JSON.stringify(body) })
|
||||
apiFn.put = (path, body) => requestJSON(path, { method: 'PUT', body: JSON.stringify(body) })
|
||||
apiFn.del = (path) => requestJSON(path, { method: 'DELETE' })
|
||||
apiFn.delete = (path) => requestJSON(path, { method: 'DELETE' })
|
||||
|
||||
export const api = apiFn
|
||||
Reference in New Issue
Block a user