Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions gatsby-browser.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { withTheme } from "./src/theme"
import { withRoot } from "./src/rootElement"
import { entryIdFromDate, makeDate } from "./src/features/utils/days"

export const wrapPageElement = ({ element }) => {
return withTheme({ element })
Expand All @@ -8,3 +9,15 @@ export const wrapPageElement = ({ element }) => {
export const wrapRootElement = ({ element }) => {
return withRoot({ element })
}

export const shouldUpdateScroll = ({ routerProps: { location } }) => {
const regex = /timeline\/?(\d\d\d\d-\d\d-\d\d)?$/
const results = location.pathname.match(regex)

if (results) {
const entryId = entryIdFromDate(makeDate(results[1]))
return `scrollTo-${entryId}`
} else {
return true
}
}
12 changes: 6 additions & 6 deletions src/features/brand/BrandLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,10 @@ import { useSignInNavItem, useSignUpNavItem } from "../navigation"

const useStyles = makeStyles((theme) => ({
root: {
"& header": {
maxWidth: "55rem",
margin: "0 auto",
},
"& main": {
"& > main": {
maxWidth: "50rem",
},
"& footer": {
"& > footer": {
maxWidth: "50rem",
},
},
Expand All @@ -47,6 +43,10 @@ const useStyles = makeStyles((theme) => ({
appBar: {
borderTop: `4px solid ${theme.palette.primary.main}`,
borderBottom: `1px solid ${theme.palette.grey[200]}`,
"& header": {
maxWidth: "55rem",
margin: "0 auto",
},
},
offset: theme.mixins.toolbar,
toolbar: {
Expand Down
1 change: 0 additions & 1 deletion src/features/cycle/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { selectEntriesSortedByDate, selectEntryTags } from "../entries"
import { selectMenstruationTag, selectInitialDaysBetween } from "../settings"

import {
makeDate,
daysBetweenDates,
isDateBefore,
isDateAfter,
Expand Down
44 changes: 32 additions & 12 deletions src/features/timeline/TimelineIndexPage.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,59 @@
import React from "react"
import React, { useEffect } from "react"
import { useSelector } from "react-redux"
import { List, makeStyles } from "@material-ui/core"
import { eachDayOfInterval, addDays, isToday } from "date-fns"

import { makeDate, intervalAfterDate } from "../utils/days"
import { makeDate, entryIdFromDate } from "../utils/days"

import { BrandLayout } from "../brand"
import { Welcome } from "../onboarding"

import { selectDaysBetween } from "../cycle"

import DaySummary from "./DaySummary"
import ForecastItem from "./ForecastItem"
import TimelineItem from "./TimelineItem"
import DatePicker from "./DatePicker"

const useStyles = makeStyles((theme) => ({
forecast: {
maxWidth: "30rem",
margin: theme.spacing(2, 0, 4),
},
}))

const CycleIndexPage = ({ entryId }) => {
const date = makeDate(entryId)
const classes = useStyles()

const selectedDate = makeDate(entryId)
const calculatedDaysBetween = useSelector(selectDaysBetween)
const afterInterval = intervalAfterDate(date, calculatedDaysBetween + 3)

const range = eachDayOfInterval({
start: addDays(selectedDate, calculatedDaysBetween * -1.5),
end: addDays(selectedDate, calculatedDaysBetween * 1.5),
})

useEffect(() => {
const scrollToId = `scrollTo-${entryIdFromDate(selectedDate)}`
const node = document.getElementById(scrollToId)
if (!node) return

node.scrollIntoView({
block: "start",
})
}, [selectedDate])

return (
<BrandLayout variant="app" toolbar={<DatePicker date={date} />}>
<DaySummary date={date} />
<Welcome />
<BrandLayout variant="app" toolbar={<DatePicker date={selectedDate} />}>
<List className={classes.forecast}>
{afterInterval.map((date) => {
return <ForecastItem key={date} date={date} />
{range.map((date) => {
return (
<>
<TimelineItem
key={date}
date={date}
selectedDate={selectedDate}
/>
{isToday(date) && <Welcome />}
</>
)
})}
</List>
</BrandLayout>
Expand Down
44 changes: 44 additions & 0 deletions src/features/timeline/TimelineItem/Entry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react"
import PropTypes from "prop-types"
import { Link } from "gatsby"
import { useSelector } from "react-redux"
import { isToday, isPast } from "date-fns"
import { Typography, ButtonBase, Paper } from "@material-ui/core"
import { Add as AddNoteIcon } from "@material-ui/icons"

import { entryIdFromDate } from "../../utils/days"
import { selectEntryNote } from "../../entries"

const Entry = ({ date, ...props }) => {
const entryId = entryIdFromDate(date)
const editPath = `/timeline/${entryId}/edit`
const entryNote = useSelector((state) => selectEntryNote(state, { date }))
const isEditable = isPast(date) || isToday(date)

if (!isEditable) return null

return (
<Paper
component="section"
elevation={1}
variant={isToday(date) ? "elevation" : "outlined"}
>
<ButtonBase component={Link} to={editPath} {...props}>
{entryNote ? (
<Typography variant="body2">{entryNote}</Typography>
) : (
<>
<AddNoteIcon fontSize="small" />
<Typography variant="button">Add note</Typography>
</>
)}
</ButtonBase>
</Paper>
)
}

Entry.propTypes = {
date: PropTypes.instanceOf(Date),
}

export default Entry
52 changes: 52 additions & 0 deletions src/features/timeline/TimelineItem/Header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from "react"
import PropTypes from "prop-types"
import { useSelector } from "react-redux"
import { format, isToday, isSameDay } from "date-fns"
import { Typography } from "@material-ui/core"

import { selectIsMenstruationForDate } from "../../entries"
import {
selectCycleDayForDate,
selectPredictedMenstruationForDate,
} from "../../cycle"

const TimelineHeader = ({ date, selectedDate, ...props }) => {
const cycleDay = useSelector((state) =>
selectCycleDayForDate(state, { date })
)

const isLoggedMenstruation = useSelector((state) =>
selectIsMenstruationForDate(state, { date })
)

const isPredictedMenstruation = useSelector((state) =>
selectPredictedMenstruationForDate(state, { date })
)

const isMenstruation = isLoggedMenstruation || isPredictedMenstruation

return (
<header {...props}>
<Typography
variant="overline"
component={isSameDay(date, selectedDate) ? "strong" : "span"}
color="textSecondary"
>
{isToday(date) ? "Today" : format(date, "EEEE, MMMM do")}
</Typography>
<Typography
variant="overline"
component={isMenstruation ? "strong" : "span"}
color={isMenstruation ? "primary" : "textSecondary"}
>
Day {cycleDay}
</Typography>
</header>
)
}

TimelineHeader.propTypes = {
date: PropTypes.instanceOf(Date),
}

export default TimelineHeader
46 changes: 46 additions & 0 deletions src/features/timeline/TimelineItem/Info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from "react"
import { useSelector } from "react-redux"
import PropTypes from "prop-types"
import { isToday, format } from "date-fns"
import { Typography } from "@material-ui/core"

import {
selectDaysBetween,
selectIsDaysBetweenCalculated,
selectIsDateCurrentCycle,
selectNextStartDate,
} from "../../cycle"
import { selectMenstruationTag } from "../../settings"

const Info = ({ date, ...props }) => {
const menstruationTag = useSelector(selectMenstruationTag)

const daysBetween = useSelector(selectDaysBetween)
const isDaysBetweenCalculated = useSelector(selectIsDaysBetweenCalculated)

const isCurrentCycle = useSelector((state) =>
selectIsDateCurrentCycle(state, { date })
)
const nextStartDate = useSelector((state) =>
selectNextStartDate(state, { date })
)

if (!isToday(date) || !nextStartDate) return null

return (
<section {...props}>
<Typography variant="body2">
<strong>#{menstruationTag}</strong> {!isCurrentCycle && "was"} estimated
to arrive <strong>{format(nextStartDate, "EEEE, MMMM do")}</strong>{" "}
based on {isDaysBetweenCalculated ? "your average" : "a default"}{" "}
<strong>{daysBetween || "?"} day</strong> cycle.{" "}
</Typography>
</section>
)
}

Info.propTypes = {
date: PropTypes.instanceOf(Date),
}

export default Info
54 changes: 54 additions & 0 deletions src/features/timeline/TimelineItem/Predictions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from "react"
import PropTypes from "prop-types"
import classNames from "classnames"
import { useSelector } from "react-redux"
import { Chip, makeStyles } from "@material-ui/core"

import { selectPredictedTagsForDate } from "../../cycle"

const useStyles = makeStyles((theme) => ({
tag: {
borderStyle: "dotted",
},
loggedTag: {
borderColor: theme.palette.text.secondary,
backgroundColor: theme.palette.background.paper,
},
}))

const Predictions = ({ date, ...props }) => {
const classes = useStyles()

const predictedTags = useSelector((state) =>
selectPredictedTagsForDate(state, { date })
)

if (predictedTags.length === 0) return null

return (
<aside {...props}>
{predictedTags
.sort(({ countA }, { countB }) => countA - countB)
.map(({ tag, logged }) => {
return (
<Chip
className={classNames(classes.tag, {
[classes.loggedTag]: logged,
})}
variant="outlined"
size="small"
key={tag}
label={`#${tag}`}
component="span"
/>
)
})}
</aside>
)
}

Predictions.propTypes = {
date: PropTypes.instanceOf(Date),
}

export default Predictions
Loading