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 SeriesPage from './src/pages/SeriesPage'
|
||||
import { useEventApi, useEventCalendar } from './src/hooks/data'
|
||||
import { useEventApi } from './src/hooks/data'
|
||||
import { useTheme } from './src/store'
|
||||
import { useTimeout } from './src/hooks/timerHooks'
|
||||
import LoaderLayout from './src/pages/LoaderLayout'
|
||||
import FourOhFour from './src/pages/404'
|
||||
import Series from './src/pages/Series'
|
||||
import Program from './src/pages/Program'
|
||||
|
||||
const App = () => {
|
||||
const { theme } = useTheme((store) => store)
|
||||
const { data: calData, calLoading } = useEventCalendar()
|
||||
const { data: seriesDataArray, loading: eventsLoading } = useEventApi()
|
||||
const { data, loading: eventsLoading } = useEventApi()
|
||||
const [minLoadTimePassed, setMinTimeUp] = useState(false)
|
||||
|
||||
useTimeout(() => {
|
||||
setMinTimeUp(true)
|
||||
}, 1500)
|
||||
|
||||
// console.log({ episodes: data.episodes, series: data.series })
|
||||
|
||||
const seriesData = data.series ? Object.values(data.series) : []
|
||||
|
||||
const seriesData = Object.values(seriesDataArray)
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
{calLoading || eventsLoading || !minLoadTimePassed ? (
|
||||
{!seriesData.length || eventsLoading || !minLoadTimePassed ? (
|
||||
<LoaderLayout />
|
||||
) : (
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Main} />
|
||||
<Route exact path="/series" component={Series} />
|
||||
<Route exact path="/program" component={Program} />
|
||||
{seriesData.length ? seriesData.map(series => (
|
||||
<Route exact path={`/series/${series.slug}`}>
|
||||
<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'
|
||||
|
||||
export const Row = styled.div`
|
||||
@ -5,6 +6,9 @@ export const Row = styled.div`
|
||||
flex-direction: ${props => (props.reverse ? 'row-reverse' : 'row')};
|
||||
justify-content: ${props => props.justify || 'flex-start'};
|
||||
align-items: ${props => props.align || 'flex-start'};
|
||||
${props => props.flex ? `
|
||||
flex: ${props.flex};
|
||||
` : ''}
|
||||
`
|
||||
|
||||
export const Column = styled.div`
|
||||
@ -12,4 +16,16 @@ export const Column = styled.div`
|
||||
flex-direction: ${props => (props.reverse ? 'column-reverse' : 'column')};
|
||||
justify-content: ${props => props.justify || '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 { 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({
|
||||
path: to,
|
||||
exact: activeOnlyWhenExact
|
||||
})
|
||||
|
||||
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>
|
||||
</A>
|
||||
)
|
||||
|
@ -91,10 +91,10 @@ export const H2 = ({ children, size, ...rest }) => (
|
||||
</Text>
|
||||
)
|
||||
|
||||
export const H3 = ({ children, size, ...rest }) => (
|
||||
export const H3 = ({ children, size, weight = '700', ...rest }) => (
|
||||
<Text
|
||||
tag="h3"
|
||||
weight="700"
|
||||
weight={weight}
|
||||
$size={size || '21'}
|
||||
lineHeight="1"
|
||||
fontFamily="Lunchtype24"
|
||||
|
@ -20,5 +20,7 @@ export default {
|
||||
lastStream: 'Last stream',
|
||||
nextStream: 'Next stream',
|
||||
episodes: 'episodes',
|
||||
today: 'today',
|
||||
tomorrow: 'tomorrow'
|
||||
},
|
||||
}
|
||||
|
@ -99,23 +99,19 @@ export const useEventCalendar = () => {
|
||||
|
||||
|
||||
export const useEventApi = () => {
|
||||
const [series, episodes, setSeries, setEpisodes] = useSeriesStore(store => [store.series, store.episodes, store.setSeries, store.setEpisodes])
|
||||
const [loading, setLoading] = useState(!!series.length)
|
||||
|
||||
const [data, setData] = useSeriesStore(store => [store.series, store.setSeries])
|
||||
const [loading, setLoading] = useState(!!data.length)
|
||||
|
||||
async function fetchData() {
|
||||
if (!series.length) {
|
||||
if (!data.length) {
|
||||
setLoading(true)
|
||||
|
||||
const { data: responseData } = await axios.get(
|
||||
`${config.EVENTS_API_URL}/events`
|
||||
)
|
||||
|
||||
setSeries(responseData)
|
||||
// setEpisodes()
|
||||
|
||||
// console.log({ episodes })
|
||||
|
||||
setData(responseData)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
@ -124,5 +120,5 @@ export const useEventApi = () => {
|
||||
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'
|
||||
|
||||
const Series = () => {
|
||||
const { data: seriesDataArray } = useEventApi()
|
||||
const { data } = useEventApi()
|
||||
const pastSeries = []
|
||||
|
||||
const currentSeries = seriesDataArray.filter(series => {
|
||||
const currentSeries = data.series ? data.series.filter(series => {
|
||||
// const seriesInTheLastMonth = series.episodes.past.filter(episode => {
|
||||
|
||||
// })
|
||||
if (series.episodes.future.length) {
|
||||
return true
|
||||
@ -24,7 +23,7 @@ const Series = () => {
|
||||
|
||||
pastSeries.push(series)
|
||||
return false
|
||||
})
|
||||
}) : []
|
||||
|
||||
|
||||
return (
|
||||
|
@ -13,6 +13,7 @@ import CrossSvg from '../../components/Svg/Cross'
|
||||
import { H1, H2, Span, Label } from '../../components/Text'
|
||||
import Link from '../../components/Link'
|
||||
import Button from '../../components/Button'
|
||||
import { slugify } from '../../helpers/string'
|
||||
|
||||
export const TrailerContainer = styled.div`
|
||||
height: 22em;
|
||||
@ -140,8 +141,6 @@ const renderTitles = titles =>
|
||||
export const EpisodeCard = ({
|
||||
title,
|
||||
image,
|
||||
|
||||
|
||||
description,
|
||||
beginsOn,
|
||||
hasPassed,
|
||||
@ -149,6 +148,7 @@ export const EpisodeCard = ({
|
||||
onClickButton,
|
||||
tzShort,
|
||||
theme,
|
||||
id
|
||||
}) => {
|
||||
const startDate = new Date(beginsOn)
|
||||
const utcDate = zonedTimeToUtc(startDate, 'Europe/Berlin')
|
||||
@ -156,7 +156,7 @@ export const EpisodeCard = ({
|
||||
const { timeZone } = Intl.DateTimeFormat().resolvedOptions()
|
||||
const zonedDate = utcToZonedTime(utcDate, timeZone)
|
||||
return (
|
||||
<VCWrapper>
|
||||
<VCWrapper id={id}>
|
||||
<DateLabel size={textSizes.lg} colour={theme.foreground}>
|
||||
{`${hasPassed ? translations.en.streamDatePast : ''}`}
|
||||
<Span bold colour={theme.foreground}>
|
||||
|
@ -2,7 +2,7 @@ import create from 'zustand'
|
||||
import { defaultTheme } from '../assets/theme'
|
||||
|
||||
export const useSeriesStore = create((set, get) => ({
|
||||
series: [],
|
||||
series: {},
|
||||
episodes: [],
|
||||
setSeries: series => set({ series }),
|
||||
setEpisodes: () => {
|
||||
|
Loading…
Reference in New Issue
Block a user