Merge remote-tracking branch 'origin/dev' into feat/ab/sh-326-team-member

This commit is contained in:
Codex Review Integration 2026-09-17 14:36:45 -03:00
commit 827d52add7
2 changed files with 65 additions and 13 deletions

View file

@ -1,4 +1,4 @@
import { useEffect, useMemo, useState, type Dispatch, type SetStateAction } from "react";
import { useEffect, useMemo, useRef, useState, type Dispatch, type SetStateAction } from "react";
import {
Accordion,
AccordionDetails,
@ -278,15 +278,25 @@ export function TeamMemberFormFields({
export function AddTeamMemberDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
const [form, setForm] = useState(emptyForm);
const [submitted, setSubmitted] = useState(false);
const createTeamMember = useCreateTeamMember();
const mutationError = createTeamMember.error;
const submissionId = useRef(0);
const { error: mutationError, isPending, mutate, reset } = useCreateTeamMember();
useEffect(() => {
submissionId.current += 1;
reset();
if (open) {
setForm(emptyForm());
setSubmitted(false);
}
}, [open]);
}, [open, reset]);
const handleClose = () => {
if (isPending) return;
submissionId.current += 1;
reset();
onClose();
};
const emailIsValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email.trim());
const errors = useMemo(
@ -340,11 +350,18 @@ export function AddTeamMemberDialog({ open, onClose }: { open: boolean; onClose:
serviceAreas: form.serviceAreas,
permissionOverrides: permissionOverrides(form.role, form.permissions),
};
createTeamMember.mutate(input, { onSuccess: onClose });
const currentSubmissionId = submissionId.current;
mutate(input, {
onSuccess: () => {
if (submissionId.current !== currentSubmissionId) return;
reset();
onClose();
},
});
};
return (
<Dialog open={open} onClose={onClose} fullWidth maxWidth="md" scroll="paper">
<Dialog open={open} onClose={handleClose} fullWidth maxWidth="md" scroll="paper">
<DialogTitle>Add Member</DialogTitle>
<DialogContent dividers>
<Typography color="text.secondary" sx={{ mb: 3 }}>
@ -365,15 +382,11 @@ export function AddTeamMemberDialog({ open, onClose }: { open: boolean; onClose:
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose} disabled={createTeamMember.isPending}>
<Button onClick={handleClose} disabled={isPending}>
Cancel
</Button>
<Button variant="contained" onClick={submit} disabled={createTeamMember.isPending}>
{createTeamMember.isPending ? (
<CircularProgress size={20} color="inherit" />
) : (
"Add Member"
)}
<Button variant="contained" onClick={submit} disabled={isPending}>
{isPending ? <CircularProgress size={20} color="inherit" /> : "Add Member"}
</Button>
</DialogActions>
</Dialog>

View file

@ -11,11 +11,14 @@ vi.mock("@/domain/team-members/use-cases/use-create-team-member", () => ({
describe("AddTeamMemberDialog", () => {
const mutate = vi.fn();
const reset = vi.fn();
beforeEach(() => {
mutate.mockReset();
reset.mockReset();
vi.mocked(useCreateTeamMember).mockReturnValue({
mutate,
reset,
isPending: false,
error: null,
} as unknown as ReturnType<typeof useCreateTeamMember>);
@ -62,4 +65,40 @@ describe("AddTeamMemberDialog", () => {
expect.any(Object),
);
}, 15000);
it("does not close while a member is being created", async () => {
const user = userEvent.setup();
const onClose = vi.fn();
vi.mocked(useCreateTeamMember).mockReturnValue({
mutate,
reset,
isPending: true,
error: null,
} as unknown as ReturnType<typeof useCreateTeamMember>);
renderWithProviders(<AddTeamMemberDialog open onClose={onClose} />, { withAuth: false });
await user.keyboard("{Escape}");
expect(onClose).not.toHaveBeenCalled();
});
it("clears a previous mutation error when reopened", () => {
const mutation = {
mutate,
reset,
isPending: false,
error: new Error("Email is already in use."),
} as unknown as ReturnType<typeof useCreateTeamMember>;
vi.mocked(useCreateTeamMember).mockReturnValue(mutation);
const { rerender } = renderWithProviders(<AddTeamMemberDialog open onClose={vi.fn()} />, {
withAuth: false,
});
expect(screen.getByText("Email is already in use.")).toBeInTheDocument();
mutation.error = null;
rerender(<AddTeamMemberDialog open={false} onClose={vi.fn()} />);
rerender(<AddTeamMemberDialog open onClose={vi.fn()} />);
expect(screen.queryByText("Email is already in use.")).not.toBeInTheDocument();
});
});