Skip to content

Commit

Permalink
Feat/handle full team deletion (#553)
Browse files Browse the repository at this point in the history
  • Loading branch information
EduardZaydler authored Oct 23, 2024
1 parent eb28f8c commit 368e187
Show file tree
Hide file tree
Showing 18 changed files with 305 additions and 73 deletions.
6 changes: 5 additions & 1 deletion src/Components/ContactList/ContactList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,11 @@ const ContactList: React.FC<IContactListProps> = ({ contacts, contactDescription
onClick={() =>
deleteContact({
id,
isTeamContact: !!teamId,
tagsToInvalidate: [
teamId
? "TeamSettings"
: "UserSettings",
],
})
}
>
Expand Down
4 changes: 3 additions & 1 deletion src/Components/ModalError/ModalError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ const cn = classNames.bind(styles);
type FooterErrorProps = {
message?: string | null;
maxWidth?: string;
margin?: string | number;
};

export default function ModalError({
message,
maxWidth,
margin,
}: FooterErrorProps): React.ReactElement | null {
return message ? (
<div className={cn("root")}>
<div className={cn("root")} style={{ margin }}>
<div style={{ maxWidth }}>
<ErrorIcon /> {message}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/Components/TagListItem/TagListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const TagListItem: FC<ItemProps> = ({
subscription: Subscription
) => {
event.stopPropagation();
await deleteSubscription({ id: subscription.id });
await deleteSubscription({ id: subscription.id, tagsToInvalidate: ["TagStats"] });
};

const handleDeleteTag = async (tag: string) => {
Expand Down
36 changes: 26 additions & 10 deletions src/Components/Teams/Confirm.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import React, { ReactElement, ReactNode, useState } from "react";
import { Button, Tooltip } from "@skbkontur/react-ui";
import { Grid } from "../Grid/Grid";
import { Flexbox } from "../Flexbox/FlexBox";
import ModalError from "../ModalError/ModalError";

interface ConfirmProps {
message: string;
action: () => Promise<void> | void;
children: ReactNode;
loading?: boolean;
isLoading?: boolean;
errorMessage?: string;
}

export function Confirm({ message, action, children, loading }: ConfirmProps): ReactElement {
export function Confirm({
message,
action,
children,
isLoading,
errorMessage = "An error occurred.",
}: ConfirmProps): ReactElement {
const [opened, setOpened] = useState(false);
const [error, setError] = useState<string>("");

const handleConfirm = async () => {
await action();
setOpened(false);
try {
await action();
setOpened(false);
} catch (error) {
setError(error);
}
};

return (
Expand All @@ -26,20 +38,24 @@ export function Confirm({ message, action, children, loading }: ConfirmProps): R
onCloseRequest={() => setOpened(false)}
closeButton={false}
render={() => (
<Grid columns={"320px"} gap="16px 8px">
{message}
<Flexbox width={350} direction="column" gap={8}>
{error ? errorMessage : message}
<ModalError margin={"0 -35px 0 -30px"} maxWidth="300" message={error} />

<Flexbox direction="row" gap={8}>
<Button
loading={loading}
loading={isLoading}
onClick={handleConfirm}
use={"primary"}
width={100}
>
Confirm
</Button>
<Button onClick={() => setOpened(false)}>Cancel</Button>
<Button disabled={isLoading} onClick={() => setOpened(false)}>
Cancel
</Button>
</Flexbox>
</Grid>
</Flexbox>
)}
>
<span onClick={() => setOpened(true)}>{children}</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { FC, useState } from "react";
import { Button } from "@skbkontur/react-ui";
import { Flexbox } from "../../Flexbox/FlexBox";
import { Hovered, HoveredShow } from "../Hovered/Hovered";
import { Team } from "../../../Domain/Team";
import { Confirm } from "../Confirm";
import DeleteIcon from "@skbkontur/react-icons/Delete";
import { useFyllyDeleteTeam } from "../../../hooks/useFullyDeleteTeam";
import { fullyDeleteTeamConfirmText } from "../../../helpers/teamOperationsConfirmMessages";

interface IConfirmFullTeamDeleteionProps {
team: Team;
}

export const ConfirmFullTeamDeleteion: FC<IConfirmFullTeamDeleteionProps> = ({
team,
}: IConfirmFullTeamDeleteionProps) => {
const { id: teamId, name: teamName } = team;
const [isConfirmOpened, setIsConfirmOpened] = useState<boolean>(false);

const {
handleFullyDeleteTeam,
isFetchingData,
isDeletingContacts,
isDeletingSubscriptions,
isDeletingUsers,
isDeletingTeam,
} = useFyllyDeleteTeam(teamId, !isConfirmOpened);

const confirmMessage = fullyDeleteTeamConfirmText(
isFetchingData,
isDeletingContacts,
isDeletingSubscriptions,
isDeletingUsers,
isDeletingTeam,
teamName
);

const isLoading =
isFetchingData ||
isDeletingContacts ||
isDeletingSubscriptions ||
isDeletingUsers ||
isDeletingTeam;

return (
<Hovered>
<Flexbox align="baseline" direction="row" gap={8}>
<h2>{teamName}</h2>
<HoveredShow>
<Confirm
isLoading={isLoading}
message={confirmMessage}
action={handleFullyDeleteTeam}
errorMessage="An error occurred during full team deletion."
>
<Button
data-tid={`Delete team ${teamName}`}
use={"link"}
icon={<DeleteIcon />}
onClick={() => setIsConfirmOpened(true)}
/>
</Confirm>
</HoveredShow>
</Flexbox>
</Hovered>
);
};
30 changes: 2 additions & 28 deletions src/Components/Teams/Team/Team.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import React, { ReactElement } from "react";
import { Button } from "@skbkontur/react-ui";
import { Flexbox } from "../../Flexbox/FlexBox";
import EditIcon from "@skbkontur/react-icons/Edit";
import DeleteIcon from "@skbkontur/react-icons/Delete";
import { Team } from "../../../Domain/Team";
import { TeamEditor } from "../TeamEditor/TeamEditor";
import { Markdown } from "../../Markdown/Markdown";
import { Hovered, HoveredShow } from "../Hovered/Hovered";
import { Confirm } from "../Confirm";
import { useDeleteTeamMutation } from "../../../services/TeamsApi";
import { ConfirmFullTeamDeleteion } from "../ConfirmFullTeamDeletion/ConfirmFullTeamDeletion";
import { useModal } from "../../../hooks/useModal";
import classNames from "classnames/bind";

Expand All @@ -22,32 +18,10 @@ interface ITeamProps {

export function Team({ team }: ITeamProps): ReactElement {
const { isModalOpen, openModal, closeModal } = useModal();
const [deleteTeam, { isLoading: isDeleting }] = useDeleteTeamMutation();

const handleDeleteTeam = async () => {
await deleteTeam({ teamId: team.id, handleLoadingLocally: true });
};

return (
<>
<Hovered>
<Flexbox align="baseline" direction="row" gap={8}>
<h2>{team.name}</h2>
<HoveredShow>
<Confirm
message={`Do you really want to remove "${team.name}" team?`}
action={handleDeleteTeam}
loading={isDeleting}
>
<Button
data-tid={`Delete team ${team.name}`}
use={"link"}
icon={<DeleteIcon />}
/>
</Confirm>
</HoveredShow>
</Flexbox>
</Hovered>
<ConfirmFullTeamDeleteion team={team} />

{team.description && (
<div className={cn("wysiwyg", "descriptionContainer")}>
Expand Down
13 changes: 10 additions & 3 deletions src/Components/Teams/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AddUserToTeam } from "./AddUserToTeam";
import { useGetUserQuery } from "../../services/UserApi";
import { useDeleteUserFromTeamMutation, useGetTeamUsersQuery } from "../../services/TeamsApi";
import { useModal } from "../../hooks/useModal";
import { getExcludeYourselfFromTeamMessage } from "../../helpers/getExcludeYourselfFromTeamMessage";
import { getExcludeYourselfFromTeamMessage } from "../../helpers/teamOperationsConfirmMessages";

interface UsersProps {
team: Team;
Expand All @@ -35,7 +35,13 @@ export function Users({ team }: UsersProps): ReactElement {
};

const handleUserRemove = async (userName: string) => {
await deleteUserFromTeam({ teamId: team.id, userName, handleLoadingLocally: true });
await deleteUserFromTeam({
teamId: team.id,
userName,
handleLoadingLocally: true,
handleErrorLocally: true,
tagsToInvalidate: ["TeamUsers"],
}).unwrap();
};

return (
Expand All @@ -49,13 +55,14 @@ export function Users({ team }: UsersProps): ReactElement {
{users.map((userName) => (
<Fragment key={userName}>
<Confirm
errorMessage="An error occured during user deletion."
message={getExcludeYourselfFromTeamMessage(
userName,
team.name,
user?.login
)}
action={() => handleUserRemove(userName)}
loading={isDeletingUser}
isLoading={isDeletingUser}
>
<Button
data-tid={`Delete user ${userName}`}
Expand Down
10 changes: 0 additions & 10 deletions src/helpers/getExcludeYourselfFromTeamMessage.ts

This file was deleted.

34 changes: 34 additions & 0 deletions src/helpers/teamOperationsConfirmMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const getExcludeYourselfFromTeamMessage = (
userName: string,
teamName: string,
currentUserLogin?: string
) => {
if (userName === currentUserLogin) {
return `You are trying to exclude yourself from the "${teamName}". If you do this, you will no longer be able to access this team. Are you sure you want to exclude yourself?`;
}
return `Exclude "${userName}" from "${teamName}"?`;
};

export const fullyDeleteTeamConfirmText = (
isFetchingData: boolean,
isDeletingContacts: boolean,
isDeletingSubscriptions: boolean,
isDeletingUsers: boolean,
isDeletingTeam: boolean,
teamName: string
) => {
switch (true) {
case isFetchingData:
return "Fetching data...";
case isDeletingContacts:
return "Deleting contacts...";
case isDeletingSubscriptions:
return "Deleting subscriptions...";
case isDeletingUsers:
return "Deleting users...";
case isDeletingTeam:
return "Deleting team...";
default:
return `Do you really want to remove "${teamName}" team? This action will also delete all team users, contacts and subscriptions.`;
}
};
40 changes: 40 additions & 0 deletions src/hooks/useDeleteAllUsersFromTeam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useDeleteUserFromTeamMutation, useGetTeamUsersQuery } from "../services/TeamsApi";
import { useGetUserQuery } from "../services/UserApi";
import { useCallback } from "react";

export const useDeleteAllUsersFromTeam = (teamId: string, skip?: boolean) => {
const { data: users, isLoading: isGettingUsers } = useGetTeamUsersQuery(
{
teamId,
handleLoadingLocally: true,
handleErrorLocally: true,
},
{ skip }
);
const { data: currentUser, isLoading: isGettingCurrentUser } = useGetUserQuery(
{
handleLoadingLocally: true,
handleErrorLocally: true,
},
{ skip }
);
const [deleteUserFromTeam, { isLoading: isDeletingUsers }] = useDeleteUserFromTeamMutation();

const usersToDelete = users?.filter((user) => user !== currentUser?.login);

const deleteAllUsersFromTeam = useCallback(async () => {
if (usersToDelete)
for (const user of usersToDelete) {
await deleteUserFromTeam({
teamId,
userName: user,
handleLoadingLocally: true,
handleErrorLocally: true,
}).unwrap();
}
}, [usersToDelete]);

const isGettingTeamUsers = isGettingCurrentUser || isGettingUsers;

return { isGettingTeamUsers, isDeletingUsers, deleteAllUsersFromTeam };
};
2 changes: 1 addition & 1 deletion src/hooks/useDeleteContact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export const useDeleteContact = (
try {
await deleteContact({
id: contact.id,
isTeamContact: !!teamId,
handleLoadingLocally: true,
handleErrorLocally: true,
tagsToInvalidate: [teamId ? "TeamSettings" : "UserSettings", "Contacts"],
}).unwrap();
onCancel();
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useDeleteSubscription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export const useDeleteSubscription = (
try {
await deleteSubscription({
id: subscription.id,
isTeamSubscription: !!teamId,
handleLoadingLocally: true,
handleErrorLocally: true,
tagsToInvalidate: [teamId ? "TeamSettings" : "UserSettings", "TagStats"],
}).unwrap();
onCancel();
} catch (error) {
Expand Down
Loading

0 comments on commit 368e187

Please sign in to comment.