All files / app/components UsersList.tsx

100% Statements 24/24
90.47% Branches 19/21
100% Functions 10/10
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 112 11311x 11x 11x         11x     295x         11x   11x 585x 287x                                                                 306x                 295x       449x   295x 295x   290x   290x       195x       225x       156x       147x       287x                                        
import { 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),
}));
 
// how many skeleton rows to show before we know how many users there are
const PLACEHOLDER_COUNT = 3;
 
const StyledUsersDiv = styled("div", {
  shouldForwardProp: (prop) => prop !== "layout",
})<{ layout?: "list" | "grid" }>(({ theme, layout = "list" }) => ({
  marginBlockStart: theme.spacing(2),
  display: "grid",
  gridTemplateColumns: "minmax(0, 1fr)",
  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.
 *
 * Skeletons show 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 (
        <StyledUsersDiv layout={layout}>
          {Array.from({ length: PLACEHOLDER_COUNT }).map((_, i) => (
            <UserSummary headlineComponent="h3" key={i} user={undefined} />
          ))}
        </StyledUsersDiv>
      );
    } 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>;
}