@material-ui/lab#Skeleton JavaScript Examples

The following examples show how to use @material-ui/lab#Skeleton. 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: ContactInfo.js    From app with MIT License 6 votes vote down vote up
// Wrap the main component so the loading widget is limited to just the section where the contact
// info is displayed.
function ContactInfoWrapped(props) {
  return (
    <Suspense fallback={<Skeleton />}>
      <ContactInfo {...props} />
    </Suspense>
  );
}
Example #2
Source File: blogs.js    From dscbppimt-official-website with MIT License 6 votes vote down vote up
function Blogs() {
    const [Blogs, setBlogs] = useState([]);
    const URL = "https://dscbppimt-cms.herokuapp.com"
    useEffect(() => {
        const data = async() => {
            const res = await Axios.get("https://dscbppimt-cms.herokuapp.com/our-blogs?_sort=Date:desc");
            setBlogs(res.data);
        }
        data();
    },[])
    return (
        <Layout>
                <Container style={{marginBottom : '4em'}}>
                <Typography variant="h4" style={{fontWeight : '500', margin : '1em 0px'}}>Our Blogs</Typography>
                    <Grid container spacing={2}>
                    {Blogs.length === 0 ? <Skeleton variant="rect" width="100%" height="150px"/> : Blogs.map(event => (
                        <Grid item xs={12} sm={6} md={12} key={event._id}>
                        <BlogCard 
                        Image={URL+event.Image.formats.thumbnail.url}
                        title={event.Title} 
                        speaker={event.Author} 
                        discription={event.Description} 
                        Platform={event.Platform}
                        url={event.Read}
                        data={event}
                        />
                        </Grid>
                    ))}
                    </Grid>
                </Container>  
        </Layout>
    )
}
Example #3
Source File: RecipeList.jsx    From archeage-tools with The Unlicense 6 votes vote down vote up
render() {
    const { maxHeight } = this.props;
    const { recipeList, loaded } = this.state;

    if (loaded !== this.getRecipeKey()) {
      const skeletons = [];
      for (let i = 0; i < 10; i++) {
        skeletons.push(<Skeleton variant="text" key={`skele-${i}`} />);
      }

      return (
        <div className="recipe-list">
          {skeletons}
        </div>
      );
    }

    return (
      <Typography component="div" className="recipe-list" style={{ maxHeight: maxHeight > 0 ? maxHeight : null }}>
        {recipeList.map(cat => this.renderRecipeCategory(cat))}
      </Typography>
    );
  }
Example #4
Source File: SkillLink.jsx    From archeage-tools with The Unlicense 6 votes vote down vote up
render() {
    const { id, skill } = this.props;
    const { name, icon, passive, ancestralElement: element } = skill;

    let text = '';
    if (passive) {
      text += '[Passive] ';
    } else if (element && element !== ELEMENT.BASIC) {
      text += `[${element}] `;
    }
    text += name;

    if (!icon) {
      text = (
        <Skeleton
          variant="text"
          style={{ display: 'inline-block', marginLeft: 4, width: 80, height: 20, transform: 'none' }}
        />
      );
    }

    return (
      <SkillTooltip skillId={id}>
        <Link className="inline-link">
          <SkillIcon id={id} className="inline" disableTooltip />
          {text}
        </Link>
      </SkillTooltip>
    );
  }
Example #5
Source File: Skill.jsx    From archeage-tools with The Unlicense 6 votes vote down vote up
render() {
    const { spentPoints, onClick, learned, remainingPoints, noRequirement, className, disableTooltip, ancestral } = this.props;
    const { id, icon, requiredLevel, passive } = this.props;
    const pointsRequired = passive ? requiredLevel : getPointReq(requiredLevel);
    const disabled = passive ? !learned
      : !learned && !ancestral && (spentPoints < pointsRequired || remainingPoints === 0);

    if (!icon) {
      return (
        <span className={cn('skill', className)}>
          <Skeleton
            variant="rect"
            width="100%"
            height="100%"
          />
        </span>
      );
    }

    return (
      <SkillTooltip
        skillId={id}
        disabled={disabled && spentPoints < pointsRequired}
        spentPoints={spentPoints}
        disableTooltip={disableTooltip}
      >
        <span
          className={cn('skill', className, { disabled, 'available': !disabled && !learned, ancestral })}
          onClick={disabled ? null : onClick}
          data-points-req={ancestral || learned || noRequirement || spentPoints >= pointsRequired ? 0 : pointsRequired}
        >
          <img src={`/images/icon/${icon}.png`} alt="" />
        </span>
      </SkillTooltip>
    );
  }
Example #6
Source File: NpcLink.jsx    From archeage-tools with The Unlicense 6 votes vote down vote up
render() {
    const { id, name, style, noLink } = this.props;

    if (!name) {
      return <Skeleton variant="text" style={{ display: 'inline-block', marginLeft: 4, width: 80 }} />;
    }

    if (noLink) {
      return (
        <Typography component="span" style={style}>
          {name}
        </Typography>
      );
    }

    return (
      <NpcTooltip npcId={id} disabled={!name}>
        <Link className="inline-link" style={style}>
          {name}
        </Link>
      </NpcTooltip>
    );
  }
Example #7
Source File: DoodadLink.jsx    From archeage-tools with The Unlicense 6 votes vote down vote up
render() {
    const { id, name, style, noLink } = this.props;

    if (!name) {
      return <Skeleton variant="text" style={{ display: 'inline-block', marginLeft: 4, width: 80 }} />;
    }

    if (noLink) {
      return (
        <Typography component="span" style={style}>
          {name}
        </Typography>
      );
    }

    return (
      <DoodadTooltip doodadId={id} disabled={!name}>
        <Link className="inline-link" style={style}>
          {name}
        </Link>
      </DoodadTooltip>
    );
  }
