@material-ui/core#ListItemAvatar JavaScript Examples

The following examples show how to use @material-ui/core#ListItemAvatar. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: SideBar.js    From surveillance-forms with MIT License 6 votes vote down vote up
SideBarCard = ({ user, onLanguageSelect, lang, langCode }) => {
  const classes = useStyles();
  //TODO: this could be done better
  const fields = {
    label: lang.t("note.lable"),
    notes: [
      lang.t("note.note1"),
      lang.t("note.note2"),
      lang.t("note.note3"),
      lang.t("note.note4"),
    ],
  };

  return (
    <Card className={classes.card}>
      <CardContent>
        <Typography variant="h6" component="h2">
          {fields.label}
        </Typography>
        <List>
          {fields.notes.map((el, index) => {
            return (
              <ListItem key={index} disableGutters>
                <ListItemAvatar style={{ flexShrink: 1 }}>
                  <Avatar style={{ background: '#fff', margin: 0 }}>
                    <CheckIcon style={{ fill: 'green', width: 20 }}/>
                  </Avatar>
                </ListItemAvatar>
                <ListItemText className="sidebarText">{el}</ListItemText>
              </ListItem>
            );
          })}
        </List>
      </CardContent>
    </Card>
  );
}
Example #2
Source File: ChatList.jsx    From react-03.03 with MIT License 6 votes vote down vote up
ChatList = ({ chats, createChat }) => {
  const classes = useStyles();
  const [name, setName, setNameState] = useInput('');
  const handleAddChat = (event) => {
    event.preventDefault();
    createChat(name);
    setNameState('');
  }

  return (
    <List className={classes.root}>

      { chats.map(({id, name}) => {
          return (
            <Link key={id} to={`/chats/${id}`}>
              <ListItem className={classes.listItem}>
                <ListItemAvatar>
                  <Avatar />
                </ListItemAvatar>
                <ListItemText
                  primary={name}
                />
              </ListItem>
            </Link>
          )
        })
      }
      <ListItem className={classes.listItem}>
        <form onSubmit={handleAddChat}>
          <input type="text" placeholder="Chat name" value={name} onChange={setName} />
          <button>Create chat</button>
        </form>
      </ListItem>
    </List>
  )
}
Example #3
Source File: ToolbarExtension.js    From eSim-Cloud with GNU General Public License v3.0 6 votes vote down vote up
export function ImageExportDialog (props) {
  const classes = useStyles()
  const { onClose, open } = props

  const handleClose = () => {
    onClose('')
  }

  const handleListItemClick = (value) => {
    onClose(value)
  }

  return (
    <Dialog onClose={handleClose} aria-labelledby="image-export-dialog-title" open={open}>
      <DialogTitle id="image-export-dialog-title">Select Image type</DialogTitle>
      <List>
        {ImgTypes.map((img) => (
          <ListItem button onClick={() => handleListItemClick(img)} key={img}>
            <ListItemAvatar>
              <Avatar className={classes.avatar}>
                {img.charAt(0).toUpperCase()}
              </Avatar>
            </ListItemAvatar>
            <ListItemText primary={img} />
          </ListItem>
        ))}
      </List>
      <DialogActions>
        <Button onClick={handleClose} color="primary" autoFocus>
          Close
        </Button>
      </DialogActions>
    </Dialog>
  )
}
Example #4
Source File: firstTimersOnlyIssue.js    From GSOD.Contributors_website with Mozilla Public License 2.0 5 votes vote down vote up
function GetIssues(props){
    const classes = useStyles();
    const {
    repo: [repo, setRepo]} = {repo: useState('GCBM.Visualisation_Tool'),...(props.state || {})};
    console.log({repo});
    const { loading, error, data } = useQuery(GET_LABELLED_ISSUES, {
      variables: { repo: repo},
    });
    //const { loading, error, data } = useQuery(GET_LABELLED_ISSUES, {variables: {state.name} ,});
    console.log(data);

    if (loading) return <p>Loading...</p>;
    console.log(error);
    if (error) return <p>Error :(</p>;

    return data.repository.issues.edges.map(({ node }) => (
      <div>
      <ListItem alignItems="flex-start">
        <ListItemAvatar>
          <Avatar alt={node.author.login} src={node.author.avatarUrl} />
        </ListItemAvatar>
        <ListItemLink href={node.url}>
        <ListItemText
          primary={node.title}
          secondary={
            <React.Fragment>
              <Typography
                component="span"
                variant="body2"
                className={classes.inline}
                color="textPrimary"
              >
                #{node.number}
              </Typography>
              " — " {node.createdAt}
            </React.Fragment>
          }
        />
        </ListItemLink>
      </ListItem>
      <Divider variant="inset" component="li" />
      </div>
    ));
  }
Example #5
Source File: PublicComments.js    From app with MIT License 5 votes vote down vote up
function CommentList({ requestId }) {
  const classes = useStyles();
  const firestore = useFirestore();

  const querySnapshot = useFirestoreCollection(
    firestore
      .collection(`${REQUESTS_COMMENTS_PUBLIC_COLLECTION}`)
      .where('requestId', '==', requestId)
      .orderBy('createdAt', 'asc'),
  );

  if (querySnapshot.empty) {
    return (
      <Box color="text.disabled">
        <Typography
          variant="body2"
          className={classes.noComments}
          data-test="no-comments">
          No public comments.
        </Typography>
      </Box>
    );
  }

  return (
    <List>
      {querySnapshot.docs.map(
        (docSnap) =>
          // When new comment is added locally, the createdAt can be the serverTimestamp() value.
          // So, we wait on rendering until any new snapshot has finished writing.
          !docSnap.metadata.hasPendingWrites && (
            <ListItem
              key={docSnap.id}
              divider
              alignItems="flex-start"
              data-test="public-comment">
              <ListItemAvatar>
                <Avatar>{docSnap.get('author.firstName').slice(0, 1)}</Avatar>
              </ListItemAvatar>
              <ListItemText
                disableTypography
                primary={
                  <Typography variant="subtitle2" data-test="comment-author">
                    {docSnap.get('author.firstName')} &ndash;{' '}
                    <Typography
                      variant="body2"
                      display="inline"
                      color="textSecondary">
                      {format(docSnap.get('createdAt').toDate(), 'p - PPPP')}
                    </Typography>
                  </Typography>
                }
                secondary={docSnap
                  .get('content')
                  .split('\n')
                  .map((content, key) => (
                    <Typography
                      variant="body1"
                      /* eslint-disable react/no-array-index-key */
                      key={key}
                      /* eslint-enable react/no-array-index-key */
                      gutterBottom
                      data-test="comment-content">
                      {content}
                    </Typography>
                  ))}
              />
            </ListItem>
          ),
      )}
    </List>
  );
}
Example #6
Source File: Inbox.js    From treetracker-admin-client with GNU Affero General Public License v3.0 5 votes vote down vote up
Inbox = ({ threads, selected, handleListItemClick }) => {
  const { paper, searchInbox, list, listItem, listText, avatar } = useStyles();
  const [search, setSearch] = useState('');

  return (
    <Paper className={paper}>
      <List className={list}>
        {threads
          .filter((thread) => {
            if (search === '') {
              return thread;
            } else if (
              thread.userName.toLowerCase().includes(search.toLowerCase())
            ) {
              return thread;
            }
          })
          .map((thread, i) => (
            <ListItem
              key={`${thread.userName}-${i}`}
              alignItems="flex-start"
              className={listItem}
              selected={thread.userName === selected}
              onClick={() => handleListItemClick(thread.userName)}
            >
              <ListItemAvatar>
                {thread.messages[0].type === 'message' ? (
                  <Avatar src={thread.avatar} className={avatar}></Avatar>
                ) : thread.messages[0].type === 'announce' ? (
                  <Announcement color="inherit" />
                ) : (
                  <Ballot color="inherit" />
                )}
              </ListItemAvatar>
              {thread.messages[0].type === 'survey' ||
              thread.messages[0].type === 'announce' ? (
                <ListItemText
                  primary={thread.messages[0].subject}
                  secondary={
                    thread.messages[0].subject
                      ? thread.messages[0].composed_at.slice(0, 10)
                      : thread.userName
                  }
                  className={listText}
                />
              ) : (
                <ListItemText primary={thread.userName} className={listText} />
              )}
              <Typography>
                {timeAgoFormatDate(
                  new Date(
                    thread.messages[thread.messages.length - 1].composed_at
                  )
                )}
              </Typography>
            </ListItem>
          ))}
      </List>
      <SearchInbox className={searchInbox} setSearch={setSearch} />
    </Paper>
  );
}
Example #7
Source File: NotificationsPopover.js    From course-manager with MIT License 5 votes vote down vote up
function NotificationItem({ notification }) {
  const { avatar, title } = renderContent(notification);

  return (
    <ListItem
      button
      to="#"
      disableGutters
      component={RouterLink}
      sx={{
        py: 1.5,
        px: 2.5,
        mt: '1px',
        ...(notification.isUnRead && {
          bgcolor: 'action.selected'
        })
      }}
    >
      <ListItemAvatar>
        <Avatar sx={{ bgcolor: 'background.neutral' }}>{avatar}</Avatar>
      </ListItemAvatar>
      <ListItemText
        primary={title}
        secondary={
          <Typography
            variant="caption"
            sx={{
              mt: 0.5,
              display: 'flex',
              alignItems: 'center',
              color: 'text.disabled'
            }}
          >
            <Box component={Icon} icon={clockFill} sx={{ mr: 0.5, width: 16, height: 16 }} />
            {formatDistanceToNow(new Date(notification.createdAt))}
          </Typography>
        }
      />
    </ListItem>
  );
}
Example #8
Source File: ParticipantsList.js    From SyntaxMeets with MIT License 5 votes vote down vote up
function ParticipantsList(props) {
	const classes = useStyles();
	const { users } = props;
	const [openList, setOpenList] = useState(false);

	const renderParticipants = () => {
		return Object.keys(users).map(elem => {
			const name = users[elem];
			return (
				<>
					<ListItem>
						<ListItemAvatar>
							<Avatar style={{ fontWeight: "bold" }}>
								{nameGenerator(name.split(" "))}
							</Avatar>
						</ListItemAvatar>
						<ListItemText
							style={{
								borderRadius: "10px",
								padding: "10px",
								color: "rgb(62 53 53)",
								border: "solid rgb(62 53 53) 1px",
								textAlign: "center",
								fontWeight: "bolder",
								wordWrap: "break-word",
								overflowWrap: "break-word",
								hyphens: "auto",
								WebkitHyphens: "auto",
							}}
							primary={name}
						/>
					</ListItem>
				</>
			);
		});
	};
	return (
		<div>
			<Button
				onClick={() => setOpenList(true)}
				variant="contained"
				color="primary"
				startIcon={<GroupIcon />}
				style={{
					fontFamily: "poppins",
					marginLeft: "15px",
					fontWeight: "600",
					color: "white",
				}}
			>
				Participants [ {Object.keys(users).length} ]
			</Button>
			<Drawer
				anchor={"right"}
				open={openList}
				onClose={() => setOpenList(false)}
			>
				<CloseSharpIcon
					style={{ padding: "5px", fontSize: "3em", cursor: "pointer" }}
					onClick={() => setOpenList(false)}
				/>
				<div
					className={classes.list}
					style={{
						display: "flex",
						flexDirection: "column",
						minHeight: "100%",
					}}
					role="presentation"
				>
					<List>{renderParticipants()}</List>
				</div>
			</Drawer>
		</div>
	);
}
Example #9
Source File: ChatMessage.js    From SyntaxMeets with MIT License 5 votes vote down vote up
ChatMessage = (props) => {
  return props.messages.map((data) => (
    <ListItem
      style={{
        flexDirection: `${
          localStorage.getItem("my_name") === data.name ? "row-reverse" : "row"
        }`,
      }}
    >
      <ListItemAvatar
        style={{
          padding: `${
            localStorage.getItem("my_name") === data.name ? "0.6rem" : "unset"
          }`,
        }}
      >
        <Avatar>{nameGenerator(data.name.split(" "))}</Avatar>
      </ListItemAvatar>
      <ListItemText
        style={{
          paddingTop: "5px",
          borderRadius: "15px",
          paddingLeft: "20px",
          paddingBottom: "10px",
          backgroundColor: `${
            localStorage.getItem("my_name") === data.name
              ? "#00b4d8"
              : "#f0f0f0"
          }`,
          color: `${
            localStorage.getItem("my_name") === data.name ? "#fff" : "#404040"
          }`,
        }}
        primary={
          <span
            style={{
              color: `${
                localStorage.getItem("my_name") === data.name
                  ? "#fff"
                  : "#404040"
              }`,
            }}
          >
            <em>{data.name}</em>
          </span>
        }
        secondary={
          <span
            style={{
              color: `${
                localStorage.getItem("my_name") === data.name
                  ? "#fff"
                  : "#404040"
              }`,
              wordWrap: "break-word",
              overflowWrap: "break-word",
              hyphens: "auto",
              WebkitHyphens: "auto",
            }}
          >
            {data.message}
          </span>
        }
      />
    </ListItem>
  ));
}
Example #10
Source File: LatestProducts.js    From EMP with MIT License 5 votes vote down vote up
LatestProducts = () => {
  const classes = useStyles();
  const [products] = useState(data);

  return (
    <Card>
      <CardHeader
        subtitle={`${products.length} in total`}
        title="Latest Products"
      />
      <Divider />
      <List>
        {products.map((product, i) => (
          <ListItem
            divider={i < products.length - 1}
            key={product.id}
          >
            <ListItemAvatar>
              <img
                alt="Product"
                className={classes.image}
                src={product.imageUrl}
              />
            </ListItemAvatar>
            <ListItemText
              primary={product.name}
              secondary={`Updated ${product.updatedAt.fromNow()}`}
            />
            <IconButton
              edge="end"
              size="small"
            >
              <MoreVertIcon />
            </IconButton>
          </ListItem>
        ))}
      </List>
      <Divider />
      <Box
        display="flex"
        justifyContent="flex-end"
        p={2}
      >
        <Button
          color="primary"
          endIcon={<ArrowRightIcon />}
          size="small"
          variant="text"
        >
          View all
        </Button>
      </Box>
    </Card>
  );
}
Example #11
Source File: DashboardSidebar.js    From eSim-Cloud with GNU General Public License v3.0 5 votes vote down vote up
// Vertical Navbar for user dashboard
export default function DashSidebar (props) {
  const classes = useStyles()
  const auth = useSelector(state => state.authReducer)
  const dispatch = useDispatch()
  // For Fetching Saved Schematics
  useEffect(() => {
    dispatch(fetchSchematics())
    dispatch(fetchOtherProjects())
    dispatch(fetchRole())
  }, [dispatch])

  return (
    <>
      <Hidden smDown>
        <div className={classes.toolbar} />
      </Hidden>
      <List>
        <ListItem
          alignItems="flex-start"
          component={RouterLink}
          to="/dashboard"
          style={{ marginTop: '15px' }}
          className={classes.sideItem}
          button
          divider
        >
          <ListItemAvatar>
            <Avatar className={classes.purple}>
              {auth.user.username.charAt(0).toUpperCase()}
            </Avatar>
          </ListItemAvatar>
          <ListItemText
            primary={auth.user.username}
            secondary={
              <React.Fragment>
                <Typography
                  component="span"
                  variant="body2"
                  color="textSecondary"
                >
                  {auth.roles !== null && auth.roles.group.map((value, key) => (<h3 key={value}>{value}</h3>))}
                </Typography>
              </React.Fragment>
            }
          />
        </ListItem>

        <ListItem
          component={RouterLink}
          to="/dashboard/profile"
          className={classes.sideItem}
          button
          divider
        >
          <ListItemText primary='My Profile' />
        </ListItem>
        <ListItem
          component={RouterLink}
          to="/dashboard/schematics"
          className={classes.sideItem}
          button
        >
          <ListItemText primary='My Schematics' />
        </ListItem>
        <Divider />
        {auth.roles && auth.roles.e_sim_reviewer &&
          <ListItem
            component={RouterLink}
            to="/dashboard/review_projects"
            className={classes.sideItem}
            button
          >
            <ListItemText primary='Review Projects' />
          </ListItem>}

      </List>
    </>
  )
}
Example #12
Source File: Notification.jsx    From zubhub with GNU Affero General Public License v3.0 5 votes vote down vote up
Notification = ({ notification, onNotificationClick }) => {
  const classes = useStyles();
  const token = useSelector(store => store.auth.token);
  const { t } = useTranslation();
  const st = dFormatter(JSON.parse('"' + notification.date + '"'));

  return (
    <Link to={notification.link} className={classes.notificationLink}>
      <ListItem
        alignItems="center"
        disableGutters
        button
        className={classes.notificationStyle}
        onClick={() => {
          if (!notification.viewed) {
            let obj = new API();
            obj.viewNotification({
              id: notification.id,
              token: token,
              body: notification,
            });
          }
          onNotificationClick();
        }}
      >
        <ListItemAvatar>
          {notification.sources.length === 1 && (
            <AvatarGroup className={classes.group}>
              <Avatar
                className={classes.image}
                src={notification.sources[0].avatar}
              />
            </AvatarGroup>
          )}
          {notification.sources.length > 1 && (
            <AvatarGroup className={classes.group}>
              <Avatar
                className={classes.firstImage}
                src={notification.sources[0].avatar}
              />
              <Avatar
                className={classes.secondImage}
                src={notification.sources[1].avatar}
              />
            </AvatarGroup>
          )}
        </ListItemAvatar>
        <ListItemText
          className={classes.text}
          classes={{ primary: classes.message, secondary: classes.time }}
          primary={
            <span
              dangerouslySetInnerHTML={{
                __html: notification.message,
              }}
            ></span>
          }
          secondary={st.value + ' ' + st.key + ' ' + 'ago'}
        />
        {!notification.viewed && <div className={classes.viewDot}></div>}
        {notification.viewed && <div className={classes.unviewed}></div>}
      </ListItem>
    </Link>
  );
}
Example #13
Source File: Comment.js    From yasn with MIT License 5 votes vote down vote up
export default function Comment(props) {
  const classes = useStyles();

  return (
    <div className={classes.root}>
      <Grid item xs={12}>
        <div className={classes.demo}>
          <List>
            {/* {generate( */}
            <ListItem>
              <ListItemAvatar>
                {props.username ? (
                  <Link to={`/user/${props.username}`}>
                    <Avatar className={classes.avatar}>
                      {props.name
                        ? props.name[0]
                        : // + props.name.split(" ")[1][0]
                          "X"}
                    </Avatar>
                  </Link>
                ) : (
                  <Avatar className={classes.avatar}>
                    {props.name
                      ? props.name[0]
                      : // + props.name.split(" ")[1][0]
                        "X"}
                  </Avatar>
                )}
              </ListItemAvatar>
              <ListItemText
                primary={props.comment}
                secondary={
                  <Moment format="MMM D" withTitle>
                    {props.date}
                  </Moment>
                }
              />
              <ListItemSecondaryAction>
                {/* <IconButton edge="end" aria-label="delete">
                  <DeleteIcon fontSize="small" />
                </IconButton> */}
              </ListItemSecondaryAction>
            </ListItem>
          </List>
        </div>
      </Grid>
    </div>
  );
}
Example #14
Source File: user-details.js    From js-miniapp with MIT License 4 votes vote down vote up
function UserDetails(props: UserDetailsProps) {
  const [state, dispatch] = useReducer(dataFetchReducer, initialState);
  const classes = useStyles();

  const buttonClassname = clsx({
    [classes.buttonFailure]: state.isError,
    [classes.buttonSuccess]: !state.isError,
  });

  function requestUserDetails() {
    const permissionsList = [
      {
        name: CustomPermissionName.USER_NAME,
        description:
          'We would like to display your Username on your profile page.',
      },
      {
        name: CustomPermissionName.PROFILE_PHOTO,
        description:
          'We would like to display your Profile Photo on your profile page.',
      },
      {
        name: CustomPermissionName.CONTACT_LIST,
        description: 'We would like to send messages to your contacts.',
      },
    ];

    props
      .requestPermissions(permissionsList)
      .then((permissions) => filterAllowedPermissions(permissions))
      .then((permissions) =>
        Promise.all([
          hasPermission(CustomPermissionName.USER_NAME, permissions)
            ? props.getUserName()
            : null,
          hasPermission(CustomPermissionName.PROFILE_PHOTO, permissions)
            ? props.getProfilePhoto()
            : null,
          hasPermission(CustomPermissionName.CONTACT_LIST, permissions)
            ? props.getContacts()
            : null,
        ])
      )
      .then(() => dispatch({ type: 'FETCH_SUCCESS' }))
      .catch((e) => {
        console.error(e);
        dispatch({ type: 'FETCH_FAILURE' });
      });
  }

  function requestPoints() {
    const permissionsList = [
      {
        name: CustomPermissionName.POINTS,
        description:
          'We would like to display your Points on your profile page.',
      },
    ];

    props
      .requestPermissions(permissionsList)
      .then((permissions) => filterAllowedPermissions(permissions))
      .then((permissions) =>
        Promise.all([
          hasPermission(CustomPermissionName.POINTS, permissions)
            ? props.getPoints()
            : null,
        ])
      )
      .then(() => dispatch({ type: 'POINTS_FETCH_SUCCESS' }))
      .catch((e) => {
        console.error(e);
        dispatch({ type: 'POINTS_FETCH_FAILURE' });
      });
  }

  function filterAllowedPermissions(permissions) {
    return permissions
      .filter(
        (permission) => permission.status === CustomPermissionStatus.ALLOWED
      )
      .map((permission) => permission.name);
  }

  function handleClick(e) {
    if (!state.isLoading) {
      e.preventDefault();
      dispatch({ type: 'FETCH_INIT' });
      requestUserDetails();
    }
  }

  function handlePointsClick(e) {
    if (!state.isLoading) {
      e.preventDefault();
      dispatch({ type: 'POINTS_FETCH_INIT' });
      requestPoints();
    }
  }

  function ProfilePhoto() {
    const hasDeniedPermission =
      state.hasRequestedPermissions &&
      !hasPermission(CustomPermissionName.PROFILE_PHOTO);

    return [
      hasDeniedPermission ? (
        <ListItemText
          primary='"Profile Photo" permission not granted.'
          className={classes.red}
          key="avatar-error"
        />
      ) : null,
      <Avatar
        src={props.profilePhoto}
        className={classes.profilePhoto}
        key="avatar"
      />,
    ];
  }

  function UserDetails() {
    const hasDeniedPermission =
      state.hasRequestedPermissions &&
      !hasPermission(CustomPermissionName.USER_NAME);

    return (
      <Paper className={classes.paper}>
        <CardHeader subheader="User Details" />
        <TextField
          variant="outlined"
          disabled={true}
          className={classes.formInput}
          id="input-name"
          error={state.isError || hasDeniedPermission}
          label={'Name'}
          value={
            hasDeniedPermission
              ? '"User Name" permission not granted.'
              : props.userName || ' '
          }
        />
      </Paper>
    );
  }

  function ContactList() {
    const hasDeniedPermision =
      state.hasRequestedPermissions &&
      !hasPermission(CustomPermissionName.CONTACT_LIST);

    return (
      <Paper className={classes.paper}>
        <CardHeader subheader="Contact List" />
        <List className={classes.contactsList}>
          {hasDeniedPermision && (
            <ListItem>
              <ListItemText
                primary='"Contacts" permission not granted.'
                className={classes.red}
              />
            </ListItem>
          )}
          {!hasDeniedPermision &&
            props.contactList &&
            props.contactList.map((contact) => (
              <ListItem divider>
                <ListItemAvatar>
                  <Avatar className={classes.contactIcon} />
                </ListItemAvatar>
                <ListItemText
                  primary={contact.id}
                  secondary={
                    <React.Fragment>
                      <Typography>
                        {contact.name && contact.name !== '' && (
                          <span>{'Name: ' + contact.name}</span>
                        )}
                      </Typography>
                      <Typography>
                        {contact.email && contact.email !== '' && (
                          <span>{'Email: ' + contact.email}</span>
                        )}
                      </Typography>
                    </React.Fragment>
                  }
                />
              </ListItem>
            ))}
        </List>
      </Paper>
    );
  }

  function PointBalance() {
    const hasDeniedPermission =
      state.hasRequestedPointPermissions &&
      !hasPermission(CustomPermissionName.POINTS);

    return (
      <Paper className={classes.paper}>
        <CardHeader subheader="Points" />
        <TextField
          variant="outlined"
          disabled={true}
          className={classes.formInput}
          id="input-points-standard"
          error={state.isPointsError || hasDeniedPermission}
          label={'Points (Standard)'}
          value={
            hasDeniedPermission
              ? '"Points" permission not granted.'
              : props.points !== undefined &&
                props.points.standard !== undefined
              ? props.points.standard.toString()
              : '-'
          }
        />
        <TextField
          variant="outlined"
          disabled={true}
          className={classes.formInput}
          id="input-points-term"
          error={state.isPointsError || hasDeniedPermission}
          label={'Points (Time-Limited)'}
          value={
            hasDeniedPermission
              ? '"Points" permission not granted.'
              : props.points !== undefined && props.points.term !== undefined
              ? props.points.term.toString()
              : '-'
          }
        />
        <TextField
          variant="outlined"
          disabled={true}
          className={classes.formInput}
          id="input-points-cash"
          error={state.isPointsError || hasDeniedPermission}
          label={'Points (Rakuten Cash)'}
          value={
            hasDeniedPermission
              ? '"Points" permission not granted.'
              : props.points !== undefined && props.points.cash !== undefined
              ? props.points.cash.toString()
              : '-'
          }
        />
      </Paper>
    );
  }

  function CardActionsForm() {
    return (
      <FormGroup column="true" className={classes.rootUserGroup}>
        <div className={classes.wrapper}>
          <Button
            onClick={handleClick}
            variant="contained"
            color="primary"
            classes={{ root: classes.button }}
            className={buttonClassname}
            disabled={state.isLoading}
            data-testid="fetchUserButton"
          >
            Fetch User Details
          </Button>
          {state.isLoading && (
            <CircularProgress size={20} className={classes.buttonProgress} />
          )}
        </div>
        {state.isError && (
          <Typography variant="body1" className={classes.error}>
            Error fetching the User Details
          </Typography>
        )}
      </FormGroup>
    );
  }

  function CardPointActionsForm() {
    return (
      <FormGroup column="true" className={classes.rootUserGroup}>
        <div className={classes.wrapper}>
          <Button
            onClick={handlePointsClick}
            variant="contained"
            color="primary"
            classes={{ root: classes.button }}
            className={buttonClassname}
            disabled={state.isPointsLoading}
            data-testid="fetchPointsButton"
          >
            Fetch Points
          </Button>
          {state.isPointsLoading && (
            <CircularProgress size={20} className={classes.buttonProgress} />
          )}
        </div>
        {state.isPointsError && (
          <Typography variant="body1" className={classes.error}>
            Error fetching the points
          </Typography>
        )}
      </FormGroup>
    );
  }

  function hasPermission(permission, permissionList: ?(string[])) {
    permissionList = permissionList || props.permissions || [];
    return permissionList.indexOf(permission) > -1;
  }

  return (
    <div className={classes.scrollable}>
      <GreyCard className={classes.card}>
        <CardContent>
          <div
            className={classes.dataFormsWrapper}
            data-testid="dataFormsWrapper"
          >
            {ProfilePhoto()}
            {UserDetails()}
            {ContactList()}
          </div>
        </CardContent>
        <CardActions classes={{ root: classes.rootCardActions }}>
          {CardActionsForm()}
        </CardActions>

        <CardContent>
          <div
            className={classes.dataFormsWrapper}
            data-testid="pointDataFormsWrapper"
          >
            {PointBalance()}
          </div>
        </CardContent>
        <CardActions classes={{ root: classes.rootCardActions }}>
          {CardPointActionsForm()}
        </CardActions>
      </GreyCard>
    </div>
  );
}
Example #15
Source File: WebChatListItem.js    From WhatsApp-Clone with MIT License 4 votes vote down vote up
WebChatListItem = ({ item, position, onItemClick }) => {
  const [userType, setUserType] = useState("");
  const classes = useStyles();

  let data = item.chat[0];

  useEffect(() => {
    setUserName();
  }, []);

  function setUserName() {
    let userType = getUserType(item);
    setUserType(userType);
  }

  return (
    <div style={{cursor:'pointer'}} onClick={() => onItemClick(item)}>
      {position > 0 && (
        <div
          light={true}
          className={classes.parentDiv}
        />
      )}
      <ListItem
        alignItems="flex-start"
        style={{
          display: "flex",
          flexDirection: "row",
          flex: 1,
          marginTop: "-1%"
        }}
      >
        <ListItemAvatar style={{ flex: 0.15, marginLeft: "-1%" }}>
          {/* <chatImage /> */}
          <Avatar alt="User" src={chatImage} className={classes.profileImage} />
        </ListItemAvatar>
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            flex: 0.7
          }}
        >
          <ListItemText
            primary={
              <Typography className={classes.userName}>
                {userType == webConstants.FRIEND
                  ? data.userName
                  : data.chatName}
              </Typography>
            }
          />
          <ListItemText
            secondary={
              <Typography className={classes.userMessage}>
                {data.chatMessage}
              </Typography>
            }
          />
        </div>
        <ListItemText
          style={{
            display: "flex",
            flex: 0.15,
            justifyContent: "flex-end",
            alignItems: "flex-end",
            flexDirection: "column"
          }}
          primary={
            <Typography className={classes.userTime}>
              {getTimeInFormat(data.chatTime)}
            </Typography>
          }
          secondary={
            <Avatar className={item.chatUnreadCount != 0 ? classes.avatarStyle : classes.emptyAvatarStyle}>
              <Typography className={classes.textMsgCount}>
                {item.chatUnreadCount}
              </Typography>
            </Avatar>
          }
        />
      </ListItem>

      {/* <Card transparent style={{ elevation: 0, marginRight: -5 }}>
        <CardItem>
          <View style={{ marginLeft: -5 }}>
            <Thumbnail
              source={
                data.chatImage === ""
                  ? PROFILE
                  : { isStatic: true, uri: data.chatImage }
              }
            />
          </View>
          <Body
            style={{
              flexDirection: "column",
              marginLeft: 15
            }}
          >
            <Text
              numberOfLines={1}
              style={[DEFAULT_STYLES.poppinsSemiBold, styles.userName]}
            >
              {userType == webConstants.FRIEND ? data.userName : data.chatName}
            </Text>

            <Text
              numberOfLines={2}
              style={[DEFAULT_STYLES.poppinsLight, styles.userMessage]}
            >
              {data.chatMessage}
            </Text>
          </Body>
          <View>
            <Text style={[DEFAULT_STYLES.poppinsSemiBold, styles.userTime]}>
              {getTimeInFormat(item.chatTime)}
            </Text>
            {data.chatUnreadCount === "" && (
              <View style={styles.textMsgCountView}>
                <Text
                  style={[DEFAULT_STYLES.poppinsSemiBold, styles.textMsgCount]}
                >
                  {data.chatUnreadCount}
                </Text>
              </View>
            )}
            {data.chatUnreadCount != "" && (
              <Icon
                style={styles.msgIcon}
                name={data.chatUnreadCount}
                type={data.chatUnreadCount}
              />
            )}
          </View>
        </CardItem>
      </Card> */}
    </div>
  );
}
Example #16
Source File: OftadehAvatarMenu.jsx    From oftadeh-react-admin with MIT License 4 votes vote down vote up
OftadehAvatarMenu = props => {
  const classes = useStyles(props);

  const [open, setOpen] = React.useState(false);
  const anchorRef = React.useRef(null);

  const handleToggle = () => {
    setOpen(prevOpen => !prevOpen);
  };

  const handleClose = event => {
    if (anchorRef.current && anchorRef.current.contains(event.target)) {
      return;
    }

    setOpen(false);
  };

  return (
    <>
      <ListItem
        button
        ref={anchorRef}
        aria-controls={open ? "menu-list-grow" : undefined}
        aria-haspopup="true"
        onClick={handleToggle}
        alignItems="flex-start"
        className={classes.paddingRightZero}
      >
        <ListItemAvatar>
          <OftadehAvatarBadge
            overlap="circle"
            anchorOrigin={{
              vertical: "bottom",
              horizontal: "right"
            }}
            variant="dot"
          >
            <Avatar
              alt="Mohammad Oftadeh"
              src="https://lh5.googleusercontent.com/-WqhFe4eMggE/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rdFUa5CK9Wi6g5qd8ZUt6apKFYSwA/photo.jpg?sz=328"
            />
          </OftadehAvatarBadge>
        </ListItemAvatar>
        <Hidden implementation="css" smDown>
          <ListItemText
            primary={
              <React.Fragment>
                <Typography component="span" variant="subtitle2">
                  Mohammad Oftadeh
                </Typography>
              </React.Fragment>
            }
            secondary={
              <React.Fragment>
                <Typography
                  component="span"
                  variant="caption"
                  className={classes.inline}
                  color="textPrimary"
                >
                  Admin
                </Typography>
              </React.Fragment>
            }
          />
        </Hidden>
      </ListItem>
      <Popper
        open={open}
        anchorEl={anchorRef.current}
        role={undefined}
        transition
        disablePortal
      >
        {({ TransitionProps, placement }) => (
          <Grow
            {...TransitionProps}
            style={{
              transformOrigin:
                placement === "bottom" ? "center top" : "center bottom"
            }}
          >
            <Paper>
              <ClickAwayListener onClickAway={handleClose}>
                <MenuList autoFocusItem={open} id="menu-list-grow">
                  <MenuItem onClick={handleClose}>
                    <ListItemIcon className={classes.menuIcon}>
                      <AccountCircle fontSize="small" />
                    </ListItemIcon>
                    Profile
                  </MenuItem>
                  <MenuItem onClick={handleClose}>
                    <ListItemIcon className={classes.menuIcon}>
                      <Settings fontSize="small" />
                    </ListItemIcon>
                    settings
                  </MenuItem>
                  <MenuItem onClick={handleClose}>
                    <ListItemIcon className={classes.menuIcon}>
                      <ExitToApp fontSize="small" />
                    </ListItemIcon>
                    Logout
                  </MenuItem>
                </MenuList>
              </ClickAwayListener>
            </Paper>
          </Grow>
        )}
      </Popper>
    </>
  );
}
Example #17
Source File: TransactionMessage.js    From akashlytics-deploy with GNU General Public License v3.0 4 votes vote down vote up
getMessage = (message, classes) => {
  switch (message.typeUrl) {
    case TransactionMessageData.Types.MSG_CLOSE_DEPLOYMENT:
      return (
        <>
          <ListItemAvatar>
            <Avatar classes={{ root: classes.avatarRoot }}>
              <PowerOffIcon classes={{ root: classes.avatarIcon }} />
            </Avatar>
          </ListItemAvatar>
          <ListItemText
            primary="Close Deployment"
            secondary={`Close deployment with dseq: ${message.value.id.dseq}`}
            classes={{ primary: classes.listItemPrimaryText }}
          />
        </>
      );
    case TransactionMessageData.Types.MSG_CREATE_CERTIFICATE:
      return (
        <>
          <ListItemAvatar>
            <Avatar classes={{ root: classes.avatarRoot }}>
              <VerifiedUserIcon classes={{ root: classes.avatarIcon }} />
            </Avatar>
          </ListItemAvatar>
          <ListItemText primary="Create Certificate" classes={{ primary: classes.listItemPrimaryText }} />
        </>
      );
    case TransactionMessageData.Types.MSG_CREATE_DEPLOYMENT:
      return (
        <>
          <ListItemAvatar>
            <Avatar classes={{ root: classes.avatarRoot }}>
              <PublishIcon classes={{ root: classes.avatarIcon }} />
            </Avatar>
          </ListItemAvatar>
          <ListItemText
            primary="Create Deployment"
            secondary={
              <>
                New deployment with dseq <strong>{message.value.id.dseq}</strong> and a deposit of <strong>{uaktToAKT(message.value.deposit.amount)}AKT</strong>
              </>
            }
            classes={{ primary: classes.listItemPrimaryText }}
          />
        </>
      );
    case TransactionMessageData.Types.MSG_UPDATE_DEPLOYMENT:
      return (
        <>
          <ListItemAvatar>
            <Avatar classes={{ root: classes.avatarRoot }}>
              <PublishIcon classes={{ root: classes.avatarIcon }} />
            </Avatar>
          </ListItemAvatar>
          <ListItemText
            primary="Update Deployment"
            secondary={
              <>
                Update deployment with dseq <strong>{message.value.id.dseq}</strong>
              </>
            }
            classes={{ primary: classes.listItemPrimaryText }}
          />
        </>
      );
    case TransactionMessageData.Types.MSG_DEPOSIT_DEPLOYMENT:
      return (
        <>
          <ListItemAvatar>
            <Avatar classes={{ root: classes.avatarRoot }}>
              <AddBoxIcon classes={{ root: classes.avatarIcon }} />
            </Avatar>
          </ListItemAvatar>
          <ListItemText
            primary="Deposit Deployment"
            secondary={
              <>
                Add funds of <strong>{uaktToAKT(message.value.amount.amount)}AKT</strong> to deployment with dseq <strong>{message.value.id.dseq}</strong>
              </>
            }
            classes={{ primary: classes.listItemPrimaryText }}
          />
        </>
      );
    case TransactionMessageData.Types.MSG_CREATE_LEASE:
      return (
        <>
          <ListItemAvatar>
            <Avatar classes={{ root: classes.avatarRoot }}>
              <ReceiptIcon classes={{ root: classes.avatarIcon }} />
            </Avatar>
          </ListItemAvatar>
          <ListItemText
            primary="Create Lease"
            secondary={
              <>
                New Lease with provider <strong>{message.value.bid_id.provider}</strong>, dseq: <strong>{message.value.bid_id.dseq}</strong>, gseq:{" "}
                <strong>{message.value.bid_id.gseq}</strong>, oseq: <strong>{message.value.bid_id.oseq}</strong>.
              </>
            }
            classes={{ primary: classes.listItemPrimaryText }}
          />
        </>
      );
    case TransactionMessageData.Types.MSG_REVOKE_CERTIFICATE:
      return (
        <>
          <ListItemAvatar>
            <Avatar classes={{ root: classes.avatarRoot }}>
              <CancelIcon classes={{ root: classes.avatarIcon }} />
            </Avatar>
          </ListItemAvatar>
          <ListItemText primary="Revoke Certificate" secondary={`Serial: ${message.value.id.serial}`} classes={{ primary: classes.listItemPrimaryText }} />
        </>
      );
    case TransactionMessageData.Types.MSG_SEND_TOKENS:
      return (
        <>
          <ListItemAvatar>
            <Avatar classes={{ root: classes.avatarRoot }}>
              <SendIcon classes={{ root: classes.avatarIcon }} />
            </Avatar>
          </ListItemAvatar>
          <ListItemText
            primary="Send"
            secondary={
              <>
                <strong>{message.value.toAddress}</strong> will receive <strong>{uaktToAKT(message.value.amount[0].amount, 6)}AKT</strong>
              </>
            }
            classes={{ primary: classes.listItemPrimaryText }}
          />
        </>
      );
    case TransactionMessageData.Types.MSG_GRANT:
      return (
        <>
          <ListItemAvatar>
            <Avatar classes={{ root: classes.avatarRoot }}>
              <AccountBalanceIcon classes={{ root: classes.avatarIcon }} />
            </Avatar>
          </ListItemAvatar>
          <ListItemText
            primary="Authorize Spend"
            secondary={
              <>
                <strong>{message.value.grantee}</strong> will be able to spend up to{" "}
                <strong>{uaktToAKT(message.value.grant.authorization.value.spend_limit.amount, 6)}AKT</strong> on your behalf. Expires:{" "}
                <FormattedDate value={new Date(message.value.grant.expiration.seconds * 1_000)} />
                &nbsp;
                <FormattedTime value={new Date(message.value.grant.expiration.seconds * 1_000)} />.
              </>
            }
            classes={{ primary: classes.listItemPrimaryText }}
          />
        </>
      );

    default:
      return null;
  }
}
Example #18
Source File: Settings.js    From hk-independent-bus-eta with GNU General Public License v3.0 4 votes vote down vote up
Settings = () => {
  const { 
    schemaVersion, versionMd5, updateTime, geoPermission, 
    setGeoPermission, renewDb, resetUsageRecord
  } = useContext ( AppContext )
  const [ updating, setUpdating ] = useState(false)
  const [ showGeoPermissionDenied, setShowGeoPermissionDenied ] = useState(false)
  const [ isCopied, setIsCopied ] = useState(false)

  const { t, i18n } = useTranslation()
  const classes = useStyles()

  useEffect(() => {
    setUpdating(false)
  }, [updateTime])

  return (
    <Paper className={classes.root}>
      <List>
        <ListItem
          button
          onClick={() => {vibrate(1);setUpdating(true);renewDb()}}
        >
          <ListItemAvatar>
            <Avatar><BuildIcon /></Avatar>
          </ListItemAvatar>
          <ListItemText 
            primary={t("架構版本")+": "+schemaVersion+" - "+versionMd5.substr(0,6)} 
            secondary={t('更新時間') + ": " + (new Date(updateTime)).toLocaleString().slice(0,20).replace(',',' ')} 
          />
        </ListItem>
        <ListItem
          button
          onClick={() => {
            vibrate(1)
            if ( geoPermission === 'granted' ) {
              setGeoPermission('closed')
            } else {
              setGeoPermission('opening')
              navigator.geolocation.getCurrentPosition(position => {
                setGeoPermission('granted')
              }, () => {
                setGeoPermission('denied')
                setShowGeoPermissionDenied(true)
              })
            }
          }}
        >
          <ListItemAvatar>
            <Avatar>{geoPermission === 'granted' ? <LocationOnIcon /> : <LocationOffIcon />}</Avatar>
          </ListItemAvatar>
          <ListItemText 
            primary={t("地理位置定位功能")} 
            secondary={t(geoPermission === 'granted' ? '開啟' : ( geoPermission === 'opening' ? '開啟中...' : '關閉' )) } 
          />
        </ListItem>
        <ListItem
          button
          onClick={() => {vibrate(1);resetUsageRecord()}}
        >
          <ListItemAvatar>
            <Avatar><DeleteIcon /></Avatar>
          </ListItemAvatar>
          <ListItemText 
            primary={t("一鍵清空用戶記錄")} 
            secondary={t("包括鎖定和常用報時")}
          />
        </ListItem>
        <Divider />
        <ListItem
          button
          component='a'
          href={`https://github.com/chunlaw/hk-independent-bus-eta/`}
          target="_blank"
          onClick={() => {vibrate(1)}}
        >
          <ListItemAvatar>
            <Avatar><GitHubIcon /></Avatar>
          </ListItemAvatar>
          <ListItemText 
            primary={"Source Code"} 
            secondary={"GPL-3.0 License"} 
          />
        </ListItem>
        <ListItem>
          <ListItemAvatar>
            <Avatar><DataUsageIcon /></Avatar>
          </ListItemAvatar>
          <ListItemText 
            primary={t("交通資料來源")} 
            secondary={t('資料一線通') + "  https://data.gov.hk" } 
          />
        </ListItem>
        <Divider />
        <ListItem
          button
          component='a'
          href={`https://donate.612fund.hk/${i18n.language}/`}
          target="_blank"
          onClick={() => {vibrate(1)}}
        >
          <ListItemAvatar>
            <Avatar><MonetizationOnIcon /></Avatar>
          </ListItemAvatar>
          <ListItemText 
            primary={t("捐款支持")} 
            secondary={t('請捐款到 612 人道支援基金') } 
          />
        </ListItem>
        <ListItem
          button
          onClick={() => {
            vibrate(1)
            if ( navigator.clipboard ) {
              navigator.clipboard.writeText('https://hkbus.app/')
              .then(() => {
                setIsCopied(true)
              })
            }
          }}
        >
          <ListItemAvatar>
            <Avatar><ShareIcon /></Avatar>
          </ListItemAvatar>
          <ListItemText 
            primary={t("複製應用程式鏈結")} 
            secondary={t('經不同媒介分享給親友') } 
          />
        </ListItem>
        <ListItem
          button
          component='a'
          href={`https://t.me/hkbusapp`}
          target="_blank"
          onClick={() => {vibrate(1)}}
        >
          <ListItemAvatar>
            <Avatar><TelegramIcon /></Avatar>
          </ListItemAvatar>
          <ListItemText 
            primary={t("Telegram 交流區")} 
            secondary={t('歡迎意見及技術交流') } 
          />
        </ListItem>
      </List>
      <Snackbar
        anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}
        open={updating}
        message={t('資料更新中')+'...'}
      />
      <Snackbar
        anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}
        open={showGeoPermissionDenied}
        autoHideDuration={1500}
        onClose={(e, reason) => {
          setShowGeoPermissionDenied(false)
        }}
        message={t('無法獲得地理位置定位功能權限')}
      />
      <Snackbar
        anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}
        open={isCopied}
        autoHideDuration={1500}
        onClose={(event, reason) => {
          setIsCopied(false);
        }}
        message={t('鏈結已複製到剪貼簿')}
      />
    </Paper>
  )
}
Example #19
Source File: index.js    From Recess with MIT License 4 votes vote down vote up
function LikePanel({ postId, user }) {
  const classes = useStyles();

  const [like, setLike] = useState(false);
  const [usersLiked, setUsersLiked] = useState([]);
  const [likeCounter, setLikeCounter] = useState(0);
  const [likeModal, setLikeModal] = useState(false);

  const onLike = (e) => {
    e.preventDefault();
    if (user) {
      db.collection("posts")
        .doc(postId)
        .collection("likes")
        .doc(user.uid)
        .set({
          username: user.displayName,
          photoURL: user.photoURL,
        })
        .then(() => {
          setLike(true);
          setLikeCounter(likeCounter + 1);
        })
        .catch((err) => console.log(err));
    }
  };

  const onDislike = (e) => {
    e.preventDefault();
    if (user) {
      db.collection("posts")
        .doc(postId)
        .collection("likes")
        .doc(user.uid)
        .delete()
        .then(() => {
          setLike(false);
          setLikeCounter(likeCounter - 1);
        })
        .catch((err) => console.log(err));
    }
  };

  // Getting Post's like data and updating like state
  useEffect(() => {
    db.collection("posts")
      .doc(postId)
      .collection("likes")
      .onSnapshot((snap) => {
        let documents = [];
        snap.forEach((doc) => {
          documents.push({
            userName: doc.data().username,
            photoURL: doc.data().photoURL,
            userId: doc.id,
          });
        });
        setUsersLiked(documents);
        setLikeCounter(documents.length);
        if (user) {
          documents.map((u) => {
            if (u.userId === user.uid) {
              setLike(true);
            }
          });
        }
      });
  }, []);

  return (
    <>
      <div className={classes.likeContainer}>
        <Button>
          {like ? (
            <FavoriteIcon onClick={onDislike} />
          ) : (
            <FavoriteBorderIcon onClick={onLike} />
          )}
        </Button>

        {likeCounter ? (
          <Typography>
            Liked by{" "}
            <b
              className={classes.likeCounter}
              onClick={() => setLikeModal(true)}
            >
              {likeCounter} {likeCounter > 1 ? " users." : " user."}
            </b>
          </Typography>
        ) : null}
      </div>

      <Modal
        className={classes.modal}
        open={likeModal}
        onClose={() => setLikeModal(false)}
        closeAfterTransition
        BackdropComponent={Backdrop}
        BackdropProps={{
          timeout: 500,
        }}
      >
        <Fade in={likeModal}>
          <div className={classes.paper}>
            <h2>Users Liked:</h2>
            <Divider />
            <List className={classes.likelist}>
              {usersLiked.map((user) => (
                <ListItem button key={user.userId}>
                  <ListItemAvatar>
                    <Avatar
                      className={classes.avatar}
                      alt={user.userName}
                      src={user?.photoURL}
                    />
                  </ListItemAvatar>
                  <ListItemText primary={user.userName} />
                </ListItem>
              ))}
            </List>
          </div>
        </Fade>
      </Modal>
    </>
  );
}
Example #20
Source File: List.js    From Dog-Book with MIT License 4 votes vote down vote up
MyList = (props) => {
  const classes = useStyles();
  const { breedName, setBreedName, setValue } = props;
  const [breeds, setBreeds] = useState(undefined);
  const [searchValue, setSearchValue] = useState();

  const [expandedPanel, setExpandedPanel] = useState(false);

  const handleAccordionChange = (key) => (event, isExpanded) => {
    setExpandedPanel(isExpanded ? key : false);
  };

  const handleChange = (event) => {
    let value = event.target.value.toLowerCase();
    setSearchValue(value);
  };

  useEffect(() => {
    axios.get("https://dog.ceo/api/breeds/list/all").then((response) => {
      console.log(response.data.message);
      setBreeds(response.data.message);
    });
  }, [searchValue]);

  return (
    <>
    <Scroll showBelow={250} />
      <Grid container justify="flex-end">
        <Grid item xs="12" md="4">
          <AutoSearchComplete
            searchValue={searchValue}
            setSearchValue={setSearchValue}
            breeds={breeds}
            handleChange={(e) => handleChange(e)}
          />
        </Grid>
      </Grid>
      {breeds ? (
        Object.keys(breeds)
          .filter((key) =>
            searchValue !== "" && searchValue !== undefined
              ? key === searchValue
              : "null"
          )
          .map((key, i) => {
            return (
              <Accordion
                key={i}
                style={{ margin: "1rem" }}
                onClick={() => {
                  setBreedName(key);
                }}
                expanded={expandedPanel === key}
                onChange={handleAccordionChange(key)}
              >
                <AccordionSummary
                  expandIcon={<ExpandMoreIcon />}
                  aria-controls="panel1a-content"
                  id="key"
                >
                  <Typography
                    style={{ textTransform: "capitalize" }}
                    className={
                      breedName === key
                        ? classes.Accordion_a
                        : classes.Accordion
                    }
                  >
                    {key}
                  </Typography>
                </AccordionSummary>
                <AccordionDetails>
                  <List>
                    {breeds[key].length === 0 ? (
                      <Typography>No Sub-Breads</Typography>
                    ) : (
                      breeds[key].map((breed) => (
                        <ListItem>
                          <ListItemAvatar>
                            <Avatar>
                              <Pets />
                            </Avatar>
                          </ListItemAvatar>
                          <ListItemText primary={breed} />
                        </ListItem>
                      ))
                    )}
                  </List>
                </AccordionDetails>
                <Divider />
                <Box p={2}>
                  <Button
                    variant="contained"
                    color="secondary"
                    onClick={() => setValue(1)}
                  >
                    Select
                  </Button>
                </Box>
              </Accordion>
            );
          })
      ) : (
        <Grid
          container
          direction="row"
          justify="center"
          alignItems="center"
          style={{ height: "60vh" }}
        >
          <Grid item>
            <CircularProgress color="secondary" size="4rem" />
          </Grid>
        </Grid>
      )}
    </>
  );
}
Example #21
Source File: GrowerDetail.js    From treetracker-admin-client with GNU Affero General Public License v3.0 4 votes vote down vote up
GrowerDetail = ({ open, growerId, onClose }) => {
  // console.log('render: grower detail');
  const classes = useStyle();
  const appContext = useContext(AppContext);
  const { growers } = useContext(GrowerContext);
  const { sendMessageFromGrower } = useContext(MessagingContext);
  const [growerRegistrations, setGrowerRegistrations] = useState(null);
  const [editDialogOpen, setEditDialogOpen] = useState(false);
  const [grower, setGrower] = useState({});
  const [deviceIdentifiers, setDeviceIdentifiers] = useState([]);
  const [snackbarOpen, setSnackbarOpen] = useState(false);
  const [snackbarLabel, setSnackbarLabel] = useState('');
  const [verificationStatus, setVerificationStatus] = useState({});
  const [loading, setLoading] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);

  useEffect(() => {
    setErrorMessage(null);
    async function loadGrowerDetail() {
      if (grower && grower.growerAccountUuid !== growerId) {
        setGrower({});
        setDeviceIdentifiers([]);
      }
      if (growerId) {
        let match;
        if (isNaN(Number(growerId))) {
          match = await getGrower({
            id: undefined,
            growerAccountUuid: growerId,
          });
        } else {
          match = await getGrower({
            id: growerId,
            growerAccountUuid: undefined,
          });
        }

        if (match.error) {
          setErrorMessage(match.message);
        }

        setGrower(match);

        if (
          match.id &&
          (!growerRegistrations ||
            (growerRegistrations.length > 0 &&
              growerRegistrations[0].planter_id !== match.id))
        ) {
          setGrowerRegistrations(null);
          api.getGrowerRegistrations(match.id).then((registrations) => {
            if (registrations && registrations.length) {
              const sortedReg = registrations.sort((a, b) =>
                a.created_at > b.created_at ? 1 : -1
              );
              const uniqueDevices = {};
              const devices = sortedReg.reduce((result, reg) => {
                if (!reg.device_identifier) {
                  return result;
                }
                if (!uniqueDevices[reg.device_identifier]) {
                  uniqueDevices[reg.device_identifier] = true;
                  // if manufacturer isn't 'apple' it's an android phone
                  result.push({
                    id: reg.device_identifier,
                    os:
                      reg.manufacturer?.toLowerCase() === 'apple'
                        ? 'iOS'
                        : 'Android',
                  });
                }
                return result;
              }, []);

              setDeviceIdentifiers(devices);
              setGrowerRegistrations(sortedReg);
            }
          });
        }
      }
    }
    loadGrowerDetail();
    // eslint-disable-next-line
  }, [growerId, growers]);

  useEffect(() => {
    async function loadCaptures() {
      if (grower.id) {
        setLoading(true);
        const [
          approvedCount,
          awaitingCount,
          rejectedCount,
        ] = await Promise.all([
          getCaptureCountGrower(true, true, grower.id),
          getCaptureCountGrower(true, false, grower.id),
          getCaptureCountGrower(false, false, grower.id),
        ]);
        setVerificationStatus({
          approved: approvedCount,
          awaiting: awaitingCount,
          rejected: rejectedCount,
        });
        setLoading(false);
      }
    }
    loadCaptures();
  }, [grower]);

  async function getCaptureCountGrower(active, approved, growerId) {
    let filter = new FilterModel();
    filter.planterId = growerId?.toString();
    filter.active = active;
    filter.approved = approved;
    const countResponse = await treeTrackerApi.getCaptureCount(filter);
    return countResponse && countResponse.count ? countResponse.count : 0;
  }

  async function getGrower(payload) {
    const { id, growerAccountUuid } = payload;
    let grower = growers?.find(
      (p) =>
        (growerAccountUuid && p.growerAccountUuid === growerAccountUuid) ||
        p.id === id
    ); // Look for a match in the context first

    if (!grower && !id) {
      const filter = new FilterGrower();
      filter.growerAccountUuid = growerAccountUuid;
      [grower] = await api.getGrowers({ filter }); // Otherwise query the API
    }

    if (!grower && !growerAccountUuid) {
      grower = await api.getGrower(id);
    }
    // throw error if no match at all
    return grower || { error: true, message: 'Sorry! No grower info found' };
  }

  function handleEditClick() {
    setEditDialogOpen(true);
  }

  function handleEditClose() {
    setEditDialogOpen(false);
    setSnackbarOpen(false);
    setSnackbarLabel('');
  }

  function confirmCopy(label) {
    setSnackbarOpen(false);
    setSnackbarLabel(label);
    setSnackbarOpen(true);
  }

  return (
    <>
      <Drawer anchor="right" open={open} onClose={onClose}>
        <Grid
          style={{
            width: GROWER_IMAGE_SIZE,
          }}
        >
          {errorMessage ? (
            <Grid container direction="column">
              <Grid item>
                <Grid container justify="space-between" alignItems="center">
                  <Grid item>
                    <Box m={4}>
                      <Typography color="primary" variant="h6">
                        Grower Detail
                      </Typography>
                      <Typography variant="h4">{errorMessage}</Typography>
                    </Box>
                  </Grid>
                  <Grid item>
                    <IconButton onClick={() => onClose()}>
                      <Close />
                    </IconButton>
                  </Grid>
                </Grid>
              </Grid>
              <Grid item className={classes.imageContainer}>
                <CardMedia className={classes.cardMedia}>
                  <Grid container className={classes.personBox}>
                    <Person className={classes.person} />
                  </Grid>
                </CardMedia>
              </Grid>
            </Grid>
          ) : (
            <Grid container direction="column">
              <Grid item>
                <Grid container justify="space-between" alignItems="center">
                  <Grid item>
                    <Box m={4}>
                      <Typography color="primary" variant="h6">
                        Grower Detail
                      </Typography>
                    </Box>
                  </Grid>
                  <Grid item>
                    <IconButton onClick={() => onClose()}>
                      <Close />
                    </IconButton>
                  </Grid>
                </Grid>
              </Grid>
              <Grid item className={classes.imageContainer}>
                {grower?.imageUrl && (
                  <OptimizedImage
                    src={grower.imageUrl}
                    width={GROWER_IMAGE_SIZE}
                    height={GROWER_IMAGE_SIZE}
                    className={classes.cardMedia}
                    fixed
                    rotation={grower.imageRotation}
                    alertTitleSize="1.6rem"
                    alertTextSize="1rem"
                    alertHeight="50%"
                  />
                )}
                {!grower.imageUrl && (
                  <CardMedia className={classes.cardMedia}>
                    <Grid container className={classes.personBox}>
                      <Person className={classes.person} />
                    </Grid>
                  </CardMedia>
                )}
                {hasPermission(appContext.user, [
                  POLICIES.SUPER_PERMISSION,
                  POLICIES.MANAGE_GROWER,
                ]) && (
                  <Fab
                    data-testid="edit-grower"
                    className={classes.editButton}
                    onClick={() => handleEditClick()}
                  >
                    <EditIcon />
                  </Fab>
                )}
              </Grid>
              <Grid item className={classes.box}>
                <Typography
                  variant="h5"
                  color="primary"
                  className={classes.name}
                >
                  {grower.firstName} {grower.lastName}
                </Typography>
                <Typography variant="body2">
                  ID: <LinkToWebmap value={grower.id} type="user" />
                </Typography>
              </Grid>
              {process.env.REACT_APP_ENABLE_MESSAGING === 'true' &&
                hasPermission(appContext.user, [POLICIES.SUPER_PERMISSION]) && (
                  <Grid item>
                    <Button
                      className={classes.messageButton}
                      onClick={() => sendMessageFromGrower(grower)}
                      component={Link}
                      to={'/messaging'}
                    >
                      Send Message
                    </Button>
                  </Grid>
                )}
              <Divider />
              <Grid container direction="column" className={classes.box}>
                <Typography variant="subtitle1">Captures</Typography>
                {loading ? (
                  <LinearProgress color="primary" />
                ) : (
                  <List className={classes.listCaptures}>
                    <Box
                      borderColor="grey.300"
                      borderRadius={10}
                      border={0.5}
                      m={0.5}
                    >
                      <ListItem>
                        <ListItemAvatar>
                          <Avatar className={classes.approvedChip}>
                            <Done />
                          </Avatar>
                        </ListItemAvatar>
                        <ListItemText
                          primary={
                            <Typography variant="h5">
                              {verificationStatus.approved || 0}
                            </Typography>
                          }
                          secondary="Approved"
                        />
                      </ListItem>
                    </Box>
                    <Box
                      borderColor="grey.300"
                      borderRadius={10}
                      border={0.5}
                      m={0.5}
                    >
                      <ListItem>
                        <ListItemAvatar>
                          <Avatar className={classes.awaitingChip}>
                            <HourglassEmptyOutlined />
                          </Avatar>
                        </ListItemAvatar>
                        <ListItemText
                          primary={
                            <Typography variant="h5">
                              {verificationStatus.awaiting || 0}
                            </Typography>
                          }
                          secondary="Awaiting"
                        />
                      </ListItem>
                    </Box>
                    <Box
                      borderColor="grey.300"
                      borderRadius={10}
                      border={0.5}
                      m={0.5}
                    >
                      <ListItem>
                        <ListItemAvatar>
                          <Avatar className={classes.rejectedChip}>
                            <Clear />
                          </Avatar>
                        </ListItemAvatar>
                        <ListItemText
                          primary={
                            <Typography variant="h5">
                              {verificationStatus.rejected || 0}
                            </Typography>
                          }
                          secondary="Rejected"
                        />
                      </ListItem>
                    </Box>
                  </List>
                )}
              </Grid>
              <Divider />
              <Grid container direction="column" className={classes.box}>
                <Typography variant="subtitle1">Email address</Typography>
                <Typography variant="body1">{grower.email || '---'}</Typography>
              </Grid>
              <Divider />
              <Grid container direction="column" className={classes.box}>
                <Typography variant="subtitle1">Phone number</Typography>
                <Typography variant="body1">{grower.phone || '---'}</Typography>
              </Grid>
              <Divider />
              <Grid container direction="column" className={classes.box}>
                <Typography variant="subtitle1">Person ID</Typography>
                <Typography variant="body1">
                  {grower.personId || '---'}
                </Typography>
              </Grid>
              <Divider />
              <Grid container direction="column" className={classes.box}>
                <Typography variant="subtitle1">Organization</Typography>
                {grower.organization || grower.organizationId ? (
                  <GrowerOrganization
                    organizationName={grower.organization}
                    assignedOrganizationId={grower.organizationId}
                  />
                ) : (
                  <Typography variant="body1">---</Typography>
                )}
              </Grid>
              <Divider />
              <Grid container direction="column" className={classes.box}>
                <Typography variant="subtitle1">Country</Typography>
                <Typography variant="body1">
                  {(growerRegistrations &&
                    growerRegistrations
                      .map((item) => item.country)
                      .filter(
                        (country, i, arr) =>
                          country && arr.indexOf(country) === i
                      )
                      .join(', ')) ||
                    '---'}
                </Typography>
              </Grid>
              <Divider />
              <Grid container direction="column" className={classes.box}>
                <Typography variant="subtitle1">Registered</Typography>
                <Typography variant="body1">
                  {(growerRegistrations &&
                    growerRegistrations.length > 0 &&
                    getDateTimeStringLocale(
                      growerRegistrations[0].created_at
                    )) ||
                    '---'}
                </Typography>
              </Grid>
              <Divider />
              <Grid container direction="column" className={classes.box}>
                <Typography variant="subtitle1">
                  Device Identifier{deviceIdentifiers.length >= 2 ? 's' : ''}
                </Typography>
                {(deviceIdentifiers.length && (
                  <table>
                    <tbody>
                      {deviceIdentifiers.map((device, i) => (
                        <tr key={i}>
                          <td>
                            <Typography variant="body1">
                              {device.id}
                              <CopyButton
                                label={'Device Identifier'}
                                value={device.id}
                                confirmCopy={confirmCopy}
                              />
                            </Typography>
                          </td>
                          <td>
                            <Typography variant="body1">
                              ({device.os})
                            </Typography>
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                )) || <Typography variant="body1">---</Typography>}
              </Grid>
            </Grid>
          )}
        </Grid>
      </Drawer>
      <CopyNotification
        snackbarLabel={snackbarLabel}
        snackbarOpen={snackbarOpen}
        setSnackbarOpen={setSnackbarOpen}
      />
      <EditGrower
        isOpen={editDialogOpen}
        grower={grower}
        onClose={handleEditClose}
      ></EditGrower>
    </>
  );
}
Example #22
Source File: Discussion.js    From app with MIT License 4 votes vote down vote up
function CommentList({ requestId }) {
  const classes = useStyles();
  const firestore = useFirestore();
  const [retries, setRetries] = useState(0);
  const { showError } = useNotifications();

  // This is used to trigger the snapshot subscription after confirming that the user
  // has permission to access the contact info.
  const [query, setQuery] = useState(null);
  const [commentDocs, setCommentDocs] = useState([]);

  // Because of timing issues, this component will likely get run before the server has applied
  // the requested document access resulting in almost a guranteed permission-denied error. So,
  // we use this effect to monitor for permission-denied until the change has propagated, at which
  // point, we do the actual doc subscription (next useEffect);
  useEffect(() => {
    async function getData() {
      try {
        const dataQuery = firestore
          .collection(`${REQUESTS_DISCUSSIONS_COLLECTION}`)
          .where('requestId', '==', requestId)
          .orderBy('createdAt', 'asc');
        // Call it once because this will throw the permission exception.
        await dataQuery.get();
        setQuery(dataQuery); // Setting this will trigger the subscription useEffect.
      } catch (err) {
        // We only try reloading if insufficient permissions.
        if (err.code !== 'permission-denied') {
          throw err;
        }
        if (retries >= 25) {
          // setAccessFailed(true);
          showError(
            'Failed to get contact info access, please try again later.',
          );
        } else {
          window.setTimeout(() => {
            setRetries(retries + 1);
          }, 1000);
        }
      }
    }
    getData();
  }, [retries, firestore, requestId, showError]);

  // Once the previous useEffect verifies that the user has access then this one does the actual
  // document subscription.
  useEffect(() => {
    if (!query) return undefined;
    const unsub = query.onSnapshot((querySnap) => {
      setCommentDocs(querySnap.docs);
    });
    return unsub;
  }, [query]);

  // const querySnapshot = useFirestoreCollection(
  //   firestore
  //     .collection(`${REQUESTS_DISCUSSIONS_COLLECTION}`)
  //     .where('requestId', '==', requestId)
  //     .orderBy('createdAt', 'asc'),
  // );

  if (!commentDocs.length) {
    return (
      <Box color="text.disabled">
        <Typography
          variant="body2"
          className={classes.noComments}
          data-test="no-comments">
          No comments yet.
        </Typography>
        <Divider className={classes.divider} />
      </Box>
    );
  }

  return (
    <List>
      {commentDocs.map(
        (docSnap) =>
          // When new comment is added locally, the createdAt can be the serverTimestamp() value.
          // So, we wait on rendering until any new snapshot has finished writing.
          !docSnap.metadata.hasPendingWrites && (
            <ListItem
              key={docSnap.id}
              divider
              alignItems="flex-start"
              data-test="private-comment">
              <ListItemAvatar>
                <Avatar>{docSnap.get('author.firstName').slice(0, 1)}</Avatar>
              </ListItemAvatar>
              <ListItemText
                disableTypography
                primary={
                  <Typography variant="subtitle2">
                    {docSnap.get('author.firstName')} &ndash;{' '}
                    <Typography
                      variant="body2"
                      display="inline"
                      color="textSecondary">
                      {format(docSnap.get('createdAt').toDate(), 'p - PPPP')}
                    </Typography>{' '}
                    {docSnap.get('kind') !== 1 && (
                      <Chip
                        variant="outlined"
                        size="small"
                        icon={kindMap[docSnap.get('kind')].icon}
                        label={kindMap[docSnap.get('kind')].shortDescription}
                      />
                    )}
                  </Typography>
                }
                secondary={docSnap
                  .get('content')
                  .split('\n')
                  .map((content, key) => (
                    // eslint-disable-next-line react/no-array-index-key
                    <Typography variant="body1" key={key} gutterBottom>
                      {content}
                    </Typography>
                  ))}
              />
            </ListItem>
          ),
      )}
    </List>
  );
}
Example #23
Source File: TemplateGallery.js    From akashlytics-deploy with GNU General Public License v3.0 4 votes vote down vote up
export function TemplateGallery(props) {
  const [selectedCategoryTitle, setSelectedCategoryTitle] = useState(null);
  const [searchTerms, setSearchTerms] = useState("");
  const [searchTermsUsed, setSearchTermsUsed] = useState(null);
  const { isLoading, categories, templates } = useTemplates();
  const query = useQueryParams();
  const history = useHistory();
  const selectedCategory = selectedCategoryTitle && categories.find((x) => x.title === selectedCategoryTitle);
  const classes = useStyles();
  const searchTermsSplit = searchTermsUsed?.split().map((x) => x.toLowerCase());
  const searchResults =
    searchTermsSplit && templates.filter((x) => searchTermsSplit.some((s) => x.name.toLowerCase().includes(s) || x.readme.toLowerCase().includes(s)));

  useEffect(() => {
    const queryCategory = query.get("category");
    const querySearch = query.get("search");

    if (queryCategory) {
      setSelectedCategoryTitle(queryCategory);
    }

    if (querySearch) {
      setSearchTerms(querySearch);
      setSearchTermsUsed(querySearch);
    }

    return () => {
      clearTimeout(timeoutId);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    const queryCategory = query.get("category");
    if (categories?.length > 0 && !queryCategory) {
      setSelectedCategoryTitle(categories[0].title);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [categories]);

  const onSearchChange = (event) => {
    const searchValue = event.target.value;
    setSearchTerms(searchValue);

    if (searchValue) {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        setSearchTermsUsed(searchValue);

        history.replace(UrlService.templates(selectedCategoryTitle, searchValue));
      }, 300);
    } else {
      setSearchTermsUsed(searchValue);
      history.replace(UrlService.templates(selectedCategoryTitle, searchValue));
    }
  };

  const onCategoryClick = (categoryTitle) => {
    setSelectedCategoryTitle(categoryTitle);
    history.replace(UrlService.templates(categoryTitle, searchTerms));
  };

  const onClearSearch = () => {
    setSearchTerms("");
    setSearchTermsUsed("");
  };

  return (
    <>
      <Helmet title="Template Gallery" />

      <LinearLoadingSkeleton isLoading={isLoading} />

      <Box padding="1rem">
        <Typography variant="h3" className={classes.title}>
          Template Gallery
        </Typography>

        <Box paddingTop={2}>
          These templates come from the{" "}
          <a href="https://github.com/ovrclk/awesome-akash" target="_blank" rel="noreferrer">
            Awesome Akash
          </a>{" "}
          repository.
        </Box>

        <TextField
          fullWidth
          label="Search"
          disabled={isLoading}
          value={searchTerms}
          onChange={onSearchChange}
          InputProps={{
            endAdornment: searchTerms && (
              <IconButton onClick={onClearSearch} size="small">
                <CloseIcon fontSize="small" />
              </IconButton>
            )
          }}
        />
      </Box>

      {searchTermsUsed || searchTerms ? (
        <ViewPanel bottomElementId="footer" overflow="auto" className={classes.templateList}>
          <List className={classes.templateList}>
            {searchResults?.map((template) => (
              <ListItem button key={template.id} component={Link} to={UrlService.templateDetails(template.id)}>
                <ListItemAvatar>
                  {template.logoUrl ? (
                    <Avatar src={template.logoUrl} variant="square" />
                  ) : (
                    <div className={classes.logoPlaceholder}>
                      <ImageIcon />
                    </div>
                  )}
                </ListItemAvatar>
                <ListItemText
                  primary={
                    <>
                      {template.name} - <strong>{template.category}</strong>
                    </>
                  }
                  secondary={template.summary}
                />
              </ListItem>
            ))}
          </List>
        </ViewPanel>
      ) : (
        <>
          <Box className={classes.gallery}>
            <ViewPanel bottomElementId="footer" overflow="auto" className={classes.categoryList}>
              <List>
                {categories
                  .sort((a, b) => (a.title < b.title ? -1 : 1))
                  .map((category) => (
                    <ListItem button key={category.title} onClick={() => onCategoryClick(category.title)} selected={category.title === selectedCategoryTitle}>
                      <ListItemText primary={`${category.title} (${category.templates.length})`} />
                    </ListItem>
                  ))}
              </List>
            </ViewPanel>

            {selectedCategory && selectedCategory.templates && (
              <ViewPanel bottomElementId="footer" overflow="auto" className={classes.templateList}>
                <List>
                  {selectedCategory.templates.map((template) => (
                    <ListItem button key={template.id} component={Link} to={UrlService.templateDetails(template.id)}>
                      <ListItemAvatar>
                        {template.logoUrl ? (
                          <Avatar src={template.logoUrl} variant="square" />
                        ) : (
                          <div className={classes.logoPlaceholder}>
                            <ImageIcon />
                          </div>
                        )}
                      </ListItemAvatar>
                      <ListItemText primary={template.name} secondary={template.summary} />
                    </ListItem>
                  ))}
                </List>
              </ViewPanel>
            )}
          </Box>
        </>
      )}
    </>
  );
}
Example #24
Source File: TemplateList.js    From akashlytics-deploy with GNU General Public License v3.0 4 votes vote down vote up
export function TemplateList(props) {
  const classes = useStyles();
  const history = useHistory();
  const { setSelectedTemplate } = props;

  function handleGithubOpen(value) {
    window.electron.openUrl(value.githubUrl);
  }

  function selectTemplate(template) {
    setSelectedTemplate(template);
    history.push("/createDeployment/editManifest");
  }

  async function fromFile() {
    const fileDef = await window.electron.openTemplateFromFile();

    if (fileDef) {
      setSelectedTemplate({
        title: "From file",
        code: "from-file",
        category: "General",
        description: fileDef.path,
        content: fileDef.content
      });
      history.push("/createDeployment/editManifest");
    }
  }

  function fromGallery() {
    history.push(UrlService.templates());
  }

  return (
    <>
      <Helmet title="Create Deployment - Template List" />

      <Box padding="1rem">
        <Typography variant="h5"><strong>What do you want to deploy?</strong></Typography>
      </Box>

      <List className={classes.root}>
        <ListItem dense button onClick={() => selectTemplate(emptyTemplate)}>
          <ListItemAvatar>
            <div className={classes.logoItem}>
              <InsertDriveFileIcon />
            </div>
          </ListItemAvatar>
          <ListItemText primary={emptyTemplate.title} secondary={emptyTemplate.description} />
          {emptyTemplate.githubUrl && (
            <ListItemSecondaryAction>
              <IconButton edge="end" aria-label="github" onClick={() => handleGithubOpen(emptyTemplate)}>
                <GitHubIcon />
              </IconButton>
            </ListItemSecondaryAction>
          )}
        </ListItem>
        <ListItem dense button onClick={() => selectTemplate(helloWorldTemplate)}>
          <ListItemAvatar>
            <div className={classes.logoItem}>
              <CloudIcon />
            </div>
          </ListItemAvatar>
          <ListItemText primary={helloWorldTemplate.title} secondary={helloWorldTemplate.description} />
          {helloWorldTemplate.githubUrl && (
            <ListItemSecondaryAction>
              <IconButton edge="end" aria-label="github" onClick={() => handleGithubOpen(helloWorldTemplate)}>
                <GitHubIcon />
              </IconButton>
            </ListItemSecondaryAction>
          )}
        </ListItem>
        <ListItem dense button onClick={() => fromFile()}>
          <ListItemAvatar>
            <div className={classes.logoItem}>
              <DescriptionIcon />
            </div>
          </ListItemAvatar>
          <ListItemText primary="From a file" secondary="Load a deploy.yml file from the computer." />
        </ListItem>
        <ListItem dense button onClick={() => fromGallery()}>
          <ListItemAvatar>
            <div className={classes.logoItem}>
              <CollectionsIcon />
            </div>
          </ListItemAvatar>
          <ListItemText primary="Browse template gallery" secondary="Explore the template gallery for a great variety of pre-made template by the community." />
        </ListItem>
      </List>
    </>
  );
}