Introduce initial version of the Lonc app with core features, styling, and configurations.
- Add base app structure, including Bootstrap setup and Alpine.js integration. - Implement authentication flow with session handling. - Integrate stock management and label creation functionalities. - Include responsive styling and theme using CSS variables and custom components. - Add API clients for Tryton-based backend. - Set up kitchen and dashboard navigation workflows. - Configure service worker for PWA support.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { API_PATHS } from '../app/config.js';
|
||||
|
||||
function normalizeBaseUrl(baseUrl) {
|
||||
return baseUrl.trim().replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function buildUrl({ baseUrl, database, kitchenId, path, query = {}, includeKitchen = true }) {
|
||||
const cleanBaseUrl = normalizeBaseUrl(baseUrl);
|
||||
const encodedDatabase = encodeURIComponent(database);
|
||||
const encodedPath = path.replace(/^\/+/, '');
|
||||
const kitchenSegment =
|
||||
includeKitchen && kitchenId
|
||||
? `/kitchen/${encodeURIComponent(String(kitchenId))}`
|
||||
: '';
|
||||
|
||||
const url = new URL(
|
||||
`${cleanBaseUrl}/${encodedDatabase}${kitchenSegment}/${encodedPath}`,
|
||||
);
|
||||
|
||||
Object.entries(query).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
url.searchParams.set(key, String(value));
|
||||
}
|
||||
});
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
async function parseResponse(response) {
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
|
||||
if (contentType.includes('application/json')) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (contentType.includes('image/')) {
|
||||
return response.blob();
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.text();
|
||||
}
|
||||
|
||||
function normalizeError(response, payload) {
|
||||
const message =
|
||||
payload?.message ||
|
||||
payload?.error ||
|
||||
`Request failed with status ${response.status}.`;
|
||||
|
||||
return new Error(message, {
|
||||
cause: {
|
||||
status: response.status,
|
||||
details: payload?.errors || payload?.details || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function apiRequest(store, path, options = {}) {
|
||||
const { config, session, activeKitchen } = store;
|
||||
|
||||
if (!config.baseUrl || !config.database) {
|
||||
throw new Error('Server URL and database name are required.');
|
||||
}
|
||||
|
||||
const headers = new Headers(options.headers || {});
|
||||
headers.set('Accept', options.accept || 'application/json');
|
||||
|
||||
if (options.body && !options.isFormData) {
|
||||
headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
if (session?.applicationKey) {
|
||||
headers.set('Authorization', `Bearer ${session.applicationKey}`);
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
buildUrl({
|
||||
baseUrl: config.baseUrl,
|
||||
database: config.database,
|
||||
kitchenId: activeKitchen?.id,
|
||||
path,
|
||||
query: options.query,
|
||||
includeKitchen: options.includeKitchen !== false,
|
||||
}),
|
||||
{
|
||||
method: options.method || 'GET',
|
||||
headers,
|
||||
body:
|
||||
options.body && !options.isFormData
|
||||
? JSON.stringify(options.body)
|
||||
: options.body || undefined,
|
||||
},
|
||||
);
|
||||
|
||||
const payload = await parseResponse(response);
|
||||
if (!response.ok) {
|
||||
throw normalizeError(response, payload);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function getPath(key) {
|
||||
return API_PATHS[key];
|
||||
}
|
||||
|
||||
export function buildKitchenApiUrl(store, path, query = {}) {
|
||||
return buildUrl({
|
||||
baseUrl: store.config.baseUrl,
|
||||
database: store.config.database,
|
||||
kitchenId: store.activeKitchen?.id,
|
||||
path,
|
||||
query,
|
||||
includeKitchen: true,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user