Example #8
Source File: Item.jsx    From archeage-tools with The Unlicense 6 votes vote down vote up
render() {
    const { id, name, icon, count, defaultGrade, overlay, inline, tooltipDisabled, showCount } = this.props;
    let { grade } = this.props;
    if (defaultGrade !== grade && defaultGrade > 1) {
      grade = defaultGrade;
    }

    if (!icon) return (
      <Skeleton
        variant="rect"
        width={inline ? 20 : '100%'}
        height={inline ? 20 : '100%'}
        style={{ display: inline ? 'inline-block' : 'block' }}
      />
    );

    return (
      <ItemTooltip itemId={id} grade={grade} disabled={tooltipDisabled || !name}>
        <span className={cn('item-icon', { [overlay]: Boolean(overlay), inline, showCount })} data-grade={grade}
              data-id={id}>
          <img src={`/images/icon/${icon}.png`} alt={name} />
          {((count > 0 && !inline) || showCount) && <span className="count">{count}</span>}
        </span>
      </ItemTooltip>
    );
  }
Example #9
Source File: PublicComments.js    From app with MIT License 6 votes vote down vote up
function PublicComments({ requestId }) {
  const user = useUser();

  return (
    <>
      <Typography variant="h6">Public Comments</Typography>
      <Suspense
        fallback={
          <>
            <Skeleton />
            <Skeleton />
            <Skeleton />
          </>
        }>
        <CommentList requestId={requestId} />
      </Suspense>
      {user && <CommentEntry requestId={requestId} />}
    </>
  );
}
Example #10
Source File: ItemLink.jsx    From archeage-tools with The Unlicense 5 votes vote down vote up
render() {
    const { id, item, plural, count, style, noLink, name } = this.props;

    let text = '';
    let width = 80;
    if (count > 1) {
      text += `${count} `;
    }
    if (name !== null) {
      text += name;
      if (name === '') {
        width = 20;
      }
    } else {
      text += item.name;
      if (count !== 1 || plural !== null) {
        text += (plural !== null ? plural : 's');
      }
    }

    if (!item.icon) {
      text = <Skeleton variant="text" style={{ display: 'inline-block', marginLeft: 4, width }} />;
    }

    if (noLink) {
      return (
        <Typography component="span" style={style}>
          <Item id={id} inline />
          {text}
        </Typography>
      );
    }

    return (
      <ItemTooltip itemId={id} disabled={!item.icon}>
        <Link className="inline-link" style={style}>
          <Item id={id} inline tooltipDisabled />
          {text}
        </Link>
      </ItemTooltip>
    );
  }
Example #11
Source File: QuestLink.jsx    From archeage-tools with The Unlicense 5 votes vote down vote up
render() {
    const { id, name, flags: flagsD, style, noLink, region, categoryId, category } = this.props;

    if (!name) {
      return <Skeleton variant="text" style={{ display: 'inline-block', marginLeft: 4, width: 80 }} />;
    }

    const flags = flagsD.filter(f => f.region === region);

    let icon = 'quest';

    if (flags.find(f => f.code === FLAG_DAILY) || flags.find(f => f.code === FLAG_WEEKLY)) {
      icon = 'daily';
    } else if (flags.find(f => f.code === FLAG_REPEATABLE)) {
      icon = 'repeat';
    } else if (categoryId === 70) {
      icon = 'vocation';
    } else if (category.storyCategory) {
      icon = 'story';
    }

    if (noLink) {
      return (
        <Typography component="span" style={style}>
          <span className={cn('quest-icon', icon)} /> {name}
        </Typography>
      );
    }

    return (
      <QuestTooltip questId={id} disabled={!name}>
        <span style={{ whiteSpace: 'nowrap' }}>
          <span className={cn('quest-icon', icon)} />
          <Link className="inline-link" style={style}>
            {name}
          </Link>
        </span>
      </QuestTooltip>
    );
  }
Example #12
Source File: FolioHeader.jsx    From archeage-tools with The Unlicense 5 votes vote down vote up
render() {
    const { open, loading, searchType, options } = this.state;
    const { items, mobile } = this.props;

    return (
      <AppBar position="static" className="section folio-header">
        <Toolbar>
          {!mobile &&
          <Typography variant="h5" className="title-text">Folio</Typography>}
          <Autocomplete
            open={open}
            onOpen={() => this.setOpen(true)}
            onClose={() => this.setOpen(false)}
            onChange={this.handleSearch}
            loading={loading}
            options={options}
            getOptionLabel={option => option.name || option}
            filterOptions={(options) => options}
            classes={{
              noOptions: 'folio-no-option',
            }}
            renderOption={option => (
              <div className="item-result" key={option.id}>
                <Item id={option.id} inline />
                {items[option.id]
                  ? <Typography variant="body2">{items[option.id].name}</Typography>
                  : <Skeleton variant="text" />}
              </div>
            )}
            freeSolo
            onInputChange={(e, value) => {
              this._handleQuery(value);
            }}
            renderInput={params => (
              <TextField
                {...params}
                label={`Search by ${searchTypes.find(type => type.value === searchType).label}`}
                fullWidth
                variant="standard"
                size="small"
                margin="none"
                InputProps={{
                  ...params.InputProps,
                  endAdornment: (
                    <>
                      {loading ? <CircularProgress color="inherit" size={20} /> : null}
                      {params.InputProps.endAdornment}
                    </>
                  ),
                }}
                InputLabelProps={{
                  ...params.InputLabelProps,
                }}
              />
            )}
          />
          <RadioGroup name="search-type" value={searchType} onChange={this.handleTypeChange} row={!mobile}>
            {searchTypes.map(searchType => (
              <FormControlLabel
                control={<Radio size="small" color="primary" />}
                {...searchType}
                key={searchType.value}
              />
            ))}
          </RadioGroup>
        </Toolbar>
      </AppBar>
    );
  }
