Verified Commit 551e3399 authored by Sandro Lutz's avatar Sandro Lutz
Browse files

Restructure page organisation

parent c3f9a3ad
......@@ -8,7 +8,7 @@ This is the home of the amiv website.
Navigate into the project directory, install all dependencies and run the development server using the following commands:
_Please note that the parameter `-g` is optional. It is used to install the package `gatsby-cli` globally on the system._
_Please note that the parameter `-g` is optional. It is being used to install the package `gatsby-cli` globally on the system._
```shell
npm install -g gatsby-cli
......@@ -16,7 +16,7 @@ This is the home of the amiv website.
gatsby develop
```
You can visit the website now under http://localhost:8000
You can visit the website now at http://localhost:8000
2. **Linting & Formatting**
......@@ -42,7 +42,7 @@ This is the home of the amiv website.
## 🧐 What's inside?
A quick look at the most important files and directories of this Gatsby project.
A quick look at the most important files and directories of the project.
```ascii
.
......@@ -83,6 +83,8 @@ A quick look at the most important files and directories of this Gatsby project.
5. **`/src/markdown`**: This directory contains markdown source files used on specific pages.
6. **`/src/pages`**: Every `js` file represents a page which path is exactly like the folder structure and filename.
**Note**: Every file starting with an underscore (`_`) in the pages directory is ignored. Those pages are mostly being used for client-only routes. Therefore no page route is created and no server-side rendering performed.
_Example: The file `/src/pages/amiv/about.js` will serve the component defined in that file as a page at the path `/amiv/about`._
7. **`/src/store`**: This directory contains all files related to data handling using `react-redux`.
......@@ -116,4 +118,4 @@ TODO: Add information about the deployment process of the project.
* [ESlint](https://github.com/eslint/eslint)
* [Prettier](https://github.com/prettier/prettier)
Most IDEs have plugins for those tools. VS Code is the recommended IDE.
Most IDEs have plugins for those tools. VS Code is the recommended IDE.
......@@ -23,6 +23,15 @@ module.exports = {
},
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/src/pages`,
// Ignore any file starting with an underscore.
// Those files are reserved for client-only routes.
ignore: [`**/_*`],
},
},
{
resolve: `gatsby-plugin-material-ui`,
options: {
......
import React, { useEffect, useState, useRef } from 'react'
import PropTypes from 'prop-types'
import { useDispatch, useSelector } from 'react-redux'
import { navigate } from 'gatsby-plugin-intl'
import animateScrollTo from 'animated-scroll-to'
import { makeStyles, useTheme } from '@material-ui/core/styles'
import { loadItem, listLoadNextPage } from '~store/common/actions'
import { STUDYDOCUMENTS } from '../../store/studydocuments/constants'
import Layout from '../layout'
import SEO from '../seo'
const useStyles = makeStyles(
{
root: {
textAlign: 'center',
paddingTop: '2em',
},
},
{ name: 'studydocuments' }
)
const StudydocumentsEditPage = ({ studydocumentId }) => {
// Selectors for all event lists
const defaultList = useSelector(state => state.studydocuments.default)
// Selector for selected item (loaded from path)
const studydocuments = useSelector(state => state.studydocuments.items)
const auth = useSelector(state => state.auth)
const dispatch = useDispatch()
// Set to the selected event id on initial page load.
const [pinnedId, setPinnedId] = useState(null)
const [shouldScroll, setShouldScroll] = useState(!!studydocumentId)
const classes = useStyles()
const theme = useTheme()
const ref = useRef(null)
useEffect(() => {
if (
auth.isLoggedIn &&
defaultList.totalPages === 0 &&
!defaultList.isPending
) {
dispatch(listLoadNextPage(STUDYDOCUMENTS, { listName: 'default' }))
}
}, [defaultList, auth.isLoggedIn])
// Handle set of pinned item on initial load.
useEffect(() => {
if (!studydocumentId) return
// Load event if not already loaded.
if (!studydocuments[studydocumentId]) {
dispatch(loadItem(STUDYDOCUMENTS, { id: studydocumentId }))
}
// Check if item already part of a list. If not, make it pinned at the top of the page.
if (!defaultList.items.includes(studydocumentId)) {
setPinnedId(studydocumentId)
}
}, [studydocumentId])
// Handle initial scroll to active element
useEffect(() => {
if (ref.current && shouldScroll) {
setShouldScroll(false)
animateScrollTo(ref.current, {
verticalOffset: -theme.shape.headerHeight,
speed: 125,
})
}
}, [ref.current, shouldScroll])
// Helper functions for shown lists and list items
const isActive = item => item === studydocumentId
const hasMorePagesToLoad = list => list.lastPageLoaded < list.totalPages
const loadMoreLabel = list => (list.error ? 'loadMoreError' : 'loadMore')
const loadMore = list => {
dispatch(listLoadNextPage(STUDYDOCUMENTS, { listName: list }))
}
const itemExpandHandler = ({ id, expanded }) => {
let path = null
if (expanded) {
path = `/studydocuments/${id}`
} else {
path = `/studydocuments`
}
if (window.location.pathname !== path) {
navigate(path)
}
}
return (
<Layout
authenticatedOnly
className={classes.root}
seoProps={{ title: 'studydocuments.title' }}
>
<div>This is great!</div>
</Layout>
)
}
StudydocumentsEditPage.propTypes = {
/** Studydocument id from path, if available. */
studydocumentId: PropTypes.string,
}
export default StudydocumentsEditPage
......@@ -43,7 +43,7 @@ const useStyles = makeStyles(
borderRadius: '4px',
},
expanded: {
backgroundColor: theme.palette.background.default,
backgroundColor: theme.palette.background.paper,
},
summaryRoot: {
padding: '0 24px 0 0',
......
import React from 'react'
import { Router } from '@reach/router'
import EventsPageComponent from '../components/events/page'
import NotFound from './404'
const EventsPage = () => {
return (
<Router>
<EventsPageComponent path="/:language/events/:eventId" />
<EventsPageComponent path="/:language/events" />
<NotFound default />
</Router>
)
}
export default EventsPage
import React, { useEffect, useState, useRef } from 'react'
import PropTypes from 'prop-types'
import { useDispatch, useSelector } from 'react-redux'
import { FormattedMessage, navigate } from 'gatsby-plugin-intl'
import animateScrollTo from 'animated-scroll-to'
import { makeStyles, useTheme } from '@material-ui/core/styles'
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
import {
Typography,
ExpansionPanel,
ExpansionPanelDetails,
ExpansionPanelSummary,
} from '@material-ui/core'
import {
loadItem,
listLoadNextPage,
listLoadAllPages,
} from '~store/common/actions'
import { EVENTS } from '~store/events/constants'
import Layout from '../../components/layout'
import EventsFilter from '../../components/events/filter'
import FilteredListLayout from '../../components/filteredListPage/layout'
import FilteredList from '../../components/filteredListPage/list'
import FilteredListItem from '../../components/filteredListPage/listItem'
import EventSummary from '../../components/events/summary'
import EventDetails from '../../components/events/details'
const useStyles = makeStyles(
theme => ({
root: {
textAlign: 'center',
paddingTop: '2em',
},
calendarRoot: {
backgroundColor: theme.palette.common.grey,
overflow: 'hidden',
borderRadius: '4px',
},
calendarDetails: {
padding: 0,
},
}),
{ name: 'events' }
)
const EventsPage = ({ eventId }) => {
// Selectors for all event lists
const upcomingWithOpenRegistration = useSelector(
state => state.events.upcomingWithOpenRegistration
)
const upcomingWithoutOpenRegistration = useSelector(
state => state.events.upcomingWithoutOpenRegistration
)
const past = useSelector(state => state.events.past)
// Selector for selected item (loaded from path)
const events = useSelector(state => state.events.items)
const dispatch = useDispatch()
// Set to the selected event id on initial page load.
const [pinnedId, setPinnedId] = useState(null)
const [shouldScroll, setShouldScroll] = useState(!!eventId)
const classes = useStyles()
const theme = useTheme()
const ref = useRef(null)
useEffect(() => {
if (
upcomingWithOpenRegistration.totalPages === 0 &&
!upcomingWithOpenRegistration.isPending
) {
dispatch(
listLoadAllPages(EVENTS, { listName: 'upcomingWithOpenRegistration' })
)
}
}, [upcomingWithOpenRegistration])
useEffect(() => {
if (
upcomingWithoutOpenRegistration.totalPages === 0 &&
!upcomingWithoutOpenRegistration.isPending
) {
dispatch(
listLoadAllPages(EVENTS, {
listName: 'upcomingWithoutOpenRegistration',
})
)
}
}, [upcomingWithoutOpenRegistration])
useEffect(() => {
if (past.totalPages === 0 && !past.isPending) {
dispatch(listLoadNextPage(EVENTS, { listName: 'past' }))
}
}, [past])
// Handle set of pinned item on initial load.
useEffect(() => {
if (!eventId) return
// Load event if not already loaded.
if (!events[eventId]) {
dispatch(loadItem(EVENTS, { id: eventId }))
}
// Check if item already part of a list. If not, make it pinned at the top of the page.
if (
!(
upcomingWithOpenRegistration.items.includes(eventId) ||
upcomingWithoutOpenRegistration.items.includes(eventId) ||
past.items.includes(eventId)
)
) {
setPinnedId(eventId)
}
}, [eventId])
// Handle initial scroll to active element
useEffect(() => {
if (ref.current && shouldScroll) {
setShouldScroll(false)
animateScrollTo(ref.current, {
verticalOffset: -theme.shape.headerHeight,
speed: 125,
})
}
}, [ref.current, shouldScroll])
// Helper functions for shown lists and list items
const isActive = item => item === eventId
const hasMorePagesToLoad = list => list.lastPageLoaded < list.totalPages
const loadMoreLabel = list => (list.error ? 'loadMoreError' : 'loadMore')
const loadMore = list => {
dispatch(listLoadNextPage(EVENTS, { listName: list }))
}
const eventExpandHandler = ({ id, expanded }) => {
let path = null
if (expanded) {
path = `/events/${id}`
} else {
path = `/events`
}
if (window.location.pathname !== path) {
navigate(path)
}
}
const placeholder = (
<FilteredListItem disabled>
<EventSummary eventId={null} />
</FilteredListItem>
)
return (
<Layout className={classes.root} seoProps={{ title: 'events.title' }}>
<FilteredListLayout>
<EventsFilter />
{/* Event Calendar */}
<ExpansionPanel>
<ExpansionPanelSummary
classes={{
root: classes.calendarRoot,
}}
expandIcon={<ExpandMoreIcon />}
>
<Typography className={classes.heading}>
<FormattedMessage id="events.agenda" />
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails classes={{ root: classes.calendarDetails }}>
<iframe
src="https://calendar.google.com/calendar/embed?showTitle=0&amp;showPrint=0&amp;showTabs=0&amp;showCalendars=0&amp;showTz=0&amp;height=600&amp;wkst=2&amp;bgcolor=%23FFFFFF&amp;src=mdk91hfvr18q8rrlh3sedlhgvo%40group.calendar.google.com&amp;color=%23B1365F&amp;ctz=Europe%2FZurich"
style={{ width: '100%', height: '550px', borderWidth: 0 }}
frameBorder="0"
scrolling="no"
/>
</ExpansionPanelDetails>
</ExpansionPanel>
{/* Pinned event */}
{pinnedId && (
<FilteredList>
<FilteredListItem
key={pinnedId}
id={pinnedId}
ref={isActive(pinnedId) ? ref : undefined}
expanded={isActive(pinnedId)}
onChange={eventExpandHandler}
>
<EventSummary eventId={pinnedId} />
<EventDetails eventId={pinnedId} />
</FilteredListItem>
</FilteredList>
)}
{/* Upcoming events with open registration */}
<FilteredList
title="events.headers.openRegistration"
placeholder={placeholder}
showPlaceholder={upcomingWithOpenRegistration.isPending}
loadMoreLabel={loadMoreLabel(upcomingWithOpenRegistration)}
loadMore={
hasMorePagesToLoad(upcomingWithOpenRegistration)
? () => loadMore('upcomingWithOpenRegistration')
: null
}
>
{upcomingWithOpenRegistration.items.map(item => (
<FilteredListItem
key={item}
id={item}
ref={isActive(item) ? ref : undefined}
expanded={isActive(item)}
onChange={eventExpandHandler}
>
<EventSummary eventId={item} />
<EventDetails eventId={item} />
</FilteredListItem>
))}
</FilteredList>
{/* Upcoming events with closed registration */}
<FilteredList
title="events.headers.upcoming"
placeholder={placeholder}
showPlaceholder={upcomingWithoutOpenRegistration.isPending}
loadMoreLabel={loadMoreLabel(upcomingWithoutOpenRegistration)}
loadMore={
hasMorePagesToLoad(upcomingWithoutOpenRegistration)
? () => loadMore('upcomingWithoutOpenRegistration')
: null
}
>
{upcomingWithoutOpenRegistration.items.map(item => (
<FilteredListItem
key={item}
id={item}
ref={isActive(item) ? ref : undefined}
expanded={isActive(item)}
onChange={eventExpandHandler}
>
<EventSummary eventId={item} />
<EventDetails eventId={item} />
</FilteredListItem>
))}
</FilteredList>
{/* Past events */}
<FilteredList
title="events.headers.past"
placeholder={placeholder}
showPlaceholder={past.isPending}
loadMoreLabel={loadMoreLabel(past)}
loadMore={hasMorePagesToLoad(past) ? () => loadMore('past') : null}
>
{past.items.map(item => (
<FilteredListItem
key={item}
id={item}
ref={isActive(item) ? ref : undefined}
expanded={isActive(item)}
onChange={eventExpandHandler}
>
<EventSummary eventId={item} />
<EventDetails eventId={item} />
</FilteredListItem>
))}
</FilteredList>
</FilteredListLayout>
</Layout>
)
}
EventsPage.propTypes = {
/** Event id from path. */
eventId: PropTypes.string,
}
export default EventsPage
import React from 'react'
import { Router } from '@reach/router'
import EventsListPage from './_list'
import NotFound from '../404'
/**
* Events Index Page
*
* This is a collection of client-only routes! All routes are handled
* by this index file on the client side only. There is no server-side
* rendering of those pages.
*
* Some pages in the same directory are being used for dynamic routes
* defined below.
*/
const EventsIndexPage = () => {
return (
<Router>
<EventsListPage path="/:language/events/:eventId" />
<EventsListPage path="/:language/events" />
<NotFound default />
</Router>
)
}
export default EventsIndexPage
import React from 'react'
import { Router } from '@reach/router'
import JobsPageComponent from '../components/jobs/page'
import NotFound from './404'
const JobsPage = () => {
return (
<Router>
<JobsPageComponent path="/:language/jobs/:jobofferId" />
<JobsPageComponent path="/:language/jobs" />
<NotFound default />
</Router>
)
}
export default JobsPage
......@@ -12,13 +12,13 @@ import {
} from '~store/common/actions'
import { JOBOFFERS } from '../../store/joboffers/constants'
import Layout from '../layout'
import JoboffersFilter from './filter'
import FilteredListLayout from '../filteredListPage/layout'
import FilteredList from '../filteredListPage/list'
import FilteredListItem from '../filteredListPage/listItem'
import JobofferSummary from './summary'
import JobofferDetails from './details'
import Layout from '../../components/layout'
import JoboffersFilter from '../../components/jobs/filter'
import FilteredListLayout from '../../components/filteredListPage/layout'
import FilteredList from '../../components/filteredListPage/list'
import FilteredListItem from '../../components/filteredListPage/listItem'
import JobofferSummary from '../../components/jobs/summary'
import JobofferDetails from '../../components/jobs/details'
const useStyles = makeStyles(
{
......
import React from 'react'
import { Router } from '@reach/router'
import JobsListPage from './_list'
import NotFound from '../404'
/**
* Jobs Index Page
*
* This is a collection of client-only routes! All routes are handled
* by this index file on the client side only. There is no server-side
* rendering of those pages.
*
* Some pages in the same directory are being used for dynamic routes
* defined below.
*/
const JobsIndexPage = () => {
return (
<Router>
<JobsListPage path="/:language/jobs/:jobofferId" />
<JobsListPage path="/:language/jobs" />
<NotFound default />
</Router>
)
}
export default JobsIndexPage
import React from 'react'
import { Router } from '@reach/router'
import StudydocumentsPageComponent from '../components/studydocuments/page'
import StudydocumentsEditPageComponent from '../components/studydocuments/editPage'
import NotFound from './404'
const StudydocumentsPage = () => {
return (
<Router>
<StudydocumentsEditPageComponent path="/:language/studydocuments/new" />
<StudydocumentsEditPageComponent path="/:language/studydocuments/:studydocumentId/edit" />
<StudydocumentsPageComponent path="/:language/studydocuments/:studydocumentId" />
<StudydocumentsPageComponent path="/:language/studydocuments" />
<NotFound default />
</Router>
)
}
export default StudydocumentsPage
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import { useDispatch, useSelector } from 'react-redux'
import { makeStyles } from '@material-ui/core/styles'
import { loadItem } from '../../store/common/actions'
import { STUDYDOCUMENTS } from '../../store/studydocuments/constants'