All files / app/components UsersList.tsx

100% Statements 23/23
90.47% Branches 19/21
100% Functions 9/9
100% Lines 21/21

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 1129x 9x 9x         9x     213x       9x 313x 152x                                                                   271x                         213x       213x   309x     213x 213x   208x   208x 53x 155x       132x       89x       171x                                        
import { CircularProgress, styled } from "@mui/material";
import UserSummary from "components/UserSummary";
import { useLiteUsers } from "features/userQueries/useLiteUsers";
import { RpcError } from "grpc-web";
import { LiteUser } from "proto/api_pb";
import { ReactNode } from "react";
 
import Alert from "./Alert";
import { EllipsisMenuItem } from "./EllipsisMenu";
 
const ContainingDiv = styled("div")(({ theme }) => ({
  padding: theme.spacing(2),
}));
 
const StyledUsersDiv = styled("div", {
  shouldForwardProp: (prop) => prop !== "layout",
})<{ layout?: "list" | "grid" }>(({ theme, layout = "list" }) => ({
  marginBlockStart: theme.spacing(2),
  display: "grid",
  gap: theme.spacing(1),
  ...(layout === "grid" && {
    gridAutoRows: "6rem",
    [theme.breakpoints.up("sm")]: {
      gridAutoRows: "5.5rem",
      gridTemplateColumns: "repeat(auto-fit, minmax(19.5rem, 1fr))",
    },
  }),
}));
 
interface UsersListProps {
  userIds: number[] | undefined;
  emptyListChildren?: ReactNode;
  endChildren?: ReactNode;
  error?: RpcError | null;
  titleIsLink?: boolean;
  layout?: "list" | "grid";
  getUserMenuItems?: (
    user: LiteUser.AsObject,
  ) => EllipsisMenuItem[] | undefined;
}
 
/**
 * A cute list of <UserSummary> components for each userId. Automatically fetches the user info.
 *
 * A spinner shows up while `userIds` is `undefined`. When this component is fetching the lite users, it will show skeletons (the right number).
 *
 * If any users are not found or userIds is an empty list, this will show `emptyListChildren`.
 *
 * The end of the list will show `endChildren` if the list is not empty (this is a good place to add a "load more" button)
 */
export default function UsersList({
  userIds,
  emptyListChildren,
  endChildren,
  error,
  titleIsLink = false,
  layout = "list",
  getUserMenuItems,
}: UsersListProps) {
  const {
    data: users,
    isLoading: isLoadingLiteUsers,
    error: usersError,
  } = useLiteUsers(userIds);
 
  // this is undefined if userIds is undefined or users hasn't loaded, otherwise it's an actual list
  const foundUsers =
    userIds &&
    (userIds.length > 0
      ? userIds.map((userId) => users?.get(userId)).filter((user) => !!user)
      : []);
 
  const inner = () => {
    if (error) {
      return <Alert severity="error">{error.message}</Alert>;
    } else Iif (usersError) {
      return <Alert severity="error">{usersError.message}</Alert>;
    } else if (!userIds) {
      return <CircularProgress />;
    } else if (isLoadingLiteUsers) {
      return (
        <StyledUsersDiv layout={layout}>
          {userIds.map((userId) => (
            <UserSummary headlineComponent="h3" key={userId} user={undefined} />
          ))}
        </StyledUsersDiv>
      );
    } else if (foundUsers && foundUsers.length > 0) {
      return (
        <StyledUsersDiv layout={layout}>
          {foundUsers.map((user) => {
            return (
              <UserSummary
                headlineComponent="h3"
                key={user.userId}
                user={user}
                titleIsLink={titleIsLink}
                menuItems={getUserMenuItems?.(user)}
              />
            );
          })}
          <>{endChildren}</>
        </StyledUsersDiv>
      );
    } else {
      return <>{emptyListChildren}</>;
    }
  };
 
  return <ContainingDiv>{inner()}</ContainingDiv>;
}