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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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>
);
}