refactor: use slide confirmation for role changes
All checks were successful
Build & Deploy Costco Grocery List / build (push) Successful in 11s
Build & Deploy Costco Grocery List / verify-images (push) Successful in 1s
Build & Deploy Costco Grocery List / deploy (push) Successful in 5s
Build & Deploy Costco Grocery List / notify (push) Successful in 0s

This commit is contained in:
Nico 2026-03-31 01:24:22 -07:00
parent cfec916392
commit 5a2848ebcf
2 changed files with 53 additions and 12 deletions

View File

@ -69,6 +69,7 @@ export default function ManageHousehold() {
const [singleUseMode, setSingleUseMode] = useState("UNLIMITED");
const [pendingDecisionId, setPendingDecisionId] = useState(null);
const [isLeaveModalOpen, setIsLeaveModalOpen] = useState(false);
const [pendingRoleChange, setPendingRoleChange] = useState(null);
const isManager = ["owner", "admin"].includes(activeHousehold?.role);
const isOwner = activeHousehold?.role === "owner";
@ -277,15 +278,10 @@ export default function ManageHousehold() {
}
};
const handleUpdateRole = async (memberId, nextRole, memberName) => {
if (!nextRole) return;
const handleConfirmRoleChange = async () => {
if (!pendingRoleChange) return;
if (
nextRole === "owner" &&
!window.confirm(`Make ${memberName} the household owner? You will become an admin.`)
) {
return;
}
const { memberId, nextRole, memberName } = pendingRoleChange;
try {
await updateMemberRole(activeHousehold.id, memberId, nextRole);
@ -298,12 +294,19 @@ export default function ManageHousehold() {
} else {
toast.success("Updated member role", `Updated role for ${memberName} to ${nextRole}`);
}
setPendingRoleChange(null);
} catch (error) {
const message = getApiErrorMessage(error, "Failed to update member role");
toast.error("Update member role failed", `Update member role failed: ${message}`);
}
};
const handleUpdateRole = (memberId, nextRole, memberName) => {
if (!nextRole) return;
setPendingRoleChange({ memberId, nextRole, memberName });
};
const handleRemoveMember = async (memberId, username) => {
if (!confirm(`Remove ${username} from this household?`)) return;
@ -640,6 +643,27 @@ export default function ManageHousehold() {
onClose={() => setIsLeaveModalOpen(false)}
onConfirm={handleLeaveHousehold}
/>
<ConfirmSlideModal
isOpen={Boolean(pendingRoleChange)}
title={
pendingRoleChange?.nextRole === "owner"
? `Transfer ownership to ${pendingRoleChange?.memberName || "this member"}?`
: `Change ${pendingRoleChange?.memberName || "this member"} to ${pendingRoleChange?.nextRole || "member"}?`
}
description={
pendingRoleChange?.nextRole === "owner"
? "Slide to confirm. They will become the household owner and you will become an admin."
: "Slide to confirm this household role change."
}
confirmLabel={
pendingRoleChange?.nextRole === "owner"
? "Transfer Ownership"
: `Make ${pendingRoleChange?.nextRole === "admin" ? "Admin" : "Member"}`
}
onClose={() => setPendingRoleChange(null)}
onConfirm={handleConfirmRoleChange}
/>
</div>
);
}

View File

@ -23,6 +23,25 @@ async function mockConfig(page: import("@playwright/test").Page) {
});
}
async function confirmSlide(page: import("@playwright/test").Page) {
const confirmModal = page.locator(".confirm-slide-modal");
await expect(confirmModal).toBeVisible();
const slider = confirmModal.locator(".confirm-slide-handle");
const track = confirmModal.locator(".confirm-slide-track");
const sliderBox = await slider.boundingBox();
const trackBox = await track.boundingBox();
if (!sliderBox || !trackBox) {
throw new Error("Confirm slide control was not measurable");
}
await page.mouse.move(sliderBox.x + sliderBox.width / 2, sliderBox.y + sliderBox.height / 2);
await page.mouse.down();
await page.mouse.move(trackBox.x + trackBox.width - 4, sliderBox.y + sliderBox.height / 2, { steps: 8 });
await page.mouse.up();
}
test("join household modal accepts invite links but rejects legacy invite codes", async ({ page }) => {
await seedAuthStorage(page);
await mockConfig(page);
@ -285,15 +304,13 @@ test("household owner can transfer ownership from household settings", async ({
});
});
page.on("dialog", async (dialog) => {
await dialog.accept();
});
await page.goto("/manage?tab=household");
await expect(page.getByRole("button", { name: "Make Owner" })).toBeVisible();
await page.getByRole("button", { name: "Make Owner" }).click();
await expect(page.getByText("Transfer ownership to nico-admin?")).toBeVisible();
await confirmSlide(page);
await expect(page.locator(".action-toast.action-toast-success")).toContainText("Transferred household ownership");
await expect(page.locator(".action-toast.action-toast-success")).toContainText("nico-admin");