chore(frontend): format files with prettier

This commit is contained in:
Piotr Oleszczyk 2026-03-03 20:51:34 +01:00
parent 0e7a39836f
commit 067e460dd2
20 changed files with 1615 additions and 1509 deletions

10
frontend/.mcp.json Normal file
View file

@ -0,0 +1,10 @@
{
"mcpServers": {
"svelte": {
"type": "stdio",
"command": "npx",
"env": {},
"args": ["-y", "@sveltejs/mcp"]
}
}
}

View file

@ -25,7 +25,7 @@ The backend must be running at `http://localhost:8000`. See `../backend/` for se
## Environment variables
| Variable | Description | Default |
|---|---|---|
| ----------------- | ------------------------------- | ----------------------- |
| `PUBLIC_API_BASE` | Base URL of the FastAPI backend | `http://localhost:8000` |
Set `PUBLIC_API_BASE` at **build time** for production:
@ -52,7 +52,7 @@ Or use the provided systemd service: `../systemd/innercontext-node.service`.
## Routes
| Route | Description |
|---|---|
| --------------------- | ------------------------ |
| `/` | Dashboard |
| `/products` | Product list |
| `/products/new` | Add product |
@ -67,7 +67,7 @@ Or use the provided systemd service: `../systemd/innercontext-node.service`.
## Key files
| File | Purpose |
|---|---|
| ------------------ | --------------------------------- |
| `src/lib/api.ts` | API client (typed fetch wrappers) |
| `src/lib/types.ts` | Shared TypeScript types |
| `src/app.css` | Tailwind v4 theme + global styles |

View file