Example #13
Source File: Discussion.js    From app with MIT License 5 votes vote down vote up
/*
 * This component assumes that the user is logged in.
 */
function Discussion({ requestId }) {
  const classes = useStyles();
  const firestore = useFirestore();
  const user = useUser();
  const userProfileSnap = useFirestoreDoc(
    firestore.doc(`${USERS_COLLECTION}/${user.uid}`),
  );

  const requestPublicSnap = useFirestoreDoc(
    firestore.doc(`${REQUESTS_PUBLIC_COLLECTION}/${requestId}`),
  );

  // console.log(requestPublicSnap.metadata);
  // console.log(requestPublicSnap.get('d.owner'));
  if (
    requestPublicSnap.metadata.hasPendingWrites ||
    (requestPublicSnap.get('d.owner') !== user.uid &&
      userProfileSnap.get('role') !== 'system-admin')
  ) {
    return null;
  }

  return (
    <Paper className={classes.paper} data-test="discussion">
      <Typography variant="h6">Private Comments</Typography>
      <Typography variant="body2">
        These comments are only shown to the assigned volunteer and the
        administrators.
      </Typography>
      <Suspense
        fallback={
          <>
            <Skeleton />
            <Skeleton />
            <Skeleton />
          </>
        }>
        <CommentList requestId={requestId} />
      </Suspense>
      {user && <CommentEntry requestId={requestId} />}
    </Paper>
  );
}
Example #14
Source File: MusicCard.jsx    From soundly with MIT License 5 votes vote down vote up
function MusicCard(props) {
  const { name, img, author_name } = props.music;

  const [isHovered, setHovered] = useState(false);

  function handleResponse() {
    setHovered(!isHovered);
  }

  const dispatch = useDispatch();

  function handlePlay() {
    dispatch(setCurrentPlaying(props.music));
    dispatch(increaseTimesPlayed(props.music.id));
  }

  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    setLoaded(true);
  }, []);

  return (
    <div className={"music-card"}>
      {!loaded ? (
        <div className={"Skeleton-top"}>
          <Skeleton variant="rect" width={210} height={210} />
          <Box pt={0.5}>
            <Skeleton />
            <Skeleton width="60%" />
          </Box>
        </div>
      ) : (
        <>
          <div onClick={handlePlay} className={"music-card-cover"} onMouseOver={handleResponse}>
            <img src={require("../assets/img/" + img).default} alt={name} style={{ borderRadius: "5px" }} />
            <div className="play-circle">
              <PlayCircleFilledWhiteIcon />
            </div>
          </div>
          <React.Fragment>
            <Name name={name} className={"song-name"} length={name.length} />
            <Name name={author_name} className={"author-name"} length={author_name.length} />
          </React.Fragment>
        </>
      )}
    </div>
  );
}
Example #15
Source File: events.js    From dscbppimt-official-website with MIT License 5 votes vote down vote up
function Events() {
    const classes = useStyles()
    const [ Events, setEvents] = useState([]);
    const [ upcomingEvents, setUpcomingEvents ] = useState([])
    const [ isLoading, setLoading ] = useState(false)
    const URL = "https://dscbppimt-cms.herokuapp.com/files/"
    useEffect(() => {
        const today = new Date()
        const todayDate = today.toISOString()
        console.log(todayDate)
        // 2020-10-11T09:10:30.698Z
        setLoading(true);
        Axios.get(`https://dscbppimt-cms.herokuapp.com/our-events?Date_gte=${todayDate}&_sort=Date:desc&_limit=2`).then(res => {
          console.log(res.data);
          setUpcomingEvents(res.data);
          setLoading(false)
        });
        Axios.get(`https://dscbppimt-cms.herokuapp.com/our-events?Date_lt=${todayDate}&_sort=Date:desc`).then(res => {
            console.log(res.data);
            setEvents(res.data);
            setLoading(false)
          });
    },[])
    return (
        <Layout>
            <Box>
                <Container style={{marginBottom : '4em'}}>
                    <Typography variant="h4" style={{fontWeight : '500', margin : '1em 0px'}}>Our Events</Typography>
                    <Grid container spacing={2}>
                    {isLoading ? <Skeleton variant="rect" width="100%" height="150px"/> : upcomingEvents.length !== 0 ? upcomingEvents.map(event => (
                        <Grid item xs={12} sm={6} md={12} key={event._id}>
                        <EventCard 
                        Image={URL+(event.Image.formats.thumbnail.url)}
                        title={event.Title} 
                        speaker={event.Speaker === 'None' ? null : event.Speaker } 
                        description={event.Description} 
                        date={event.Date}
                        data={event.Image}
                        register={event.Register}
                        learn={event.Learn}
                        />
                        </Grid>
                    )) : <Container style={{width: '100%', textAlign: 'center', margin: '4em 0'}}><Typography align="center" >No Upcoming Events</Typography></Container>}
                    </Grid>
                </Container>
            </Box>
            <Container style={{padding : '2em'}}>
                <Box style={{display : 'flex', justifyContent : 'space-between'}}>
                    <Typography variant="h6">Past Events</Typography>
                </Box>
                <TableContainer component={Paper} className={classes.tableContainer}>
      <Table className={classes.table} aria-label="simple table">
        <TableHead>
          <TableRow>
            <TableCell align="center">Event</TableCell>
            <TableCell align="center">Speaker</TableCell>
            <TableCell align="center">Date</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {Events.map((row) => (
            <TableRow key={row.Title}>
              <TableCell component="th" scope="row" align="center">{row.Title}</TableCell>
              <TableCell align="center">{row.Speaker}</TableCell>
              <TableCell align="center">{row.Date}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
            </Container>

        </Layout>
    )
}
Example #16
Source File: index.js    From dscbppimt-official-website with MIT License 5 votes vote down vote up
export default function Index() {
  const [Events, setEvents] = useState([]);
  const [ isLoading, setLoading ] = useState(false)
  const URL = "https://dscbppimt-cms.herokuapp.com"
  useEffect(() => {
    const data = async() => {
        let dataArray = [];
        const today = new Date()
        const todayDate = today.toISOString()
        console.log(todayDate)
        // 2020-10-11T09:10:30.698Z
        Axios.get(`https://dscbppimt-cms.herokuapp.com/our-events?Date_gte=${todayDate}&_sort=Date:desc&_limit=2`).then(res => {
          dataArray = dataArray.concat(res.data)
          console.log(dataArray);
          setEvents(dataArray);
        });
    }
    data();
},[])
  return (
    <Layout>
      <Head>
        <title>DSC BPPIMT</title>
      </Head>
      <Header />
      <AboutCardView />
      <Container>
        <Box style={{padding : '2em 0px',display : 'flex', justifyContent : 'space-between'}} className={styles.eventsCard}>
          <Typography variant="h5" style={{fontWeight : '600', marginBottom : '.5em'}} className={styles.title}>Upcoming <Box color="primary.main" style={{display : 'inline'}}>Events</Box> and <Box color="primary.main" style={{display : 'inline'}}>Meetups</Box>.</Typography>
          <Link href="/events"><Button component="button">View All</Button></Link>
        </Box>
        
        <Grid container spacing={2} style={{padding : '0 0 2em 0'}}>
        {isLoading ? <Skeleton variant="rect" width="100%" height="150px"/>  : Events.length !== 0 ? Events.map(event => (
                        <Grid item xs={12} sm={6} md={12} key={event._id}>
                        <EventCard 
                        Image={event.Image ? URL+(event.Image.url) : ''}
                        title={event.Title} 
                        speaker={event.Speaker === 'None' ? null : event.Speaker } 
                        description={event.Description} 
                        date={event.Date}
                        register={event.Register}
                        learn={event.Learn}
                        />
                        </Grid>
                    )) : <Container style={{width: '100%', textAlign: 'center', margin: '5em 0'}}><Typography align="center" >No Upcoming Events</Typography></Container>}
        </Grid>
      </Container>
      <ContactCardView />
    </Layout>
  );
}
Example #17
Source File: index.jsx    From redive_linebot with MIT License 5 votes vote down vote up
DataList = () => {
  const [{ data = {}, error, loading }, refetch] = useAxios("/api/Game/World/Boss/Feature/Message");
  const { data: messageData = [] } = data;
  const columns = [
    {
      headerName: "頭像",
      field: "icon_url",
      flex: 0.5,
      renderCell: genAvatar,
    },
    { headerName: "訊息樣板", field: "template", flex: 2 },
    {
      headerName: "操作",
      field: "id",
      flex: 1.4,
      // eslint-disable-next-line react/display-name
      renderCell: rawData => <ControlButtons value={rawData.value} onDeleteComplete={refetch} />,
    },
  ];

  useEffect(() => {
    refetch();
    return () => {};
  }, [window.location.pathname]);

  if (loading) {
    return <Skeleton animation="wave" variant="rect" width="100%" height={300} />;
  }

  if (error) {
    return <Alert severity="error">發生錯誤,請確認是否有管理權限!</Alert>;
  }

  return (
    <div style={{ width: "100%" }}>
      <DataGrid
        columns={columns}
        rows={messageData}
        autoHeight
        disableColumnMenu
        disableColumnFilter
        disableColumnSelector
      />
    </div>
  );
}
Example #18
Source File: Home.jsx    From soundly with MIT License 5 votes vote down vote up
function Home() {
  const [screenSize, setScreenSize] = useState(undefined);
  const [currMusic, setCurrMusic] = useState(null);
  const [Page, setCurrPage] = useState(<MusicCardContainer />);

  let pathname = window.location.pathname;
  useEffect(() => {
    setCurrPage(getCurrPage(pathname));
  }, [pathname]);

  window.addEventListener("resize", handleResize);

  function handleResize() {
    setScreenSize(window.innerWidth);
  }

  useEffect(() => {
    handleResize();
    return () => window.removeEventListener("resize", handleResize);
  });

  const useStyle = useContext(ThemeContext);
  const { playing, bannerOpen } = useSelector(state => state.musicReducer);

  useEffect(() => {
    setCurrMusic(playing);
  }, [playing]);

  const [loaded, setLoaded] = useState(false);
  useEffect(() => {
    setLoaded(true);
  }, []);

  return (
    <div style={useStyle.component} className={"home-container"}>
      {!loaded ? (
        <div className="Home-skeleton">
          <Skeleton animation={"wave"} variant={"rect"} height={"100vh"} />
        </div>
      ) : (
        <>
          <section className={"home-music-container"}>
            <div className="sidebar-home">
              <SideBar />
            </div>
            <div className="main-home">
              <div id={"main-content"}>
                {screenSize <= 970 ? <MobileTopNavigation /> : <Navigation />}
                  <div className={"page-content"}>
                    {Page}
                  </div>
                
              </div>
            </div>
          </section>
          {bannerOpen && (
            <section className="current-large-banner">
              <CurrentPlayingLarge />
            </section>
          )}
          <React.Fragment>
            {currMusic ? <FooterMusicPlayer music={currMusic} /> : <FooterSelectMusic />}
            {/* <FooterMusicPlayer music={currMusic} /> */}
            {screenSize <= 970 && <BottomNavigationMobile />}
          </React.Fragment>
        </>
      )}
    </div>
  );
}
Example #19
Source File: [...categorySlug].js    From react-storefront-starter-app with Apache License 2.0 4 votes vote down vote up
Subcategory = lazyProps => {
  const [store, updateStore] = useSearchResultsStore(lazyProps)
  const classes = useStyles()
  const theme = useTheme()
  let { pageData, loading } = store

  if (pageData.isLanding) {
    return (
      <>
        <Breadcrumbs items={!loading && pageData.breadcrumbs} />
        <Grid item xs={12}>
          {!loading ? (
            <Typography
              component="h1"
              variant="h4"
              gutterBottom
              align="center"
              className={classes.landingTitleSpacing}
            >
              {pageData.name}
            </Typography>
          ) : (
            <Skeleton height={32} style={{ marginBottom: theme.spacing(1) }} />
          )}
        </Grid>
        {!loading && <LandingCmsSlots cmsBlocks={pageData.cmsBlocks} />}
      </>
    )
  }

  // Here is an example of how you can customize the URL scheme for filtering and sorting - /s/1?color=red,blue=sort=pop
  // Note that if you change this, you also need to change pages/api/[...categorySlug].js to correctly handle the query parameters
  // you send it.
  const queryForState = useCallback(state => {
    const { filters, page, sort } = state
    const query = {}

    for (let filter of filters) {
      const [name, value] = filter.split(':')

      console.log(name, value)

      if (query[name]) {
        query[name] = `${query[name]},${value}`
      } else {
        query[name] = value
      }
    }

    if (query.more) {
      delete query.more
    }

    if (page > 0) {
      query.page = page
    } else {
      delete query.page
    }

    if (sort) {
      query.sort = sort
    } else {
      delete query.sort
    }

    console.log('query', query)

    return query
  }, [])

  return (
    <>
      <Breadcrumbs items={!loading && pageData.breadcrumbs} />
      <SearchResultsProvider store={store} updateStore={updateStore} queryForState={queryForState}>
        <Container maxWidth="lg" style={{ paddingTop: theme.spacing(2) }}>
          <Head>{loading ? null : <title>{pageData.title}</title>}</Head>
          <BackToTop />
          <Hbox align="flex-start">
            <Hidden implementation="css" xsDown>
              <div className={classes.sideBar}>
                <Hidden xsDown>
                  {/* Display the filters for desktop screen sizes */}
                  <Filter classes={{ root: classes.sideBar }} expandAll submitOnChange />
                </Hidden>
              </div>
            </Hidden>
            <Grid container style={{ position: 'relative' }}>
              <LoadMask show={store.reloading} transparent align="top" />
              <Grid item xs={12}>
                {!loading ? (
                  <Typography component="h1" variant="h6" gutterBottom>
                    {pageData.name}
                  </Typography>
                ) : (
                  <Skeleton height={32} style={{ marginBottom: theme.spacing(1) }} />
                )}
              </Grid>
              <Grid item xs={6} style={{ paddingRight: theme.spacing(1) }}>
                <Hidden implementation="css" smUp>
                  {/* Display a button that opens the filter drawer on mobile screen sizes */}
                  <FilterButton style={{ width: '100%' }} />
                </Hidden>
              </Grid>
              <Grid item xs={6} style={{ display: 'flex', justifyContent: 'flex-end' }}>
                {/* The sort button is automatically responsive.  It will show as a dropdown on desktop, and open a drawer on mobile */}
                <SortButton className={classes.sortButton} />
              </Grid>
              <Grid item xs={6}></Grid>
              <Grid item xs={12} style={{ display: 'flex', justifyContent: 'flex-end' }}>
                {loading ? (
                  <Skeleton
                    width={90}
                    height={14}
                    style={{ marginBottom: 4 }}
                    className={classes.total}
                  />
                ) : (
                  <Typography variant="caption" className={classes.total}>
                    <span>
                      {pageData.total} total {pageData.total === 1 ? 'item' : 'items'}
                    </span>
                  </Typography>
                )}
              </Grid>
              <Grid item xs={12}>
                {!loading ? (
                  <ResponsiveTiles autoScrollToNewTiles>
                    {pageData.products.map((product, i) => (
                      <ProductItem key={product.id} product={product} index={i} />
                    ))}
                  </ResponsiveTiles>
                ) : (
                  <ResponsiveTiles>
                    {(() => {
                      const tiles = []
                      for (let i = 0; i < 10; i++) {
                        tiles.push(
                          <div
                            key={i}
                            style={{ marginTop: theme.spacing(2), marginBottom: theme.spacing(2) }}
                          >
                            <Fill height="100%" style={{ marginBottom: theme.spacing(1) }}>
                              <Skeleton variant="rect" />
                            </Fill>
                            <Skeleton height={26} />
                            <ProductOptionSelector
                              skeleton={4}
                              variant="swatch"
                              size="small"
                              optionProps={{
                                size: 'small',
                                showLabel: false,
                              }}
                            />
                            <Skeleton height={18} />
                            <Skeleton height={24} style={{ marginTop: '5px' }} />
                          </div>
                        )
                      }
                      return tiles
                    })()}
                  </ResponsiveTiles>
                )}
              </Grid>
              <Grid item xs={12}>
                {!loading && <ShowMore variant="button" style={{ paddingBottom: 200 }} />}
              </Grid>
            </Grid>
          </Hbox>
        </Container>
      </SearchResultsProvider>
    </>
  )
}
Example #20
Source File: [productId].js    From react-storefront-starter-app with Apache License 2.0 4 votes vote down vote up
Product = React.memo(lazyProps => {
  const theme = useTheme()
  const [confirmationOpen, setConfirmationOpen] = useState(false)
  const [addToCartInProgress, setAddToCartInProgress] = useState(false)
  const [state, updateState] = useLazyState(lazyProps, {
    pageData: { quantity: 1, carousel: { index: 0 } },
  })
  const classes = useStyles()
  const product = get(state, 'pageData.product') || {}
  const color = get(state, 'pageData.color') || {}
  const size = get(state, 'pageData.size') || {}
  const quantity = get(state, 'pageData.quantity')
  const { actions } = useContext(SessionContext)
  const { loading } = state

  // This is provided when <ForwardThumbnail> is wrapped around product links
  const { thumbnail } = useContext(PWAContext)

  // Adds an item to the cart
  const handleSubmit = async event => {
    event.preventDefault() // prevent the page location from changing
    setAddToCartInProgress(true) // disable the add to cart button until the request is finished

    try {
      // send the data to the server
      await actions.addToCart({
        product,
        quantity,
        color: color.id,
        size: size.id,
      })

      // open the confirmation dialog
      setConfirmationOpen(true)
    } finally {
      // re-enable the add to cart button
      setAddToCartInProgress(false)
    }
  }

  const header = (
    <Row>
      <Typography variant="h6" component="h1" gutterBottom>
        {product ? product.name : <Skeleton style={{ height: '1em' }} />}
      </Typography>
      <Hbox>
        <Typography style={{ marginRight: theme.spacing(2) }}>{product.priceText}</Typography>
        <Rating value={product.rating} reviewCount={10} />
      </Hbox>
    </Row>
  )

  // Fetch variant data upon changing color or size options
  useDidMountEffect(() => {
    const query = qs.stringify({ color: color.id, size: size.id }, { addQueryPrefix: true })
    fetchVariant(`/api/p/${product.id}${query}`)
      .then(res => res.json())
      .then(data => {
        return updateState({ ...state, pageData: { ...state.pageData, ...data.pageData } })
      })
      .catch(e => {
        if (!StaleResponseError.is(e)) {
          throw e
        }
      })
  }, [color.id, size.id])

  return (
    <>
      <Breadcrumbs items={!loading && state.pageData.breadcrumbs} />
      <Container maxWidth="lg" style={{ paddingTop: theme.spacing(2) }}>
        <form onSubmit={handleSubmit} method="post" action-xhr="/api/cart">
          <Grid container spacing={4}>
            <Grid item xs={12} sm={6} md={5}>
              <Hidden implementation="css" smUp>
                {header}
              </Hidden>
              <MediaCarousel
                className={classes.carousel}
                lightboxClassName={classes.lightboxCarousel}
                thumbnail={thumbnail.current}
                height="100%"
                media={color.media || (product && product.media)}
              />
            </Grid>
            <Grid item xs={12} sm={6} md={7}>
              <Grid container spacing={4}>
                <Grid item xs={12}>
                  <Hidden implementation="css" xsDown>
                    <div style={{ paddingBottom: theme.spacing(1) }}>{header}</div>
                  </Hidden>
                  {product ? (
                    <>
                      <Hbox style={{ marginBottom: 10 }}>
                        <Label>COLOR: </Label>
                        <Typography>{color.text}</Typography>
                      </Hbox>
                      <ProductOptionSelector
                        options={product.colors}
                        value={color}
                        onChange={value =>
                          updateState({ ...state, pageData: { ...state.pageData, color: value } })
                        }
                        strikeThroughDisabled
                        optionProps={{
                          showLabel: false,
                        }}
                      />
                    </>
                  ) : (
                    <div>
                      <Skeleton style={{ height: 14, marginBottom: theme.spacing(2) }}></Skeleton>
                      <Hbox>
                        <Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
                        <Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
                        <Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
                      </Hbox>
                    </div>
                  )}
                </Grid>
                <Grid item xs={12}>
                  {product ? (
                    <>
                      <Hbox style={{ marginBottom: 10 }}>
                        <Label>SIZE: </Label>
                        <Typography>{size.text}</Typography>
                      </Hbox>
                      <ProductOptionSelector
                        options={product.sizes}
                        value={size}
                        strikeThroughDisabled
                        onChange={value =>
                          updateState({ ...state, pageData: { ...state.pageData, size: value } })
                        }
                      />
                    </>
                  ) : (
                    <div>
                      <Skeleton style={{ height: 14, marginBottom: theme.spacing(2) }}></Skeleton>
                      <Hbox>
                        <Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
                        <Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
                        <Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
                      </Hbox>
                    </div>
                  )}
                </Grid>
                <Grid item xs={12}>
                  <Hbox>
                    <Label>QTY:</Label>
                    <QuantitySelector
                      value={quantity}
                      onChange={value =>
                        updateState({ ...state, pageData: { ...state.pageData, quantity: value } })
                      }
                    />
                  </Hbox>
                </Grid>
                <Grid item xs={12}>
                  <Button
                    key="button"
                    type="submit"
                    variant="contained"
                    color="primary"
                    size="large"
                    data-th="add-to-cart"
                    className={clsx(classes.docked, classes.noShadow)}
                    disabled={addToCartInProgress}
                  >
                    Add to Cart
                  </Button>
                  <AddToCartConfirmation
                    open={confirmationOpen}
                    setOpen={setConfirmationOpen}
                    product={product}
                    color={color}
                    size={size}
                    quantity={quantity}
                    price={product.priceText}
                  />
                </Grid>
              </Grid>
            </Grid>
          </Grid>
          <Grid item xs={12}>
            <TabPanel>
              <CmsSlot label="Description">{product.description}</CmsSlot>
              <CmsSlot label="Specs">{product.specs}</CmsSlot>
            </TabPanel>
          </Grid>
          <Grid item xs={12}>
            <Lazy style={{ minHeight: 285 }}>
              <SuggestedProducts product={product} />
            </Lazy>
          </Grid>
        </form>
      </Container>
    </>
  )
})
Example #21
Source File: ContactInfo.js    From app with MIT License 4 votes vote down vote up
// This component assumes that the user has access to the given contact info and should not be used
// directly. Even though it assumes permissions, it also has a workaround for a timing issues
// related to permission when permission is granted on the request page.
function ContactDetails({ requestId }) {
  const classes = useStyles();
  const firestore = useFirestore();
  const { showError } = useNotifications();

  const [retries, setRetries] = useState(0);
  const [contactInfo, setContactInfo] = useState(null);
  const [accessFailed, setAccessFailed] = useState(false);

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

  // 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 ref = firestore.doc(
          `${REQUESTS_CONTACT_INFO_COLLECTION}/${requestId}`,
        );
        // Call it once because this will throw the permission exception.
        await ref.get();
        setDocRef(ref); // 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 (!docRef) return undefined;
    const unsub = docRef.onSnapshot((docSnap) => {
      setContactInfo(docSnap);
    });
    return unsub;
  }, [docRef]);

  if (!contactInfo) {
    return <Skeleton />;
  }

  const phone = contactInfo.get('phone');
  const email = contactInfo.get('email');

  if (accessFailed) {
    return <div className={classes.info}>Failed to get access.</div>;
  }

  return (
    <div className={classes.info}>
      <Typography variant="body2">
        Phone: {phone ? <a href={`tel:${phone}`}>{phone}</a> : 'Not provided'}
      </Typography>
      <Typography variant="body2">
        Email:{' '}
        {email ? <a href={`mailto:${email}`}>{email}</a> : 'Not provided'}
      </Typography>
      {contactInfo.get('contactInfo') && (
        <Typography variant="body2">
          Contact Info: {contactInfo.get('contactInfo')}
        </Typography>
      )}
    </div>
  );
}
Example #22
Source File: NewsPost.jsx    From archeage-tools with The Unlicense 4 votes vote down vote up
render() {
    const { match: { params: { postId, action } }, mobile } = this.props;

    if (action === 'edit') {
      return <EditNewsPost {...this.props} />;
    }

    const { id, title, body, author, createDate, editDate, comments, commentCount, loading } = (this.state || this.props);
    const isEdited = (editDate && editDate !== createDate);
    const standalone = postId !== null;

    const dateBlock = loading
      ? <Skeleton variant="text" width={60} />
      : (
        <OptionalTooltip
          title={isEdited ? `Edited: ${new Date(editDate).toLocaleString(navigator.language || 'en-US')}` : null}
        >
          <Typography variant="overline" className={cn({ 'mark-tooltip': isEdited })}>
            {moment(createDate).format('MMM DD, YYYY')}
          </Typography>
        </OptionalTooltip>
      );

    const authorBlock = loading
      ? <Skeleton variant="text" width={60} className={cn({ 'title-text': standalone })} />
      : <Typography variant="subtitle2" className={cn({ 'title-text': !mobile, 'news-author': !mobile })}>
        {mobile && 'Written '} by {mobile ? <Username user={author} /> : author}
      </Typography>;

    let titleNode = <Skeleton variant="text" width={180} />;
    if (!loading) {
      if (standalone) {
        titleNode = title;
      } else {
        titleNode = <Link to={`/news/${id}`} color="inherit">{title}</Link>;
      }
    }

    return (
      <>
        <div className="section">
          <AppBar position="static">
            <Toolbar variant="dense">
              <Typography variant="h5" className={cn({ 'title-text': mobile })}>
                {titleNode}
              </Typography>
              {!mobile && authorBlock}
              {!mobile && dateBlock}
              {!loading &&
              <IfPerm permission="news.edit">
                <Tooltip title="Edit Post">
                  <IconButton color="inherit" onClick={() => push(`/news/${id}/edit`)}>
                    <CreateIcon />
                  </IconButton>
                </Tooltip>
              </IfPerm>}
            </Toolbar>
          </AppBar>
          {!loading &&
          <Paper>
            {/* <DraftJSRender contentState={stringToContentState(body)} />*/}
            <Viewer value={body} />
            {(!standalone || mobile) &&
            <CardActions className="paper-action" disableSpacing>
              {!standalone &&
              <Button
                startIcon={<ChatBubbleIcon />}
                onClick={() => push(`/news/${id}#comments`)}
              >
                {commentCount}{!mobile && ' Comments'}
              </Button>}
              {mobile &&
              (standalone
                ? <>
                  {authorBlock}
                  {dateBlock}
                </>
                : <div style={{ textAlign: 'right' }}>
                  {authorBlock}
                  {dateBlock}
                </div>)}
            </CardActions>}
          </Paper>}
          {loading &&
          <Paper>
            <div className="body-container">
              <Skeleton variant="rect" width="100%" height={200} />
            </div>
          </Paper>}
        </div>
        {standalone && id && !loading &&
        <Comments
          postId={`NEWS-${id}`}
          comments={comments}
          commentCount={commentCount}
        />}
      </>
    );
  }
