fix: stabilize frontend e2e selectors and auth/session reliability (#373)

This commit is contained in:
Daniel Volz
2026-03-02 23:21:57 +01:00
committed by GitHub
parent 1a348c62f5
commit 56d244aa61
19 changed files with 485 additions and 43 deletions
+6 -2
View File
@@ -157,7 +157,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return;
}
}
log.warn("[Auth] Session refresh failed, clearing local user state", { correlationId });
log.debug("[Auth] Session refresh unavailable, clearing local user state", { correlationId });
setUser(null);
} else {
log.warn("[Auth] Unexpected /auth/me response", { status: res.status, correlationId });
@@ -181,7 +181,11 @@ export function AuthProvider({ children }: { children: ReactNode }) {
);
const res = await fetch("/api/auth/refresh", init);
if (!res.ok) {
log.warn("[Auth] Token refresh rejected", { status: res.status, correlationId });
if (res.status === 401) {
log.debug("[Auth] Token refresh rejected (unauthenticated)", { status: res.status, correlationId });
} else {
log.warn("[Auth] Token refresh rejected", { status: res.status, correlationId });
}
}
return res.ok;
} catch (error) {
+1 -3
View File
@@ -715,9 +715,7 @@ export function MobileEditModal({
<div className="stock-total-field">
<p className="sub">
<strong>{totalLabel}:</strong> {deriveTotalFromForm(form)}
{form.packageType !== "tube" && form.packageType !== "liquid_container"
? ` ${deriveTotalFromForm(form) === 1 ? t("common.pill") : t("common.pills")}`
: ""}
{` ${deriveTotalFromForm(form) === 1 ? t("common.pill") : t("common.pills")}`}
</p>
</div>
</div>
+14 -2
View File
@@ -423,7 +423,12 @@ export function SharedSchedule() {
// Use intakes (with per-intake takenBy) if available, fallback to blisters (legacy)
const intakes =
med.intakes ||
med.blisters.map((b) => ({ ...b, takenBy: null as string | null, intakeRemindersEnabled: false }));
med.blisters.map((b) => ({
...b,
intakeUnit: null,
takenBy: null as string | null,
intakeRemindersEnabled: false,
}));
intakes.forEach((intake, intakeIdx) => {
// Filter: only include intakes for this person (null = everyone, or matches share's takenBy)
@@ -535,7 +540,14 @@ export function SharedSchedule() {
const depletion: Record<string, number | null> = {};
for (const med of data.medications) {
const intakes = med.intakes || med.blisters.map((b) => ({ ...b, takenBy: null as string | null }));
const intakes =
med.intakes ||
med.blisters.map((b) => ({
...b,
intakeUnit: null,
takenBy: null as string | null,
intakeRemindersEnabled: false,
}));
// Count unique people from all intakes (for per-intake takenBy)
const uniquePeople = new Set<string>();
+2 -2
View File
@@ -213,7 +213,7 @@ export function useMedicationForm(): UseMedicationFormReturn {
every: String(i.every),
startDate: toDateValue(i.start),
startTime: toTimeValue(i.start),
intakeUnit: i.intakeUnit ?? "ml",
intakeUnit: (i.intakeUnit ?? "ml") as FormIntake["intakeUnit"],
takenBy: i.takenBy ?? "", // Convert null to empty string for form
intakeRemindersEnabled: i.intakeRemindersEnabled,
}))
@@ -222,7 +222,7 @@ export function useMedicationForm(): UseMedicationFormReturn {
every: String(s.every),
startDate: toDateValue(s.start),
startTime: toTimeValue(s.start),
intakeUnit: "ml",
intakeUnit: "ml" as const,
takenBy: "", // Legacy blisters have no per-intake takenBy
intakeRemindersEnabled: med.intakeRemindersEnabled ?? false,
}));
+6 -3
View File
@@ -1005,7 +1005,8 @@ export function DashboardPage() {
🤖
</span>
)}
<span className="dose-btn-label">{t("common.undo")}</span>
<span aria-hidden="true"></span>
</button>
) : (
<button
@@ -1287,7 +1288,8 @@ export function DashboardPage() {
🤖
</span>
)}
<span className="dose-btn-label">{t("common.undo")}</span>
<span aria-hidden="true"></span>
</button>
) : (
<button
@@ -1532,7 +1534,8 @@ export function DashboardPage() {
🤖
</span>
)}
<span className="dose-btn-label">{t("common.undo")}</span>
<span aria-hidden="true"></span>
</button>
) : (
<button
+1 -1
View File
@@ -1708,7 +1708,7 @@ export function MedicationsPage() {
<div key={idx} className="blister-row">
<div className="blister-inputs">
<label>
{getUsageLabel(intake.intakeUnit)}
{getUsageLabel(intake.intakeUnit ?? "ml")}
<FormNumberStepper
value={intake.usage}
onChange={(nextValue) => setIntakeValue(idx, "usage", nextValue)}
+1 -1
View File
@@ -121,7 +121,7 @@ body.modal-open {
.route-transition-mask.active {
transition: none;
opacity: 1;
pointer-events: auto;
pointer-events: none;
}
.hero {
@@ -15,6 +15,8 @@ const defaultForm: FormState = {
packCount: "1",
blistersPerPack: "1",
pillsPerBlister: "1",
packageAmountValue: "0",
packageAmountUnit: "ml",
looseTablets: "0",
totalPills: "",
pillWeightMg: "",
+3 -3
View File
@@ -1286,9 +1286,9 @@ describe("getNextReminderForMed", () => {
vi.useRealTimers();
});
const mockT = (key: string, options?: Record<string, number>) => {
if (options?.count) return `${key} (${options.count})`;
if (options?.days) return `${key} (${options.days})`;
const mockT = (key: string, options?: Record<string, unknown>) => {
if (typeof options?.count === "number") return `${key} (${options.count})`;
if (typeof options?.days === "number") return `${key} (${options.days})`;
return key;
};
+21 -1
View File
@@ -5,13 +5,19 @@
export type PackageType = "blister" | "bottle" | "tube" | "liquid_container";
// Common medication dose units
export type DoseUnit = "mg" | "g" | "mcg" | "ml";
export type DoseUnit = "mg" | "g" | "mcg" | "ml" | "units";
export type MedicationForm = "tablet" | "capsule" | "topical" | "liquid";
export type PillForm = "tablet" | "capsule";
export type LifecycleCategory = "refill_when_empty" | "treatment_period";
export type PackageAmountUnit = "ml" | "g";
export const DOSE_UNITS: { value: DoseUnit; label: string }[] = [
{ value: "mg", label: "mg" },
{ value: "g", label: "g" },
{ value: "mcg", label: "mcg (µg)" },
{ value: "ml", label: "ml" },
{ value: "units", label: "units" },
];
export type Blister = {
@@ -50,7 +56,14 @@ export type Medication = {
lastStockCorrectionAt?: string | null;
pillWeightMg?: number | null;
doseUnit?: DoseUnit | null; // Unit for the dose (mg, g, mcg, ml, IU, etc.)
medicationForm?: MedicationForm | null;
pillForm?: PillForm | null;
lifecycleCategory?: LifecycleCategory | null;
packageAmountValue?: number | null;
packageAmountUnit?: PackageAmountUnit | null;
medicationStartDate?: string | null;
medicationEndDate?: string | null;
autoMarkObsoleteAfterEndDate?: boolean;
blisters: Blister[]; // Legacy array format
intakes?: Intake[]; // New intake format with per-intake takenBy
imageUrl?: string | null;
@@ -114,15 +127,22 @@ export type FormState = {
name: string;
genericName: string;
takenBy: string[]; // Medication-level takenBy (legacy/compatibility)
medicationForm: MedicationForm;
pillForm: PillForm;
lifecycleCategory: LifecycleCategory;
packageType: PackageType;
packCount: string;
blistersPerPack: string;
pillsPerBlister: string;
packageAmountValue: string;
packageAmountUnit: PackageAmountUnit;
totalPills: string; // For bottle type: total capacity
looseTablets: string; // For blister: extra loose pills; for bottle: current stock
pillWeightMg: string;
doseUnit: DoseUnit; // Unit for the dose (mg, g, mcg, ml, IU, etc.)
medicationStartDate: string;
medicationEndDate: string;
autoMarkObsoleteAfterEndDate: boolean;
expiryDate: string;
notes: string;
prescriptionEnabled: boolean;