started program guide list
This commit is contained in:
parent
be6aa96e1b
commit
36fd61d3ac
13
index.js
13
index.js
@ -5,35 +5,38 @@ import { BrowserRouter, Route, Switch } from 'react-router-dom'
|
|||||||
|
|
||||||
import Main from './app'
|
import Main from './app'
|
||||||
import SeriesPage from './src/pages/SeriesPage'
|
import SeriesPage from './src/pages/SeriesPage'
|
||||||
import { useEventApi, useEventCalendar } from './src/hooks/data'
|
import { useEventApi } from './src/hooks/data'
|
||||||
import { useTheme } from './src/store'
|
import { useTheme } from './src/store'
|
||||||
import { useTimeout } from './src/hooks/timerHooks'
|
import { useTimeout } from './src/hooks/timerHooks'
|
||||||
import LoaderLayout from './src/pages/LoaderLayout'
|
import LoaderLayout from './src/pages/LoaderLayout'
|
||||||
import FourOhFour from './src/pages/404'
|
import FourOhFour from './src/pages/404'
|
||||||
import Series from './src/pages/Series'
|
import Series from './src/pages/Series'
|
||||||
|
import Program from './src/pages/Program'
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const { theme } = useTheme((store) => store)
|
const { theme } = useTheme((store) => store)
|
||||||
const { data: calData, calLoading } = useEventCalendar()
|
const { data, loading: eventsLoading } = useEventApi()
|
||||||
const { data: seriesDataArray, loading: eventsLoading } = useEventApi()
|
|
||||||
const [minLoadTimePassed, setMinTimeUp] = useState(false)
|
const [minLoadTimePassed, setMinTimeUp] = useState(false)
|
||||||
|
|
||||||
useTimeout(() => {
|
useTimeout(() => {
|
||||||
setMinTimeUp(true)
|
setMinTimeUp(true)
|
||||||
}, 1500)
|
}, 1500)
|
||||||
|
|
||||||
|
// console.log({ episodes: data.episodes, series: data.series })
|
||||||
|
|
||||||
|
const seriesData = data.series ? Object.values(data.series) : []
|
||||||
|
|
||||||
const seriesData = Object.values(seriesDataArray)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
{calLoading || eventsLoading || !minLoadTimePassed ? (
|
{!seriesData.length || eventsLoading || !minLoadTimePassed ? (
|
||||||
<LoaderLayout />
|
<LoaderLayout />
|
||||||
) : (
|
) : (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={Main} />
|
<Route exact path="/" component={Main} />
|
||||||
<Route exact path="/series" component={Series} />
|
<Route exact path="/series" component={Series} />
|
||||||
|
<Route exact path="/program" component={Program} />
|
||||||
{seriesData.length ? seriesData.map(series => (
|
{seriesData.length ? seriesData.map(series => (
|
||||||
<Route exact path={`/series/${series.slug}`}>
|
<Route exact path={`/series/${series.slug}`}>
|
||||||
<SeriesPage data={series} />
|
<SeriesPage data={series} />
|
||||||
|
43
src/components/EpisodeCard/index.js
Normal file
43
src/components/EpisodeCard/index.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { h } from 'preact'
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import Link from '../Link'
|
||||||
|
import { H2, H3, Label } from '../Text'
|
||||||
|
import strings from '../../data/strings'
|
||||||
|
import { andList } from '../../helpers/string'
|
||||||
|
import { colours } from '../../assets/theme'
|
||||||
|
import { Img, Left, Right, Title, Row, Column, StyledButton as Button } from './styles'
|
||||||
|
import { useEventApi } from '../../hooks/data'
|
||||||
|
|
||||||
|
const EpisodeCard = ({ image, title, seriesId, beginsOn, id, ...rest }) => {
|
||||||
|
const { data: { series: allSeries } } = useEventApi()
|
||||||
|
|
||||||
|
const series = seriesId ? allSeries.filter(({ id }) => id === seriesId)[0] : {}
|
||||||
|
const hosts = series.hosts ? series.hosts.map(host => host.actor.name) : null
|
||||||
|
const startTime = format(new Date(beginsOn), 'ha')
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row align="stretch" {...rest}>
|
||||||
|
<Left >
|
||||||
|
<Link to={`/series/${series.slug}#${id}`}><Img src={image} /></Link>
|
||||||
|
<Column justify="space-between" flex={1}>
|
||||||
|
<Title size={24} colour={colours.rose} weight="500">{title}</Title>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<H3 weight="500">From: <Link textProps={{
|
||||||
|
weight: '700'
|
||||||
|
}} to={`/series/${series.slug}`}>{`${series.title}: ${series.subtitle}`}</Link></H3>
|
||||||
|
{hosts ? <Label size={16} colour={colours.rose}>— {andList(hosts)}</Label> : null}
|
||||||
|
</div>
|
||||||
|
<Button>Play now</Button>
|
||||||
|
</Column>
|
||||||
|
</Left>
|
||||||
|
<Right>
|
||||||
|
<Label size={24} colour={colours.rose} weight="600">{startTime}</Label>
|
||||||
|
</Right>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default EpisodeCard
|
39
src/components/EpisodeCard/styles.js
Normal file
39
src/components/EpisodeCard/styles.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import { colours } from '../../assets/theme'
|
||||||
|
import { Label, H2 } from '../Text'
|
||||||
|
import { Row as FlexRow, Column as FlexColumn } from '../Flex'
|
||||||
|
import Button from '../Button'
|
||||||
|
|
||||||
|
export const Row = styled(FlexRow)`
|
||||||
|
|
||||||
|
`
|
||||||
|
export const Column = styled(FlexColumn)`
|
||||||
|
height: 100%;
|
||||||
|
max-width: 50%;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Left = styled(FlexRow)`
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Title = styled(H2)`
|
||||||
|
/* max-width: 50% */
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Right = styled.div`
|
||||||
|
|
||||||
|
`
|
||||||
|
export const StyledButton = styled(Button)`
|
||||||
|
width: max-content;
|
||||||
|
padding: 0.3em 2em;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Img = styled.div`
|
||||||
|
background: url(${({ src }) => src});
|
||||||
|
width: 380px;
|
||||||
|
height: 215px;
|
||||||
|
background-size: cover;
|
||||||
|
position: relative;
|
||||||
|
background-position: center;
|
||||||
|
margin-right: 1em;
|
||||||
|
`
|
@ -1,3 +1,4 @@
|
|||||||
|
import { bool, number, oneOf } from 'prop-types'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
export const Row = styled.div`
|
export const Row = styled.div`
|
||||||
@ -5,6 +6,9 @@ export const Row = styled.div`
|
|||||||
flex-direction: ${props => (props.reverse ? 'row-reverse' : 'row')};
|
flex-direction: ${props => (props.reverse ? 'row-reverse' : 'row')};
|
||||||
justify-content: ${props => props.justify || 'flex-start'};
|
justify-content: ${props => props.justify || 'flex-start'};
|
||||||
align-items: ${props => props.align || 'flex-start'};
|
align-items: ${props => props.align || 'flex-start'};
|
||||||
|
${props => props.flex ? `
|
||||||
|
flex: ${props.flex};
|
||||||
|
` : ''}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const Column = styled.div`
|
export const Column = styled.div`
|
||||||
@ -12,4 +16,16 @@ export const Column = styled.div`
|
|||||||
flex-direction: ${props => (props.reverse ? 'column-reverse' : 'column')};
|
flex-direction: ${props => (props.reverse ? 'column-reverse' : 'column')};
|
||||||
justify-content: ${props => props.justify || 'flex-start'};
|
justify-content: ${props => props.justify || 'flex-start'};
|
||||||
align-items: ${props => props.align || 'flex-start'};
|
align-items: ${props => props.align || 'flex-start'};
|
||||||
|
${props => props.flex ? `
|
||||||
|
flex: ${props.flex};
|
||||||
|
` : ''}
|
||||||
`
|
`
|
||||||
|
const propTypes = {
|
||||||
|
reverse: bool,
|
||||||
|
justify: oneOf(['flex-start', 'flex-end', 'center', 'space-around', 'space-between']),
|
||||||
|
align: oneOf(['flex-start', 'flex-end', 'center', 'stretch']),
|
||||||
|
flex: number
|
||||||
|
}
|
||||||
|
|
||||||
|
Row.propTypes = propTypes
|
||||||
|
Column.propTypes = propTypes
|
@ -5,14 +5,14 @@ import { useRouteMatch } from 'react-router-dom'
|
|||||||
import { A } from '../Text'
|
import { A } from '../Text'
|
||||||
import { RRLink } from './styles'
|
import { RRLink } from './styles'
|
||||||
|
|
||||||
const Link = ({ children, to, activeOnlyWhenExact = true, href, textProps: { colour, size } = {}, navLink, ...rest }) => {
|
const Link = ({ children, to, activeOnlyWhenExact = true, href, textProps: { colour, size, weight } = {}, navLink, ...rest }) => {
|
||||||
const match = useRouteMatch({
|
const match = useRouteMatch({
|
||||||
path: to,
|
path: to,
|
||||||
exact: activeOnlyWhenExact
|
exact: activeOnlyWhenExact
|
||||||
})
|
})
|
||||||
|
|
||||||
return href ? <A href={href} colour={colour} size={size} fontFamily={navLink ? 'Lunchtype24' : null} {...rest}>{children}</A> : (
|
return href ? <A href={href} colour={colour} size={size} fontFamily={navLink ? 'Lunchtype24' : null} {...rest}>{children}</A> : (
|
||||||
<A as="span" colour={colour} size={size} {...rest}>
|
<A as="span" colour={colour} size={size} weight={weight} {...rest}>
|
||||||
<RRLink to={to} $navLink={navLink} $colour={colour} $selected={match && navLink}>{children}</RRLink>
|
<RRLink to={to} $navLink={navLink} $colour={colour} $selected={match && navLink}>{children}</RRLink>
|
||||||
</A>
|
</A>
|
||||||
)
|
)
|
||||||
|
@ -91,10 +91,10 @@ export const H2 = ({ children, size, ...rest }) => (
|
|||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const H3 = ({ children, size, ...rest }) => (
|
export const H3 = ({ children, size, weight = '700', ...rest }) => (
|
||||||
<Text
|
<Text
|
||||||
tag="h3"
|
tag="h3"
|
||||||
weight="700"
|
weight={weight}
|
||||||
$size={size || '21'}
|
$size={size || '21'}
|
||||||
lineHeight="1"
|
lineHeight="1"
|
||||||
fontFamily="Lunchtype24"
|
fontFamily="Lunchtype24"
|
||||||
|
@ -20,5 +20,7 @@ export default {
|
|||||||
lastStream: 'Last stream',
|
lastStream: 'Last stream',
|
||||||
nextStream: 'Next stream',
|
nextStream: 'Next stream',
|
||||||
episodes: 'episodes',
|
episodes: 'episodes',
|
||||||
|
today: 'today',
|
||||||
|
tomorrow: 'tomorrow'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -99,23 +99,19 @@ export const useEventCalendar = () => {
|
|||||||
|
|
||||||
|
|
||||||
export const useEventApi = () => {
|
export const useEventApi = () => {
|
||||||
const [series, episodes, setSeries, setEpisodes] = useSeriesStore(store => [store.series, store.episodes, store.setSeries, store.setEpisodes])
|
const [data, setData] = useSeriesStore(store => [store.series, store.setSeries])
|
||||||
const [loading, setLoading] = useState(!!series.length)
|
const [loading, setLoading] = useState(!!data.length)
|
||||||
|
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
if (!series.length) {
|
if (!data.length) {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
const { data: responseData } = await axios.get(
|
const { data: responseData } = await axios.get(
|
||||||
`${config.EVENTS_API_URL}/events`
|
`${config.EVENTS_API_URL}/events`
|
||||||
)
|
)
|
||||||
|
|
||||||
setSeries(responseData)
|
|
||||||
// setEpisodes()
|
|
||||||
|
|
||||||
// console.log({ episodes })
|
|
||||||
|
|
||||||
|
setData(responseData)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,5 +120,5 @@ export const useEventApi = () => {
|
|||||||
fetchData()
|
fetchData()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return { loading, data: series }
|
return { loading, data }
|
||||||
}
|
}
|
35
src/pages/Program/helpers.js
Normal file
35
src/pages/Program/helpers.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { isBefore, format, isToday, isTomorrow } from 'date-fns'
|
||||||
|
import { capitaliseFirstLetter } from '../../helpers/string'
|
||||||
|
import strings from '../../data/strings'
|
||||||
|
|
||||||
|
export const getScheduleFromData = data => data.filter(item => {
|
||||||
|
const { endsOn } = item
|
||||||
|
const today = format(new Date(), 'yyyy/M/d')
|
||||||
|
const broadcastEnd = new Date(endsOn)
|
||||||
|
return !isBefore(broadcastEnd, new Date(today))
|
||||||
|
})
|
||||||
|
.reduce((obj, item) => {
|
||||||
|
const newObject = obj
|
||||||
|
const { beginsOn } = item
|
||||||
|
const startDay = format(new Date(beginsOn), 'yyyy-MM-dd')
|
||||||
|
|
||||||
|
if (newObject[startDay]) {
|
||||||
|
newObject[startDay] = [...newObject[startDay], item]
|
||||||
|
} else {
|
||||||
|
newObject[startDay] = [item]
|
||||||
|
}
|
||||||
|
|
||||||
|
return newObject
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const formatDay = (dateTime, lang = 'en') => {
|
||||||
|
let day = ''
|
||||||
|
const date = new Date(dateTime)
|
||||||
|
console.log({ date })
|
||||||
|
if (isToday(date)) day = strings[lang].today
|
||||||
|
if (isTomorrow(date)) day = strings[lang].tomorrow
|
||||||
|
else day = format(date, 'cccc MMM d')
|
||||||
|
return capitaliseFirstLetter(day)
|
||||||
|
}
|
42
src/pages/Program/index.js
Normal file
42
src/pages/Program/index.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
|
import { isWithinInterval } from 'date-fns'
|
||||||
|
import { h } from 'preact'
|
||||||
|
import { H1, H2 } from '../../components/Text'
|
||||||
|
import strings from '../../data/strings'
|
||||||
|
import { useEventApi } from '../../hooks/data'
|
||||||
|
import { Content, ScheduleList, Day } from './styles'
|
||||||
|
|
||||||
|
import Page from '../../layouts/Page'
|
||||||
|
import { formatDay, getScheduleFromData } from './helpers'
|
||||||
|
import EpisodeCard from '../../components/EpisodeCard'
|
||||||
|
import { colours } from '../../assets/theme'
|
||||||
|
|
||||||
|
const Program = () => {
|
||||||
|
const { data } = useEventApi()
|
||||||
|
|
||||||
|
const episodes = getScheduleFromData(data.episodes)
|
||||||
|
console.log({ episodes })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page title={strings.en.program}>
|
||||||
|
<Content>
|
||||||
|
<ScheduleList>
|
||||||
|
|
||||||
|
{Object.keys(episodes || {}).sort((a, b) => new Date(a) - new Date(b)).map(day => (
|
||||||
|
<Day>
|
||||||
|
<H1 colour={colours.rose}>{formatDay(day)}</H1>
|
||||||
|
{episodes[day].map(episode => (
|
||||||
|
<EpisodeCard {...episode} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
</Day>
|
||||||
|
))}
|
||||||
|
</ScheduleList>
|
||||||
|
{/* <H1>Program</H1> */}
|
||||||
|
|
||||||
|
</Content>
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Program
|
42
src/pages/Program/styles.js
Normal file
42
src/pages/Program/styles.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import { screenSizes } from '../../assets/theme'
|
||||||
|
import { Row } from '../../components/Flex'
|
||||||
|
|
||||||
|
export const SeriesGrid = styled.div`
|
||||||
|
margin-left: 32px;
|
||||||
|
`
|
||||||
|
export const ScheduleList = styled.ul`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Day = styled.div`
|
||||||
|
margin: 0 0 6em 0;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Content = styled.div`
|
||||||
|
width: 80vw;
|
||||||
|
max-width: ${screenSizes.lg}px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 64px 0;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
/* ${mediaQuery.lessThan('lg')`
|
||||||
|
max-height: calc(100vh - 200px);
|
||||||
|
`}
|
||||||
|
|
||||||
|
${mediaQuery.lessThan('md')`
|
||||||
|
max-width: 600px;
|
||||||
|
padding: 8px 0;
|
||||||
|
margin: 0 32px;
|
||||||
|
`}; */
|
@ -11,12 +11,11 @@ import SeriesCard from '../../components/SeriesCard'
|
|||||||
import { colours } from '../../assets/theme'
|
import { colours } from '../../assets/theme'
|
||||||
|
|
||||||
const Series = () => {
|
const Series = () => {
|
||||||
const { data: seriesDataArray } = useEventApi()
|
const { data } = useEventApi()
|
||||||
const pastSeries = []
|
const pastSeries = []
|
||||||
|
|
||||||
const currentSeries = seriesDataArray.filter(series => {
|
const currentSeries = data.series ? data.series.filter(series => {
|
||||||
// const seriesInTheLastMonth = series.episodes.past.filter(episode => {
|
// const seriesInTheLastMonth = series.episodes.past.filter(episode => {
|
||||||
|
|
||||||
// })
|
// })
|
||||||
if (series.episodes.future.length) {
|
if (series.episodes.future.length) {
|
||||||
return true
|
return true
|
||||||
@ -24,7 +23,7 @@ const Series = () => {
|
|||||||
|
|
||||||
pastSeries.push(series)
|
pastSeries.push(series)
|
||||||
return false
|
return false
|
||||||
})
|
}) : []
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -13,6 +13,7 @@ import CrossSvg from '../../components/Svg/Cross'
|
|||||||
import { H1, H2, Span, Label } from '../../components/Text'
|
import { H1, H2, Span, Label } from '../../components/Text'
|
||||||
import Link from '../../components/Link'
|
import Link from '../../components/Link'
|
||||||
import Button from '../../components/Button'
|
import Button from '../../components/Button'
|
||||||
|
import { slugify } from '../../helpers/string'
|
||||||
|
|
||||||
export const TrailerContainer = styled.div`
|
export const TrailerContainer = styled.div`
|
||||||
height: 22em;
|
height: 22em;
|
||||||
@ -140,8 +141,6 @@ const renderTitles = titles =>
|
|||||||
export const EpisodeCard = ({
|
export const EpisodeCard = ({
|
||||||
title,
|
title,
|
||||||
image,
|
image,
|
||||||
|
|
||||||
|
|
||||||
description,
|
description,
|
||||||
beginsOn,
|
beginsOn,
|
||||||
hasPassed,
|
hasPassed,
|
||||||
@ -149,6 +148,7 @@ export const EpisodeCard = ({
|
|||||||
onClickButton,
|
onClickButton,
|
||||||
tzShort,
|
tzShort,
|
||||||
theme,
|
theme,
|
||||||
|
id
|
||||||
}) => {
|
}) => {
|
||||||
const startDate = new Date(beginsOn)
|
const startDate = new Date(beginsOn)
|
||||||
const utcDate = zonedTimeToUtc(startDate, 'Europe/Berlin')
|
const utcDate = zonedTimeToUtc(startDate, 'Europe/Berlin')
|
||||||
@ -156,7 +156,7 @@ export const EpisodeCard = ({
|
|||||||
const { timeZone } = Intl.DateTimeFormat().resolvedOptions()
|
const { timeZone } = Intl.DateTimeFormat().resolvedOptions()
|
||||||
const zonedDate = utcToZonedTime(utcDate, timeZone)
|
const zonedDate = utcToZonedTime(utcDate, timeZone)
|
||||||
return (
|
return (
|
||||||
<VCWrapper>
|
<VCWrapper id={id}>
|
||||||
<DateLabel size={textSizes.lg} colour={theme.foreground}>
|
<DateLabel size={textSizes.lg} colour={theme.foreground}>
|
||||||
{`${hasPassed ? translations.en.streamDatePast : ''}`}
|
{`${hasPassed ? translations.en.streamDatePast : ''}`}
|
||||||
<Span bold colour={theme.foreground}>
|
<Span bold colour={theme.foreground}>
|
||||||
|
@ -2,7 +2,7 @@ import create from 'zustand'
|
|||||||
import { defaultTheme } from '../assets/theme'
|
import { defaultTheme } from '../assets/theme'
|
||||||
|
|
||||||
export const useSeriesStore = create((set, get) => ({
|
export const useSeriesStore = create((set, get) => ({
|
||||||
series: [],
|
series: {},
|
||||||
episodes: [],
|
episodes: [],
|
||||||
setSeries: series => set({ series }),
|
setSeries: series => set({ series }),
|
||||||
setEpisodes: () => {
|
setEpisodes: () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user