diff --git a/frontend/src/components/Auth.tsx b/frontend/src/components/Auth.tsx index bb3d991..9fc860b 100644 --- a/frontend/src/components/Auth.tsx +++ b/frontend/src/components/Auth.tsx @@ -1,5 +1,6 @@ import { createContext, type ReactNode, useCallback, useContext, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import { log } from "../utils/logger"; import { ConfirmModal } from "./ConfirmModal"; import { PasswordInput } from "./PasswordInput"; @@ -109,7 +110,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { } setLoading(false); } catch (err) { - console.error(`Failed to fetch auth state (attempt ${retryCount + 1}/${maxRetries + 1}):`, err); + log.error(`Failed to fetch auth state (attempt ${retryCount + 1}/${maxRetries + 1}):`, err); // Retry on connection errors or 5xx errors (server might be restarting) if (retryCount < maxRetries) { diff --git a/frontend/src/context/AppContext.tsx b/frontend/src/context/AppContext.tsx index d8b30b5..d13d337 100644 --- a/frontend/src/context/AppContext.tsx +++ b/frontend/src/context/AppContext.tsx @@ -5,6 +5,7 @@ import { useAuth } from "../components/Auth"; import { useCollapsedDays, useDoses, useMedications, useRefill, useSettings, useShare } from "../hooks"; import type { Coverage, Medication, ScheduleEvent, StockThresholds } from "../types"; import { getSystemLocale } from "../utils/formatters"; +import { log } from "../utils/logger"; import { buildSchedulePreview, calculateCoverage, computeMissedPastDoseIds, isDoseDismissed } from "../utils/schedule"; // ============================================================================= @@ -514,7 +515,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) { document.body.removeChild(a); URL.revokeObjectURL(url); } catch (err) { - console.error("Export error:", err); + log.error("Export error:", err); } setExporting(false); }, @@ -568,7 +569,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) { try { data = text ? JSON.parse(text) : {}; } catch { - console.error("Import response parse error:", text); + log.error("Import response parse error:", text); alert(`${t("exportImport.importError")}: Server returned invalid response`); return; } @@ -590,7 +591,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) { settingsHook.loadSettings(); doses.loadTakenDoses(); } catch (err) { - console.error("Import error:", err); + log.error("Import error:", err); alert(t("exportImport.importError")); } diff --git a/frontend/src/pages/MedicationsPage.tsx b/frontend/src/pages/MedicationsPage.tsx index 7da8a6e..1835bd3 100644 --- a/frontend/src/pages/MedicationsPage.tsx +++ b/frontend/src/pages/MedicationsPage.tsx @@ -6,6 +6,7 @@ import { useMedicationForm, useUnsavedChangesWarning } from "../hooks"; import type { DoseUnit, Medication } from "../types"; import { DOSE_UNITS, FIELD_LIMITS, getPackageSize } from "../types"; import { combineDateAndTime, formatDateTime, formatNumber } from "../utils/formatters"; +import { log } from "../utils/logger"; export function MedicationsPage() { const { t } = useTranslation(); @@ -299,7 +300,7 @@ export function MedicationsPage() { setOriginalForm(form); } } catch (err) { - console.error("Save error:", err); + log.error("Save error:", err); alert(err instanceof Error && err.message ? err.message : t("common.saveFailed")); } diff --git a/frontend/src/utils/logger.ts b/frontend/src/utils/logger.ts new file mode 100644 index 0000000..d6e42b0 --- /dev/null +++ b/frontend/src/utils/logger.ts @@ -0,0 +1,39 @@ +/** + * Frontend logger that respects LOG_LEVEL (injected at build time via Vite define). + * Mirrors the backend logger API so usage is consistent across the stack. + * + * Levels (high → low): silent > error > warn > info > debug + * Default: "warn" — only warn and error messages are shown. + */ + +declare const __LOG_LEVEL__: string; + +const LOG_LEVELS: Record = { + silent: 50, + error: 40, + warn: 30, + info: 20, + debug: 10, +}; + +const configuredLevel = typeof __LOG_LEVEL__ !== "undefined" ? __LOG_LEVEL__.toLowerCase() : "warn"; +const threshold = LOG_LEVELS[configuredLevel] ?? LOG_LEVELS.warn; + +function shouldLog(level: string): boolean { + return (LOG_LEVELS[level] ?? 0) >= threshold; +} + +export const log = { + debug(...args: unknown[]): void { + if (shouldLog("debug")) console.debug(...args); + }, + info(...args: unknown[]): void { + if (shouldLog("info")) console.info(...args); + }, + warn(...args: unknown[]): void { + if (shouldLog("warn")) console.warn(...args); + }, + error(...args: unknown[]): void { + if (shouldLog("error")) console.error(...args); + }, +}; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index eae03e3..e5372d0 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -14,6 +14,7 @@ export default defineConfig({ plugins: [react()], define: { __APP_VERSION__: JSON.stringify(packageJson.version || "unknown"), + __LOG_LEVEL__: JSON.stringify(process.env.LOG_LEVEL || "warn"), }, server: { port: 5173,