chore: move screens into routes folder

This commit is contained in:
Kyle Gill
2019-05-18 16:14:38 -06:00
parent f8f6eda47b
commit 35e11d6134
20 changed files with 22 additions and 36 deletions

314
src/routes/Day.js Normal file
View File

@@ -0,0 +1,314 @@
import React from "react"
import { addDays, subDays, format, isAfter, startOfYesterday } from "date-fns"
import { BeatLoader } from "react-spinners"
import styled from "@emotion/styled"
/** @jsx jsx */
import { jsx, css, keyframes } from "@emotion/core"
import { compose } from "recompose"
import { withTheme } from "emotion-theming"
import { withFirebase } from "components/firebase"
import { withAuthentication } from "components/session"
import { OnlineContext } from "components/context/online"
import { SIZES } from "styles/constants"
import Seek from "components/Seek"
import Icon from "components/Icon"
const EntryHeading = styled.div`
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: ${SIZES.medium};
`
const EntryInfo = styled.div`
display: flex;
flex-direction: row;
align-items: center;
`
const JournalHeading = styled.h2`
font-weight: 700;
font-size: ${SIZES.tiny};
color: ${props => props.theme.colors.secondary};
display: block;
`
const SavedMessaged = styled.div`
display: grid;
grid-template-columns: auto auto;
grid-gap: 5px;
color: ${props => props.theme.colors.secondary};
font-size: ${SIZES.tiny};
user-select: none;
`
const OfflineNotice = styled.div`
padding: 5px;
color: ${props => props.theme.colors.secondary};
border: 1px solid;
border-color: ${props => props.theme.colors.tertiary};
font-size: ${SIZES.tiny};
border-radius: 3px;
`
const JournalEntryArea = styled.textarea`
font-family: sans-serif;
flex-grow: 0.85;
color: ${props => props.theme.colors.primary};
caret-color: ${props => props.theme.colors.secondary};
background-color: transparent;
line-height: 1.5;
letter-spacing: 0.5px;
height: calc(100vh - 300px);
width: 100%;
border: none;
resize: none;
outline: none;
font-size: ${SIZES.small};
border-radius: 1px;
margin-top: ${SIZES.tiny};
padding-top: 0px;
padding-bottom: 0px;
&::placeholder {
color: ${props => props.theme.colors.tertiary};
}
&::selection {
background: ${props => props.theme.colors.hover};
}
&:focus {
box-shadow: 0 0 0 8px ${props => props.theme.colors.bodyBackground},
0 0 0 10px ${props => props.theme.colors.hover};
}
`
const Buttons = styled.div`
display: flex;
flex-direction: row;
margin-top: 20px;
`
const fadeKeyFrames = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`
const LoadingSpinner = styled(BeatLoader)`
opacity: 0;
`
const AUTOSAVE_DELAY = 2000
class Day extends React.Component {
state = {
text: "",
loading: true,
saving: false,
lastSavedAt: new Date(),
lastEditedAt: new Date(),
}
timeout = 0
static contextType = OnlineContext
componentDidMount() {
const { year, month, day } = this.props
this.getDocRef(year, month, day, false)
}
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.uri !== prevProps.uri) {
console.log("here we go")
const [
,
,
prevYear,
prevMonth,
prevDay,
] = prevProps.location.pathname.split("/")
const { text } = this.state
this.saveText(text, prevYear, prevMonth, prevDay)
const [, , year, month, day] = this.props.location.pathname.split("/")
this.onRouteChanged(year, month, day)
}
}
onRouteChanged = (year, month, day) => {
this.setState({ loading: true })
this.getDocRef(year, month, day, false)
}
getDocRef = (year, month, day, cacheFirst) => {
const { firebase, authUser } = this.props
const getOptions = {
source: cacheFirst ? "cache" : "default",
}
const docRef = firebase.db
.collection("entries")
.doc(`${year}${month}${day}-${authUser.uid}`)
this.getData(docRef, getOptions)
}
getData = (docRef, options) => {
docRef
.get(options)
.then(doc => {
if (doc.data()) {
this.setState({ text: doc.data().text, loading: false })
} else {
this.setState({ text: "", loading: false })
}
})
.catch(err => {
console.warn("entry not found in cache")
// no doc was found, so reset the entry area to blank
this.setState({ loading: false, text: "" })
// for cache first, server second fetching, dangerous with potential overwriting of data
// docRef.get().then(doc => {
// if (doc.data()) {
// this.setState({ text: doc.data().text, loading: false });
// } else {
// this.setState({ text: "", loading: false });
// }
// });
})
}
onChangeText = e => {
if (this.timeout) clearTimeout(this.timeout)
const text = e.target.value
const { year, month, day } = this.props
this.setState({ text, lastEditedAt: new Date() })
this.timeout = setTimeout(() => {
this.saveText(text, year, month, day)
}, AUTOSAVE_DELAY)
}
onInsertTime = () => {
const entryTextArea = document.getElementById("entry-text-area")
const cursorIndex = entryTextArea.selectionStart
const { text } = this.state
const { year, month, day } = this.props
const insertAt = (str, sub, pos) =>
`${str.slice(0, pos)}${sub}${str.slice(pos)}`
const newText = insertAt(text, format(new Date(), "h:mma "), cursorIndex)
this.setState({
text: newText,
})
entryTextArea.focus()
this.saveText(newText, year, month, day)
}
saveText = (text, year, month, day) => {
this.setState({ saving: true })
const { firebase, authUser } = this.props
firebase.db
.collection("entries")
.doc(`${year}${month}${day}-${authUser.uid}`)
.set(
{
text,
day: Number(day),
year: Number(year),
month: Number(month),
userId: authUser.uid,
},
{
merge: true,
}
)
.then(() => {
this.setState({ saving: false, lastSavedAt: new Date() })
})
.catch(() => {
console.warn("saving will occur when back online")
this.setState({ saving: false })
})
}
render() {
const { year, month, day, theme } = this.props
const online = this.context
const { text, loading, saving, lastSavedAt, lastEditedAt } = this.state
const currentDay = new Date(year, month - 1, day)
if (!currentDay) return
const hasSavedChanges = lastSavedAt >= lastEditedAt
return (
<>
<Seek
title={format(currentDay, "YYYY MMM DD - dddd")}
prev={format(subDays(currentDay, 1), "/YYYY/MM/DD")}
next={format(addDays(currentDay, 1), "/YYYY/MM/DD")}
disableNext={isAfter(currentDay, startOfYesterday())}
/>
<EntryHeading>
<JournalHeading>RECORD THOUGHTS ABOUT YOUR DAY</JournalHeading>
<EntryInfo>
{online && (
<SavedMessaged>
{saving ? (
<>
Saving
<LoadingSpinner
color={theme.colors.quarternary}
size={5}
css={css`
animation: 1s ease-in;
`}
/>
</>
) : hasSavedChanges ? (
`Last saved at ${format(lastSavedAt, "h:mma")}`
) : (
"Unsaved changes"
)}
</SavedMessaged>
)}
{!online && <OfflineNotice>Offline</OfflineNotice>}
</EntryInfo>
</EntryHeading>
{loading ? (
<div style={{ marginTop: 10 }}>
<LoadingSpinner
color={theme.colors.quarternary}
size={10}
margin="4px"
css={css`
animation: ${fadeKeyFrames} 1s ease-in;
`}
/>
</div>
) : (
<>
<JournalEntryArea
id="entry-text-area"
autoFocus={true}
placeholder="Start writing..."
onChange={e => this.onChangeText(e)}
value={text}
css={css`
animation: ${fadeKeyFrames} 0.2s ease-in;
`}
/>
<Buttons>
<Icon
name="Clock"
label="Quick Add Time"
labelRight
onClick={() => this.onInsertTime()}
/>{" "}
</Buttons>
</>
)}
</>
)
}
}
export default compose(
withFirebase,
withTheme,
withAuthentication
)(Day)

89
src/routes/Month.js Normal file
View File

@@ -0,0 +1,89 @@
import React, { Component } from "react"
import styled from "@emotion/styled"
import {
isAfter,
isThisYear,
isThisMonth,
format,
addMonths,
subMonths,
getDaysInMonth,
startOfMonth,
} from "date-fns"
import { AppLink as Link } from "components/elements"
import Seek from "components/Seek"
const YearCardGrid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(75px, 1fr));
grid-gap: 20px;
margin-top: 20px;
`
const YearCard = styled.div`
color: ${props =>
props.disabled
? props.theme.colors.quarternary
: props.theme.colors.secondary};
border: 1px solid;
border-color: ${props => props.theme.colors.quarternary};
padding: 25px;
border-radius: 5px;
text-align: center;
user-select: none;
&:hover {
border-color: ${props => !props.disabled && props.theme.colors.tertiary};
}
`
class Month extends Component {
render() {
const { year, month } = this.props
const currentDay = new Date(year, month - 1)
// include all months unless it's this year
let dayIndexesToInclude = 31
if (isThisYear(currentDay)) {
dayIndexesToInclude = new Date().getDate()
}
let yearCards = []
for (let i = 0; i < getDaysInMonth(currentDay); i++) {
const isDisabled = dayIndexesToInclude <= i && isThisMonth(currentDay)
if (isDisabled) {
yearCards.push(
<YearCard disabled={isDisabled} key={i}>
{i + 1}
</YearCard>
)
} else {
yearCards.push(
<Link
key={i}
to={format(new Date(year, month - 1, i + 1), "/YYYY/MM/DD")}
style={{ textDecoration: "none" }}
>
<YearCard key={i}>{i + 1}</YearCard>
</Link>
)
}
}
return (
<>
<Seek
title={format(currentDay, "YYYY MMM")}
prev={format(subMonths(currentDay, 1), "/YYYY/MM")}
next={format(addMonths(currentDay, 1), "/YYYY/MM")}
disableNext={isAfter(
currentDay,
startOfMonth(subMonths(new Date(), 1))
)}
/>
<YearCardGrid>{yearCards}</YearCardGrid>
</>
)
}
}
export default Month

