fix: stabilize frontend e2e selectors and auth/session reliability (#373)
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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: "",
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user