next#InferGetServerSidePropsType TypeScript Examples

The following examples show how to use next#InferGetServerSidePropsType. 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: index.tsx    From tams-club-cal with MIT License 7 votes vote down vote up
Auth = ({ token, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();

    // When loaded, check if the user has a token parameter in the url
    // If they do, return the user to the previous page they were on
    // Otherwise, redirect them to the home page and show an error
    useEffect(() => {
        if (error) {
            setTimeout(() => {
                router.push('/profile');
            }, 3000);
        } else {
            // Saves the token into the cookies so the user doesn't have to re-login
            // TODO: This does not seem to work for long TT-TT
            const cookies = new Cookies();
            cookies.set('token', token, { sameSite: 'strict', path: '/' });

            // Return the user to the previous page they were on
            const prev = cookies.get('prev');
            cookies.remove('prev', { path: '/' });
            if (prev) router.push(prev);
            else router.push('/profile/dashboard');
        }
    }, []);

    return (
        <PageWrapper>
            <TitleMeta title="Authenticating" path="/auth" />
            <RobotBlockMeta />
            <Loading error={error}>
                Error logging in. Please check your internet and allow cookies then try again. Redirecting you to the
                login page in 3 seconds...
            </Loading>
        </PageWrapper>
    );
}
Example #2
Source File: news.tsx    From next-translate-routes with MIT License 6 votes vote down vote up
NewsPage: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ news }) => (
  <Layout title="News | next-translate-routes Example">
    <h1>News</h1>
    {news.map(({ id, date, content }) => (
      <p key={id}>
        {new Date(date).toLocaleString()} - {content}
      </p>
    ))}
    <p>
      <Link href="/">
        <a>Go home</a>
      </Link>
    </p>
  </Layout>
)
Example #3
Source File: my-account.tsx    From nextjs-strapi-boilerplate with MIT License 6 votes vote down vote up
MyAccountPage: InferGetServerSidePropsType<typeof getServerSideProps> = ({
  session,
}) => {
  if (!session) {
    return <AccessDeniedIndicator />;
  }

  return (
    <WithGraphQL session={session}>
      <Head>
        <title>My Account Page</title>
      </Head>
      <Page />
    </WithGraphQL>
  );
}
Example #4
Source File: feeds.tsx    From nextjs-strapi-boilerplate with MIT License 6 votes vote down vote up
FeedsPage: InferGetServerSidePropsType<typeof getServerSideProps> = ({
  session,
}) => {
  if (!session) {
    return <AccessDeniedIndicator />;
  }

  return (
    <WithGraphQL session={session}>
      <Head>
        <title>Feeds Page</title>
      </Head>
      <Page />
    </WithGraphQL>
  );
}
Example #5
Source File: index.tsx    From tams-club-cal with MIT License 5 votes vote down vote up
ClubList = ({ clubList, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const [clubCardList, setClubCardList] = useState(<Loading />);
    const [tableView, setTableView] = useState(false);
    const [sort, setSort] = useState('name');
    const [reverse, setReverse] = useState(false);

    // Update the club component list when the club list changes
    useEffect(() => {
        if (error) return;

        // Sort the list of clubs using a custom sorting method
        // We sort by advised first then sorting method (only name for now)
        const sortedList = clubList.sort((a, b) => {
            if (a.advised !== b.advised) return a.advised ? -1 : 1;
            return (reverse ? -1 : 1) * a[sort].localeCompare(b[sort]);
        });

        // Create a list of ClubCard components from the sorted list
        setClubCardList(
            <Grid container spacing={4} sx={{ marginBottom: 4 }}>
                {sortedList.map((c) => (
                    <Grid item xs={12} sm={6} lg={4} key={c.name} sx={{ flexGrow: { lg: 1, xs: 0 } }}>
                        <ClubCard club={c} />
                    </Grid>
                ))}
            </Grid>
        );
    }, [clubList, reverse]);

    // Return error if bad
    if (error) {
        return (
            <PageWrapper>
                <Loading error>
                    Could not get club list. Please reload the page or contact the site manager to fix this issue.
                </Loading>
            </PageWrapper>
        );
    }

    return (
        <PageWrapper>
            <TitleMeta title="Clubs" path="/clubs" />
            <Container maxWidth={false} sx={{ maxWidth: 1280 }}>
                <Box
                    width="100%"
                    marginBottom={2}
                    display="flex"
                    alignItems="center"
                    height={48}
                    justifyContent="flex-end"
                >
                    <SortSelect
                        value={sort}
                        setValue={setSort}
                        reverse={reverse}
                        setReverse={setReverse}
                        options={['name']}
                    />
                    <ViewSwitcher tableView={tableView} setTableView={setTableView} />
                </Box>
                <AddButton color="primary" label="Club" path="/edit/clubs" />
                {tableView ? <ClubTable clubs={clubList} /> : clubCardList}
            </Container>
        </PageWrapper>
    );
}
Example #6
Source File: index.tsx    From tams-club-cal with MIT License 5 votes vote down vote up
Login = ({ authorized, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const [popupEvent, setPopupEvent] = useState<PopupEvent>(null);
    const [backdrop, setBackdrop] = useState(false);
    const router = useRouter();
    const cookies = new Cookies();

    // On login success, send googleId and accessToken to the backend server
    // and do token exchange
    const responseGoogle = async (response) => {
        // Login backdrop
        setBackdrop(true);

        // Token exchange
        const token = await postLogin(response.tokenId);
        if (token.status !== 200) {
            setPopupEvent(createPopupEvent('Could not log in. Please check your connection and try again.', 4));
            return;
        }

        // Save token on frontend
        const cookies = new Cookies();
        cookies.set('token', token.data, { sameSite: 'strict', path: '/' });

        // Remove backdrop
        setBackdrop(false);

        // Send user to dashboard
        router.push('/profile/dashboard');
    };

    const errorMessage = (response) => {
        setPopupEvent(createPopupEvent(`Could not log in: ${response.error}`, 3));
    };

    // Redirect user if they are logged in
    useEffect(() => {
        if (authorized) router.push('/profile/dashboard');
    }, []);

    return (
        <PageWrapper>
            <TitleMeta title="Login" path="/profile" />
            <Popup event={popupEvent} />
            <UploadBackdrop open={backdrop} text="Logging in..." />
            <Card sx={{ maxWidth: 500, mx: 'auto', height: 'max-content' }}>
                <CardContent>
                    <Typography variant="h2" component="h1" sx={{ textAlign: 'center' }}>
                        Login
                    </Typography>
                    <Box sx={{ justifyContent: 'center', display: 'flex', marginTop: 3, marginBottom: 3 }}>
                        <GoogleLogin
                            clientId={CLIENT_ID}
                            buttonText="Login with Google"
                            onSuccess={responseGoogle}
                            onFailure={errorMessage}
                            cookiePolicy={'single_host_origin'}
                            theme="dark"
                        ></GoogleLogin>
                    </Box>
                    <Typography sx={{ color: (theme) => darkSwitchGrey(theme) }}>
                        {error ? 'Error getting login page! Please check your internet and try again!' : loginText}
                    </Typography>
                </CardContent>
            </Card>
        </PageWrapper>
    );
}
Example #7
Source File: dashboard.tsx    From tams-club-cal with MIT License 5 votes vote down vote up
Dashboard = ({ authorized, error, level, info }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();

    // Redirect the user to the admin page
    const toAdmin = () => router.push('/profile/admin');

    // Log the user out by removing their auth cookies
    const logout = () => {
        const cookies = new Cookies();
        cookies.remove('token', { path: '/' });
        cookies.set('success', 'Successfully logged out!', { path: '/' });
        router.push('/profile');
    };

    // Redirect user if they are not logged in
    useEffect(() => {
        const cookies = new Cookies();

        // If missing or bad token, return user to login page
        if (!authorized) {
            // cookies.remove('token', { path: '/' });
            router.push('/profile');
        }
    }, []);

    // Redirect user if unauthorized
    if (!authorized) {
        return (
            <PageWrapper>
                <Loading error={error}>
                    Unable to load dashboard. Please reload the page or contact the site manager to fix this issue.
                </Loading>
            </PageWrapper>
        );
    }

    return (
        <PageWrapper>
            <TitleMeta title="Profile" path="/profile/dashboard" />
            <Container>
                <Card>
                    <CardContent>
                        <Typography variant="h2" component="h1">
                            User Information
                        </Typography>
                        <TableContainer>
                            <Table>
                                <TableHead>
                                    <TableRow>
                                        <TableCell>Username</TableCell>
                                        <TableCell>Email</TableCell>
                                        <TableCell>Access Level</TableCell>
                                    </TableRow>
                                </TableHead>
                                <TableBody>
                                    <TableRow>
                                        <TableCell>{info.name}</TableCell>
                                        <TableCell>{info.email}</TableCell>
                                        <TableCell>{accessLevelToString(level)}</TableCell>
                                    </TableRow>
                                </TableBody>
                            </Table>
                        </TableContainer>
                        <Box sx={{ display: 'flex', justifyContent: 'center', marginTop: 2 }}>
                            <Button onClick={logout}>Logout</Button>
                        </Box>
                        {level === AccessLevel.ADMIN ? (
                            <Box sx={{ display: 'flex', justifyContent: 'center', marginTop: 2 }}>
                                <Button onClick={toAdmin}>Go to Admin Dashboard</Button>
                            </Box>
                        ) : null}
                    </CardContent>
                </Card>
            </Container>
        </PageWrapper>
    );
}
Example #8
Source File: index.tsx    From tams-club-cal with MIT License 5 votes vote down vote up
Home = ({ eventList, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const [eventComponentList, setEventComponentList] = useState<JSX.Element | JSX.Element[]>(
        <Loading sx={{ marginBottom: 4 }} />
    );

    // This hook will first make sure the event list is not empty/null,
    // then it will call a util function to split up multi-day events,
    // group the events by date, and create a list of EventListSections,
    // each containing a list of events for that day.
    useEffect(() => {
        // Make sure event list is not null
        if (eventList === null) return;

        // Set text to the end of the events list if empty
        if (eventList.length === 0) {
            setEventComponentList(
                <Typography variant="h6" component="h2" sx={listTextFormat}>
                    No events planned... Click the + to add one!
                </Typography>
            );
            return;
        }

        // Split the events into groups by date
        // TODO: Put this in a util function
        const eventGroupList = [];
        let tempList = [];
        eventList.forEach((event, index) => {
            if (tempList.length > 0 && isSameDate(tempList[tempList.length - 1].start, event.start)) {
                tempList.push(event);
            } else {
                if (index !== 0) eventGroupList.push(tempList);
                tempList = [event];
            }
        });
        eventGroupList.push(tempList);

        // Map each group item to an EventListSection object
        const groupedComponents = eventGroupList.map((group, index) => (
            <EventListSection eventList={group} key={index} />
        ));

        // No more message at the bottom!
        groupedComponents.push(
            <Typography key="nomore" sx={listTextFormat}>
                No more events... Click the + to add one!
            </Typography>
        );

        // Display list
        setEventComponentList(groupedComponents);
    }, [eventList]);

    // Show error message if errored
    if (error) {
        return (
            <HomeBase title={`Events`}>
                <Loading error sx={{ marginBottom: 4 }}>
                    Could not get activity list. Please reload the page or contact the site manager to fix this issue.
                </Loading>
            </HomeBase>
        );
    }

    return (
        <HomeBase>
            <Container
                maxWidth="lg"
                sx={{
                    height: 'max-content',
                    overflowX: 'hidden',
                }}
            >
                <AddButton color="primary" label="Event" path="/edit/events" />
                {eventComponentList}
            </Container>
        </HomeBase>
    );
}
Example #9
Source File: admin.tsx    From tams-club-cal with MIT License 5 votes vote down vote up
Admin = ({ authorized, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();

    // Redirect the user if they are illegally here
    useEffect(() => {
        if (!authorized) {
            router.push('/profile');
        }
    }, []);

    // Return error if user is not logged in and is not an admin
    if (!authorized) {
        return (
            <PageWrapper>
                <Loading error={error}>
                    Could not get data. Please reload the page or contact the site manager to fix this issue.
                </Loading>
            </PageWrapper>
        );
    }

    return (
        <PageWrapper>
            <TitleMeta title="Admin Dashboard" path="/profile/admin" />
            <RobotBlockMeta />
            <Container>
                <Typography variant="h2" component="h1" sx={{ textAlign: 'center', padding: 2 }}>
                    Admin Dashboard
                </Typography>
                <Typography variant="h3" sx={{ textAlign: 'center', marginTop: 3 }}>
                    Edit External Links
                </Typography>
                <Typography sx={{ textAlign: 'center', marginTop: '0.5rem', marginBottom: '1.5rem' }}>
                    All icon names are found from{' '}
                    <Link href="https://fonts.google.com/icons?icon.style=Rounded" target="_blank">
                        Google Material Symbols
                    </Link>
                    . Type the icon name as shown on the icon list with underscore case.
                </Typography>
                <EditLinkList />
                <Typography variant="h3" sx={{ textAlign: 'center', marginTop: 3 }}>
                    Manage Resources
                </Typography>
                <ManageResources />
                <Typography variant="h3" sx={{ textAlign: 'center', marginTop: 3 }}>
                    Change User Permissions
                </Typography>
                <ChangeUserPermissions />
                <Typography variant="h3" sx={{ textAlign: 'center', marginTop: 3 }}>
                    Feedback
                </Typography>
                <Typography sx={{ textAlign: 'center', color: '#888', paddingTop: 2 }}>To be added</Typography>
                <List></List>
            </Container>
        </PageWrapper>
    );
}
Example #10
Source File: [[...date]].tsx    From tams-club-cal with MIT License 4 votes vote down vote up
Reservations = ({ now, reservationList, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();
    const [reservationComponentList, setReservationComponentList] = useState(null);
    const [week, setWeek] = useState(dayjs(now));

    // Redirect the user to the current week on click
    const goToToday = () => {
        setWeek(dayjs());
    }

    // When the list of reservations updates, re-render the reservation components
    // This will create a table of reservations
    useEffect(() => {
        // If the reservation list is null, do nothing
        if (error) return;

        // Break up reservations into days and sort the reservations
        const brokenUpReservationList: BrokenReservation[] = [];
        reservationList.forEach((r) => {
            // Use variable to track the current day that we are working on
            let curr = dayjs(r.start);

            // If the reservation lasts all day, simply create one day-long entry and return
            // The span attribute corresponds to the number of hours the reservation lasts
            // This will start at 6am and end at midnight the next day, spanning 18 hours
            if (r.allDay) {
                brokenUpReservationList.push({
                    start: curr.startOf('day').add(6, 'hour'),
                    end: curr.startOf('day').add(1, 'day'),
                    span: 18,
                    data: r,
                });
                return;
            }

            // Iterate through the days until we reach the end of the reservation
            // This is useful for splitting up reservations that span multiple days
            while (!curr.isSame(dayjs(r.end), 'day')) {
                // If the current hour is 23 and the "end" of the event is within the next hour, break
                // This is to prevent events that end at midnight from appearing on the next day
                if (curr.hour() === 23 && curr.add(1, 'hour').isSame(dayjs(r.end), 'hour')) break;

                // Calculate the number of hours between the current hour and the end of the day
                const currEnd = curr.add(1, 'day').startOf('day');
                const currSpan = currEnd.diff(curr, 'hour');

                // Create an entry for the section of the event that spans this day
                // This entry will span the entire length of the day if it crosses over the entire day
                // However, the span will be less than 24 if it is less than the entire day
                brokenUpReservationList.push({ start: curr, end: currEnd, span: currSpan, data: r });
                curr = currEnd;
            }

            // Now we calculate the number of hours between the current hour and the end of the reservation
            // This is because the while loop will break on the last day of the reservation and we must
            // push the last segment on manually
            const span = dayjs(r.end).diff(curr, 'hour');
            brokenUpReservationList.push({ start: curr, end: dayjs(r.end), span, data: r });
        });

        // The reservation list is sorted by start date
        const sortedReservationList = brokenUpReservationList.sort((a, b) => a.start.valueOf() - b.start.valueOf());

        // Create a cut reservation list that only contains reservation blocks that
        // start at 6am or after -> this will remove all blocks between 12am and 6am
        // and truncate blocks that start within this interval but end after
        const cutReservationList = [];
        sortedReservationList.forEach((r) => {
            // If the reservation starts outside of the interval, simply add it to the list
            if (r.start.hour() >= 6) {
                cutReservationList.push(r);
            } else {
                // If the reservation starts within the 6am-12am interval,
                // and ends after this interval, keep the block but truncate the start
                // Otherwise we will simply ignore the block and remove it from the list
                if (r.end.hour() >= 6 || r.end.day() !== r.start.day()) {
                    const diff = 6 - r.start.hour();
                    cutReservationList.push({ ...r, start: r.start.hour(6), span: r.span - diff });
                }
            }
        });

        // Calculate start/end dates for list
        const start = week.startOf('week');
        const end = start.add(7, 'day');

        // Create the actual reservation components by iterating through the sorted list
        // and incrementing days when the next event in the sorted list is on the next day
        // This will continue to increment until the last day of the week
        // TODO: Add a catch for an infinite loop
        const components = [];
        let currTime = start;
        while (currTime.isBefore(end, 'day')) {
            components.push(
                <ReservationDay
                    reservationList={cutReservationList.filter((r) => currTime.isSame(dayjs(r.start), 'day'))}
                    date={currTime}
                    key={currTime.valueOf()}
                />
            );
            currTime = currTime.add(1, 'day');
        }

        // Update the state variable with the list of reservations
        setReservationComponentList(
            <Box display="flex" flexDirection="column">
                {components}
            </Box>
        );
    }, [reservationList]);

    // Redirect the user to the new week if it changes and is not the same as the current
    useEffect(() => {
        // If the week is invalid (ie. user manually changed the text input), do nothing
        if (isNaN(week.valueOf())) return;

        // If the week is the same as before, don't do anything either
        if (week.isSame(now, 'week')) return;

        // Otherwise, redirect the user to the new week
        router.push(`/events/reservations/${week.format('YYYY/M/D')}`);
    }, [week]);

    // Send error if cannot get data
    if (error) {
        return (
            <HomeBase noDrawer>
                <Loading error sx={{ marginBottom: 4 }}>
                    Could not get reservation list. Please reload the page or contact the site manager to fix this
                    issue.
                </Loading>
            </HomeBase>
        );
    }

    return (
        <HomeBase noDrawer>
            <TitleMeta title="Reservations" path="/events/reservations" />
            <Box display="flex">
                <DatePicker
                    inputFormat="[Week of] MMM D, YYYY"
                    label="Select week to show"
                    value={week}
                    onChange={setWeek}
                    renderInput={(params) => (
                        <TextField
                            {...params}
                            variant="standard"
                            sx={{ marginLeft: { sm: 4, xs: 2 } }}
                        />
                    )}
                />
                <Button variant="outlined" onClick={goToToday} sx={{ mx: 2 }}>Today</Button>
            </Box>
            <AddButton color="primary" label="Event" path="/edit/events" />
            {reservationComponentList === null ? <Loading /> : reservationComponentList}
        </HomeBase>
    );
}
Example #11
Source File: index.tsx    From tams-club-cal with MIT License 4 votes vote down vote up
Volunteering = ({ volunteeringList, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const [filteredList, setFilteredList] = useState([]);
    const [volunteeringCardList, setVolunteeringCardList] = useState(<Loading />);
    const [anchorEl, setAnchorEl] = useState(null);
    const [listView, setListView] = useState(false);
    const [sort, setSort] = useState('name');
    const [reverse, setReverse] = useState(false);
    const [filters, setFilters] = useState({
        open: false,
        limited: false,
        semester: false,
        setTimes: false,
        weekly: false,
    });

    // Filter the volunteering list based on the filters and sort
    useEffect(() => {
        if (error) return;

        // We set the filtered list to the volunteering list
        // after filtering and sorting it.
        setFilteredList(
            volunteeringList
                .filter((item) => {
                    // If no filters selected, show all results
                    if (!(filters.open || filters.limited || filters.semester || filters.setTimes || filters.weekly))
                        return true;

                    // TODO: See if we can create a better/more intuitive filter system

                    // See if all true filters match and return false if not
                    // This will filter OUT any results that don't contain the required filters
                    if (filters.open && !item.filters.open) return false;
                    if (filters.limited && !item.filters.limited) return false;
                    if (filters.semester && !item.filters.semester) return false;
                    if (filters.setTimes && !item.filters.setTimes) return false;
                    if (filters.weekly && !item.filters.weekly) return false;
                    return true;
                })
                .sort((a, b) => {
                    const rev = reverse ? -1 : 1;
                    if (typeof a[sort] === 'string') {
                        return rev * a[sort].localeCompare(b[sort]);
                    } else {
                        return rev * (a[sort] - b[sort]);
                    }
                })
        );
    }, [volunteeringList, filters, sort, reverse]);

    // Create a list of ClubCard components from the filtered list
    useEffect(() => {
        // If the filtered list is not created, don't do anything
        if (filteredList === null || error) return;

        // Map the filtered list to a list of ClubCard components
        setVolunteeringCardList(
            <Grid container spacing={4} sx={{ marginBottom: 4 }}>
                {filteredList.map((v) => (
                    <Grid item xs={12} sm={6} lg={4} key={v.name} sx={{ flexGrow: { lg: 0, xs: 1 } }}>
                        <VolunteeringCard volunteering={v} />
                    </Grid>
                ))}
            </Grid>
        );
    }, [filteredList]);

    // Open the popup element on click
    // The setAchorEl is used for the Popover component
    const openFilters = (event) => {
        setAnchorEl(event.currentTarget);
    };

    // Close the popup element by setting the anchor element to null
    const closeFilters = () => {
        setAnchorEl(null);
    };

    // Toggle the filters open/closed when clicked
    const handleChange = (event) => {
        setFilters({ ...filters, [event.target.name]: event.target.checked });
    };

    // Show error if errored
    if (error) {
        return (
            <PageWrapper>
                <Loading error>
                    Could not get volunteering list. Please reload the page or contact the site manager to fix this
                    issue.
                </Loading>
            </PageWrapper>
        );
    }

    return (
        <PageWrapper>
            <TitleMeta title="Volunteering" path="/volunteering" />
            <Container maxWidth={false} sx={{ maxWidth: 1280 }}>
                <AddButton color="primary" label="Volunteering" path="/edit/volunteering" />
                <Box width="100%" marginBottom={2} display="flex" alignItems="center">
                    <Tooltip title="Filters">
                        <IconButton onClick={openFilters} size="large">
                            <FilterListIcon />
                        </IconButton>
                    </Tooltip>
                    <Typography
                        sx={{
                            marginLeft: 2,
                            flexGrow: 1,
                            fontWeight: 500,
                            color: (theme) => darkSwitchGrey(theme),
                        }}
                    >
                        Filter
                    </Typography>
                    <SortSelect
                        value={sort}
                        setValue={setSort}
                        reverse={reverse}
                        setReverse={setReverse}
                        options={['name', 'club']}
                    />
                    <ViewSwitcher tableView={listView} setTableView={setListView} sx={{ float: 'right' }} />
                </Box>
                {listView ? <VolunteeringTable volunteering={filteredList} /> : volunteeringCardList}
                <Popover
                    open={anchorEl !== null}
                    anchorEl={anchorEl}
                    onClose={closeFilters}
                    anchorOrigin={{
                        vertical: 'bottom',
                        horizontal: 'center',
                    }}
                >
                    <Box padding={3}>
                        <FormControl component="fieldset">
                            <FormLabel component="legend" sx={{ marginBottom: 1 }}>
                                Filter Volunteering
                            </FormLabel>
                            <FormGroup>
                                <FormControlLabel
                                    control={<Checkbox checked={filters.open} onChange={handleChange} name="open" />}
                                    label="Open"
                                />
                                <FormControlLabel
                                    control={
                                        <Checkbox checked={filters.limited} onChange={handleChange} name="limited" />
                                    }
                                    label="Limited Spots"
                                />
                                <FormControlLabel
                                    control={
                                        <Checkbox checked={filters.semester} onChange={handleChange} name="semester" />
                                    }
                                    label="Semester Long Committment"
                                />
                                <FormControlLabel
                                    control={
                                        <Checkbox checked={filters.setTimes} onChange={handleChange} name="setTimes" />
                                    }
                                    label="Set Time Slots"
                                />
                                <FormControlLabel
                                    control={
                                        <Checkbox checked={filters.weekly} onChange={handleChange} name="weekly" />
                                    }
                                    label="Repeats Weekly"
                                />
                            </FormGroup>
                        </FormControl>
                    </Box>
                </Popover>
            </Container>
        </PageWrapper>
    );
}
Example #12
Source File: [id].tsx    From tams-club-cal with MIT License 4 votes vote down vote up
VolunteeringDisplay = ({ volunteering, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();

    // Return to the previous page, but preserve the view
    // that was paassed in the URL (ie. keep table view)
    const back = () => {
        const prevView = getParams('view');
        router.push(`/volunteering${prevView ? `?view=${prevView}` : ''}`);
    };

    // Show error message if errored
    if (error) {
        return (
            <PageWrapper>
                <TitleMeta title="Volunteering" path={'/volunteering'} />
                <RobotBlockMeta />
                <Loading error sx={{ marginBottom: 4 }}>
                    Could not get club data. Please reload the page or contact the site manager to fix this issue.
                </Loading>
            </PageWrapper>
        );
    }

    return (
        <PageWrapper>
            <ResourceMeta
                resource="volunteering"
                name={volunteering.name}
                path={`/volunteering/${volunteering.id}`}
                description={volunteering.description}
            />
            <Container sx={{ maxWidth: { xl: '50%', md: '75%', xs: '100%' } }}>
                <AddButton color="secondary" label="Volunteering" path={`/edit/volunteering/${volunteering.id}`} edit />
                <Card>
                    <CardContent>
                        <Box
                            sx={{
                                display: 'flex',
                                flexDirection: { lg: 'row', xs: 'column' },
                            }}
                        >
                            <Box
                                sx={{
                                    flexBasis: 'calc(50% - 24px)',
                                    flexShrink: 0,
                                    flexGrow: 1,
                                    paddingRight: 3,
                                }}
                            >
                                <Typography
                                    variant="subtitle2"
                                    sx={{
                                        fontSize: '1.1rem',
                                        color: volunteering.filters.open ? 'primary.main' : 'error.main',
                                    }}
                                >
                                    {volunteering.filters.open ? 'Open' : 'Closed'}
                                </Typography>
                                <Typography variant="h2">{volunteering.name}</Typography>
                                <Typography variant="subtitle1" sx={{ color: (theme) => darkSwitchGrey(theme) }}>
                                    {volunteering.club}
                                </Typography>
                                <Paragraph text={volunteering.description} />
                            </Box>
                            <Hidden mdDown>
                                <Divider orientation="vertical" flexItem />
                            </Hidden>
                            <FilterList
                                filters={volunteering.filters}
                                sx={{
                                    flexBasis: '50%',
                                    flexShrink: 0,
                                    flexGrow: 1,
                                }}
                            />
                        </Box>
                    </CardContent>
                    <CardActions>
                        <Button size="small" onClick={back} sx={{ margin: 'auto' }}>
                            Back
                        </Button>
                    </CardActions>
                </Card>
            </Container>
        </PageWrapper>
    );
}
Example #13
Source File: [[...date]].tsx    From tams-club-cal with MIT License 4 votes vote down vote up
Calendar = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();
    const [calendarDays, setCalendarDays] = useState(null);
    const [month, setMonth] = useState(null);
    const [rows, setRows] = useState(0);
    const now = dayjs(props.now);

    // Once the events are fetched, they are parsed by splitting multi-day events
    // These events are then grouped by date and each calendar day is created
    useEffect(() => {
        if (props.error) return;

        // Parse the fetched events by splitting multi-day events into separate event objects
        const events = parsePublicEventList(props.activities);

        // Create the actual list of calendar days by grouping
        // events into their days and adding it to the components list
        let dayComponents = [];
        for (let i = 0; i < props.totalDays; i++) {
            // Get the current day by adding i number of days to the first in the month
            const currDay = dayjs(props.firstDateInMonth).add(i, 'day');

            // Filter out the events for the current day from the events list
            const currentDayEvents = events.filter((e) => dayjs(e.start).isSame(currDay, 'day'));

            // If the CalendarDay is the current date or this month, style it differently
            const isToday = dayjs().isSame(currDay, 'day');
            const thisMonth = now.isSame(currDay, 'month');

            // Add a CalendarDay to the list of components
            dayComponents.push(
                <CalendarDay
                    activities={currentDayEvents}
                    date={currDay}
                    key={i}
                    noRight={i % 7 === 6}
                    otherMonth={!thisMonth}
                    isToday={isToday}
                    rows={props.numRows}
                />
            );
        }

        // Save components to state variables to display
        setCalendarDays(dayComponents);
        setRows(props.numRows);
        setMonth(now.format('MMMM YYYY'));
    }, [props.activities]);

    // Adjust the offset of month when user clicks on the arrow buttons
    // The change that is passed in will be +1 or -1 depending on which arrow button was clicked
    const offsetMonth = (forward: boolean) => {
        const newMonth = forward ? now.add(1, 'month') : now.subtract(1, 'month');
        router.push(`/events/calendar/${newMonth.format('YYYY/M')}`);
    };

    // Create the days of the week as the header
    const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    const calendarHeaderList = days.map((date) => (
        <Typography
            key={date}
            sx={{
                flexGrow: 1,
                textAlign: 'center',
                fontWeight: 300,
            }}
        >
            {date}
        </Typography>
    ));

    return (
        <HomeBase unsetHeight>
            <TitleMeta title="Calendar" path="/events/calendar" /> 
            <Box display="flex" flexDirection="column" sx={{ flexGrow: 1 }}>
                <AddButton color="primary" label="Event" path="/edit/events" />
                <Box width="100%" display="flex" justifyContent="center" alignItems="center">
                    <IconButton size="small" onClick={offsetMonth.bind(this, false)}>
                        <ArrowBackIosRoundedIcon />
                    </IconButton>
                    <Typography variant="h1" component="h2" sx={{ width: 250, textAlign: 'center' }}>
                        {month}
                    </Typography>
                    <IconButton size="small" onClick={offsetMonth.bind(this, true)}>
                        <ArrowForwardIosRoundedIcon />
                    </IconButton>
                </Box>
                <Box width="100%" display="flex" marginTop={1}>
                    {calendarHeaderList}
                </Box>
                {calendarDays === null ? (
                    <Loading />
                ) : (
                    <Box
                        sx={{
                            borderTop: 'solid 1px',
                            borderTopColor: (theme) =>
                                darkSwitch(theme, theme.palette.grey[300], theme.palette.grey[700]),
                            display: 'grid',
                            gridTemplateColumns: 'repeat(7, minmax(0, 1fr))',
                            flexGrow: 1,
                            ...rowStyles[rows],
                        }}
                    >
                        {calendarDays}
                    </Box>
                )}
            </Box>
        </HomeBase>
    );
}
Example #14
Source File: [id].tsx    From tams-club-cal with MIT License 4 votes vote down vote up
EventDisplay = ({ event, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();

    // Go back to the previous screen that took the user here
    // If a specific view was passed in, go back to that view
    const back = () => {
        const prevView = getParams('view');
        router.push(`/${prevView ? `events/${prevView}` : ''}`);
    };

    // Show error message if errored
    // TODO: Differentiate between invalid ID error and could not connect error
    if (error) {
        return (
            <HomeBase>
                <TitleMeta title="Events" path={'/events'} />
                <RobotBlockMeta />
                <Loading error sx={{ marginBottom: 4 }}>
                    Could not get event data. Make sure the ID you have is valid and reload the page.
                </Loading>
            </HomeBase>
        );
    }

    const location = data.rooms.find((d) => d.value === event.location);
    const reserved = event.reservation ? ' (Reserved)' : '';

    return (
        <HomeBase noActionBar>
            <ResourceMeta
                resource="events"
                name={event.name}
                path={`/events/${event.id}`}
                description={event.description}
            />
            <Container maxWidth={false} sx={{ maxWidth: { lg: '60%', md: '75%', xs: '100%' } }}>
                <AddButton color="secondary" label="Event" path={`/edit/events/${event.id}`} edit />
                <Card sx={{ marginBottom: 3 }}>
                    <CardContent>
                        <Box
                            sx={{
                                width: '100%',
                                display: 'flex',
                                justifyContent: 'center',
                                flexDirection: { lg: 'row', xs: 'column' },
                            }}
                        >
                            <Box
                                sx={{
                                    width: { lg: '50%', xs: '100%' },
                                    textAlign: 'left',
                                    padding: { lg: 1, xs: 0 },
                                }}
                            >
                                <Typography sx={eventTypeStyle}>{event.type}</Typography>
                                <Typography variant="h2" component="h1">
                                    {event.name}
                                </Typography>
                                <Typography
                                    variant="subtitle1"
                                    component="p"
                                    sx={{ marginBottom: 4, color: (theme) => darkSwitchGrey(theme) }}
                                >
                                    {event.club}
                                </Typography>
                                <Typography variant="h3" gutterBottom sx={{ fontWeight: 400 }}>
                                    {formatEventDate(event)}
                                </Typography>
                                <Typography variant="h3" gutterBottom sx={{ fontWeight: 400 }}>
                                    {formatEventTime(event, event.noEnd, true)}
                                </Typography>
                                {event.repeats !== RepeatingStatus.NONE ? (
                                    <Typography
                                        variant="h3"
                                        sx={{ color: (theme) => darkSwitchGrey(theme), fontWeight: 400, marginTop: 2 }}
                                    >
                                        {`Repeats ${
                                            event.repeats === RepeatingStatus.WEEKLY ? 'weekly' : 'monthly'
                                        } until ${formatDate(event.repeatsUntil, 'dddd, MMMM D, YYYY')}`}
                                    </Typography>
                                ) : null}
                                {event.repeats === RepeatingStatus.NONE ? null : event.repeatOriginId !== event.id ? (
                                    <Link
                                        href={`/events/${event.repeatOriginId}`}
                                        sx={{ marginTop: 1, display: 'block', maxWidth: 'max-content' }}
                                    >
                                        Go to original event
                                    </Link>
                                ) : (
                                    <Typography sx={{ marginTop: 1, color: (theme) => darkSwitchGrey(theme) }}>
                                        This is the original repeating event!
                                    </Typography>
                                )}
                                <Typography
                                    variant="h3"
                                    sx={{
                                        marginTop: 6,
                                        color: (theme) => darkSwitchGrey(theme),
                                        fontSize: '0.9rem',
                                    }}
                                >
                                    {event.location === 'none'
                                        ? null
                                        : `Location: ${location ? location.label : event.location}${reserved}`}
                                </Typography>
                            </Box>
                            <Hidden mdDown>
                                <Divider orientation="vertical" flexItem />
                            </Hidden>
                            <Hidden mdUp>
                                <Divider orientation="horizontal" flexItem sx={{ marginTop: 2 }} />
                            </Hidden>
                            <Paragraph
                                text={event.description}
                                sx={{
                                    width: { lg: '50%', xs: '100%' },
                                    textAlign: 'left',
                                    margin: { lg: '0 0 0 12px', xs: '16px 0 0 0' },
                                    padding: { lg: '8px 0', xs: 0 },
                                    color: (theme: Theme) =>
                                        darkSwitch(theme, theme.palette.grey[700], theme.palette.grey[200]),
                                }}
                            />
                        </Box>
                    </CardContent>
                    <CardActions>
                        <Button size="small" onClick={back} sx={{ margin: 'auto' }}>
                            Back
                        </Button>
                    </CardActions>
                </Card>
            </Container>
        </HomeBase>
    );
}
Example #15
Source File: [[...id]].tsx    From tams-club-cal with MIT License 4 votes vote down vote up
EditVolunteering = ({ volunteering, id, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();
    const [backdrop, setBackdrop] = useState(false);
    const [popupEvent, setPopupEvent] = useState<PopupEvent>(null);
    const { handleSubmit, control, setValue } = useForm();

    // When the user submits the form, either create or update the volunteering opportunity
    const onSubmit = async (data) => {
        // If the name is empty, do nothing
        if (!('name' in data)) return;

        // Create the volunteering object from the data
        const newVolunteering = createVolunteering(
            id,
            data.name,
            data.club,
            data.description,
            createFilters(data.limited, data.semester, data.setTimes, data.weekly, data.open),
            volunteering.history
        );

        // Start the upload process
        setBackdrop(true);

        // If the ID is null, create the volunteering, otherwise update it
        const res = id === null ? await postVolunteering(newVolunteering) : await putVolunteering(newVolunteering, id);

        // Finished uploading
        setBackdrop(false);

        // If the request was successful, redirect to the volunteering page, otherwise display an error
        if (res.status === 204) {
            new Cookies().set('success', id ? 'update-volunteering' : 'add-volunteering', {
                sameSite: 'strict',
                path: '/',
            });
            back();
        } else setPopupEvent(createPopupEvent('Unable to upload data. Please refresh the page or try again.', 4));
    };

    // Returns the user back to the volunteering display page
    const back = () => {
        router.push(`/volunteering${id ? `/${id}` : ''}`);
    };

    // On load
    useEffect(() => {
        // Send error if can't fetch resource
        if (error) {
            setPopupEvent(
                createPopupEvent('Error fetching volunteering info. Please refresh the page or add a new event.', 4)
            );
        }

        // When the volunteering data is loaded, set the open value to the controller as a seperate variable
        setValue('open', volunteering.filters.open);
    }, []);

    return (
        <EditWrapper>
            <TitleMeta title={`${id ? 'Edit' : 'Add'} Volunteering`} path={`/edit/volunteering/${id ? id : ''}`} />
            <UploadBackdrop open={backdrop} />
            <Popup event={popupEvent} />
            <Typography variant="h1" sx={{ textAlign: 'center', fontSize: '3rem' }}>
                {id ? 'Edit Volunteering' : 'Add Volunteering'}
            </Typography>
            {id ? <AddButton editHistory path={`/edit/history/volunteering/${id}`} /> : null}
            <FormWrapper onSubmit={handleSubmit(onSubmit)}>
                {/* TODO: Make a BoxWrapper component as this css is repeated so much across all forms */}
                <Box sx={{ marginBottom: 3, display: 'flex', flexDirection: { lg: 'row', xs: 'column' } }}>
                    <Controller
                        control={control}
                        name="open"
                        render={({ field: { onChange, onBlur, value } }) => (
                            <FormControlLabel
                                label="Open"
                                labelPlacement="start"
                                control={
                                    <Switch
                                        color="primary"
                                        value={value}
                                        onChange={onChange}
                                        onBlur={onBlur}
                                        checked={value}
                                        defaultChecked={volunteering.filters.open}
                                    />
                                }
                            />
                        )}
                    />
                    <Spacer />
                    <ControlledTextField
                        control={control}
                        setValue={setValue}
                        value={volunteering.name}
                        label="Volunteering Name"
                        name="name"
                        variant="outlined"
                        grow
                        required
                        errorMessage="Please enter a name"
                        sx={{ marginLeft: { lg: 2, xs: 0 } }}
                    />
                    <Spacer />
                    <ControlledTextField
                        control={control}
                        setValue={setValue}
                        value={volunteering.club}
                        label="Club"
                        name="club"
                        variant="outlined"
                        grow
                        required
                        errorMessage="Please enter a club name"
                    />
                </Box>
                <Hidden mdDown>
                    <Typography sx={{ display: 'inline', marginRight: { lg: 16, xs: 0 } }}>Filters:</Typography>
                </Hidden>
                <ControlledFilterCheckbox
                    control={control}
                    setValue={setValue}
                    name="limited"
                    label="Limited Slots"
                    value={volunteering.filters.limited}
                />
                <ControlledFilterCheckbox
                    control={control}
                    setValue={setValue}
                    name="semester"
                    label="Semester Long Committment"
                    value={volunteering.filters.semester}
                />
                <ControlledFilterCheckbox
                    control={control}
                    setValue={setValue}
                    name="setTimes"
                    label="Set Time Slots"
                    value={volunteering.filters.setTimes}
                />
                <ControlledFilterCheckbox
                    control={control}
                    setValue={setValue}
                    name="weekly"
                    label="Repeats Weekly"
                    value={volunteering.filters.weekly}
                />
                <ControlledTextField
                    control={control}
                    setValue={setValue}
                    value={volunteering.description}
                    label="Description (optional)"
                    name="description"
                    variant="outlined"
                    area
                    sx={{ marginTop: 2 }}
                />
                <TwoButtonBox success="Submit" onCancel={back} onSuccess={onSubmit} submit right />
            </FormWrapper>
        </EditWrapper>
    );
}
Example #16
Source File: [id].tsx    From tams-club-cal with MIT License 4 votes vote down vote up
HistoryDisplay = ({
    historyList,
    error,
    resource,
    id,
    name,
}: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();
    const [components, setComponents] = useState(null);
    const [currHistory, setCurrHistory] = useState(null);
    const [popupOpen, setPopupOpen] = useState(false);

    // When the history list is updated, create the table rows (edit history entries)
    useEffect(() => {
        (async () => {
            if (error) return;

            // Create the table rows and set the component list to the state variable
            // We use Promise.all here because the parseEditor api call is asynchronous
            // The Promise.all function will resolve the map of promises to actual values before setting the state
            setComponents(
                await Promise.all(
                    historyList.map(async (history: History, i: number) => (
                        <TableRow
                            onClick={openPopup.bind(this, i)}
                            key={i}
                            sx={{
                                transition: '0.3s',
                                cursor: 'pointer',
                                '&:hover': {
                                    backgroundColor: (theme) =>
                                        darkSwitch(theme, theme.palette.grey[200], theme.palette.grey[700]),
                                },
                            }}
                        >
                            <TableCell>{calculateEditDate(history.time)}</TableCell>
                            <TableCell>
                                {i === historyList.length - 1
                                    ? 'Resource created'
                                    : `${history.fields.length} fields were updated`}
                            </TableCell>
                            <TableCell>{await parseEditor(history.editor)}</TableCell>
                        </TableRow>
                    ))
                )
            );
        })();
    }, [historyList]);

    // Opens the popup for a single edit when clicked
    const openPopup = (index: React.MouseEventHandler<HTMLTableRowElement>) => {
        setCurrHistory(index);
        setPopupOpen(true);
    };

    // Returns the user to the pervious page
    // If the user was on the edit page, return them there, or else return them to the edit page for the resource
    const back = () => {
        const view = getParams('view');
        if (view) router.push('/edit');
        else router.push(`/edit/${resource}/${id}`);
    };

    return (
        <EditWrapper>
            <ResourceMeta
                resource={resource}
                name={name}
                path={`/edit/${resource}/${id}`}
                description={`Edit history for ${name} (${resource})`}
                editHistory
            />
            <RobotBlockMeta />
            <TableContainer>
                {components === null ? (
                    error ? (
                        <Loading error flat>
                            Invalid {resource} ID. Please return to the home page and check the ID. The resource you are
                            trying to see the edit history for might be deleted. Please contact the site administrator
                            if you believe that this error is incorrect.
                        </Loading>
                    ) : (
                        <Loading />
                    )
                ) : (
                    <React.Fragment>
                        <Typography
                            variant="h1"
                            sx={{
                                textAlign: 'center',
                                marginBottom: 1.5,
                            }}
                        >
                            {`Edit History for ${name}`}
                        </Typography>
                        <Link href={`/edit/${resource}/${id}`} sx={{ textAlign: 'center', display: 'block' }}>
                            Edit this resource
                        </Link>
                        <Table>
                            <TableHead>
                                <TableRow>
                                    <TableCell>Time</TableCell>
                                    <TableCell>Edits</TableCell>
                                    <TableCell>Editor</TableCell>
                                </TableRow>
                            </TableHead>
                            <TableBody>{components}</TableBody>
                        </Table>
                        <HistoryPopup
                            history={historyList[currHistory]}
                            name={name}
                            open={popupOpen}
                            close={setPopupOpen.bind(this, false)}
                        />
                        <Button
                            onClick={back}
                            sx={{
                                margin: 'auto',
                                marginTop: 12,
                                display: 'block',
                            }}
                        >
                            Back
                        </Button>
                    </React.Fragment>
                )}
            </TableContainer>
        </EditWrapper>
    );
}
Example #17
Source File: [[...id]].tsx    From tams-club-cal with MIT License 4 votes vote down vote up
EditEvents = ({ event, id, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();
    const [backdrop, setBackdrop] = useState(false);
    const [prevStart, setPrevStart] = useState(null);
    const [popupEvent, setPopupEvent] = useState<PopupEvent>();
    const [displayError, setDisplayError] = useState(false);
    const {
        handleSubmit,
        setError,
        clearErrors,
        setValue,
        watch,
        control,
        formState: { errors },
    } = useForm();
    const watchStart: Dayjs = watch('start');
    const watchEnd: Dayjs = watch('end');
    const watchNoEnd: boolean = watch('noEnd');
    const watchAllDay: boolean = watch('allDay');
    const watchLocation: string = watch('location');
    const watchOtherLocation: string = watch('otherLocation');
    const watchPublicEvent: boolean = watch('publicEvent');
    const watchReservation: boolean = watch('reservation');
    const watchRepeatsWeekly: boolean = watch('repeatsWeekly');
    const watchRepeatsMonthly: boolean = watch('repeatsMonthly');
    const watchRepeatsUntil: Dayjs = watch('repeatsUntil');

    // When the user submits the form, either create or update the event
    const onSubmit = async (data: SubmitData) => {
        // Calculate the start and end times
        // For the start time, use the "date" input field to calculate if all day
        // For the end time, if allDay or noEnd, simply set to the same as the start time
        const startTime = data.allDay ? data.date.startOf('day').valueOf() : data.start.valueOf();
        const endTime = data.allDay || data.noEnd ? startTime : data.end.valueOf();

        // Make sure events do not last for more than 100 days
        if (dayjs(endTime).diff(startTime, 'day') > 100) {
            setPopupEvent(createPopupEvent('Events cannot last more than 100 days!', 3));
            return;
        }

        // Check to make sure repeating data is valid and set the variables if true
        let repeats = 0;
        if (data.repeatsWeekly || data.repeatsMonthly) {
            // Make sure there is actually an end time
            if (data.noEnd) {
                setPopupEvent(createPopupEvent('Repeating events cannot have no end time set', 3));
                return;
            }

            // Set the status based on the event name
            // and calculate the number of times each event repeats
            let repeatCount = 0;
            if (data.repeatsWeekly) {
                repeats = RepeatingStatus.WEEKLY;
                repeatCount = dayjs(data.start).diff(data.repeatsUntil, 'week');
            } else {
                repeats = RepeatingStatus.MONTHLY;
                repeatCount = dayjs(data.start).diff(data.repeatsUntil, 'month');
            }

            // Make sure that the number of repeating events is not abhorently long (>100)
            if (Math.abs(repeatCount) > 100) {
                setPopupEvent(
                    createPopupEvent(
                        'Events cannot repeat more than 100 times! Please check the repeats until date or create multiple repeating events.',
                        3
                    )
                );
                return;
            }
        }

        // Start the upload process display because the reservation might take a bit to find
        setBackdrop(true);

        // Check for the conditions for creating a reservation
        if (data.reservation) {
            // If location is "none"/"other", return error
            if (data.location === 'none' || data.location === 'other') {
                setPopupEvent(createPopupEvent('Please select a valid (non-custom) location for the reservation.', 3));
                setBackdrop(false);
                return;
            }

            // If there is no end time, return error
            if (data.noEnd) {
                setPopupEvent(createPopupEvent('Reservations must have an end time.', 3));
                setBackdrop(false);
                return;
            }

            // Check to make sure that the reservation is not already taken
            const overlaps = await getOverlappingReservations(data.location, startTime, endTime);
            if (overlaps.status !== 200) {
                setPopupEvent(
                    createPopupEvent(
                        'There was an error checking reservation time. Please check your connection and try again.',
                        4
                    )
                );
                setBackdrop(false);
                return;
            }
            if (overlaps.data.length !== 0 && overlaps.data[0].id !== event.id) {
                // TODO: Log error to admins if the overlaps length is > 1
                setPopupEvent(createPopupEvent('There is already a reservation during that time!', 3));
                setBackdrop(false);
                return;
            }

            // If also repeating, make sure none of those are overlapping too D:
            // This will only check if it is the original repeating event
            // TODO: ain't this redundant with the backend idk
            if (repeats !== RepeatingStatus.NONE && event.id === event.repeatOriginId) {
                const unit = repeats === RepeatingStatus.WEEKLY ? 'week' : 'month';
                let currStart = dayjs(startTime).add(1, unit);
                let currEnd = dayjs(endTime).add(1, unit);
                const repEnd = data.repeatsUntil.startOf('day').add(1, 'day');
                while (currStart.isBefore(repEnd)) {
                    const repOverlap = await getOverlappingReservations(
                        data.location,
                        currStart.valueOf(),
                        currEnd.valueOf()
                    );
                    if (repOverlap.status !== 200) {
                        setPopupEvent(
                            createPopupEvent(
                                'There was an error checking reservation time. Please check your connection and try again.',
                                4
                            )
                        );
                        setBackdrop(false);
                        return;
                    }
                    if (repOverlap.data.length !== 0 && repOverlap.data[0].id !== event.id) {
                        setPopupEvent(
                            createPopupEvent(
                                `There is already a reservation during the repeating event on ${currStart.format(
                                    'MM/DD/YYYY'
                                )}!`,
                                3
                            )
                        );
                        setBackdrop(false);
                        return;
                    }
                    currStart = currStart.add(1, unit);
                    currEnd = currEnd.add(1, unit);
                }
            }
        }

        // If the event is repeating, set repeatsUntil value
        const repeatsUntil = repeats === RepeatingStatus.NONE ? null : data.repeatsUntil.valueOf();

        // Create the event object from the data
        const newEvent = createEvent(
            id,
            event.eventId,
            data.type,
            data.name,
            data.club,
            data.description,
            startTime,
            endTime,
            data.location === 'other' ? data.otherLocation : data.location,
            data.noEnd,
            data.allDay,
            repeats,
            repeatsUntil,
            event.repeatOriginId,
            data.publicEvent,
            data.reservation,
            event.history
        );

        // If the event ID is null, create the event, otherwise update it
        const res = id === null ? await postEvent(newEvent) : await putEvent(newEvent, id);

        // Finished uploading
        setBackdrop(false);

        // If the event was created successfully, redirect to the event page, otherwise display an error
        if (res.status === 204) {
            new Cookies().set('success', id ? 'update-event' : 'add-event', { sameSite: 'strict', path: '/' });
            back();
        } else setPopupEvent(createPopupEvent('Unable to upload data. Please refresh the page or try again.', 4));
    };

    // Returns the user back to the event display page
    const back = () => {
        router.push(`/events${id ? `/${id}` : ''}`);
    };

    // On load
    useEffect(() => {
        // Send error if can't fetch resource
        if (error) {
            setPopupEvent(
                createPopupEvent('Error fetching event info. Please refresh the page or add a new event.', 4)
            );
        }

        // Set "prevStart" variable to the starting time to use later
        setPrevStart(event.start);
    }, []);

    // Change start and end times if event changes
    useEffect(() => {
        setValue('start', dayjs(event.start));
    }, [event]);

    // Offset the end time if startTime is changed to the same duration
    useEffect(() => {
        if (!watchEnd || errors.end) return;
        const diff = watchEnd.valueOf() - prevStart;
        setValue('end', dayjs(watchStart.valueOf() + diff));
        setPrevStart(watchStart.valueOf());
    }, [watchStart]);

    // Set an error if the end time is set before the start time
    useEffect(() => {
        if (!watchEnd) return;
        if (watchAllDay || watchNoEnd) {
            clearErrors('end');
            return;
        }
        if (watchEnd.isBefore(watchStart)) setError('end', { message: 'End is before start' });
        else clearErrors('end');
    }, [watchStart, watchEnd, watchAllDay, watchNoEnd]);

    // Set the date of the "all day" date input to the same as the start time
    useEffect(() => {
        if (watchAllDay) setValue('date', watchStart);
    }, [watchAllDay]);

    // Check to see if the other location is empty and clear errors if the location is changed
    useEffect(() => {
        if (watchLocation !== 'other') {
            clearErrors('otherLocation');
        }
    }, [watchLocation]);

    // Set display error if neither public or reservation is selected
    useEffect(() => {
        setDisplayError(!watchPublicEvent && !watchReservation);
    }, [watchPublicEvent, watchReservation]);

    // If the location is a custom one, replace with "other" in form
    const defaultLocation = id
        ? data.rooms.findIndex((r) => r.value === event.location) === -1
            ? 'other'
            : event.location
        : 'none';

    return (
        <EditWrapper>
            <TitleMeta title={`${id ? 'Edit' : 'Add'} Event`} path={`/edit/events/${id ? id : ''}`} />
            <RobotBlockMeta />
            <UploadBackdrop open={backdrop} />
            <Popup event={popupEvent} />
            <Typography variant="h1" sx={{ textAlign: 'center', fontSize: '3rem' }}>
                {id ? 'Edit Event' : 'Add Event'}
            </Typography>
            {id ? <AddButton editHistory path={`/edit/history/events/${id}`} /> : null}
            <FormWrapper onSubmit={handleSubmit(onSubmit)}>
                <Box sx={{ marginBottom: 3, display: 'flex', flexDirection: { lg: 'row', xs: 'column' } }}>
                    <ControlledTextField
                        control={control}
                        setValue={setValue}
                        value={event.type}
                        label="Event Type"
                        name="type"
                        variant="outlined"
                        required
                        errorMessage="Please enter a type"
                    />
                    <Spacer />
                    <ControlledTextField
                        control={control}
                        setValue={setValue}
                        value={event.name}
                        label="Event Name"
                        name="name"
                        variant="outlined"
                        grow
                        required
                        errorMessage="Please enter a name"
                    />
                </Box>
                <Box sx={{ marginBottom: 3, display: 'flex', flexDirection: { lg: 'row', xs: 'column' } }}>
                    <ControlledTextField
                        control={control}
                        setValue={setValue}
                        value={event.club}
                        label="Club"
                        name="club"
                        variant="outlined"
                        grow
                        required
                        errorMessage="Please enter a club name"
                    />
                    <Spacer />
                    <LocationSelect
                        control={control}
                        setValue={setValue}
                        value={defaultLocation}
                        sx={{ minWidth: 250 }}
                    />
                    {watchLocation === 'other' ? (
                        <React.Fragment>
                            <Spacer />
                            <ControlledTextField
                                control={control}
                                setValue={setValue}
                                value={defaultLocation === 'other' ? event.location : ''}
                                label="Custom Location"
                                name="otherLocation"
                                variant="outlined"
                                errorMessage="Please enter a custom location"
                                validate={() =>
                                    watchLocation !== 'other' ||
                                    (watchOtherLocation && watchOtherLocation.trim() !== '')
                                }
                            />
                        </React.Fragment>
                    ) : null}
                </Box>
                <Box
                    sx={{
                        marginBottom: 3,
                        display: 'flex',
                        justifyContent: 'center',
                        flexDirection: { lg: 'row', xs: 'column' },
                    }}
                >
                    {watchAllDay ? (
                        <DateInput control={control} name="date" label="Date" value={event.start} />
                    ) : (
                        <DateTimeInput
                            control={control}
                            name="start"
                            label={watchNoEnd ? 'Date/time' : 'Start date/time'}
                            value={event.start}
                            required
                        />
                    )}
                    <Spacer />
                    <DateTimeInput
                        name="end"
                        label="End date/time"
                        control={control}
                        value={event.end}
                        disabled={watchNoEnd || watchAllDay}
                        errorMessage="End time cannot be before start time"
                        validate={() => watchNoEnd || watchAllDay || watchStart.isBefore(watchEnd)}
                        required
                        end
                    />
                    <ControlledCheckbox
                        control={control}
                        name="noEnd"
                        label="No end time"
                        value={event.noEnd}
                        setValue={setValue}
                        sx={{ marginLeft: { lg: 2, xs: 0 }, marginTop: { lg: 0, xs: 2 } }}
                    />
                    <ControlledCheckbox
                        control={control}
                        name="allDay"
                        label="All day event"
                        value={event.allDay}
                        setValue={setValue}
                        sx={{ marginLeft: 0 }}
                    />
                </Box>
                <ControlledTextField
                    control={control}
                    setValue={setValue}
                    value={event.description}
                    label="Description (optional)"
                    name="description"
                    variant="outlined"
                    area
                />
                <ControlledCheckbox
                    control={control}
                    name="publicEvent"
                    label="Show on public calendar (Will show this event on the schedule view and create a calendar event)"
                    value={event.publicEvent}
                    setValue={setValue}
                    sx={{ display: 'block' }}
                />
                <ControlledCheckbox
                    control={control}
                    name="reservation"
                    label="Create room reservation (Will show on reservation calendar; must select non-custom location and valid times)"
                    value={event.reservation}
                    setValue={setValue}
                    sx={{ display: 'block' }}
                />
                {displayError ? (
                    <Typography sx={{ color: (theme) => theme.palette.error.main }}>
                        Your event must show on the public calendar, reservation list, or both!
                    </Typography>
                ) : null}
                <Box sx={{ height: 24 }} />
                <Typography
                    sx={{ display: { lg: 'inline', xs: 'block' }, marginRight: { lg: 2, xs: 0 }, fontWeight: 600 }}
                >
                    Repeating:
                </Typography>
                <ControlledCheckbox
                    control={control}
                    name="repeatsWeekly"
                    label="Repeats Weekly"
                    value={event.repeats === RepeatingStatus.WEEKLY}
                    setValue={setValue}
                    disabled={watchRepeatsMonthly || (event.repeatOriginId && event.repeatOriginId !== event.id)}
                />
                <ControlledCheckbox
                    control={control}
                    name="repeatsMonthly"
                    label="Repeats Monthly"
                    value={event.repeats === RepeatingStatus.MONTHLY}
                    setValue={setValue}
                    disabled={watchRepeatsWeekly || (event.repeatOriginId && event.repeatOriginId !== event.id)}
                />
                <DateInput
                    control={control}
                    name="repeatsUntil"
                    label="Repeat Until (Inclusive)"
                    value={event.repeatsUntil}
                    disabled={
                        (!watchRepeatsMonthly && !watchRepeatsWeekly) ||
                        (event.repeatOriginId && event.repeatOriginId !== event.id)
                    }
                    errorMessage="Repeats Until must be after start time"
                    validate={() =>
                        (!watchRepeatsMonthly && !watchRepeatsWeekly) || watchRepeatsUntil.isAfter(watchStart)
                    }
                />
                {event.repeats !== RepeatingStatus.NONE && event.repeatOriginId && event.repeatOriginId !== event.id ? (
                    <React.Fragment>
                        <Alert
                            severity="warning"
                            sx={{ my: 3, backgroundColor: (theme) => darkSwitch(theme, null, '#3f372a') }}
                        >
                            Changing this event will NOT affect the other event repetitions! Additionally, this event
                            will no longer be able to be edited by the original repeating event and act as its own
                            event.
                            <Link
                                href={`/edit/events/${event.repeatOriginId}`}
                                sx={{ color: (theme) => darkSwitch(theme, theme.palette.primary.dark, null) }}
                            >
                                {' '}
                                Click here to go to the original event and make edits to ALL event repetitions and
                                repeating status.
                            </Link>
                        </Alert>
                    </React.Fragment>
                ) : null}
                {event.repeats !== RepeatingStatus.NONE && event.repeatOriginId && event.repeatOriginId === event.id ? (
                    <Alert
                        severity="info"
                        sx={{ my: 3, backgroundColor: (theme) => darkSwitch(theme, null, '#304249') }}
                    >
                        If you edit this event, all repeated instances of the event will be modified. If the time or
                        repeating behavior is changed, repeated events will be recreated.
                    </Alert>
                ) : null}
                <TwoButtonBox success="Submit" onCancel={back} submit right />
            </FormWrapper>
        </EditWrapper>
    );
}
Example #18
Source File: [[...id]].tsx    From tams-club-cal with MIT License 4 votes vote down vote up
EditClubs = ({ club, id, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();
    const [backdrop, setBackdrop] = useState(false);
    const [cover, setCover] = useState<Blob>(null);
    const [profilePics, setProfilePics] = useState(null);
    const [popupEvent, setPopupEvent] = useState<PopupEvent>();
    const {
        register,
        control,
        handleSubmit,
        setValue,
        formState: { errors },
    } = useForm();

    useEffect(() => {
        if (club === null) return;
        setValue('advised', club.advised ? 'advised' : 'independent');
    }, [club]);

    // When the user submits the form, either create or update the club
    const onSubmit = async (data) => {
        // If the name is empty, do nothing
        if (!('name' in data)) return;

        // Process the exec data, profile pictures, and picture data
        const { execs, execProfilePics, execPhotos } = data.execs
            ? processExecs(data.execs)
            : { execs: [], execProfilePics: [], execPhotos: [] };

        // Filter out deleted commmittees and process the lists
        const filteredCommittees = data.committees ? data.committees.filter((c) => !c.deleted) : [];
        const committees = filteredCommittees.map((c) => ({
            ...c,
            heads: processLinkObjectList(c.heads),
            links: processLinkObjectList(c.links),
        }));

        // Process the link object list
        const links = processLinkObjectList(data.links);

        // Create the club object from the data
        const newClub = createClub(
            id,
            data.name,
            data.advised === 'advised',
            links,
            data.description,
            club.coverImgThumbnail,
            club.coverImg,
            execs,
            committees,
            club.history
        );

        // Create the club image object containing cover and exec profile picture blobs
        const newImages = createClubImageBlobs(cover, execProfilePics);

        // Start the upload process
        setBackdrop(true);

        // If the club ID is null, create the club, otherwise update it
        const res =
            id === null
                ? await postClub(newClub, newImages, execPhotos)
                : await putClub(newClub, newImages, execPhotos, id);

        // Finished uploading
        setBackdrop(false);

        // If the response is successful, redirect to the club page, otherwise display an error
        if (res.status === 204) {
            new Cookies().set('success', id ? 'update-club' : 'add-club', { sameSite: 'strict', path: '/' });
            back();
        } else setPopupEvent(createPopupEvent('Unable to upload data. Please refresh the page or try again.', 4));
    };

    // Process execs by filtering out deleted execs and adding an attribute that indicates if the exec has a new profile pic
    const processExecs = (execs: (Exec & { deleted: boolean })[]) => {
        const cleanedExecs = execs.map((e) => (e.deleted ? null : e));
        const outProfilePics = profilePics.filter((p: Blob, i: number) => cleanedExecs[i] !== null);
        const outExecs = cleanedExecs.filter((e) => e !== null);
        const hasNewPicture = outProfilePics.map((p: Blob) => p !== null);
        return { execs: outExecs, execProfilePics: outProfilePics, execPhotos: hasNewPicture };
    };

    // Returns the user back to the club display page
    const back = () => {
        router.push(`/clubs${id ? `/${id}` : ''}`);
    };

    // On load
    useEffect(() => {
        // Send error if can't fetch resource
        if (error) {
            setPopupEvent(createPopupEvent('Error fetching club info. Please refresh the page or add a new event.', 4));
        }

        // When the club data changes, set the value of the advised variable in the form controller to a string
        // Also set up the profile pics array
        setValue('advised', club.advised ? 'advised' : 'independent');
        setProfilePics(Array(club.execs.length).fill(null));
    }, []);

    return (
        <EditWrapper>
            <TitleMeta title={`${id ? 'Edit' : 'Add'} Club`} path={`/edit/clubs/${id ? id : ''}`} />
            <RobotBlockMeta />
            <UploadBackdrop open={backdrop} />
            <Popup event={popupEvent} />
            <Typography variant="h1" sx={{ textAlign: 'center', fontSize: '3rem' }}>
                {id ? 'Edit Club' : 'Add Club'}
            </Typography>
            {id ? <AddButton editHistory path={`/edit/history/clubs/${id}`} /> : null}
            <FormWrapper onSubmit={handleSubmit(onSubmit)}>
                <Box sx={{ marginBottom: 3, display: 'flex', flexDirection: { lg: 'row', xs: 'column' } }}>
                    <ControlledSelect
                        control={control}
                        setValue={setValue}
                        value={club.advised ? 'advised' : 'independent'}
                        name="advised"
                        variant="outlined"
                        sx={{ height: 56 }}
                    >
                        <MenuItem value="advised">Advised</MenuItem>
                        <MenuItem value="independent">Independent</MenuItem>
                    </ControlledSelect>
                    <Spacer />
                    <ControlledTextField
                        control={control}
                        setValue={setValue}
                        value={club.name}
                        label="Club Name"
                        name="name"
                        variant="outlined"
                        grow
                        required
                        errorMessage="Please enter a name"
                    />
                </Box>
                <ControlledTextField
                    control={control}
                    setValue={setValue}
                    value={club.description}
                    label="Description (optional)"
                    name="description"
                    variant="outlined"
                    area
                />
                <ImageUpload
                    setValue={setCover}
                    src={club.coverImg}
                    default="/default-cover.webp"
                    alt="cover photo"
                    width={300}
                    height={125}
                    aspect={12 / 5}
                />
                <LinkInputList
                    control={control}
                    register={register}
                    setValue={setValue}
                    name="links"
                    label="Link (start with http/https)"
                    links={club.links}
                />
                <Typography variant="h2" sx={{ paddingTop: 4, textAlign: 'center', fontSize: '2rem' }}>
                    Execs
                </Typography>
                <EditExecList
                    control={control}
                    register={register}
                    setValue={setValue}
                    errors={errors}
                    profilePics={profilePics}
                    setProfilePics={setProfilePics}
                    execList={club.execs}
                />
                <Typography variant="h2" sx={{ paddingTop: 4, textAlign: 'center', fontSize: '2rem' }}>
                    Committees
                </Typography>
                <EditCommitteeList
                    control={control}
                    register={register}
                    setValue={setValue}
                    errors={errors}
                    committeeList={club.committees}
                />
                <TwoButtonBox success="Submit" onCancel={back} onSuccess={onSubmit} submit right />
            </FormWrapper>
        </EditWrapper>
    );
}
Example #19
Source File: [id].tsx    From tams-club-cal with MIT License 4 votes vote down vote up
ClubDisplay = ({ club, error }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
    const router = useRouter();
    const [links, setLinks] = useState(null);
    const [tabValue, setTabValue] = useState(0);

    // When the club data is loaded, create the list of links
    useEffect(() => {
        // If the club is not loaded, do nothing.
        if (club === null) return;

        // Map the links in a club to a link object.
        setLinks(
            club.links.map((link) => (
                <Link
                    href={link}
                    variant="body1"
                    key={link}
                    target="_blank"
                    sx={{
                        display: 'block',
                        overflow: 'hidden',
                        textOverflow: 'ellipsis',
                        whiteSpace: 'nowrap',
                    }}
                >
                    {link}
                </Link>
            ))
        );
    }, [club]);

    // If the user changes to the committees or execs tab,
    // update the state value to match.
    const handleTabChange = (event: React.SyntheticEvent<Element, Event>, newValue: number) => {
        setTabValue(newValue);
    };

    // Return to the previous page, but preserve the view
    // that was paassed in the URL (ie. keep table view)
    const back = () => {
        const prevView = getParams('view');
        router.push(`/clubs${prevView ? `?view=${prevView}` : ''}`);
    };

    // Show error message if errored
    if (error) {
        return (
            <PageWrapper>
                <TitleMeta title="Clubs" path={'/clubs'} />
                <RobotBlockMeta />
                <Loading error sx={{ marginBottom: 4 }}>
                    Could not get club data. Please reload the page or contact the site manager to fix this issue.
                </Loading>
            </PageWrapper>
        );
    }

    return (
        <PageWrapper>
            <ResourceMeta
                resource="clubs"
                name={club.name}
                path={`/clubs/${club.id}`}
                description={club.description}
                imgSrc={club.coverImg}
            />
            <RobotBlockMeta />
            <Container sx={{ maxWidth: { xl: '50%', md: '75%', xs: '100%' } }}>
                <AddButton color="secondary" label="Club" path={`/edit/clubs/${club.id}`} edit />
                <Card sx={{ marginBottom: 4 }}>
                    <CardMedia
                        sx={{
                            width: '100%',
                            height: 'auto',
                            display: 'block',
                        }}
                    >
                        <CustomImage
                            src={club.coverImg}
                            default="/default-cover.webp"
                            sx={{ width: '100%', height: 'auto' }}
                        />
                    </CardMedia>
                    <CardContent sx={{ padding: 3 }}>
                        <Typography sx={{ color: club.advised ? 'primary.main' : 'secondary.main' }}>
                            {club.advised ? 'Advised' : 'Independent'}
                        </Typography>
                        <Typography variant="h1">{club.name}</Typography>
                        <Paragraph
                            text={club.description}
                            sx={{ marginTop: 2, color: (theme: Theme) => darkSwitchGrey(theme) }}
                        />
                        <Typography variant="h6">Links</Typography>
                        {links}
                        <Tabs
                            centered
                            value={tabValue}
                            onChange={handleTabChange}
                            indicatorColor="secondary"
                            textColor="secondary"
                            aria-label="execs and committees tab"
                            sx={{ marginTop: 3 }}
                        >
                            <Tab label="Execs"></Tab>
                            <Tab label="Committees"></Tab>
                        </Tabs>
                        <Paper
                            elevation={0}
                            variant="outlined"
                            square
                            sx={{ paddingTop: 2, display: tabValue === 0 ? 'block' : 'none' }}
                        >
                            {club.execs.length === 0 ? (
                                <Typography sx={emptyTextStyle}>No execs...</Typography>
                            ) : (
                                club.execs.map((e) => <ExecCard exec={e} key={e.name}></ExecCard>)
                            )}
                        </Paper>
                        <Paper
                            elevation={0}
                            variant="outlined"
                            square
                            sx={{ paddingTop: 2, display: tabValue === 1 ? 'block' : 'none' }}
                        >
                            {club.committees.length === 0 ? (
                                <Typography sx={emptyTextStyle}>No committees...</Typography>
                            ) : (
                                club.committees.map((c) => <CommitteeCard committee={c} key={c.name}></CommitteeCard>)
                            )}
                        </Paper>
                    </CardContent>
                    <CardActions>
                        <Button size="small" onClick={back} sx={{ margin: 'auto' }}>
                            Back
                        </Button>
                    </CardActions>
                </Card>
            </Container>
        </PageWrapper>
    );
}