178
src/routes/Search.js Normal file
View File

@@ -0,0 +1,178 @@
import { Component } from "react"
/** @jsx jsx */
import { jsx, css, keyframes } from "@emotion/core"
import styled from "@emotion/styled"
import { compose } from "recompose"
import { withTheme } from "emotion-theming"
import { BeatLoader } from "react-spinners"
import { AppLink as Link } from "components/elements"
import { Input } from "components/elements"
import { withFirebase } from "components/firebase"
import { withAuthentication } from "components/session"
import { pad } from "utils/date"
const SearchGrid = styled.div`
display: grid;
grid-template-columns: 1fr;
grid-gap: 10px;
margin-bottom: 60px;
`
const SearchLayout = styled.div`
width: 100%;
align-self: center;
margin-top: 20px;
`
const SearchResult = styled.div`
margin-top: 5px;
padding: 20px;
border-radius: 5px;
color: ${props => props.theme.colors.primary};
border: 1px solid;
border-color: ${props => props.theme.colors.quarternary};
&:hover {
cursor: pointer;
border-color: ${props => props.theme.colors.tertiary};
}
`
const fadeKeyFrames = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`
const LoadingSpinner = styled(BeatLoader)`
opacity: 0;
`
class Search extends Component {
state = {
entries: [],
allEntries: [],
searchInput: "",
loading: true,
}
componentDidMount() {
this.getEntries()
}
onChange = event => {
const searchInput = event.target.value
this.setState({ searchInput })
this.filterEntries(searchInput)
}
filterEntries = searchTerm => {
const { allEntries } = this.state
if (searchTerm === "") {
this.setState({ entries: allEntries })
} else {
const filteredEntries = allEntries.filter(entry => {
return entry.text.toLowerCase().includes(searchTerm.toLowerCase())
})
this.setState({ entries: filteredEntries })
}
}
getEntries = async _ => {
const { firebase, authUser } = this.props
const entriesRef = await firebase.db
.collection("entries")
.where("userId", "==", authUser.uid)
.get()
const entries = entriesRef.docs.map(doc => doc.data()).reverse()
// const sortedEntries = entries.sort((a, b) => {
// return (
// new Date(b.year, b.month - 1, b.day) -
// new Date(a.year, a.month - 1, a.day)
// )
// })
// console.log(sortedEntries)
this.setState({ entries, allEntries: entries, loading: false })
}
render() {
const { entries, searchInput, loading } = this.state
const { theme } = this.props
return (
<SearchLayout>
<SearchGrid>
<Input
autoFocus={true}
value={searchInput}
onChange={e => this.onChange(e)}
type="text"
placeholder="Search..."
colors={theme.colors}
/>
{loading ? (
<div style={{ marginTop: 10, margin: "0 auto" }}>
<LoadingSpinner
color={theme.colors.quarternary}
size={10}
margin="4px"
css={css`
animation: ${fadeKeyFrames} 1s ease-in;
`}
/>
</div>
) : entries.length > 0 ? (
entries.map(
(entry, index) =>
entry.text.length > 1 && (
<Link
key={index}
to={`${entry.year}/${pad(entry.month)}/${pad(entry.day)}`}
style={{ textDecoration: "none" }}
>
<SearchResult
css={css`
animation: ${fadeKeyFrames} 0.2s ease-in;
`}
>
<div
css={css`
font-style: italic;
color: ${theme.colors.secondary};
margin-bottom: 5px;
`}
>
{entry.day}/{entry.month}/{entry.year}
</div>
<div>
{entry.text.substring(0, 128)}
{entry.text.length >= 128 && "..."}
</div>
</SearchResult>
</Link>
)
)
) : (
<div
css={css`
text-align: center;
font-style: italic;
color: ${theme.colors.tertiary};
margin-top: 5px;
`}
>
No entries to display
</div>
)}
</SearchGrid>
</SearchLayout>
)
}
}
export default compose(
withFirebase,
withTheme,
withAuthentication
)(Search)

200
src/routes/Start.js Normal file
View File

@@ -0,0 +1,200 @@
import React, { Component } from "react"
import { Link, StaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"
import styled from "@emotion/styled"
import { withTheme } from "emotion-theming"
import { SIZES } from "styles/constants"
import { Button, P } from "components/elements"
import { todayUrl } from "utils/date"
import Icon from "components/Icon"
import Logo from "components/Logo"
const StartGrid = styled.div`
margin-top: 30px;
text-align: center;
line-height: 1.5;
color: ${props => props.theme.colors.primary};
height: 100%;
`
const FeatureGrid = styled.div`
display: grid;
text-align: left;
grid-template-rows: 1fr;
grid-gap: 30px;
`
const FeatureRow = styled.div`
display: grid;
grid-template-columns: 120px 3fr;
`
const FeatureText = styled.div`
display: flex;
flex-direction: column;
`
const FeatureTitle = styled.div`
font-size: ${SIZES["normal"]};
font-weight: 600;
color: ${props => props.theme.colors.secondary};
`
const FeatureDescription = styled.div`
color: ${props => props.theme.colors.secondary};
`
const Footer = styled.footer`
margin-top: 120px;
padding: 30px 0px;
text-align: center;
color: ${props => props.theme.colors.secondary};
`
const FooterLink = styled(Link)`
cursor: pointer;
color: ${props => props.theme.colors.secondary};
text-decoration: none;
margin: 10px;
&:hover {
color: ${props => props.theme.colors.tertiary};
}
`
const features = [
{
icon: "Monitor",
title: "Cross Platform",
desc:
"Write from any internet connected device, with pages optimized for all screen sizes",
},
{
icon: "Package",
title: "Install as an App",
desc:
"Add to your home screen on iPhone or Adroid to use it like you would an app",
},
{
icon: "CloudOff",
title: "Offline Capable",
desc:
"Record thoughts as they come to you, whether you have internet or not, your entries are saved when you get a connection",
},
{
icon: "Search",
title: "Full Text Search",
desc:
"Search through your entries by text to quickly find past entries and recall what you've written",
},
{
icon: "Download",
title: "Export",
desc:
"Download all of your journal entries at any time for back-up or safe keeping ",
},
]
class Start extends Component {
render() {
const { theme } = this.props
return (
<StartGrid>
<h1>Record what's on your mind, from anywhere</h1>
<P style={{ letterSpacing: 1.1, marginBottom: 30 }}>
Journaling can improve your health and help you take inventory of your
day. Sol Journal works offline and from any device. Use it as a place
to record thoughts and events from the day.
</P>
<Link to={`/app${todayUrl()}`} style={{ textDecoration: "none" }}>
<Button colors={theme.colors}>Start Writing</Button>
</Link>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
margin: "30 auto",
maxHeight: 350,
}}
>
<StaticQuery
query={graphql`
query {
landingGraphicLight: file(
relativePath: { eq: "landing-graphic-light.png" }
) {
childImageSharp {
fluid(maxWidth: 320) {
...GatsbyImageSharpFluid
}
}
}
landingGraphicDark: file(
relativePath: { eq: "landing-graphic-dark.png" }
) {
childImageSharp {
fluid(maxWidth: 320) {
...GatsbyImageSharpFluid
}
}
}
}
`}
render={data => {
return theme.name === "Light" ? (
<Img
style={{
maxWidth: 320,
width: "100%",
maxHeight: 350,
height: "100%",
}}
fluid={data.landingGraphicLight.childImageSharp.fluid}
/>
) : (
<Img fluid={data.LandingGraphicDark.childImageSharp.fluid} />
)
}}
/>
</div>
<div
style={{
margin: "60px 0px",
borderTop: `1px solid ${theme.colors.quarternary}`,
}}
/>
<h2>Features</h2>
<P style={{ letterSpacing: 1.1, marginBottom: 30 }}>
Lightweight with the functionalities you need for journaling, and none
of the things you don't:
</P>
<FeatureGrid>
{features.map(feature => (
<FeatureRow>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Icon disabled name={feature.icon} size={60} />
</div>
<FeatureText>
<FeatureTitle>{feature.title}</FeatureTitle>
<FeatureDescription>{feature.desc}</FeatureDescription>
</FeatureText>
</FeatureRow>
))}
</FeatureGrid>
<Footer>
<div>
<Logo color={theme.colors.logo} />
</div>
<div>
<FooterLink>View on GitHub</FooterLink>
<FooterLink to="terms">Terms of Service</FooterLink>
<FooterLink to="privacy">Privacy Policy</FooterLink>
</div>
<div>&copy; 2019</div>
</Footer>
</StartGrid>
)
}
}
export default withTheme(Start)

181
src/routes/User.js Normal file
View File

@@ -0,0 +1,181 @@
import React from "react"
import styled from "@emotion/styled"
import { withTheme } from "emotion-theming"
import { compose } from "recompose"
import { format } from "date-fns"
import { BeatLoader } from "react-spinners"
import { SIZES } from "styles/constants"
import { withFirebase } from "components/firebase"
import { withAuthentication } from "components/session"
import SignOut from "components/SignOut"
import { Button } from "components/elements"
const ProfileGrid = styled.div`
display: grid;
grid-template-rows: 1fr;
grid-gap: 10px;
margin-top: 20px;
`
const ProfileSection = styled.div`
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
`
const ProfileSectionHeader = styled.h2`
font-size: ${SIZES.normal};
color: ${props => props.theme.colors.tertiary};
`
const ProfileSectionText = styled.span`
font-size: ${SIZES.normal};
color: ${props => props.theme.colors.secondary};
`
class User extends React.Component {
state = {
files: [],
exporting: false,
}
getEntries = async _ => {
const { firebase, authUser } = this.props
const entriesRef = await firebase.db
.collection("entries")
.where("userId", "==", authUser.uid)
.get()
const entries = entriesRef.docs.map(doc => doc.data())
const editedEntries = entries.map(entry => {
return { ...entry, userId: undefined }
})
return editedEntries
}
clearFiles = () => {
if (this.state.files.length) {
this.state.files.forEach(({ data }) => {
window.URL.revokeObjectURL(data)
})
}
}
prepareExport = async () => {
try {
this.clearFiles()
this.setState({ exporting: true, files: [] })
const data = await this.getEntries()
const blob = new Blob([JSON.stringify(data)], {
type: "text/json;charset=utf-8",
})
const file = {
name: `journal-export-${format(new Date(), "MMDDYYYY")}.json`,
data: window.URL.createObjectURL(blob),
}
this.setState({ files: [file], exporting: false })
} catch (e) {
window.alert(
"Your export ran into an issue, sorry :( if you continue to have problmes you can reach out to kylerobertgill@gmail.com"
)
console.error(e)
this.setState({ files: [], exporting: 0 })
}
}
render() {
const { authUser, theme, firebase } = this.props
const { exporting, files } = this.state
return (
<ProfileGrid>
<ProfileSection>
<ProfileSectionHeader>
User: <ProfileSectionText>{authUser.email}</ProfileSectionText>
<div>
<ProfileSectionText style={{ fontWeight: 400 }}>
{authUser.emailVerified
? "Email has been verified"
: "Email not verified"}
</ProfileSectionText>
</div>
</ProfileSectionHeader>
<SignOut />
</ProfileSection>
<ProfileSection>
<ProfileSectionHeader>
Reset Password{" "}
<div>
<ProfileSectionText style={{ fontWeight: 400 }}>
send an email with reset instructions
</ProfileSectionText>
</div>
</ProfileSectionHeader>
<Button
fontSize="small"
colors={theme.colors}
onClick={() => {
console.log("reset!")
firebase.doPasswordReset(authUser.email)
}}
>
Send Reset
</Button>
</ProfileSection>
<ProfileSection>
<ProfileSectionHeader>
Export Journal Entries{" "}
<div>
<ProfileSectionText style={{ fontWeight: 400 }}>
download all journal entries into a JSON file
</ProfileSectionText>
</div>
</ProfileSectionHeader>
{files.length ? (
<a
download={files[0].name}
href={files[0].data}
style={{ textDecoration: "none" }}
>
<Button
fontSize="small"
colors={theme.colors}
onClick={() => {
setTimeout(() => {
this.clearFiles()
this.setState({ exporting: 0, files: [] })
}, 1500)
}}
>
Download
</Button>
</a>
) : (
<Button
fontSize="small"
colors={theme.colors}
onClick={() => this.prepareExport()}
>
{exporting ? (
<BeatLoader
color={theme.colors.secondary}
size={10}
margin="4px"
/>
) : (
"Export"
)}
</Button>
)}
</ProfileSection>
</ProfileGrid>
)
}
}
export default compose(
withFirebase,
withAuthentication,
withTheme
)(User)

61
src/routes/Welcome.js Normal file
View File

@@ -0,0 +1,61 @@
import React, { Component } from "react"
import { Link } from "gatsby"
import styled from "@emotion/styled"
import { withTheme } from "emotion-theming"
import { Button, P } from "components/elements"
import Logo from "components/Logo"
import { todayUrl } from "utils/date"
const WelcomeGrid = styled.div`
margin-top: 30px;
line-height: 1.5;
color: ${props => props.theme.colors.primary};
height: 100%;
`
const Footer = styled.footer`
margin-top: 120px;
padding: 30px 0px;
text-align: center;
color: ${props => props.theme.colors.secondary};
`
const FooterLink = styled(Link)`
cursor: pointer;
color: ${props => props.theme.colors.secondary};
text-decoration: none;
margin: 10px;
&:hover {
color: ${props => props.theme.colors.tertiary};
}
`
class Welcome extends Component {
render() {
const { theme } = this.props
return (
<WelcomeGrid>
<h1>Your Space for Wandering Thoughts and Ideas</h1>
<P style={{ letterSpacing: 1.1, marginBottom: 30 }}>
This your space for wandering thoughts and ideas. Write about whatever
comes to mind.
</P>
<Link to={`/app${todayUrl()}`} style={{ textDecoration: "none" }}>
<Button colors={theme.colors}>Write about today</Button>
</Link>
<Footer>
<div>
<Logo color={theme.colors.logo} />
</div>
<div>
<FooterLink>View on GitHub</FooterLink>
<FooterLink to="terms">Terms of Service</FooterLink>
<FooterLink to="privacy">Privacy Policy</FooterLink>
</div>
<div>&copy; 2019</div>
</Footer>
</WelcomeGrid>
)
}
}
export default withTheme(Welcome)

73
src/routes/Year.js Normal file
View File

@@ -0,0 +1,73 @@
import React, { Component } from "react"
import { addYears, subYears, format, isThisYear, getMonth } from "date-fns"
import styled from "@emotion/styled"
import { withTheme } from "emotion-theming"
import { AppLink as Link } from "components/elements"
import Seek from "components/Seek"
import { months } from "utils/date"
const MonthCardGrid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
grid-gap: 20px;
margin-top: 20px;
`
const MonthCard = styled.div`
color: ${props =>
props.disabled
? props.theme.colors.quarternary
: props.theme.colors.secondary};
border: 1px solid;
border-color: ${props => props.theme.colors.quarternary};
padding: 40px;
border-radius: 5px;
text-align: center;
user-select: none;
&:hover {
border-color: ${props => !props.disabled && props.theme.colors.tertiary};
}
`
class Year extends Component {
render() {
const { year } = this.props
const currentDate = new Date(year, 0, 1)
// include all months unless it's this year
let monthIndexesToInclude = 11
if (isThisYear(currentDate)) {
monthIndexesToInclude = getMonth(new Date())
}
return (
<div>
<Seek
title={year}
prev={format(subYears(currentDate, 1), "/YYYY")}
next={format(addYears(currentDate, 1), "/YYYY")}
disableNext={year >= new Date().getFullYear()}
/>
<MonthCardGrid>
{months.long.map((month, index) => {
const isDisabled = monthIndexesToInclude < index
return isDisabled ? (
<MonthCard key={index} disabled={isDisabled}>
{month}
</MonthCard>
) : (
<Link
key={index}
to={format(new Date(year, index), "/YYYY/MM")}
style={{ textDecoration: "none" }}
>
<MonthCard>{month}</MonthCard>
</Link>
)
})}
</MonthCardGrid>
</div>
)
}
}
export default withTheme(Year)