series grid page
This commit is contained in:
parent
84ea28cd7d
commit
b2fad77434
6
index.js
6
index.js
@ -7,6 +7,8 @@ import SeriesPage from './src/pages/SeriesPage'
|
|||||||
import { useEventApi, useEventCalendar } from './src/hooks/data'
|
import { useEventApi, useEventCalendar } from './src/hooks/data'
|
||||||
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 Series from './src/pages/Series'
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const { data: calData, calLoading } = useEventCalendar()
|
const { data: calData, calLoading } = useEventCalendar()
|
||||||
@ -26,10 +28,14 @@ const App = () => {
|
|||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={Main} />
|
<Route exact path="/" component={Main} />
|
||||||
|
<Route exact path="/series" component={Series} />
|
||||||
{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} />
|
||||||
</Route>)) : null}
|
</Route>)) : null}
|
||||||
|
<Route path="*">
|
||||||
|
<FourOhFour />
|
||||||
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
@ -1,2 +1,22 @@
|
|||||||
@import 'reset';
|
@import 'reset';
|
||||||
@import 'fontface';
|
@import 'fontface';
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #112b39;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
scrollbar-color: #feb9b3;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background-color: #f6f4f5;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #feb9b3;
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -21,13 +21,13 @@ export const textSizes = {
|
|||||||
sm: 14,
|
sm: 14,
|
||||||
md: 16,
|
md: 16,
|
||||||
lg: 21,
|
lg: 21,
|
||||||
xl: 32,
|
xl: 28,
|
||||||
xxl: 92,
|
xxl: 92,
|
||||||
hg: 200,
|
hg: 200,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultTheme = {
|
export const defaultTheme = {
|
||||||
background: colours.rose, foreground: colours.midnight, highlight: colours.highlight
|
background: colours.midnightDarker, foreground: colours.rose, highlight: colours.highlight
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
15
src/components/Flex/index.js
Normal file
15
src/components/Flex/index.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
export const Row = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: ${props => (props.reverse ? 'row-reverse' : 'row')};
|
||||||
|
justify-content: ${props => props.justify || 'flex-start'};
|
||||||
|
align-items: ${props => props.align || 'flex-start'};
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Column = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: ${props => (props.reverse ? 'column-reverse' : 'column')};
|
||||||
|
justify-content: ${props => props.justify || 'flex-start'};
|
||||||
|
align-items: ${props => props.align || 'flex-start'};
|
||||||
|
`
|
36
src/components/Header/index.js
Normal file
36
src/components/Header/index.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { h } from 'preact'
|
||||||
|
import { Link as ReactLink } from 'react-router-dom'
|
||||||
|
import { RightBox, StyledRow as Row } from './styles'
|
||||||
|
import { ImageLogo } from '../Logo'
|
||||||
|
import Link from '../Link'
|
||||||
|
|
||||||
|
import navigation from '../../data/navigation'
|
||||||
|
import { colours, textSizes } from '../../assets/theme'
|
||||||
|
|
||||||
|
const Navigation = ({ theme = {}, lang = 'en' }) => (
|
||||||
|
<RightBox as="nav">
|
||||||
|
{navigation[lang].map(navItem => <Link navLink to={navItem.to} href={navItem.href} textProps={{
|
||||||
|
size: textSizes.xl, colour: theme.foreground || colours.rose
|
||||||
|
}}>{navItem.label}</Link>)}
|
||||||
|
</RightBox>
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
const BigHeader = ({ theme = {}, lang = 'en', ...rest }) => (
|
||||||
|
<Row theme={theme} align="center" justify="space-between" {...rest}>
|
||||||
|
<ReactLink to="/">
|
||||||
|
<ImageLogo />
|
||||||
|
</ReactLink>
|
||||||
|
<Navigation theme={theme} lang={lang} {...rest} />
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
|
||||||
|
const MiniHeader = ({ theme = {}, lang = 'en', ...rest }) => (
|
||||||
|
<Row theme={theme} align="center" justify="space-between" miniHeader {...rest}>
|
||||||
|
<Navigation theme={theme} lang={lang} {...rest} />
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
|
||||||
|
const Header = ({ miniHeader, ...rest }) => miniHeader ? <MiniHeader {...rest} /> : <BigHeader {...rest} />
|
||||||
|
|
||||||
|
export default Header
|
30
src/components/Header/styles.js
Normal file
30
src/components/Header/styles.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import { Row } from '../Flex'
|
||||||
|
|
||||||
|
|
||||||
|
export const StyledRow = styled(Row)`
|
||||||
|
pointer-events: all;
|
||||||
|
height: 100px;
|
||||||
|
background-color: ${({ theme }) => theme.background};
|
||||||
|
color: ${({ theme }) => theme.foreground};
|
||||||
|
position: absolute;
|
||||||
|
left: ${({ miniHeader }) => miniHeader ? 'initial' : '0'};
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 80px;
|
||||||
|
mix-blend-mode: exclusion;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const RightBox = styled(Row)`
|
||||||
|
a {
|
||||||
|
margin-right: 4em;
|
||||||
|
}
|
||||||
|
`
|
@ -1,6 +1,23 @@
|
|||||||
import styled from 'styled-components'
|
import { h } from 'preact'
|
||||||
|
|
||||||
|
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 match = useRouteMatch({
|
||||||
|
path: to,
|
||||||
|
exact: activeOnlyWhenExact
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log({ colour })
|
||||||
|
|
||||||
|
return href ? <A href={href} colour={colour} size={size} fontFamily={navLink ? 'Lunchtype24' : null} {...rest}>{children}</A> : (
|
||||||
|
<A as="span" colour={colour} size={size} {...rest}>
|
||||||
|
<RRLink to={to} $navLink={navLink} $colour={colour} $selected={match && navLink}>{children}</RRLink>
|
||||||
|
</A>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const Link = styled.a`
|
|
||||||
text-decoration: none;
|
|
||||||
`
|
|
||||||
export default Link
|
export default Link
|
32
src/components/Link/styles.js
Normal file
32
src/components/Link/styles.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { colours } from '../../assets/theme'
|
||||||
|
|
||||||
|
export const RRLink = styled(Link)`
|
||||||
|
color: ${props => { console.log({ col: props.$colour }); return props.$colour || colours.highlight }};
|
||||||
|
text-decoration: none;
|
||||||
|
${props => props.$navLink ? `
|
||||||
|
font-family: Lunchtype24;
|
||||||
|
` : ''};
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
${props => props.$selected ? `
|
||||||
|
&::before, &::after {
|
||||||
|
color: ${colours.highlight};
|
||||||
|
font-size: 21px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
font-family: 'Karla';
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '< ';
|
||||||
|
left: -1em;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
content: ' >';
|
||||||
|
right: -1em;
|
||||||
|
}
|
||||||
|
` : ''};
|
||||||
|
`
|
@ -3,6 +3,8 @@ import { bool, string } from 'prop-types'
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { colours } from '../../assets/theme'
|
import { colours } from '../../assets/theme'
|
||||||
|
|
||||||
|
import LogoBird from '../../../static/logo.png'
|
||||||
|
|
||||||
const Logo = ({ colour = colours.offwhite, ...rest }) => (
|
const Logo = ({ colour = colours.offwhite, ...rest }) => (
|
||||||
<LogoSvg
|
<LogoSvg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -56,4 +58,10 @@ export const RFLogo = ({ colour = colours.offwhite, ...rest }) => (
|
|||||||
</LogoSvg>
|
</LogoSvg>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const Img = styled.img`
|
||||||
|
mix-blend-mode: exclusion;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ImageLogo = (props) => <Img className="img-logo" src={LogoBird} alt={props.alt} {...props} />
|
||||||
|
|
||||||
export default Logo
|
export default Logo
|
||||||
|
@ -4,8 +4,8 @@ import MarkdownRenderer from 'markdown-to-jsx'
|
|||||||
import { MarkdownWrapper } from './styles'
|
import { MarkdownWrapper } from './styles'
|
||||||
import { P, A, H1, H2, H3, Span } from '../Text'
|
import { P, A, H1, H2, H3, Span } from '../Text'
|
||||||
|
|
||||||
const Markdown = ({ children, withLinebreaks, options, ...rest }) => (
|
const Markdown = ({ children, withLinebreaks, options, theme, ...rest }) => (
|
||||||
<MarkdownWrapper $withLinebreaks={withLinebreaks}>
|
<MarkdownWrapper $withLinebreaks={withLinebreaks} theme={theme}>
|
||||||
<MarkdownRenderer
|
<MarkdownRenderer
|
||||||
options={{
|
options={{
|
||||||
overrides: {
|
overrides: {
|
||||||
|
@ -5,7 +5,7 @@ import { P } from '../Text'
|
|||||||
|
|
||||||
export const MarkdownWrapper = styled.span`
|
export const MarkdownWrapper = styled.span`
|
||||||
a {
|
a {
|
||||||
color: ${colours.highlight};
|
color: ${({ theme }) => theme && theme.highlight || colours.highlight};
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
40
src/components/SeriesCard/index.js
Normal file
40
src/components/SeriesCard/index.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { formatDistanceToNow } from 'date-fns'
|
||||||
|
import { h } from 'preact'
|
||||||
|
import Link from '../Link'
|
||||||
|
import { H2, H3, Label } from '../Text'
|
||||||
|
import strings from '../../data/strings'
|
||||||
|
import { Img, LabelBlock, Wrapper } from './styles'
|
||||||
|
import { andList } from '../../helpers/string'
|
||||||
|
import { colours } from '../../assets/theme'
|
||||||
|
|
||||||
|
const SeriesCard = ({ series: { image, episodes: allEpisodes, title, subtitle, hosts: hostsArray, slug, }, isPast, ...rest }) => {
|
||||||
|
const episodes = allEpisodes[isPast ? 'past' : 'future']
|
||||||
|
const hosts = hostsArray.map(({ actor }) => actor.name)
|
||||||
|
|
||||||
|
// return episodes.length ? (
|
||||||
|
return (
|
||||||
|
<Link to={`series/${slug}`}>
|
||||||
|
<Wrapper {...rest}>
|
||||||
|
<Img src={image}>
|
||||||
|
<LabelBlock
|
||||||
|
$position="top"
|
||||||
|
>
|
||||||
|
{episodes.length} {strings.en.episodes}
|
||||||
|
</LabelBlock>
|
||||||
|
<LabelBlock
|
||||||
|
$position="bottom"
|
||||||
|
>
|
||||||
|
{isPast ? strings.en.lastStream : strings.en.nextStream} {episodes && episodes.length && formatDistanceToNow(new Date(episodes[0].endsOn), { addSuffix: true })}
|
||||||
|
</LabelBlock>
|
||||||
|
</Img>
|
||||||
|
|
||||||
|
<H2 size={32} colour={colours.rose}>{title}</H2>
|
||||||
|
<H3 size={21} colour={colours.rose}>{subtitle}</H3>
|
||||||
|
{hosts.length ? <Label size={16} colour={colours.rose}>— {andList(hosts)}</Label> : null}
|
||||||
|
</Wrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
// ) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SeriesCard
|
43
src/components/SeriesCard/styles.js
Normal file
43
src/components/SeriesCard/styles.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import { colours } from '../../assets/theme'
|
||||||
|
import { Label } from '../Text'
|
||||||
|
|
||||||
|
export const Wrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 3em;
|
||||||
|
width: min-content;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 0.1em;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover div {
|
||||||
|
filter: invert()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Img = styled.div`
|
||||||
|
background: url(${({ src }) => src});
|
||||||
|
width: 380px;
|
||||||
|
height: 215px;
|
||||||
|
background-size: cover;
|
||||||
|
position: relative;
|
||||||
|
background-position: center;
|
||||||
|
margin: 0 0 1em 0;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const LabelBlock = styled(Label).attrs(props => ({
|
||||||
|
colour: props.colour || colours.midnightDarker
|
||||||
|
}))`
|
||||||
|
position: absolute;
|
||||||
|
background-color: ${colours.white};
|
||||||
|
right: 0;
|
||||||
|
${({ $position }) => $position === 'top' ?
|
||||||
|
'top: 0;' : 'bottom: 0;'
|
||||||
|
};
|
||||||
|
padding: 4px
|
||||||
|
`
|
@ -78,11 +78,24 @@ export const H1 = ({ children, size, ...rest }) => (
|
|||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const H2 = ({ children, ...rest }) => (
|
export const H2 = ({ children, size, ...rest }) => (
|
||||||
<Text
|
<Text
|
||||||
tag="h2"
|
tag="h2"
|
||||||
weight="700"
|
weight="700"
|
||||||
$size="25"
|
$size={size || '25'}
|
||||||
|
lineHeight="1"
|
||||||
|
fontFamily="Lunchtype24"
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const H3 = ({ children, size, ...rest }) => (
|
||||||
|
<Text
|
||||||
|
tag="h3"
|
||||||
|
weight="700"
|
||||||
|
$size={size || '21'}
|
||||||
lineHeight="1"
|
lineHeight="1"
|
||||||
fontFamily="Lunchtype24"
|
fontFamily="Lunchtype24"
|
||||||
{...rest}
|
{...rest}
|
||||||
@ -103,8 +116,8 @@ export const P = ({ children, size, ...rest }) => (
|
|||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
export const Span = ({ children, ...rest }) => (
|
export const Span = ({ children, colour, size, weight, ...rest }) => (
|
||||||
<Text tag="span" colour="inherit" $size="inherit" weight="inherit" {...rest}>
|
<Text tag="span" colour={colour || 'inherit'} $size={size || 'inherit'} weight={weight || 'inherit'} {...rest}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
@ -120,8 +133,8 @@ export const Label = ({ children, size, ...rest }) => (
|
|||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
export const A = ({ children, ...rest }) => (
|
export const A = ({ children, colour, fontFamily, ...rest }) => (
|
||||||
<Text tag="a" colour={colours.highlight} {...rest}>
|
<Text tag="a" colour={colour || colours.highlight} fontFamily={fontFamily} {...rest}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,7 @@ export const TextBase = styled.span`
|
|||||||
selectable,
|
selectable,
|
||||||
underline,
|
underline,
|
||||||
$sizeUnit,
|
$sizeUnit,
|
||||||
}) => css`
|
}) => css`
|
||||||
font-family: ${fontFamily};
|
font-family: ${fontFamily};
|
||||||
font-weight: ${weight};
|
font-weight: ${weight};
|
||||||
text-align: ${align};
|
text-align: ${align};
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
## Credits
|
|
||||||
|
|
||||||
A [NEW DESIGN CONGRESS](https://newdesigncongress.org) project
|
|
||||||
|
|
||||||
Series operated by **[RECLAIMFUTURES](https://reclaimfutures.org)**
|
|
||||||
|
|
||||||
Infrastructure by **[UNDERSCO.RE](https://undersco.re)**
|
|
||||||
|
|
||||||
Stream Design by **[BENJAMIN JONES](mailto:benjamin@newdesigncongress.org)**
|
|
||||||
|
|
||||||
Host & Research by **[CADE DIEHM](https://shiba.computer)**
|
|
||||||
|
|
||||||
Stream Backgrounds by **[IGNATIUS GILFEDDER](https://ignatius.design)**
|
|
||||||
|
|
||||||
Music \*\*\*\*by: [♥ GOJII ♥](https://gojii.bandcamp.com/), [ABELARD](https://abelard.bandcamp.com/), [Frog of Earth](https://frogoftheearth.bandcamp.com/) & [spatial manufacture ltd.](https://spatialmanufactureltd.bandcamp.com/)\*\*
|
|
||||||
|
|
||||||
Simulcasted at [UNDERSCORE TV](https://stream.undersco.re) and [Twitch](https://twitch.tv/newdesigncongress)
|
|
@ -1,3 +0,0 @@
|
|||||||
[NEW DESIGN CONGRESS](https://newdesigncongress.org) x [RECLAIMFUTURES](https://reclaimfutures.org) present _The Para-Real: Finding the Future in Unexpected Places,_ a livestream series about subcultures building livelihoods in spite of platform exploitation. Over 12 episodes streamed weekly, we meet filmmakers who have never met their actors, artists building their own networks of value, documentarians exploring digital identity, and members of resilient subcultures. All of these people share a commonality: they have an innate understanding of the _Para-Real,_ and have seized upon it to better their surroundings.
|
|
||||||
|
|
||||||
Between the digital realm and our physical world, _The Para-Real_ is a third space, equally material but poorly understood. The _Para-Real_ is where class politics, economics and the outcomes of hardware and infrastructure design collide. It manifests as the desire for play that turns young Minecraft players into network administrators, the moments where digital security meets physical safety, the creation of mutually-supportive artist-driven marketplaces or the tension inherent in Virtual Reality’s land-grab against the living room. The _Para-Real_ is the embodiment of the observation, ‘_We shape our tools, and thereafter our tools shape us._’ _The future is not a Zoom call_. The digital systems we are confined to today merge protocol with platform to prey on isolation and extract value from labour. That we grapple with this incarnation of the digital realm indicates a dominant cartel in decline. In its place is a vacuum. We must resist the immature groupthink of the 90s’ vision of what the Internet can be. The _Para-Real_ is once again contested space.
|
|
16
src/data/navigation.js
Normal file
16
src/data/navigation.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export default {
|
||||||
|
en: [
|
||||||
|
{
|
||||||
|
label: 'Program guide',
|
||||||
|
to: '/program'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Series',
|
||||||
|
to: '/series'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Archive',
|
||||||
|
href: 'https://tv.undersco.re/'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
en: {
|
en: {
|
||||||
nextStream: 'Program',
|
program: 'Program',
|
||||||
pastStream: 'Previous Episodes',
|
pastStream: 'Previous Episodes',
|
||||||
nowPlaying: 'Now playing',
|
nowPlaying: 'Now playing',
|
||||||
noStreams: 'No upcoming streams, check back soon.',
|
noStreams: 'No upcoming streams, check back soon.',
|
||||||
@ -11,5 +11,14 @@ export default {
|
|||||||
watchEpisode: 'Watch Episode',
|
watchEpisode: 'Watch Episode',
|
||||||
watchTrailer: 'Watch the trailer',
|
watchTrailer: 'Watch the trailer',
|
||||||
joinStream: 'Click to join stream',
|
joinStream: 'Click to join stream',
|
||||||
|
fourohfour: '404',
|
||||||
|
pageNotFound: 'Page not found',
|
||||||
|
goHome: 'Return to Home',
|
||||||
|
series: 'Series',
|
||||||
|
currentSeries: 'Current Series',
|
||||||
|
pastSeries: 'Past Series',
|
||||||
|
lastStream: 'Last stream',
|
||||||
|
nextStream: 'Next stream',
|
||||||
|
episodes: 'episodes',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -28,3 +28,37 @@ export const camelise = str => {
|
|||||||
})
|
})
|
||||||
.replace(/\s+/g, '');
|
.replace(/\s+/g, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Takes an array of strings and returns a string list separated by
|
||||||
|
* commas and an 'and' connector before the last item.
|
||||||
|
* @param {Array} list - List of strings || 2d array of strings. [0] = string to display, [1] = string to inject into before/after.
|
||||||
|
* @param {string} [connector] - Optional connector to join the last item, defaults to '&'.
|
||||||
|
* @param {Object} [wrapEachItem] - Optional object to wrap each item with a string.
|
||||||
|
* @param {string} [wrapEachItem.before] - Optional string to prefix each item.
|
||||||
|
* @param {string} [wrapEachItem.after] - Optional string to suffix each item.
|
||||||
|
* @param {string} [replace] - String to be replaced by second part of list item array if supplied.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const andList = (list, connector = '&', wrapEachItem, replace = '{#}') => {
|
||||||
|
if (!Array.isArray(list)) return list;
|
||||||
|
|
||||||
|
const wrapItem = (item) => {
|
||||||
|
if (wrapEachItem) {
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
return `${wrapEachItem.before.replace(replace, item[1])}${item[0]}${wrapEachItem.after.replace(replace, item[1])}`;
|
||||||
|
}
|
||||||
|
return `${wrapEachItem.before}${item}${wrapEachItem.after}`;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
};
|
||||||
|
|
||||||
|
return list.map((item, index) => {
|
||||||
|
if (index + 1 === list.length && index !== 0) {
|
||||||
|
return ` ${connector} ${wrapItem(item)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.length === 1 || index === list.length - 2 ? wrapItem(item) : `${wrapItem(item)}, `;
|
||||||
|
}).join('');
|
||||||
|
};
|
5
src/helpers/utils.js
Normal file
5
src/helpers/utils.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const splitArray = (arr, num = 2) => arr.reduce((result, value, index, array) => {
|
||||||
|
if (index % num === 0)
|
||||||
|
result.push(array.slice(index, index + num))
|
||||||
|
return result
|
||||||
|
}, [])
|
@ -3,6 +3,7 @@ import axios from 'axios'
|
|||||||
import ICAL from 'ical.js'
|
import ICAL from 'ical.js'
|
||||||
import config from '../data/config'
|
import config from '../data/config'
|
||||||
import { useSeriesStore } from '../store/index'
|
import { useSeriesStore } from '../store/index'
|
||||||
|
import 'regenerator-runtime/runtime'
|
||||||
|
|
||||||
export const useEventCalendar = () => {
|
export const useEventCalendar = () => {
|
||||||
const [data, setData] = useState([])
|
const [data, setData] = useState([])
|
||||||
@ -99,7 +100,7 @@ export const useEventCalendar = () => {
|
|||||||
|
|
||||||
export const useEventApi = () => {
|
export const useEventApi = () => {
|
||||||
const [series, setSeries] = useSeriesStore(store => [store.series, store.setSeries])
|
const [series, setSeries] = useSeriesStore(store => [store.series, store.setSeries])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(!!series.length)
|
||||||
|
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
|
@ -1,27 +1,29 @@
|
|||||||
import { h } from 'preact'
|
import { h } from 'preact'
|
||||||
import { bool } from 'prop-types'
|
import { bool } from 'prop-types'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
import { H1, Label } from '../../components/Text'
|
import { H1 } from '../../components/Text'
|
||||||
import {
|
import {
|
||||||
Wrapper,
|
Wrapper,
|
||||||
TaglineContainer,
|
|
||||||
Content,
|
Content,
|
||||||
Hero,
|
Hero,
|
||||||
FadeBottom,
|
FadeTop,
|
||||||
|
PositionedLink,
|
||||||
} from './styles'
|
} from './styles'
|
||||||
|
|
||||||
import { colours } from '../../assets/theme'
|
import Header from '../../components/Header'
|
||||||
import { NdcLogo, RFLogo } from '../../components/Logo'
|
import { ImageLogo } from '../../components/Logo'
|
||||||
|
|
||||||
const InfoLayout = ({ title, subtitle, image, children, theme }) => {
|
const InfoLayout = ({ title, subtitle, image, children, theme }) => (
|
||||||
console.log({ theme })
|
|
||||||
return (
|
|
||||||
<Wrapper theme={theme}>
|
<Wrapper theme={theme}>
|
||||||
|
<PositionedLink to="/" theme={theme}>
|
||||||
|
<ImageLogo />
|
||||||
|
</PositionedLink>
|
||||||
<Content>
|
<Content>
|
||||||
{children}
|
{children}
|
||||||
</Content>
|
</Content>
|
||||||
<Hero image={image}>
|
<Hero image={image}>
|
||||||
<div>
|
<Header theme={{ foreground: theme.background, background: 'transparent', }} miniHeader />
|
||||||
<H1>{title}</H1>
|
<H1>{title}</H1>
|
||||||
<H1
|
<H1
|
||||||
css={`
|
css={`
|
||||||
@ -30,24 +32,9 @@ const InfoLayout = ({ title, subtitle, image, children, theme }) => {
|
|||||||
>
|
>
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</H1>
|
</H1>
|
||||||
</div>
|
<FadeTop colour={theme.foreground} />
|
||||||
<TaglineContainer>
|
|
||||||
<a href="https://newdesigncongress.org/">
|
|
||||||
<NdcLogo active colour={colours.offwhite} />
|
|
||||||
</a>
|
|
||||||
<Label size="24">{'//'}</Label>
|
|
||||||
<a href="https://reclaimfutures.org/">
|
|
||||||
<RFLogo active colour={colours.offwhite} />
|
|
||||||
</a>
|
|
||||||
</TaglineContainer>
|
|
||||||
<FadeBottom />
|
|
||||||
</Hero>
|
</Hero>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
InfoLayout.propTypes = {
|
|
||||||
loading: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InfoLayout
|
export default InfoLayout
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { Link } from 'react-router-dom'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { colours } from '../../assets/theme'
|
import { colours } from '../../assets/theme'
|
||||||
|
import { ImageLogo as Logo } from '../../components/Logo'
|
||||||
import Logo from '../../components/Logo'
|
|
||||||
|
|
||||||
const heroWidth = 'calc(100vw - 600px - 4em)'
|
const heroWidth = 'calc(100vw - 600px - 4em)'
|
||||||
|
|
||||||
@ -29,9 +29,9 @@ export const Wrapper = styled.div`
|
|||||||
h3 {
|
h3 {
|
||||||
color: ${props => props.theme.foreground};
|
color: ${props => props.theme.foreground};
|
||||||
}
|
}
|
||||||
a {
|
/* a {
|
||||||
color: ${props => props.theme.highlight};
|
color: ${props => props.theme.highlight};
|
||||||
}
|
} */
|
||||||
|
|
||||||
button {
|
button {
|
||||||
color: ${props => props.theme.foreground};
|
color: ${props => props.theme.foreground};
|
||||||
@ -51,23 +51,8 @@ export const Top = styled.div`
|
|||||||
width: 50%;
|
width: 50%;
|
||||||
`
|
`
|
||||||
|
|
||||||
const gradientColourLight = '#F8E5E2'
|
const getGradient = (direction, colour) =>
|
||||||
const gradientColourDark = colours.midnightDarker
|
`linear-gradient(to ${direction}, ${colour}ee 0%,${colour}00 100%);`
|
||||||
const getGradient = (direction, lightDark) =>
|
|
||||||
`linear-gradient(to ${direction}, ${lightDark === 'dark' ? gradientColourDark : gradientColourLight
|
|
||||||
}ee 0%,${lightDark === 'dark' ? gradientColourDark : gradientColourLight
|
|
||||||
}00 100%);`
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
export const Fade = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
background-color: linear;
|
|
||||||
position: fixed;
|
|
||||||
padding: 2em 0 1em 2em;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background: ${getGradient('bottom')};
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Hero = styled.div`
|
export const Hero = styled.div`
|
||||||
width: ${heroWidth};
|
width: ${heroWidth};
|
||||||
@ -83,16 +68,17 @@ export const Hero = styled.div`
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: flex-end;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
h1,
|
h1,
|
||||||
h2 {
|
h2 {
|
||||||
color: ${colours.offwhite};
|
color: ${colours.offwhite};
|
||||||
}
|
}
|
||||||
|
|
||||||
h1:not(:last-of-type) {
|
h1{
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
&:not(:last-of-type) {
|
||||||
font-size: 12vw;
|
font-size: 12vw;
|
||||||
margin-bottom: 0.5em;
|
|
||||||
|
|
||||||
@media screen and (max-height: 600px) {
|
@media screen and (max-height: 600px) {
|
||||||
font-size: 20vh;
|
font-size: 20vh;
|
||||||
@ -100,7 +86,7 @@ export const Hero = styled.div`
|
|||||||
@media screen and (max-width: 1200px) {
|
@media screen and (max-width: 1200px) {
|
||||||
font-size: 20vh;
|
font-size: 20vh;
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
|
|
||||||
@media screen and (max-width: 1000px) {
|
@media screen and (max-width: 1000px) {
|
||||||
display: none;
|
display: none;
|
||||||
@ -118,27 +104,43 @@ export const LoaderWrapper = styled.div`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const Content = styled.div`
|
export const Content = styled.div`
|
||||||
/* margin-bottom: 3em; */
|
padding-top: 80px;
|
||||||
|
|
||||||
|
img.img-logo {
|
||||||
|
max-height: 80px;
|
||||||
|
mix-blend-mode: exclusion;
|
||||||
|
/* padding: 1em; */
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const PositionedLogo = styled(Logo)``
|
export const PositionedLink = styled(Link)`
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: ${({ theme }) => theme.background};
|
||||||
|
|
||||||
export const FadeBottom = styled.div`
|
&:hover img {
|
||||||
background: ${getGradient('top', 'dark')};
|
filter: invert(1);
|
||||||
|
mix-blend-mode: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 80px;
|
||||||
|
mix-blend-mode: exclusion;
|
||||||
|
padding: 0.5em 2em 0;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const FadeTop = styled.div`
|
||||||
|
background: ${({ colour }) => colour ? getGradient('bottom', colour) : colours.rose};
|
||||||
width: ${heroWidth};
|
width: ${heroWidth};
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0em;
|
top: 0em;
|
||||||
padding-bottom: 0.5em;
|
padding-bottom: 0.5em;
|
||||||
right: 0;
|
right: 0;
|
||||||
/* left: 0; */
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
min-height: 75px;
|
min-height: 75px;
|
||||||
|
|
||||||
/* @media screen and (max-width: 800px) {
|
|
||||||
h1 {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
`
|
`
|
||||||
|
|
||||||
export const TaglineContainer = styled.div`
|
export const TaglineContainer = styled.div`
|
||||||
|
@ -3,10 +3,12 @@ import PropTypes, { oneOfType, shape, string } from 'prop-types'
|
|||||||
|
|
||||||
import SEO from '../../components/Seo'
|
import SEO from '../../components/Seo'
|
||||||
|
|
||||||
// import Header from '../../molecules/Header'
|
import Header from '../../components/Header'
|
||||||
import { capitaliseFirstLetter } from '../../helpers/string'
|
import { capitaliseFirstLetter } from '../../helpers/string'
|
||||||
|
import { defaultTheme } from '../../assets/theme'
|
||||||
|
import { ThemedBlock } from './styles'
|
||||||
|
|
||||||
const Page = ({ children, title = '', description, metaImg, backTo, noindex }) => (
|
const Page = ({ children, title = '', description, metaImg, backTo, noindex, withHeader = true, theme = defaultTheme }) => (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<SEO
|
<SEO
|
||||||
title={title.toLowerCase() === 'index' ? title : capitaliseFirstLetter(title)}
|
title={title.toLowerCase() === 'index' ? title : capitaliseFirstLetter(title)}
|
||||||
@ -14,8 +16,10 @@ const Page = ({ children, title = '', description, metaImg, backTo, noindex }) =
|
|||||||
metaImg={metaImg}
|
metaImg={metaImg}
|
||||||
noindex={noindex}
|
noindex={noindex}
|
||||||
/>
|
/>
|
||||||
{/* <Header /> */}
|
{withHeader ? <Header theme={theme} /> : null}
|
||||||
|
<ThemedBlock theme={theme} withHeader={withHeader}>
|
||||||
{children}
|
{children}
|
||||||
|
</ThemedBlock>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
12
src/layouts/Page/styles.js
Normal file
12
src/layouts/Page/styles.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
// import { defaultTheme } from '../../assets/theme'
|
||||||
|
|
||||||
|
export const ThemedBlock = styled.div`
|
||||||
|
color: ${({ theme }) => theme.foreground};
|
||||||
|
background-color: ${({ theme }) => theme.background};
|
||||||
|
padding-top: ${({ withHeader }) => withHeader ? '100px' : 0};
|
||||||
|
min-height: 100vh;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 100vw;
|
||||||
|
box-sizing: border-box;
|
||||||
|
`
|
23
src/pages/404/index.js
Normal file
23
src/pages/404/index.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { h } from 'preact'
|
||||||
|
|
||||||
|
import translations from '../../data/strings'
|
||||||
|
import { H2 } from '../../components/Text'
|
||||||
|
import {
|
||||||
|
Wrapper,
|
||||||
|
Title,
|
||||||
|
StyledLink as Link
|
||||||
|
} from './styles'
|
||||||
|
import { textSizes } from '../../assets/theme'
|
||||||
|
import Page from '../../layouts/Page'
|
||||||
|
|
||||||
|
const FourOhFour = () => (
|
||||||
|
<Page title={translations.en.pageNotFound} noindex>
|
||||||
|
<Wrapper>
|
||||||
|
<Title size={textSizes.hg}>{translations.en.fourohfour}</Title>
|
||||||
|
<H2>{translations.en.pageNotFound}</H2>
|
||||||
|
<Link to="/">{translations.en.goHome}</Link>
|
||||||
|
</Wrapper>
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default FourOhFour
|
33
src/pages/404/styles.js
Normal file
33
src/pages/404/styles.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import { colours } from '../../assets/theme'
|
||||||
|
import { H1 } from '../../components/Text'
|
||||||
|
import Link from '../../components/Link'
|
||||||
|
|
||||||
|
export const Wrapper = styled.div`
|
||||||
|
height: calc(100vh - 100px);
|
||||||
|
width: 100vw;
|
||||||
|
padding: 2em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background-color: ${colours.midnightDarker};
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (max-width: 1200px) {
|
||||||
|
padding: 1.5em;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Title = styled(H1)`
|
||||||
|
margin-bottom: 0.2em
|
||||||
|
`
|
||||||
|
|
||||||
|
export const StyledLink = styled(Link)`
|
||||||
|
margin-top: 1em;
|
||||||
|
`
|
||||||
|
|
@ -4,14 +4,14 @@ import bg from '../../assets/img/hero/1lg.png'
|
|||||||
|
|
||||||
// import { H1 } from '../../components/Text'
|
// import { H1 } from '../../components/Text'
|
||||||
|
|
||||||
import Logo from '../../components/Logo'
|
import { ImageLogo as Logo } from '../../components/Logo'
|
||||||
|
|
||||||
const heroWidth = '66vw'
|
const heroWidth = 'calc(100vw - 600px - 4em)'
|
||||||
|
|
||||||
export const Wrapper = styled.div`
|
export const Wrapper = styled.div`
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
padding: 2em;
|
/* padding: 2em; */
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: ${colours.midnightDarker};
|
background-color: ${colours.midnightDarker};
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -25,10 +25,10 @@ export const Wrapper = styled.div`
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1200px) {
|
@media screen and (max-width: 1200px) {
|
||||||
padding: 1.5em;
|
/* padding: 1.5em; */
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 800px) {
|
@media screen and (max-width: 800px) {
|
||||||
padding: 1em;
|
/* padding: 1em; */
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ export const LoaderWrapper = styled.div`
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 33vw;
|
width: calc(600px + 4em);
|
||||||
|
|
||||||
@media screen and (max-width: 1000px) {
|
@media screen and (max-width: 1000px) {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
@ -108,9 +108,10 @@ export const Content = styled.div`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const PositionedLogo = styled(Logo)`
|
export const PositionedLogo = styled(Logo)`
|
||||||
position: fixed;
|
/* position: fixed; */
|
||||||
top: 2em;
|
max-height: 80px;
|
||||||
left: 3em;
|
mix-blend-mode: exclusion;
|
||||||
|
padding: 1em;
|
||||||
`
|
`
|
||||||
|
|
||||||
export const FadeBottom = styled.div`
|
export const FadeBottom = styled.div`
|
||||||
|
53
src/pages/Series/index.js
Normal file
53
src/pages/Series/index.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/* 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, SeriesGrid, SeriesRow } from './styles'
|
||||||
|
|
||||||
|
import Page from '../../layouts/Page'
|
||||||
|
import SeriesCard from '../../components/SeriesCard'
|
||||||
|
import { colours } from '../../assets/theme'
|
||||||
|
|
||||||
|
const Series = () => {
|
||||||
|
const { data: seriesDataArray } = useEventApi()
|
||||||
|
const pastSeries = []
|
||||||
|
|
||||||
|
const currentSeries = seriesDataArray.filter(series => {
|
||||||
|
// const seriesInTheLastMonth = series.episodes.past.filter(episode => {
|
||||||
|
|
||||||
|
// })
|
||||||
|
if (series.episodes.future.length) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
pastSeries.push(series)
|
||||||
|
return false
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page title={strings.en.series}>
|
||||||
|
<Content>
|
||||||
|
<SeriesGrid>
|
||||||
|
<H1 colour={colours.rose}>{strings.en.currentSeries}</H1>
|
||||||
|
<SeriesRow>
|
||||||
|
{currentSeries.map(series => (
|
||||||
|
<SeriesCard series={series} />
|
||||||
|
))}
|
||||||
|
</SeriesRow>
|
||||||
|
<H1 colour={colours.rose}>{strings.en.pastSeries}</H1>
|
||||||
|
<SeriesRow>
|
||||||
|
{pastSeries.map(series => (
|
||||||
|
<SeriesCard series={series} isPast />
|
||||||
|
))}
|
||||||
|
</SeriesRow>
|
||||||
|
</SeriesGrid>
|
||||||
|
</Content>
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Series
|
14
src/pages/Series/styles.js
Normal file
14
src/pages/Series/styles.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import { Row } from '../../components/Flex'
|
||||||
|
|
||||||
|
export const Content = styled.div`
|
||||||
|
padding-top: 64px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const SeriesGrid = styled.div`
|
||||||
|
margin-left: 32px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const SeriesRow = styled(Row)`
|
||||||
|
margin: 3em 0 6em 0;
|
||||||
|
`
|
@ -18,6 +18,9 @@ import {
|
|||||||
|
|
||||||
import config from '../../data/config'
|
import config from '../../data/config'
|
||||||
import Page from '../../layouts/Page'
|
import Page from '../../layouts/Page'
|
||||||
|
import { splitArray } from '../../helpers/utils'
|
||||||
|
import Header from '../../components/Header'
|
||||||
|
import theme from '../../assets/theme'
|
||||||
|
|
||||||
const SeriesPage = ({ data }) => {
|
const SeriesPage = ({ data }) => {
|
||||||
|
|
||||||
@ -39,14 +42,17 @@ const SeriesPage = ({ data }) => {
|
|||||||
tzShort = tzShort[1].match(/[A-Z]/g).join('')
|
tzShort = tzShort[1].match(/[A-Z]/g).join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const links = data.links.length ? splitArray(data.links, 2) : null
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page title={data.title}>
|
<Page title={data.title} theme={data.theme} withHeader={false}>
|
||||||
<InfoLayout title={data.title} subtitle={data.subtitle} image={data.image} theme={data.theme}>
|
<InfoLayout title={data.title} subtitle={data.subtitle} image={data.image} theme={data.theme}>
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<InfoContent>
|
<InfoContent>
|
||||||
<H1>{data.title}:</H1>
|
<H1>{data.title}:</H1>
|
||||||
<H1>{data.subtitle}</H1>
|
<H1>{data.subtitle}</H1>
|
||||||
<Markdown withLinebreaks>{data.description}</Markdown>
|
<Markdown withLinebreaks theme={data.theme}>{data.description}</Markdown>
|
||||||
|
|
||||||
{data.trailer ? (
|
{data.trailer ? (
|
||||||
<TrailerContainer>
|
<TrailerContainer>
|
||||||
@ -54,9 +60,9 @@ const SeriesPage = ({ data }) => {
|
|||||||
</TrailerContainer>
|
</TrailerContainer>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{data.links.length ?
|
{links ?
|
||||||
<Row wrap>
|
links.map(linkRow => <Row>
|
||||||
{data.links.map(link => (
|
{linkRow.map(link => (
|
||||||
<a
|
<a
|
||||||
href={link.resourceUrl}
|
href={link.resourceUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -65,12 +71,12 @@ const SeriesPage = ({ data }) => {
|
|||||||
<Button>{link.summary}</Button>
|
<Button>{link.summary}</Button>
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
</Row> : null
|
</Row>) : null
|
||||||
}
|
}
|
||||||
</InfoContent>
|
</InfoContent>
|
||||||
{data.episodes.future.length ? (
|
{data.episodes.future.length ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Title>{translations.en.nextStream}:</Title>
|
<Title>{translations.en.program}:</Title>
|
||||||
{data.episodes.future.map(feeditem => (
|
{data.episodes.future.map(feeditem => (
|
||||||
<EpisodeCard
|
<EpisodeCard
|
||||||
theme={data.theme}
|
theme={data.theme}
|
||||||
@ -89,18 +95,16 @@ const SeriesPage = ({ data }) => {
|
|||||||
theme={data.theme}
|
theme={data.theme}
|
||||||
key={feeditem.beginsOn}
|
key={feeditem.beginsOn}
|
||||||
hasPassed
|
hasPassed
|
||||||
onClickButton={() =>
|
onClickButton={() => null} // todo: fix this
|
||||||
setEmbedUrl(`${config.peertube_root}${feeditem.embedPath}`)
|
|
||||||
}
|
|
||||||
{...feeditem}
|
{...feeditem}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : null}
|
) : null}
|
||||||
<InfoContent>
|
{data.credits ? <InfoContent>
|
||||||
<Title>Credits</Title>
|
<Title>Credits</Title>
|
||||||
<Markdown>{credits}</Markdown>
|
<Markdown theme={data.theme}>{credits}</Markdown>
|
||||||
</InfoContent>
|
</InfoContent> : null}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
</InfoLayout>
|
</InfoLayout>
|
||||||
|
@ -9,10 +9,9 @@ import Markdown from '../../components/Markdown'
|
|||||||
import Logo from '../../components/Logo'
|
import Logo from '../../components/Logo'
|
||||||
import translations from '../../data/strings'
|
import translations from '../../data/strings'
|
||||||
import CrossSvg from '../../components/Svg/Cross'
|
import CrossSvg from '../../components/Svg/Cross'
|
||||||
import PlaySvg from '../../components/Svg/Play'
|
|
||||||
|
|
||||||
import { P, 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'
|
||||||
|
|
||||||
export const TrailerContainer = styled.div`
|
export const TrailerContainer = styled.div`
|
||||||
@ -104,7 +103,8 @@ export const PositionedCross = styled(CrossSvg)`
|
|||||||
|
|
||||||
export const VCWrapper = styled.div`
|
export const VCWrapper = styled.div`
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: 0 0 6em 2px;
|
margin: 0 0 0 2px;
|
||||||
|
padding-bottom: 3em;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
@ -139,13 +139,15 @@ const renderTitles = titles =>
|
|||||||
|
|
||||||
export const EpisodeCard = ({
|
export const EpisodeCard = ({
|
||||||
title,
|
title,
|
||||||
|
image,
|
||||||
|
|
||||||
|
|
||||||
description,
|
description,
|
||||||
beginsOn,
|
beginsOn,
|
||||||
hasPassed,
|
hasPassed,
|
||||||
videoUrl,
|
videoUrl,
|
||||||
onClickButton,
|
onClickButton,
|
||||||
tzShort,
|
tzShort,
|
||||||
image,
|
|
||||||
theme,
|
theme,
|
||||||
}) => {
|
}) => {
|
||||||
const startDate = new Date(beginsOn)
|
const startDate = new Date(beginsOn)
|
||||||
@ -174,7 +176,7 @@ export const EpisodeCard = ({
|
|||||||
<VCImg src={image} alt="" />
|
<VCImg src={image} alt="" />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
<Markdown withLinebreaks>{description}</Markdown>
|
<Markdown theme={theme}>{description}</Markdown>
|
||||||
{hasPassed ? (
|
{hasPassed ? (
|
||||||
<Button onClick={onClickButton}>{translations.en.watchEpisode}</Button>
|
<Button onClick={onClickButton}>{translations.en.watchEpisode}</Button>
|
||||||
) : (
|
) : (
|
||||||
@ -193,13 +195,10 @@ export const EpisodeCard = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
EpisodeCard.propTypes = {
|
EpisodeCard.propTypes = {
|
||||||
title: string,
|
|
||||||
description: string,
|
description: string,
|
||||||
beginsOn: instanceOf(Date),
|
beginsOn: instanceOf(Date),
|
||||||
onClickButton: func,
|
onClickButton: func,
|
||||||
tzShort: string,
|
tzShort: string,
|
||||||
image: string,
|
|
||||||
previewPath: string,
|
|
||||||
hasPassed: bool,
|
hasPassed: bool,
|
||||||
videoUrl: string,
|
videoUrl: string,
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
export const getMetadataByKey = (episode, key) => {
|
export const getMetadataByKey = (episode, key, firstItem = true) => {
|
||||||
const filteredItems = episode.metadata.length ? episode.metadata.filter(
|
const filteredItems = episode.metadata.length ? episode.metadata.filter(
|
||||||
meta => meta.key === key
|
meta => meta.key.includes(key)
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
if (filteredItems) {
|
if (filteredItems) {
|
||||||
return filteredItems[0].value
|
return firstItem ? filteredItems[0].value : filteredItems
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@ -16,7 +16,7 @@ export const getPostByKey = (posts, key) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getResourcesByKey = (resources, key, lookup = 'title') => {
|
export const getResourcesByKey = (resources, key, lookup = 'title') => {
|
||||||
const filteredResources = resources.elements.length ? resources.elements.filter(resource => resource[lookup] === key) : []
|
const filteredResources = resources.elements.length ? resources.elements.filter(resource => resource[lookup].includes(key)) : []
|
||||||
|
|
||||||
if (!filteredResources.length) return null
|
if (!filteredResources.length) return null
|
||||||
|
|
||||||
@ -24,3 +24,12 @@ export const getResourcesByKey = (resources, key, lookup = 'title') => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getPeertubeIDfromUrl = (string) => string && string.includes('https://tv.undersco.re') ? string.split('/').pop() : string
|
export const getPeertubeIDfromUrl = (string) => string && string.includes('https://tv.undersco.re') ? string.split('/').pop() : string
|
||||||
|
|
||||||
|
export const getEpisodeCode = (episode) => {
|
||||||
|
const metaItems = getMetadataByKey(episode, 'mz:plain:', false)
|
||||||
|
if (metaItems && metaItems.length) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'TODO'
|
||||||
|
}
|
@ -2,14 +2,15 @@ import create from 'zustand'
|
|||||||
import striptags from 'striptags'
|
import striptags from 'striptags'
|
||||||
import { isFuture, isPast } from 'date-fns'
|
import { isFuture, isPast } from 'date-fns'
|
||||||
import { slugify } from '../helpers/string'
|
import { slugify } from '../helpers/string'
|
||||||
import { getMetadataByKey, getPostByKey, getResourcesByKey, getPeertubeIDfromUrl } from './helpers'
|
import { getMetadataByKey, getPostByKey, getResourcesByKey, getPeertubeIDfromUrl, getEpisodeCode } from './helpers'
|
||||||
import { colours, defaultTheme } from '../assets/theme'
|
import { colours, defaultTheme } from '../assets/theme'
|
||||||
|
|
||||||
export const [useSeriesStore] = create(set => ({
|
export const [useSeriesStore] = create(set => ({
|
||||||
series: [],
|
series: [],
|
||||||
|
|
||||||
setSeries: seriesArray => {
|
setSeries: seriesArray => {
|
||||||
const allSeries = seriesArray.map(({ name, organizedEvents, posts, resources, banner, summary }) => {
|
const allSeries = seriesArray.map(({ name, organizedEvents, posts, resources, banner, summary, members }) => {
|
||||||
|
|
||||||
|
|
||||||
const allEpisodes = organizedEvents.elements.length ? organizedEvents.elements.map(ep => ({
|
const allEpisodes = organizedEvents.elements.length ? organizedEvents.elements.map(ep => ({
|
||||||
title: ep.title,
|
title: ep.title,
|
||||||
@ -18,11 +19,15 @@ export const [useSeriesStore] = create(set => ({
|
|||||||
description: ep.description,
|
description: ep.description,
|
||||||
media: ep.media,
|
media: ep.media,
|
||||||
image: ep.picture ? ep.picture.url : null,
|
image: ep.picture ? ep.picture.url : null,
|
||||||
peertubeId: getPeertubeIDfromUrl(getMetadataByKey(ep, 'mz:live:peertube:url')),
|
peertubeId: getPeertubeIDfromUrl(getMetadataByKey(ep, 'peertube:url')),
|
||||||
|
code: getEpisodeCode(ep)
|
||||||
})) : []
|
})) : []
|
||||||
|
|
||||||
const trailer = getResourcesByKey(resources, 'SERIES_TRAILER')?.[0]?.resourceUrl ?? null
|
const trailer = getResourcesByKey(resources, 'SERIES_TRAILER')?.[0]?.resourceUrl ?? null
|
||||||
const theme = striptags(getPostByKey(posts, 'THEME'))
|
const theme = striptags(getPostByKey(posts, 'THEME'))
|
||||||
|
const orgLinks = getResourcesByKey(resources, 'ORG_LINK:')?.map(link => link.title)
|
||||||
|
|
||||||
|
console.log({ orgLinks })
|
||||||
|
|
||||||
|
|
||||||
const series = {
|
const series = {
|
||||||
@ -39,8 +44,9 @@ export const [useSeriesStore] = create(set => ({
|
|||||||
slug: slugify(name),
|
slug: slugify(name),
|
||||||
credits: getPostByKey(posts, 'SERIES_CREDITS'),
|
credits: getPostByKey(posts, 'SERIES_CREDITS'),
|
||||||
trailer,
|
trailer,
|
||||||
links: getResourcesByKey(resources, 'LINK') || [],
|
links: getResourcesByKey(resources, 'SERIES_LINK') || [],
|
||||||
theme: theme ? JSON.parse(theme) : defaultTheme
|
theme: theme ? JSON.parse(theme) : defaultTheme,
|
||||||
|
hosts: members.elements.filter(({ actor }) => actor.name !== 'streamappbot')
|
||||||
}
|
}
|
||||||
return series
|
return series
|
||||||
})
|
})
|
||||||
|
BIN
static/logo.png
Normal file
BIN
static/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
Loading…
Reference in New Issue
Block a user