fix: guard unsaved map logout
This commit is contained in:
parent
d78dca7b1a
commit
0fec9dcc3e
@ -77,6 +77,8 @@ export default function LocationMapManager() {
|
||||
const svgRef = useRef(null);
|
||||
const toastRef = useRef(toast);
|
||||
const loadRequestRef = useRef(0);
|
||||
const guardedClickTargetRef = useRef(null);
|
||||
const allowGuardedClickRef = useRef(false);
|
||||
|
||||
const location = mapState?.location || stores.find((store) => String(store.id) === String(locationId));
|
||||
const canManage = Boolean(mapState?.can_manage ?? ["owner", "admin"].includes(activeHousehold?.role));
|
||||
@ -144,10 +146,13 @@ export default function LocationMapManager() {
|
||||
useEffect(() => {
|
||||
if (!shouldGuardLeave) {
|
||||
setPendingLeaveTarget(null);
|
||||
guardedClickTargetRef.current = null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const handleDocumentClick = (event) => {
|
||||
if (allowGuardedClickRef.current) return;
|
||||
|
||||
if (
|
||||
event.defaultPrevented ||
|
||||
event.button !== 0 ||
|
||||
@ -160,6 +165,15 @@ export default function LocationMapManager() {
|
||||
}
|
||||
|
||||
const target = event.target instanceof Element ? event.target : null;
|
||||
const button = target?.closest("button");
|
||||
if (button?.classList.contains("user-dropdown-logout")) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
guardedClickTargetRef.current = button;
|
||||
setPendingLeaveTarget({ type: "click" });
|
||||
return;
|
||||
}
|
||||
|
||||
const anchor = target?.closest("a[href]");
|
||||
if (!anchor || (anchor.target && anchor.target !== "_self") || anchor.hasAttribute("download")) {
|
||||
return;
|
||||
@ -610,6 +624,18 @@ export default function LocationMapManager() {
|
||||
const confirmPendingLeave = useCallback(() => {
|
||||
const target = pendingLeaveTarget;
|
||||
setPendingLeaveTarget(null);
|
||||
if (target?.type === "click") {
|
||||
const guardedClickTarget = guardedClickTargetRef.current;
|
||||
guardedClickTargetRef.current = null;
|
||||
if (guardedClickTarget) {
|
||||
allowGuardedClickRef.current = true;
|
||||
guardedClickTarget.click();
|
||||
window.setTimeout(() => {
|
||||
allowGuardedClickRef.current = false;
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (target?.type === "path" && target.path) {
|
||||
navigate(target.path);
|
||||
return;
|
||||
@ -774,7 +800,10 @@ export default function LocationMapManager() {
|
||||
title="Leave with unsaved changes?"
|
||||
description="Your draft edits are only on this screen right now. Save the draft before leaving if you want to keep them."
|
||||
confirmLabel="Leave Map"
|
||||
onClose={() => setPendingLeaveTarget(null)}
|
||||
onClose={() => {
|
||||
guardedClickTargetRef.current = null;
|
||||
setPendingLeaveTarget(null);
|
||||
}}
|
||||
onConfirm={confirmPendingLeave}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1502,6 +1502,69 @@ test("admin app navigation warns when draft edits are unsaved", async ({ page })
|
||||
await expect(page).toHaveURL(/\/$/);
|
||||
});
|
||||
|
||||
test("admin logout warns when draft edits are unsaved", async ({ page }) => {
|
||||
await mockMapShell(page);
|
||||
|
||||
const mapState = draftMapState([
|
||||
{
|
||||
id: 1373,
|
||||
location_map_id: 900,
|
||||
zone_id: 501,
|
||||
zone_name: "Bakery",
|
||||
type: "zone",
|
||||
label: "Bakery",
|
||||
x: 40,
|
||||
y: 40,
|
||||
width: 260,
|
||||
height: 160,
|
||||
rotation: 0,
|
||||
locked: false,
|
||||
visible: true,
|
||||
sort_order: 1,
|
||||
},
|
||||
], true);
|
||||
|
||||
let logoutCalls = 0;
|
||||
await page.route("**/households/1/locations/10/map", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(mapState),
|
||||
});
|
||||
});
|
||||
await page.route("**/auth/logout", async (route) => {
|
||||
logoutCalls += 1;
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ message: "Logged out" }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto("/stores/100/locations/10/map");
|
||||
|
||||
await page.getByRole("button", { name: "Continue Editing" }).click();
|
||||
await page.getByRole("button", { name: "Edit Areas" }).click();
|
||||
await page.locator(".location-map-object", { hasText: "Bakery" }).locator("rect").first().click();
|
||||
await page.getByRole("textbox", { name: "Label" }).fill("Unsaved Logout Bakery");
|
||||
|
||||
await page.getByRole("button", { name: "User menu" }).click();
|
||||
await page.getByRole("button", { name: "Logout", exact: true }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Leave with unsaved changes?" })).toBeVisible();
|
||||
expect(logoutCalls).toBe(0);
|
||||
await page.getByRole("button", { name: "Cancel" }).click();
|
||||
await expect(page).toHaveURL(/\/stores\/100\/locations\/10\/map$/);
|
||||
await expect(page.getByRole("textbox", { name: "Label" })).toHaveValue("Unsaved Logout Bakery");
|
||||
expect(logoutCalls).toBe(0);
|
||||
|
||||
await page.getByRole("button", { name: "Logout", exact: true }).click();
|
||||
await confirmSlide(page);
|
||||
|
||||
await expect(page).toHaveURL(/\/login$/);
|
||||
expect(logoutCalls).toBe(1);
|
||||
});
|
||||
|
||||
test("admin publish saves pending map edits before publishing", async ({ page }) => {
|
||||
await mockMapShell(page);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user