@ -1,5 +1,5 @@
import { paraglideMiddleware } from '$lib/paraglide/server.js';
import type { Handle } from '@sveltejs/kit';
import { paraglideMiddleware } from "$lib/paraglide/server.js";
import type { Handle } from "@sveltejs/kit";
export const handle: Handle = async ({ event, resolve }) => {
return paraglideMiddleware(event.request, () => resolve(event));

View file

@ -1,5 +1,5 @@
import { browser } from '$app/environment';
import { PUBLIC_API_BASE } from '$env/static/public';
import { browser } from "$app/environment";
import { PUBLIC_API_BASE } from "$env/static/public";
import type {
ActiveIngredient,
BatchSuggestion,
@ -16,19 +16,19 @@ import type {
Routine,
RoutineSuggestion,
RoutineStep,
SkinConditionSnapshot
} from './types';
SkinConditionSnapshot,
} from "./types";
// ─── Core fetch helpers ──────────────────────────────────────────────────────
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
// Server-side uses PUBLIC_API_BASE (e.g. http://localhost:8000).
// Browser-side uses /api so nginx proxies the request on the correct host.
const base = browser ? '/api' : PUBLIC_API_BASE;
const base = browser ? "/api" : PUBLIC_API_BASE;
const url = `${base}${path}`;
const res = await fetch(url, {
headers: { 'Content-Type': 'application/json', ...init.headers },
...init
headers: { "Content-Type": "application/json", ...init.headers },
...init,
});
if (!res.ok) {
const detail = await res.json().catch(() => ({ detail: res.statusText }));
@ -41,10 +41,10 @@ async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
export const api = {
get: <T>(path: string) => request<T>(path),
post: <T>(path: string, body: unknown) =>
request<T>(path, { method: 'POST', body: JSON.stringify(body) }),
request<T>(path, { method: "POST", body: JSON.stringify(body) }),
patch: <T>(path: string, body: unknown) =>
request<T>(path, { method: 'PATCH', body: JSON.stringify(body) }),
del: (path: string) => request<void>(path, { method: 'DELETE' })
request<T>(path, { method: "PATCH", body: JSON.stringify(body) }),
del: (path: string) => request<void>(path, { method: "DELETE" }),
};
// ─── Products ────────────────────────────────────────────────────────────────
@ -57,54 +57,88 @@ export interface ProductListParams {
is_tool?: boolean;
}
export function getProducts(params: ProductListParams = {}): Promise<Product[]> {
export function getProducts(
params: ProductListParams = {},
): Promise<Product[]> {
const q = new URLSearchParams();
if (params.category) q.set('category', params.category);
if (params.brand) q.set('brand', params.brand);
if (params.targets) params.targets.forEach((t) => q.append('targets', t));
if (params.is_medication != null) q.set('is_medication', String(params.is_medication));
if (params.is_tool != null) q.set('is_tool', String(params.is_tool));
if (params.category) q.set("category", params.category);
if (params.brand) q.set("brand", params.brand);
if (params.targets) params.targets.forEach((t) => q.append("targets", t));
if (params.is_medication != null)
q.set("is_medication", String(params.is_medication));
if (params.is_tool != null) q.set("is_tool", String(params.is_tool));
const qs = q.toString();
return api.get(`/products${qs ? `?${qs}` : ''}`);
return api.get(`/products${qs ? `?${qs}` : ""}`);
}
export const getProduct = (id: string): Promise<Product> => api.get(`/products/${id}`);
export const createProduct = (body: Record<string, unknown>): Promise<Product> =>
api.post('/products', body);
export const updateProduct = (id: string, body: Record<string, unknown>): Promise<Product> =>
api.patch(`/products/${id}`, body);
export const deleteProduct = (id: string): Promise<void> => api.del(`/products/${id}`);
export const getProduct = (id: string): Promise<Product> =>
api.get(`/products/${id}`);
export const createProduct = (
body: Record<string, unknown>,
): Promise<Product> => api.post("/products", body);
export const updateProduct = (
id: string,
body: Record<string, unknown>,
): Promise<Product> => api.patch(`/products/${id}`, body);
export const deleteProduct = (id: string): Promise<void> =>
api.del(`/products/${id}`);
export const getInventory = (productId: string): Promise<ProductInventory[]> =>
api.get(`/products/${productId}/inventory`);
export const createInventory = (
productId: string,
body: Record<string, unknown>
): Promise<ProductInventory> => api.post(`/products/${productId}/inventory`, body);
export const updateInventory = (id: string, body: Record<string, unknown>): Promise<ProductInventory> =>
api.patch(`/inventory/${id}`, body);
export const deleteInventory = (id: string): Promise<void> => api.del(`/inventory/${id}`);
body: Record<string, unknown>,
): Promise<ProductInventory> =>
api.post(`/products/${productId}/inventory`, body);
export const updateInventory = (
id: string,
body: Record<string, unknown>,
): Promise<ProductInventory> => api.patch(`/inventory/${id}`, body);
export const deleteInventory = (id: string): Promise<void> =>
api.del(`/inventory/${id}`);
export interface ProductParseResponse {
name?: string; brand?: string; line_name?: string; sku?: string; url?: string; barcode?: string;
category?: string; recommended_time?: string; texture?: string; absorption_speed?: string;
leave_on?: boolean; price_tier?: string;
size_ml?: number; full_weight_g?: number; empty_weight_g?: number; pao_months?: number;
inci?: string[]; actives?: ActiveIngredient[];
recommended_for?: string[]; targets?: string[];
contraindications?: string[]; usage_notes?: string;
fragrance_free?: boolean; essential_oils_free?: boolean;
alcohol_denat_free?: boolean; pregnancy_safe?: boolean;
name?: string;
brand?: string;
line_name?: string;
sku?: string;
url?: string;
barcode?: string;
category?: string;
recommended_time?: string;
texture?: string;
absorption_speed?: string;
leave_on?: boolean;
price_tier?: string;
size_ml?: number;
full_weight_g?: number;
empty_weight_g?: number;
pao_months?: number;
inci?: string[];
actives?: ActiveIngredient[];
recommended_for?: string[];
targets?: string[];
contraindications?: string[];
usage_notes?: string;
fragrance_free?: boolean;
essential_oils_free?: boolean;
alcohol_denat_free?: boolean;
pregnancy_safe?: boolean;
product_effect_profile?: ProductEffectProfile;
ph_min?: number; ph_max?: number;
incompatible_with?: ProductInteraction[]; synergizes_with?: string[];
ph_min?: number;
ph_max?: number;
incompatible_with?: ProductInteraction[];
synergizes_with?: string[];
context_rules?: ProductContext;
min_interval_hours?: number; max_frequency_per_week?: number;
is_medication?: boolean; is_tool?: boolean; needle_length_mm?: number;
min_interval_hours?: number;
max_frequency_per_week?: number;
is_medication?: boolean;
is_tool?: boolean;
needle_length_mm?: number;
}
export const parseProductText = (text: string): Promise<ProductParseResponse> =>
api.post('/products/parse-text', { text });
api.post("/products/parse-text", { text });
// ─── Routines ────────────────────────────────────────────────────────────────
@ -114,26 +148,37 @@ export interface RoutineListParams {
part_of_day?: string;
}
export function getRoutines(params: RoutineListParams = {}): Promise<Routine[]> {
export function getRoutines(
params: RoutineListParams = {},
): Promise<Routine[]> {
const q = new URLSearchParams();
if (params.from_date) q.set('from_date', params.from_date);
if (params.to_date) q.set('to_date', params.to_date);
if (params.part_of_day) q.set('part_of_day', params.part_of_day);
if (params.from_date) q.set("from_date", params.from_date);
if (params.to_date) q.set("to_date", params.to_date);
if (params.part_of_day) q.set("part_of_day", params.part_of_day);
const qs = q.toString();
return api.get(`/routines${qs ? `?${qs}` : ''}`);
return api.get(`/routines${qs ? `?${qs}` : ""}`);
}
export const getRoutine = (id: string): Promise<Routine> => api.get(`/routines/${id}`);
export const createRoutine = (body: Record<string, unknown>): Promise<Routine> =>
api.post('/routines', body);
export const updateRoutine = (id: string, body: Record<string, unknown>): Promise<Routine> =>
api.patch(`/routines/${id}`, body);
export const deleteRoutine = (id: string): Promise<void> => api.del(`/routines/${id}`);
export const getRoutine = (id: string): Promise<Routine> =>
api.get(`/routines/${id}`);
export const createRoutine = (
body: Record<string, unknown>,
): Promise<Routine> => api.post("/routines", body);
export const updateRoutine = (
id: string,
body: Record<string, unknown>,
): Promise<Routine> => api.patch(`/routines/${id}`, body);
export const deleteRoutine = (id: string): Promise<void> =>
api.del(`/routines/${id}`);
export const addRoutineStep = (routineId: string, body: Record<string, unknown>): Promise<RoutineStep> =>
api.post(`/routines/${routineId}/steps`, body);
export const updateRoutineStep = (stepId: string, body: Record<string, unknown>): Promise<RoutineStep> =>
api.patch(`/routines/steps/${stepId}`, body);
export const addRoutineStep = (
routineId: string,
body: Record<string, unknown>,
): Promise<RoutineStep> => api.post(`/routines/${routineId}/steps`, body);
export const updateRoutineStep = (
stepId: string,
body: Record<string, unknown>,
): Promise<RoutineStep> => api.patch(`/routines/steps/${stepId}`, body);
export const deleteRoutineStep = (stepId: string): Promise<void> =>
api.del(`/routines/steps/${stepId}`);
@ -143,7 +188,7 @@ export const suggestRoutine = (body: {
notes?: string;
include_minoxidil_beard?: boolean;
leaving_home?: boolean;
}): Promise<RoutineSuggestion> => api.post('/routines/suggest', body);
}): Promise<RoutineSuggestion> => api.post("/routines/suggest", body);
export const suggestBatch = (body: {
from_date: string;
@ -151,13 +196,17 @@ export const suggestBatch = (body: {
notes?: string;
include_minoxidil_beard?: boolean;
minimize_products?: boolean;
}): Promise<BatchSuggestion> => api.post('/routines/suggest-batch', body);
}): Promise<BatchSuggestion> => api.post("/routines/suggest-batch", body);
export const getGroomingSchedule = (): Promise<GroomingSchedule[]> =>
api.get('/routines/grooming-schedule');
export const createGroomingScheduleEntry = (body: Record<string, unknown>): Promise<GroomingSchedule> =>
api.post('/routines/grooming-schedule', body);
export const updateGroomingScheduleEntry = (id: string, body: Record<string, unknown>): Promise<GroomingSchedule> =>
api.get("/routines/grooming-schedule");
export const createGroomingScheduleEntry = (
body: Record<string, unknown>,
): Promise<GroomingSchedule> => api.post("/routines/grooming-schedule", body);
export const updateGroomingScheduleEntry = (
id: string,
body: Record<string, unknown>,
): Promise<GroomingSchedule> =>
api.patch(`/routines/grooming-schedule/${id}`, body);
export const deleteGroomingScheduleEntry = (id: string): Promise<void> =>
api.del(`/routines/grooming-schedule/${id}`);
@ -169,31 +218,37 @@ export interface MedicationListParams {
product_name?: string;
}
export function getMedications(params: MedicationListParams = {}): Promise<MedicationEntry[]> {
export function getMedications(
params: MedicationListParams = {},
): Promise<MedicationEntry[]> {
const q = new URLSearchParams();
if (params.kind) q.set('kind', params.kind);
if (params.product_name) q.set('product_name', params.product_name);
if (params.kind) q.set("kind", params.kind);
if (params.product_name) q.set("product_name", params.product_name);
const qs = q.toString();
return api.get(`/health/medications${qs ? `?${qs}` : ''}`);
return api.get(`/health/medications${qs ? `?${qs}` : ""}`);
}
export const getMedication = (id: string): Promise<MedicationEntry> =>
api.get(`/health/medications/${id}`);
export const createMedication = (body: Record<string, unknown>): Promise<MedicationEntry> =>
api.post('/health/medications', body);
export const createMedication = (
body: Record<string, unknown>,
): Promise<MedicationEntry> => api.post("/health/medications", body);
export const updateMedication = (
id: string,
body: Record<string, unknown>
body: Record<string, unknown>,
): Promise<MedicationEntry> => api.patch(`/health/medications/${id}`, body);
export const deleteMedication = (id: string): Promise<void> =>
api.del(`/health/medications/${id}`);
export const getMedicationUsages = (medicationId: string): Promise<MedicationUsage[]> =>
export const getMedicationUsages = (
medicationId: string,
): Promise<MedicationUsage[]> =>
api.get(`/health/medications/${medicationId}/usages`);
export const createMedicationUsage = (
medicationId: string,
body: Record<string, unknown>
): Promise<MedicationUsage> => api.post(`/health/medications/${medicationId}/usages`, body);
body: Record<string, unknown>,
): Promise<MedicationUsage> =>
api.post(`/health/medications/${medicationId}/usages`, body);
// ─── Health Lab results ────────────────────────────────────────────────────
@ -205,23 +260,28 @@ export interface LabResultListParams {
to_date?: string;
}
export function getLabResults(params: LabResultListParams = {}): Promise<LabResult[]> {
export function getLabResults(
params: LabResultListParams = {},
): Promise<LabResult[]> {
const q = new URLSearchParams();
if (params.test_code) q.set('test_code', params.test_code);
if (params.flag) q.set('flag', params.flag);
if (params.lab) q.set('lab', params.lab);
if (params.from_date) q.set('from_date', params.from_date);
if (params.to_date) q.set('to_date', params.to_date);
if (params.test_code) q.set("test_code", params.test_code);
if (params.flag) q.set("flag", params.flag);
if (params.lab) q.set("lab", params.lab);
if (params.from_date) q.set("from_date", params.from_date);
if (params.to_date) q.set("to_date", params.to_date);
const qs = q.toString();
return api.get(`/health/lab-results${qs ? `?${qs}` : ''}`);
return api.get(`/health/lab-results${qs ? `?${qs}` : ""}`);
}
export const getLabResult = (id: string): Promise<LabResult> =>
api.get(`/health/lab-results/${id}`);
export const createLabResult = (body: Record<string, unknown>): Promise<LabResult> =>
api.post('/health/lab-results', body);
export const updateLabResult = (id: string, body: Record<string, unknown>): Promise<LabResult> =>
api.patch(`/health/lab-results/${id}`, body);
export const createLabResult = (
body: Record<string, unknown>,
): Promise<LabResult> => api.post("/health/lab-results", body);
export const updateLabResult = (
id: string,
body: Record<string, unknown>,
): Promise<LabResult> => api.patch(`/health/lab-results/${id}`, body);
export const deleteLabResult = (id: string): Promise<void> =>
api.del(`/health/lab-results/${id}`);
@ -233,24 +293,28 @@ export interface SnapshotListParams {
overall_state?: string;
}
export function getSkinSnapshots(params: SnapshotListParams = {}): Promise<SkinConditionSnapshot[]> {
export function getSkinSnapshots(
params: SnapshotListParams = {},
): Promise<SkinConditionSnapshot[]> {
const q = new URLSearchParams();
if (params.from_date) q.set('from_date', params.from_date);
if (params.to_date) q.set('to_date', params.to_date);
if (params.overall_state) q.set('overall_state', params.overall_state);
if (params.from_date) q.set("from_date", params.from_date);
if (params.to_date) q.set("to_date", params.to_date);
if (params.overall_state) q.set("overall_state", params.overall_state);
const qs = q.toString();
return api.get(`/skincare${qs ? `?${qs}` : ''}`);
return api.get(`/skincare${qs ? `?${qs}` : ""}`);
}
export const getSkinSnapshot = (id: string): Promise<SkinConditionSnapshot> =>
api.get(`/skincare/${id}`);
export const createSkinSnapshot = (body: Record<string, unknown>): Promise<SkinConditionSnapshot> =>
api.post('/skincare', body);
export const createSkinSnapshot = (
body: Record<string, unknown>,
): Promise<SkinConditionSnapshot> => api.post("/skincare", body);
export const updateSkinSnapshot = (
id: string,
body: Record<string, unknown>
body: Record<string, unknown>,
): Promise<SkinConditionSnapshot> => api.patch(`/skincare/${id}`, body);
export const deleteSkinSnapshot = (id: string): Promise<void> => api.del(`/skincare/${id}`);
export const deleteSkinSnapshot = (id: string): Promise<void> =>
api.del(`/skincare/${id}`);
export interface SkinPhotoAnalysisResponse {
overall_state?: string;
@ -267,11 +331,16 @@ export interface SkinPhotoAnalysisResponse {
notes?: string;
}
export async function analyzeSkinPhotos(files: File[]): Promise<SkinPhotoAnalysisResponse> {
export async function analyzeSkinPhotos(
files: File[],
): Promise<SkinPhotoAnalysisResponse> {
const body = new FormData();
for (const file of files) body.append('photos', file);
const base = browser ? '/api' : PUBLIC_API_BASE;
const res = await fetch(`${base}/skincare/analyze-photos`, { method: 'POST', body });
for (const file of files) body.append("photos", file);
const base = browser ? "/api" : PUBLIC_API_BASE;
const res = await fetch(`${base}/skincare/analyze-photos`, {
method: "POST",
body,
});
if (!res.ok) {
const detail = await res.json().catch(() => ({ detail: res.statusText }));
throw new Error(detail?.detail ?? res.statusText);

View file

@ -1,65 +1,92 @@
// ─── Enums ──────────────────────────────────────────────────────────────────
export type AbsorptionSpeed = 'very_fast' | 'fast' | 'moderate' | 'slow' | 'very_slow';
export type BarrierState = 'intact' | 'mildly_compromised' | 'compromised';
export type DayTime = 'am' | 'pm' | 'both';
export type GroomingAction = 'shaving_razor' | 'shaving_oneblade' | 'dermarolling';
export type AbsorptionSpeed =
| "very_fast"
| "fast"
| "moderate"
| "slow"
| "very_slow";
export type BarrierState = "intact" | "mildly_compromised" | "compromised";
export type DayTime = "am" | "pm" | "both";
export type GroomingAction =
| "shaving_razor"
| "shaving_oneblade"
| "dermarolling";
export type IngredientFunction =
| 'humectant'
| 'emollient'
| 'occlusive'
| 'exfoliant_aha'
| 'exfoliant_bha'
| 'exfoliant_pha'
| 'retinoid'
| 'antioxidant'
| 'soothing'
| 'barrier_support'
| 'brightening'
| 'anti_acne'
| 'ceramide'
| 'niacinamide'
| 'sunscreen'
| 'peptide'
| 'hair_growth_stimulant'
| 'prebiotic'
| 'vitamin_c'
| 'anti_aging';
export type InteractionScope = 'same_step' | 'same_day' | 'same_period';
export type MedicationKind = 'prescription' | 'otc' | 'supplement' | 'herbal' | 'other';
export type OverallSkinState = 'excellent' | 'good' | 'fair' | 'poor';
export type PartOfDay = 'am' | 'pm';
export type PriceTier = 'budget' | 'mid' | 'premium' | 'luxury';
| "humectant"
| "emollient"
| "occlusive"
| "exfoliant_aha"
| "exfoliant_bha"
| "exfoliant_pha"
| "retinoid"
| "antioxidant"
| "soothing"
| "barrier_support"
| "brightening"
| "anti_acne"
| "ceramide"
| "niacinamide"
| "sunscreen"
| "peptide"
| "hair_growth_stimulant"
| "prebiotic"
| "vitamin_c"
| "anti_aging";
export type InteractionScope = "same_step" | "same_day" | "same_period";
export type MedicationKind =
| "prescription"
| "otc"
| "supplement"
| "herbal"
| "other";
export type OverallSkinState = "excellent" | "good" | "fair" | "poor";
export type PartOfDay = "am" | "pm";
export type PriceTier = "budget" | "mid" | "premium" | "luxury";
export type ProductCategory =
| 'cleanser'
| 'toner'
| 'essence'
| 'serum'
| 'moisturizer'
| 'spf'
| 'mask'
| 'exfoliant'
| 'hair_treatment'
| 'tool'
| 'spot_treatment'
| 'oil';
export type ResultFlag = 'N' | 'ABN' | 'POS' | 'NEG' | 'L' | 'H';
| "cleanser"
| "toner"
| "essence"
| "serum"
| "moisturizer"
| "spf"
| "mask"
| "exfoliant"
| "hair_treatment"
| "tool"
| "spot_treatment"
| "oil";
export type ResultFlag = "N" | "ABN" | "POS" | "NEG" | "L" | "H";
export type SkinConcern =
| 'acne'
| 'rosacea'
| 'hyperpigmentation'
| 'aging'
| 'dehydration'
| 'redness'
| 'damaged_barrier'
| 'pore_visibility'
| 'uneven_texture'
| 'hair_growth'
| 'sebum_excess';
export type SkinTexture = 'smooth' | 'rough' | 'flaky' | 'bumpy';
export type SkinType = 'dry' | 'oily' | 'combination' | 'sensitive' | 'normal' | 'acne_prone';
| "acne"
| "rosacea"
| "hyperpigmentation"
| "aging"
| "dehydration"
| "redness"
| "damaged_barrier"
| "pore_visibility"
| "uneven_texture"
| "hair_growth"
| "sebum_excess";
export type SkinTexture = "smooth" | "rough" | "flaky" | "bumpy";
export type SkinType =
| "dry"
| "oily"
| "combination"
| "sensitive"
| "normal"
| "acne_prone";
export type StrengthLevel = 1 | 2 | 3;
export type TextureType = 'watery' | 'gel' | 'emulsion' | 'cream' | 'oil' | 'balm' | 'foam' | 'fluid';
export type TextureType =
| "watery"
| "gel"
| "emulsion"
| "cream"
| "oil"
| "balm"
| "foam"
| "fluid";
// ─── Product types ───────────────────────────────────────────────────────────
export interface ActiveIngredient {