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 11210x 10x 10x         10x     245x       10x 378x 184x                                                                   249x                         245x       245x   373x     245x 245x   240x   240x 53x 187x       132x       121x       235x                                        
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>;
}