Skip to content

Commit 4a36c98

Browse files
committed
fix(web): cancel stale invite dialog close timer
1 parent 9065a67 commit 4a36c98

2 files changed

Lines changed: 53 additions & 1 deletion

File tree

apps/web/src/components/InviteDialog.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export function InviteDialog({
111111
const rowsRef = useRef<HTMLDivElement | null>(null);
112112
const roleTriggerRefs = useRef<Array<HTMLButtonElement | null>>([]);
113113
const roleMenuRef = useRef<HTMLDivElement | null>(null);
114+
const autoCloseTimerRef = useRef<number | null>(null);
114115
const [roleMenuPos, setRoleMenuPos] = useState<CSSProperties | null>(null);
115116
const roleListboxId = useId();
116117

@@ -163,6 +164,12 @@ export function InviteDialog({
163164
if (!open) setOpenRoleIndex(null);
164165
}, [open]);
165166

167+
useEffect(() => () => {
168+
if (autoCloseTimerRef.current === null) return;
169+
window.clearTimeout(autoCloseTimerRef.current);
170+
autoCloseTimerRef.current = null;
171+
}, [open]);
172+
166173
// Reset the submit lifecycle each time the dialog opens so a prior error /
167174
// success never lingers on the next invite.
168175
useEffect(() => {
@@ -337,7 +344,8 @@ export function InviteDialog({
337344
}, { requestId });
338345
setSuccess(true);
339346
onSubmit?.(valid);
340-
window.setTimeout(() => {
347+
autoCloseTimerRef.current = window.setTimeout(() => {
348+
autoCloseTimerRef.current = null;
341349
onClose();
342350
setRows([{ email: '', role: DEFAULT_ROLE }]);
343351
setSuccess(false);

e2e/ui/workspace-team-interactions.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,50 @@ test('[P0] team owner completes a multi-row invite with explicit roles', async (
670670
await expect(dialog).toHaveCount(0, { timeout: 5_000 });
671671
});
672672

673+
test('[P1] stale invite success timer does not close a reopened dialog', async ({ page }) => {
674+
await page.addInitScript(
675+
({ workspaceId, workspaceMemberId }) => {
676+
window.sessionStorage.setItem(
677+
'od.workspaceSelection.v1',
678+
JSON.stringify({ workspaceId, workspaceMemberId }),
679+
);
680+
},
681+
{
682+
workspaceId: TEAM_OWNER.workspaceId,
683+
workspaceMemberId: TEAM_OWNER.workspaceMemberId,
684+
},
685+
);
686+
await wireWorkspaceMocks(page, TEAM_OWNER, [PERSONAL, TEAM_OWNER]);
687+
await gotoHome(page);
688+
await ensureRailOpen(page);
689+
await page.clock.install();
690+
await page.clock.pauseAt((await page.evaluate(() => Date.now())) + 60_000);
691+
692+
await page.getByTestId('workspace-switcher').click();
693+
await page.getByRole('menu').getByRole('menuitem', { name: 'Invite colleague' }).click();
694+
695+
const dialog = page.getByRole('dialog', { name: 'Invite members' });
696+
const emailInput = dialog.getByPlaceholder('Enter email address…').first();
697+
await emailInput.fill('first@example.com');
698+
await dialog.getByRole('button', { name: 'Confirm and invite' }).click();
699+
await expect(dialog.getByRole('button', { name: 'Invitation sent' })).toBeVisible();
700+
701+
await dialog.getByRole('button', { name: 'Close' }).click();
702+
await page.getByTestId('workspace-switcher').click();
703+
await page.getByRole('menu').getByRole('menuitem', { name: 'Invite colleague' }).click();
704+
await expect(dialog).toBeVisible();
705+
await emailInput.fill('second@example.com');
706+
707+
await page.clock.fastForward(999);
708+
await expect(dialog).toBeVisible();
709+
await expect(emailInput).toHaveValue('second@example.com');
710+
711+
await page.clock.fastForward(1);
712+
713+
await expect(dialog).toBeVisible();
714+
await expect(emailInput).toHaveValue('second@example.com');
715+
});
716+
673717
test('[P0] full team routes every invite entry to Vela seat resolution without opening the local dialog', async ({
674718
page,
675719
}) => {

0 commit comments

Comments
 (0)