Example #23
Source File: RequestPage.js    From app with MIT License 4 votes vote down vote up
function RequestPage() {
  const classes = useStyles();
  const { requestId } = useParams();
  const firestore = useFirestore();
  const user = useUser();

  const requestPublicSnap = useFirestoreDoc(
    firestore.doc(`${REQUESTS_PUBLIC_COLLECTION}/${requestId}`),
  );

  if (!requestPublicSnap.exists) {
    return (
      <Container>
        <Helmet>
          <title>Request Not Found</title>
        </Helmet>
        <Paper className={classes.paper} data-test="request-not-found">
          Request Not Found
        </Paper>
      </Container>
    );
  }

  let immediacy = 0;
  if (requestPublicSnap.exists) {
    immediacy = parseInt(requestPublicSnap.get('d.immediacy'), 10);
  }

  const { latitude, longitude } =
    requestPublicSnap.get('d.generalLocation') || {};
  const generalLocationName =
    requestPublicSnap.get('d.generalLocationName') || '';

  const mapImage = (
    <img
      className={classes.map}
      alt={requestPublicSnap.get('d.generalLocationName')}
      title={requestPublicSnap.get('d.generalLocationName')}
      src={`https://maps.googleapis.com/maps/api/staticmap?key=${process.env.REACT_APP_FIREBASE_API_KEY}&center=${latitude},${longitude}&markers=${latitude},${longitude}&size=280x280&zoom=10`}
    />
  );

  return (
    <>
      <Helmet>
        <title>
          {requestPublicSnap.get('d.firstName') || ''} &ndash;{' '}
          {generalLocationName}
        </title>
      </Helmet>
      <Container
        className={classes.header}
        data-test="request-title"
        maxWidth={false}>
        <Container>
          <Typography variant="h5" gutterBottom>
            {requestPublicSnap.get('d.firstName')}{' '}
            {/* {user &&
              user.uid &&
              requestPublicSnap.get('d.owner') === user.uid &&
              `${requestPublicSnap.get('d.lastName')} `} */}
            &ndash; {generalLocationName}
          </Typography>
        </Container>
      </Container>

      <Container className={classes.bodyContainer}>
        <Paper className={classes.paper} data-test="request-info">
          <Hidden smUp>
            <div className={classes.mobileImageContainer}>{mapImage}</div>
          </Hidden>

          <div className={classes.basicInfoContainer}>
            <div className={classes.basicInfo}>
              <Typography variant="h6" gutterBottom>
                {immediacy === 1
                  ? 'Not urgent'
                  : immediacy <= 5
                  ? 'Not very urgent'
                  : 'URGENT'}
                :{' '}
                {requestPublicSnap.get('d.needs') &&
                  requestPublicSnap
                    .get('d.needs')
                    .map((item) => (
                      <React.Fragment key={item}>
                        {allCategoryMap[item] ? (
                          <Chip
                            label={allCategoryMap[item].shortDescription}
                            className={classes.needChip}
                          />
                        ) : (
                          <Alert severity="error">
                            Could not find &apos;{item}&apos; in all category
                            map.
                          </Alert>
                        )}
                      </React.Fragment>
                    ))}
              </Typography>

              <Typography variant="caption" gutterBottom>
                REQUESTED
              </Typography>
              <Typography variant="h6" gutterBottom>
                {requestPublicSnap.get('d.createdAt') &&
                  format(
                    requestPublicSnap.get('d.createdAt').toDate(),
                    'EEE, MMM d, yyyy h:mm a',
                  )}
              </Typography>

              <Typography variant="caption">CONTACT</Typography>
              <Typography variant="h6" gutterBottom>
                <ContactInfo requestId={requestId} />
              </Typography>

              <Typography variant="caption" gutterBottom>
                OTHER DETAILS
              </Typography>
              {requestPublicSnap.get('d.otherDetails') ? (
                <Typography variant="h6" gutterBottom>
                  {requestPublicSnap.get('d.otherDetails')}
                </Typography>
              ) : (
                <Box color="text.disabled">
                  <Typography
                    variant="body2"
                    gutterBottom
                    className={classes.noDetails}>
                    No other details provided.
                  </Typography>
                </Box>
              )}
            </div>
            <Hidden xsDown>{mapImage}</Hidden>
          </div>

          <Divider className={classes.divider} />

          <Suspense fallback={<Skeleton />}>
            <RequestActions requestPublicSnapshot={requestPublicSnap} />
          </Suspense>
        </Paper>

        <Paper className={classes.paper} data-test="public-comments">
          <PublicComments requestId={requestId} />
        </Paper>

        {/* Only show for authenticated users. */}
        {user && user.uid && <Discussion requestId={requestId} />}
      </Container>
    </>
  );
}