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 11310x 10x 10x         10x     277x         10x   10x 548x 269x                                                                 285x                 277x       421x   277x 277x   272x   272x       183x       211x       148x       137x       267x                                        
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>;
}