mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 13:59:00 +00:00
feat(team-members): warn when the invite to a corrected email could not be sent
This commit is contained in:
parent
ca42d6e59f
commit
383d5c3c6c
5 changed files with 97 additions and 1 deletions
|
|
@ -88,9 +88,12 @@ export function mapTeamMemberListItems(rawItems: unknown[]): TeamMemberListItem[
|
||||||
export function mapTeamMemberDetail(raw: unknown): TeamMemberDetail {
|
export function mapTeamMemberDetail(raw: unknown): TeamMemberDetail {
|
||||||
const record = (typeof raw === "object" && raw !== null ? raw : {}) as Record<string, unknown>;
|
const record = (typeof raw === "object" && raw !== null ? raw : {}) as Record<string, unknown>;
|
||||||
const listItem = mapTeamMemberListItem(record);
|
const listItem = mapTeamMemberListItem(record);
|
||||||
|
// Present only on an update that re-sent a pending member's invite.
|
||||||
|
const inviteEmailSent = record.inviteEmailSent ?? record.InviteEmailSent;
|
||||||
return {
|
return {
|
||||||
...listItem,
|
...listItem,
|
||||||
serviceAreas: readStringArray(record, "serviceAreas", "ServiceAreas"),
|
serviceAreas: readStringArray(record, "serviceAreas", "ServiceAreas"),
|
||||||
permissions: readPermissions(record),
|
permissions: readPermissions(record),
|
||||||
|
...(typeof inviteEmailSent === "boolean" && { inviteEmailSent }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ export interface TeamPermissionValue {
|
||||||
export interface TeamMemberDetail extends TeamMemberListItem {
|
export interface TeamMemberDetail extends TeamMemberListItem {
|
||||||
serviceAreas: string[];
|
serviceAreas: string[];
|
||||||
permissions: TeamPermissionValue[];
|
permissions: TeamPermissionValue[];
|
||||||
|
/** Set only after an update that changed a pending member's email and re-sent their invite. */
|
||||||
|
inviteEmailSent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateTeamMemberInput {
|
export interface UpdateTeamMemberInput {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,15 @@ export function useTeamMemberDetail(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const UPDATED_INVITE_EMAIL_FAILED_MESSAGE =
|
||||||
|
"Team member updated, but the invite to their new email couldn't be sent. Use Resend invite.";
|
||||||
|
|
||||||
|
function updatedMessage(member: TeamMemberDetail): string {
|
||||||
|
return member.inviteEmailSent === true
|
||||||
|
? `Team member updated. A new invite was sent to ${member.email}.`
|
||||||
|
: "Team member updated.";
|
||||||
|
}
|
||||||
|
|
||||||
interface UpdateVariables {
|
interface UpdateVariables {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
input: UpdateTeamMemberInput;
|
input: UpdateTeamMemberInput;
|
||||||
|
|
@ -36,7 +45,11 @@ export function useUpdateTeamMember(): UseMutationResult<TeamMemberDetail, Error
|
||||||
onSuccess: (member) => {
|
onSuccess: (member) => {
|
||||||
void queryClient.invalidateQueries({ queryKey: queryKeys.teamMembers.all });
|
void queryClient.invalidateQueries({ queryKey: queryKeys.teamMembers.all });
|
||||||
void queryClient.setQueryData(queryKeys.teamMembers.detail(member.id), member);
|
void queryClient.setQueryData(queryKeys.teamMembers.detail(member.id), member);
|
||||||
toast.success("Team member updated.");
|
if (member.inviteEmailSent === false) {
|
||||||
|
toast.warning(UPDATED_INVITE_EMAIL_FAILED_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast.success(updatedMessage(member));
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
toast.error(error.message || "Failed to update team member");
|
toast.error(error.message || "Failed to update team member");
|
||||||
|
|
|
||||||
|
|
@ -54,4 +54,10 @@ describe("mapTeamMemberDetail", () => {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps the invite email result only when the update reported one", () => {
|
||||||
|
expect(mapTeamMemberDetail({ id: "u1", inviteEmailSent: false }).inviteEmailSent).toBe(false);
|
||||||
|
expect(mapTeamMemberDetail({ Id: "u1", InviteEmailSent: true }).inviteEmailSent).toBe(true);
|
||||||
|
expect(mapTeamMemberDetail({ id: "u1" })).not.toHaveProperty("inviteEmailSent");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
72
src/test/domain/team-members/use-update-team-member.test.tsx
Normal file
72
src/test/domain/team-members/use-update-team-member.test.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { act, renderHook, waitFor } from "@testing-library/react";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { teamMembersApi } from "@/domain/team-members/api/team-members-api";
|
||||||
|
import type {
|
||||||
|
TeamMemberDetail,
|
||||||
|
UpdateTeamMemberInput,
|
||||||
|
} from "@/domain/team-members/types/team-member";
|
||||||
|
import {
|
||||||
|
UPDATED_INVITE_EMAIL_FAILED_MESSAGE,
|
||||||
|
useUpdateTeamMember,
|
||||||
|
} from "@/domain/team-members/use-cases/use-team-member-detail";
|
||||||
|
|
||||||
|
vi.mock("react-toastify", () => ({
|
||||||
|
toast: { success: vi.fn(), warning: vi.fn(), error: vi.fn() },
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/domain/team-members/api/team-members-api", () => ({
|
||||||
|
teamMembersApi: { update: vi.fn() },
|
||||||
|
}));
|
||||||
|
|
||||||
|
const UPDATED = {
|
||||||
|
id: "user-7",
|
||||||
|
name: "Taylor Reed",
|
||||||
|
email: "taylor.reed@example.com",
|
||||||
|
pendingRegistration: true,
|
||||||
|
serviceAreas: ["East"],
|
||||||
|
permissions: [],
|
||||||
|
} as unknown as TeamMemberDetail;
|
||||||
|
|
||||||
|
function wrapper({ children }: { children: ReactNode }) {
|
||||||
|
const client = new QueryClient({ defaultOptions: { mutations: { retry: false } } });
|
||||||
|
return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update(updated: TeamMemberDetail) {
|
||||||
|
vi.mocked(teamMembersApi.update).mockResolvedValue(updated);
|
||||||
|
const { result } = renderHook(() => useUpdateTeamMember(), { wrapper });
|
||||||
|
act(() => result.current.mutate({ id: "user-7", input: {} as UpdateTeamMemberInput }));
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("useUpdateTeamMember", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("confirms an ordinary update", async () => {
|
||||||
|
await update(UPDATED);
|
||||||
|
|
||||||
|
expect(toast.success).toHaveBeenCalledWith("Team member updated.");
|
||||||
|
expect(toast.warning).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("tells the admin a new invite went to the corrected email", async () => {
|
||||||
|
await update({ ...UPDATED, inviteEmailSent: true });
|
||||||
|
|
||||||
|
expect(toast.success).toHaveBeenCalledWith(
|
||||||
|
"Team member updated. A new invite was sent to taylor.reed@example.com.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("warns the admin to re-send when the new invite could not be emailed", async () => {
|
||||||
|
await update({ ...UPDATED, inviteEmailSent: false });
|
||||||
|
|
||||||
|
expect(toast.warning).toHaveBeenCalledWith(UPDATED_INVITE_EMAIL_FAILED_MESSAGE);
|
||||||
|
expect(toast.success).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue