chore: resize writing area, add name to navbar

This commit is contained in:
Kyle Gill
2019-03-29 10:08:02 -06:00
parent faf5473d5d
commit eee11f9437
34 changed files with 386 additions and 369 deletions

View File

@@ -1,18 +1,20 @@
import React, { Component } from "react"; import React, { Component } from "react"
import { BrowserRouter as Router, Route } from "react-router-dom"; import { BrowserRouter as Router, Route } from "react-router-dom"
import styled from "@emotion/styled"; import styled from "@emotion/styled"
import { ThemeProvider } from "emotion-theming"; import { ThemeProvider } from "emotion-theming"
import theme from "./styles/theme"; import { SIZES } from "./styles/constants"
import Navbar from "./components/Navbar";
import Day from "./components/screens/Day";
import Month from "./components/screens/Month";
import Year from "./components/screens/Year";
import User from "./components/screens/User";
import Login from "./components/screens/Login";
import Register from "./components/screens/Register";
import { withAuthentication } from "./components/session"; import theme from "./styles/theme"
import Navbar from "./components/Navbar"
import Day from "./components/screens/Day"
import Month from "./components/screens/Month"
import Year from "./components/screens/Year"
import User from "./components/screens/User"
import Login from "./components/screens/Login"
import Register from "./components/screens/Register"
import { withAuthentication } from "./components/session"
const RouteLayout = styled.div` const RouteLayout = styled.div`
display: flex; display: flex;
@@ -20,32 +22,32 @@ const RouteLayout = styled.div`
height: 100%; height: 100%;
margin: 0 auto; margin: 0 auto;
padding: 0 10px; padding: 0 10px;
max-width: 720px; max-width: ${SIZES.maxWidth};
min-height: calc(100vh - 60px); min-height: calc(100vh - 60px);
background-color: ${props => props.theme.colors.bodyBackground}; background-color: ${props => props.theme.colors.bodyBackground};
`; `
class App extends Component { class App extends Component {
state = { state = {
authUser: JSON.parse(localStorage.getItem("authUser")), authUser: JSON.parse(localStorage.getItem("authUser")),
selectedTheme: "LIGHT" selectedTheme: "LIGHT",
}; }
onChangeTheme = () => { onChangeTheme = () => {
const { selectedTheme } = this.state; const { selectedTheme } = this.state
const root = document.documentElement; const root = document.documentElement
const newTheme = selectedTheme === "LIGHT" ? "DARK" : "LIGHT"; const newTheme = selectedTheme === "LIGHT" ? "DARK" : "LIGHT"
root.style.setProperty( root.style.setProperty(
"background-color", "background-color",
theme[newTheme].colors.bodyBackground theme[newTheme].colors.bodyBackground
); )
this.setState({ selectedTheme: newTheme }); this.setState({ selectedTheme: newTheme })
}; }
render() { render() {
const { selectedTheme } = this.state; const { selectedTheme } = this.state
const currentTheme = theme[selectedTheme]; const currentTheme = theme[selectedTheme]
return ( return (
<ThemeProvider theme={currentTheme}> <ThemeProvider theme={currentTheme}>
<Router> <Router>
@@ -68,8 +70,8 @@ class App extends Component {
</RouteLayout> </RouteLayout>
</Router> </Router>
</ThemeProvider> </ThemeProvider>
); )
} }
} }
export default withAuthentication(App); export default withAuthentication(App)

View File

@@ -1,9 +1,9 @@
import React from 'react'; import React from "react"
import ReactDOM from 'react-dom'; import ReactDOM from "react-dom"
import App from './App'; import App from "./App"
it('renders without crashing', () => { it("renders without crashing", () => {
const div = document.createElement('div'); const div = document.createElement("div")
ReactDOM.render(<App />, div); ReactDOM.render(<App />, div)
ReactDOM.unmountComponentAtNode(div); ReactDOM.unmountComponentAtNode(div)
}); })

View File

@@ -35,6 +35,7 @@ const Icon = ({ name, ...rest }) => (
{name === "ChevronLeft" && <ChevronLeft />} {name === "ChevronLeft" && <ChevronLeft />}
{name === "ChevronRight" && <ChevronRight />} {name === "ChevronRight" && <ChevronRight />}
{name === "Circle" && <Circle />} {name === "Circle" && <Circle />}
{name === "Edit2" && <Edit2 />}
{name === "Moon" && <Moon />} {name === "Moon" && <Moon />}
{name === "Sun" && <Sun />} {name === "Sun" && <Sun />}
{name === "User" && <User />} {name === "User" && <User />}

View File

@@ -1,2 +1,2 @@
import Icon from "./Icon"; import Icon from "./Icon"
export default Icon; export default Icon

View File

@@ -1,4 +1,4 @@
import React from "react"; import React from "react"
const Logo = ({ color }) => { const Logo = ({ color }) => {
return ( return (
@@ -123,7 +123,7 @@ const Logo = ({ color }) => {
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>
); )
}; }
export default Logo; export default Logo

View File

@@ -1,38 +1,49 @@
import React from "react"; import React from "react"
import { Link } from "react-router-dom"; import { Link } from "react-router-dom"
import styled from "@emotion/styled"; import styled from "@emotion/styled"
import { compose } from "recompose"; import { compose } from "recompose"
import { withTheme } from "emotion-theming"; import { withTheme } from "emotion-theming"
import { todayUrl, yearUrl } from "../../utils/date"; import { SIZES } from "../../styles/constants"
import { todayUrl, yearUrl } from "../../utils/date"
import Logo from "../Logo"; import Logo from "../Logo"
import Icon from "../Icon"; import Icon from "../Icon"
import { withAuthentication } from "../session"; import { withAuthentication } from "../session"
const Header = styled.div` const Header = styled.div`
background-color: ${props => props.theme.colors.headerBackground}; background-color: ${props => props.theme.colors.headerBackground};
height: 60px; height: 60px;
display: grid; display: grid;
grid-template-areas: "... nav ..."; grid-template-areas: "... nav ...";
grid-template-columns: 1fr minmax(240px, 720px) 1fr; grid-template-columns: 1fr minmax(240px, ${SIZES.maxWidth}) 1fr;
grid-gap: 10px; grid-gap: 10px;
align-items: center; align-items: center;
border-width: 1px; border-width: 1px;
border-color: ${props => props.theme.colors.quarternary}; border-color: ${props => props.theme.colors.quarternary};
border-style: solid; border-style: solid;
`; `
const Nav = styled.div` const Nav = styled.div`
grid-area: nav; grid-area: nav;
max-width: 720px; max-width: ${SIZES.maxWidth};
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-content: center; align-content: center;
`; `
const LogoSection = styled.div` const LogoSection = styled.div`
width: 40px; height: 40px;
`; display: flex;
flex-direction: row;
align-items: center;
font-family: "Montserrat", sans-serif;
font-weight: 700;
font-size: 18px;
`
const LogoText = styled.span`
color: ${props => props.color};
margin-left: 5px;
`
const NavIcons = styled.div` const NavIcons = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -40,27 +51,29 @@ const NavIcons = styled.div`
* + * { * + * {
margin-left: 10px; margin-left: 10px;
} }
`; `
const Navbar = ({ authUser, theme, toggleTheme }) => ( const Navbar = ({ authUser, theme, toggleTheme }) => (
<Header> <Header>
<Nav> <Nav>
<LogoSection> <LogoSection>
<Logo color={theme.colors.logo} /> <Logo color={theme.colors.logo} />
<LogoText color={theme.colors.primary}>SOL</LogoText>{" "}
<LogoText color={theme.colors.secondary}>JOURNAL</LogoText>
</LogoSection> </LogoSection>
<NavIcons> <NavIcons>
<Icon
onClick={() => toggleTheme()}
name={theme.name === "Dark" ? "Sun" : "Moon"}
/>
{authUser ? ( {authUser ? (
<React.Fragment> <React.Fragment>
<Link to={todayUrl()}>
<Icon name="Edit2" />
</Link>
<Link to={yearUrl()}> <Link to={yearUrl()}>
<Icon name="Calendar" /> <Icon name="Calendar" />
</Link> </Link>
<Link to={todayUrl()}> <Icon
<Icon name="Book" /> onClick={() => toggleTheme()}
</Link> name={theme.name === "Dark" ? "Sun" : "Moon"}
/>
<Link to={"/user"}> <Link to={"/user"}>
<Icon name="User" /> <Icon name="User" />
</Link> </Link>
@@ -75,9 +88,9 @@ const Navbar = ({ authUser, theme, toggleTheme }) => (
</NavIcons> </NavIcons>
</Nav> </Nav>
</Header> </Header>
); )
export default compose( export default compose(
withAuthentication, withAuthentication,
withTheme withTheme
)(Navbar); )(Navbar)

View File

@@ -1,2 +1,2 @@
import Navbar from "./Navbar"; import Navbar from "./Navbar"
export default Navbar; export default Navbar

View File

@@ -1,10 +1,10 @@
import React from "react"; import React from "react"
import styled from "@emotion/styled"; import styled from "@emotion/styled"
import { withTheme } from "emotion-theming"; import { withTheme } from "emotion-theming"
import { Link } from "react-router-dom"; import { Link } from "react-router-dom"
import { SIZES } from "../../styles/constants"; import { SIZES } from "../../styles/constants"
import Icon from "../Icon"; import Icon from "../Icon"
const SeekHeader = styled.header` const SeekHeader = styled.header`
display: flex; display: flex;
@@ -14,17 +14,17 @@ const SeekHeader = styled.header`
border-bottom-width: 1px; border-bottom-width: 1px;
border-bottom-style: solid; border-bottom-style: solid;
border-color: ${props => props.theme.colors.quarternary}; border-color: ${props => props.theme.colors.quarternary};
`; `
const SeekH1 = styled.h1` const SeekH1 = styled.h1`
display: block; display: block;
font-size: ${SIZES.normal}; font-size: ${SIZES.normal};
color: ${props => props.theme.colors.secondary}; color: ${props => props.theme.colors.secondary};
`; `
const SeekArrows = styled.div` const SeekArrows = styled.div`
display: grid; display: grid;
grid-template-columns: auto auto; grid-template-columns: auto auto;
grid-gap: 10px; grid-gap: 10px;
`; `
const Seek = ({ title = "", prev = "", next = "", disableNext, theme }) => ( const Seek = ({ title = "", prev = "", next = "", disableNext, theme }) => (
<SeekHeader> <SeekHeader>
@@ -38,7 +38,7 @@ const Seek = ({ title = "", prev = "", next = "", disableNext, theme }) => (
disabled={disableNext} disabled={disableNext}
name="ChevronRight" name="ChevronRight"
style={{ style={{
color: disableNext ? theme.colors.hover : theme.colors.secondary color: disableNext ? theme.colors.hover : theme.colors.secondary,
}} }}
/> />
) : ( ) : (
@@ -47,13 +47,13 @@ const Seek = ({ title = "", prev = "", next = "", disableNext, theme }) => (
disabled={disableNext} disabled={disableNext}
name="ChevronRight" name="ChevronRight"
style={{ style={{
color: disableNext ? theme.colors.hover : theme.colors.secondary color: disableNext ? theme.colors.hover : theme.colors.secondary,
}} }}
/> />
</Link> </Link>
)} )}
</SeekArrows> </SeekArrows>
</SeekHeader> </SeekHeader>
); )
export default withTheme(Seek); export default withTheme(Seek)

View File

@@ -1,2 +1,2 @@
import Seek from "./Seek"; import Seek from "./Seek"
export default Seek; export default Seek

View File

@@ -1,11 +1,11 @@
import React from "react"; import React from "react"
import { withFirebase } from "../firebase"; import { withFirebase } from "../firebase"
const SignOutButton = ({ firebase }) => ( const SignOutButton = ({ firebase }) => (
<button type="button" onClick={firebase.doSignOut}> <button type="button" onClick={firebase.doSignOut}>
Sign Out Sign Out
</button> </button>
); )
export default withFirebase(SignOutButton); export default withFirebase(SignOutButton)

View File

@@ -1,2 +1,2 @@
import SignOut from "./SignOut"; import SignOut from "./SignOut"
export default SignOut; export default SignOut

View File

@@ -1,11 +1,11 @@
import React from "react"; import React from "react"
const FirebaseContext = React.createContext(null); const FirebaseContext = React.createContext(null)
export const withFirebase = Component => props => ( export const withFirebase = Component => props => (
<FirebaseContext.Consumer> <FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />} {firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer> </FirebaseContext.Consumer>
); )
export default FirebaseContext; export default FirebaseContext

View File

@@ -1,6 +1,6 @@
import app from "firebase/app"; import app from "firebase/app"
import "firebase/auth"; import "firebase/auth"
import "firebase/firestore"; import "firebase/firestore"
const config = { const config = {
apiKey: process.env.REACT_APP_FIREBASE_API_KEY, apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
@@ -8,43 +8,43 @@ const config = {
databaseURL: process.env.REACT_APP_DEV_DATABASE_URL, databaseURL: process.env.REACT_APP_DEV_DATABASE_URL,
projectId: process.env.REACT_APP_DEV_PROJECT_ID, projectId: process.env.REACT_APP_DEV_PROJECT_ID,
storageBucket: process.env.REACT_APP_DEV_STORAGE_BUCKET, storageBucket: process.env.REACT_APP_DEV_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_DEV_MESSAGING_SENDER_ID messagingSenderId: process.env.REACT_APP_DEV_MESSAGING_SENDER_ID,
}; }
class Firebase { class Firebase {
constructor() { constructor() {
app.initializeApp(config); app.initializeApp(config)
this.auth = app.auth(); this.auth = app.auth()
this.db = app.firestore(); this.db = app.firestore()
app app
.firestore() .firestore()
.enablePersistence() .enablePersistence()
.catch(function(err) { .catch(function(err) {
if (err.code === "failed-precondition") { if (err.code === "failed-precondition") {
console.error("firestore won't work offline with multiple tabs open"); console.error("firestore won't work offline with multiple tabs open")
} else if (err.code === "unimplemented") { } else if (err.code === "unimplemented") {
console.error( console.error(
"current browser can't take advantage of firestore offline" "current browser can't take advantage of firestore offline"
); )
} }
}); })
} }
// Auth // Auth
doCreateUserWithEmailAndPassword = (email, password) => doCreateUserWithEmailAndPassword = (email, password) =>
this.auth.createUserWithEmailAndPassword(email, password); this.auth.createUserWithEmailAndPassword(email, password)
doSignInWithEmailAndPassword = (email, password) => doSignInWithEmailAndPassword = (email, password) =>
this.auth.signInWithEmailAndPassword(email, password); this.auth.signInWithEmailAndPassword(email, password)
doSignOut = () => { doSignOut = () => {
this.auth.signOut(); this.auth.signOut()
window.location.replace("/login"); window.location.replace("/login")
}; }
doPasswordReset = email => this.auth.sendPasswordResetEmail(email); doPasswordReset = email => this.auth.sendPasswordResetEmail(email)
doPasswordUpdate = password => this.auth.currentUser.updatePassword(password); doPasswordUpdate = password => this.auth.currentUser.updatePassword(password)
} }
export default Firebase; export default Firebase

View File

@@ -1,6 +1,6 @@
import FirebaseContext, { withFirebase } from "./context"; import FirebaseContext, { withFirebase } from "./context"
import Firebase from "./fire"; import Firebase from "./fire"
export default Firebase; export default Firebase
export { FirebaseContext, withFirebase }; export { FirebaseContext, withFirebase }

View File

@@ -1,23 +1,23 @@
import React, { Component } from "react"; import React, { Component } from "react"
import styled from "@emotion/styled"; import styled from "@emotion/styled"
import { compose } from "recompose"; import { compose } from "recompose"
import { withRouter } from "react-router-dom"; import { withRouter } from "react-router-dom"
import { withTheme } from "emotion-theming"; import { withTheme } from "emotion-theming"
import { withFirebase } from "../../firebase"; import { withFirebase } from "../../firebase"
import { withAuthentication } from "../../session"; import { withAuthentication } from "../../session"
import { addDays, subDays, format, isAfter, startOfYesterday } from "date-fns"; import { addDays, subDays, format, isAfter, startOfYesterday } from "date-fns"
import { BeatLoader } from "react-spinners"; import { BeatLoader } from "react-spinners"
import { SIZES } from "../../../styles/constants"; import { SIZES } from "../../../styles/constants"
import Seek from "../../Seek"; import Seek from "../../Seek"
const JournalHeading = styled.h2` const JournalHeading = styled.h2`
font-weight: 700; font-weight: 700;
font-size: ${SIZES.tiny}; font-size: ${SIZES.tiny};
color: ${props => props.theme.colors.secondary}; color: ${props => props.theme.colors.secondary};
margin-top: ${SIZES.medium}; margin-top: ${SIZES.medium};
`; `
const JournalEntryArea = styled.textarea` const JournalEntryArea = styled.textarea`
font-family: sans-serif; font-family: sans-serif;
flex-grow: 0.8; flex-grow: 0.8;
@@ -42,60 +42,60 @@ const JournalEntryArea = styled.textarea`
box-shadow: 0 0 0 8px ${props => props.theme.colors.bodyBackground}, box-shadow: 0 0 0 8px ${props => props.theme.colors.bodyBackground},
0 0 0 10px ${props => props.theme.colors.hover}; 0 0 0 10px ${props => props.theme.colors.hover};
} }
`; `
const AUTOSAVE_DELAY = 2000; const AUTOSAVE_DELAY = 2000
class Day extends Component { class Day extends Component {
state = { state = {
text: "", text: "",
loading: true loading: true,
}; }
timeout = 0; timeout = 0
retrievedFromServer = false; retrievedFromServer = false
componentDidMount() { componentDidMount() {
const { const {
history, history,
match: { match: {
params: { year, month, day } params: { year, month, day },
} },
} = this.props; } = this.props
history.listen((location, action) => { history.listen((location, action) => {
const [, year, month, day] = location.pathname.split("/"); const [, year, month, day] = location.pathname.split("/")
this.onRouteChanged(year, month, day); this.onRouteChanged(year, month, day)
}); })
this.getDocRef(year, month, day, false); this.getDocRef(year, month, day, false)
} }
onRouteChanged = (year, month, day) => { onRouteChanged = (year, month, day) => {
this.setState({ loading: true }); this.setState({ loading: true })
this.getDocRef(year, month, day, false); this.getDocRef(year, month, day, false)
}; }
getDocRef = (year, month, day, cacheFirst) => { getDocRef = (year, month, day, cacheFirst) => {
const { firebase, authUser } = this.props; const { firebase, authUser } = this.props
const getOptions = { const getOptions = {
source: cacheFirst ? "cache" : "default" source: cacheFirst ? "cache" : "default",
}; }
const docRef = firebase.db const docRef = firebase.db
.collection("entries") .collection("entries")
.doc(`${year}${month}${day}-${authUser.uid}`); .doc(`${year}${month}${day}-${authUser.uid}`)
this.getData(docRef, getOptions); this.getData(docRef, getOptions)
}; }
getData = (docRef, options) => { getData = (docRef, options) => {
docRef docRef
.get(options) .get(options)
.then(doc => { .then(doc => {
if (doc.data()) { if (doc.data()) {
this.setState({ text: doc.data().text, loading: false }); this.setState({ text: doc.data().text, loading: false })
} else { } else {
this.setState({ text: "", loading: false }); this.setState({ text: "", loading: false })
} }
}) })
.catch(err => { .catch(err => {
console.warn("entry not found in cache"); console.warn("entry not found in cache")
// for cache first, server second fetching, dangerous with potential overwriting of data // for cache first, server second fetching, dangerous with potential overwriting of data
// docRef.get().then(doc => { // docRef.get().then(doc => {
// if (doc.data()) { // if (doc.data()) {
@@ -104,28 +104,28 @@ class Day extends Component {
// this.setState({ text: "", loading: false }); // this.setState({ text: "", loading: false });
// } // }
// }); // });
}); })
}; }
onChangeText = e => { onChangeText = e => {
if (this.timeout) clearTimeout(this.timeout); if (this.timeout) clearTimeout(this.timeout)
const text = e.target.value; const text = e.target.value
this.setState({ text }); this.setState({ text })
this.timeout = setTimeout(() => { this.timeout = setTimeout(() => {
console.log(text); console.log(text)
this.saveText(text); this.saveText(text)
}, AUTOSAVE_DELAY); }, AUTOSAVE_DELAY)
}; }
saveText = text => { saveText = text => {
const { const {
match: { match: {
params: { year, month, day } params: { year, month, day },
}, },
firebase, firebase,
authUser authUser,
} = this.props; } = this.props
firebase.db firebase.db
.collection("entries") .collection("entries")
.doc(`${year}${month}${day}-${authUser.uid}`) .doc(`${year}${month}${day}-${authUser.uid}`)
@@ -134,24 +134,24 @@ class Day extends Component {
text, text,
day: Number(day), day: Number(day),
year: Number(year), year: Number(year),
month: Number(month) month: Number(month),
}, },
{ {
merge: true merge: true,
} }
); )
}; }
render() { render() {
const { const {
match: { match: {
params: { year, month, day } params: { year, month, day },
}, },
theme theme,
} = this.props; } = this.props
const { text, loading } = this.state; const { text, loading } = this.state
const currentDay = new Date(year, month - 1, day); const currentDay = new Date(year, month - 1, day)
if (!currentDay) return; if (!currentDay) return
return ( return (
<> <>
@@ -175,7 +175,7 @@ class Day extends Component {
/> />
)} )}
</> </>
); )
} }
} }
@@ -184,4 +184,4 @@ export default compose(
withTheme, withTheme,
withAuthentication, withAuthentication,
withRouter withRouter
)(Day); )(Day)

View File

@@ -1,2 +1,2 @@
import Day from "./Day"; import Day from "./Day"
export default Day; export default Day

View File

@@ -1,8 +1,8 @@
import React, { Component } from "react"; import React, { Component } from "react"
import { withRouter, Link } from "react-router-dom"; import { withRouter, Link } from "react-router-dom"
import { format } from "date-fns"; import { format } from "date-fns"
import { FirebaseContext } from "../../firebase"; import { FirebaseContext } from "../../firebase"
const LoginPage = ({ history }) => ( const LoginPage = ({ history }) => (
<div> <div>
@@ -14,38 +14,38 @@ const LoginPage = ({ history }) => (
Don't have an account? <Link to={"/register"}>Sign Up</Link> Don't have an account? <Link to={"/register"}>Sign Up</Link>
</p> </p>
</div> </div>
); )
class LoginFormBase extends Component { class LoginFormBase extends Component {
constructor(props) { constructor(props) {
super(props); super(props)
this.state = { email: "", password: "", error: null }; this.state = { email: "", password: "", error: null }
} }
onSubmit = event => { onSubmit = event => {
event.preventDefault(); event.preventDefault()
const { email, password } = this.state; const { email, password } = this.state
this.props.firebase this.props.firebase
.doSignInWithEmailAndPassword(email, password) .doSignInWithEmailAndPassword(email, password)
.then(() => { .then(() => {
this.setState({ email: "", password: "", error: null }); this.setState({ email: "", password: "", error: null })
this.props.history.push(format(new Date(), "/YYYY/MM/DD")); this.props.history.push(format(new Date(), "/YYYY/MM/DD"))
}) })
.catch(error => { .catch(error => {
this.setState({ error }); this.setState({ error })
}); })
}; }
onChange = event => { onChange = event => {
this.setState({ [event.target.name]: event.target.value }); this.setState({ [event.target.name]: event.target.value })
}; }
render() { render() {
const { email, password, error } = this.state; const { email, password, error } = this.state
const isInvalid = password === "" || email === ""; const isInvalid = password === "" || email === ""
return ( return (
<form onSubmit={this.onSubmit}> <form onSubmit={this.onSubmit}>
@@ -69,12 +69,12 @@ class LoginFormBase extends Component {
{error && <p>{error.message}</p>} {error && <p>{error.message}</p>}
</form> </form>
); )
} }
} }
const LoginForm = withRouter(LoginFormBase); const LoginForm = withRouter(LoginFormBase)
export default LoginPage; export default LoginPage
export { LoginForm }; export { LoginForm }

View File

@@ -1,2 +1,2 @@
import Login from "./Login"; import Login from "./Login"
export default Login; export default Login

View File

@@ -1,6 +1,6 @@
import React, { Component } from "react"; import React, { Component } from "react"
import { Link } from "react-router-dom"; import { Link } from "react-router-dom"
import styled from "@emotion/styled"; import styled from "@emotion/styled"
import { import {
isAfter, isAfter,
isThisYear, isThisYear,
@@ -8,17 +8,17 @@ import {
addMonths, addMonths,
subMonths, subMonths,
getDaysInMonth, getDaysInMonth,
startOfMonth startOfMonth,
} from "date-fns"; } from "date-fns"
import Seek from "../../Seek"; import Seek from "../../Seek"
const YearCardGrid = styled.div` const YearCardGrid = styled.div`
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(75px, 1fr));
grid-gap: 20px; grid-gap: 20px;
margin-top: 20px; margin-top: 20px;
`; `
const YearCard = styled.div` const YearCard = styled.div`
color: ${props => color: ${props =>
props.disabled props.disabled
@@ -26,39 +26,39 @@ const YearCard = styled.div`
: props.theme.colors.secondary}; : props.theme.colors.secondary};
border: 1px solid; border: 1px solid;
border-color: ${props => props.theme.colors.quarternary}; border-color: ${props => props.theme.colors.quarternary};
padding: 30px; padding: 25px;
border-radius: 5px; border-radius: 5px;
text-align: center; text-align: center;
user-select: none; user-select: none;
&:hover { &:hover {
border-color: ${props => !props.disabled && props.theme.colors.tertiary}; border-color: ${props => !props.disabled && props.theme.colors.tertiary};
} }
`; `
class Month extends Component { class Month extends Component {
render() { render() {
const { const {
match: { match: {
params: { year, month } params: { year, month },
} },
} = this.props; } = this.props
const currentDay = new Date(year, month - 1); const currentDay = new Date(year, month - 1)
// include all months unless it's this year // include all months unless it's this year
let dayIndexesToInclude = 31; let dayIndexesToInclude = 31
if (isThisYear(currentDay)) { if (isThisYear(currentDay)) {
dayIndexesToInclude = new Date().getDate(); dayIndexesToInclude = new Date().getDate()
} }
let yearCards = []; let yearCards = []
for (let i = 0; i < getDaysInMonth(currentDay); i++) { for (let i = 0; i < getDaysInMonth(currentDay); i++) {
const isDisabled = dayIndexesToInclude <= i; const isDisabled = dayIndexesToInclude <= i
if (isDisabled) { if (isDisabled) {
yearCards.push( yearCards.push(
<YearCard disabled={isDisabled} key={i}> <YearCard disabled={isDisabled} key={i}>
{i + 1} {i + 1}
</YearCard> </YearCard>
); )
} else { } else {
yearCards.push( yearCards.push(
<Link <Link
@@ -68,7 +68,7 @@ class Month extends Component {
> >
<YearCard key={i}>{i + 1}</YearCard> <YearCard key={i}>{i + 1}</YearCard>
</Link> </Link>
); )
} }
} }
@@ -85,8 +85,8 @@ class Month extends Component {
/> />
<YearCardGrid>{yearCards}</YearCardGrid> <YearCardGrid>{yearCards}</YearCardGrid>
</> </>
); )
} }
} }
export default Month; export default Month

View File

@@ -1,2 +1,2 @@
import Month from "./Month"; import Month from "./Month"
export default Month; export default Month

View File

@@ -1,7 +1,7 @@
import React, { Component } from "react"; import React, { Component } from "react"
import { withRouter } from "react-router-dom"; import { withRouter } from "react-router-dom"
import { FirebaseContext } from "../../firebase"; import { FirebaseContext } from "../../firebase"
const RegisterPage = ({ history }) => ( const RegisterPage = ({ history }) => (
<div> <div>
@@ -10,24 +10,24 @@ const RegisterPage = ({ history }) => (
{firebase => <RegisterForm history={history} firebase={firebase} />} {firebase => <RegisterForm history={history} firebase={firebase} />}
</FirebaseContext.Consumer> </FirebaseContext.Consumer>
</div> </div>
); )
class RegisterForm extends Component { class RegisterForm extends Component {
constructor(props) { constructor(props) {
super(props); super(props)
this.state = { this.state = {
username: "", username: "",
email: "", email: "",
passwordOne: "", passwordOne: "",
passwordTwo: "", passwordTwo: "",
error: null error: null,
}; }
} }
onSubmit = event => { onSubmit = event => {
const { email, passwordOne } = this.state; const { email, passwordOne } = this.state
const { firebase } = this.props; const { firebase } = this.props
firebase firebase
.doCreateUserWithEmailAndPassword(email, passwordOne) .doCreateUserWithEmailAndPassword(email, passwordOne)
@@ -37,37 +37,37 @@ class RegisterForm extends Component {
email: "", email: "",
passwordOne: "", passwordOne: "",
passwordTwo: "", passwordTwo: "",
error: null error: null,
}); })
const { user } = result; const { user } = result
console.log(user); console.log(user)
firebase.db firebase.db
.collection("users") .collection("users")
.doc(user.uid) .doc(user.uid)
.set({ .set({
email: user.email, email: user.email,
theme: "LIGHT" theme: "LIGHT",
}); })
this.props.history.push("/home"); this.props.history.push("/home")
}) })
.catch(error => { .catch(error => {
this.setState({ error }); this.setState({ error })
}); })
event.preventDefault(); event.preventDefault()
}; }
onChange = event => { onChange = event => {
this.setState({ [event.target.name]: event.target.value }); this.setState({ [event.target.name]: event.target.value })
}; }
render() { render() {
const { username, email, passwordOne, passwordTwo, error } = this.state; const { username, email, passwordOne, passwordTwo, error } = this.state
const isInvalid = const isInvalid =
passwordOne !== passwordTwo || passwordOne !== passwordTwo ||
passwordOne === "" || passwordOne === "" ||
email === "" || email === "" ||
username === ""; username === ""
return ( return (
<form onSubmit={this.onSubmit}> <form onSubmit={this.onSubmit}>
@@ -105,10 +105,10 @@ class RegisterForm extends Component {
{error && <p>{error.message}</p>} {error && <p>{error.message}</p>}
</form> </form>
); )
} }
} }
export default withRouter(RegisterPage); export default withRouter(RegisterPage)
export { RegisterForm }; export { RegisterForm }

View File

@@ -1,2 +1,2 @@
import Register from "./Register"; import Register from "./Register"
export default Register; export default Register

View File

@@ -1,31 +1,31 @@
import React from "react"; import React from "react"
import { withFirebase } from "../../firebase"; import { withFirebase } from "../../firebase"
import SignOut from "../../SignOut"; import SignOut from "../../SignOut"
class User extends React.Component { class User extends React.Component {
state = { state = {
name: "" name: "",
}; }
updateInput = e => { updateInput = e => {
this.setState({ this.setState({
[e.target.name]: e.target.value [e.target.name]: e.target.value,
}); })
}; }
addUser = e => { addUser = e => {
e.preventDefault(); e.preventDefault()
const { firebase } = this.props; const { firebase } = this.props
firebase.db firebase.db
.collection("users") .collection("users")
.doc() .doc()
.add({ .add({
name: this.state.name name: this.state.name,
}); })
this.setState({ name: "" }); this.setState({ name: "" })
}; }
render() { render() {
return ( return (
@@ -42,8 +42,8 @@ class User extends React.Component {
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
</React.Fragment> </React.Fragment>
); )
} }
} }
export default withFirebase(User); export default withFirebase(User)

View File

@@ -1,2 +1,2 @@
import User from "./User"; import User from "./User"
export default User; export default User

View File

@@ -1,18 +1,18 @@
import React, { Component } from "react"; import React, { Component } from "react"
import { Link } from "react-router-dom"; import { Link } from "react-router-dom"
import styled from "@emotion/styled"; import styled from "@emotion/styled"
import { addYears, subYears, format, isThisYear, getMonth } from "date-fns"; import { addYears, subYears, format, isThisYear, getMonth } from "date-fns"
import { withTheme } from "emotion-theming"; import { withTheme } from "emotion-theming"
import { months } from "../../../utils/date"; import { months } from "../../../utils/date"
import Seek from "../../Seek"; import Seek from "../../Seek"
const MonthCardGrid = styled.div` const MonthCardGrid = styled.div`
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
grid-gap: 20px; grid-gap: 20px;
margin-top: 20px; margin-top: 20px;
`; `
const MonthCard = styled.div` const MonthCard = styled.div`
color: ${props => color: ${props =>
props.disabled props.disabled
@@ -27,21 +27,21 @@ const MonthCard = styled.div`
&:hover { &:hover {
border-color: ${props => !props.disabled && props.theme.colors.tertiary}; border-color: ${props => !props.disabled && props.theme.colors.tertiary};
} }
`; `
class Year extends Component { class Year extends Component {
render() { render() {
const { const {
match: { match: {
params: { year } params: { year },
} },
} = this.props; } = this.props
const currentDate = new Date(year, 0, 1); const currentDate = new Date(year, 0, 1)
// include all months unless it's this year // include all months unless it's this year
let monthIndexesToInclude = 11; let monthIndexesToInclude = 11
if (isThisYear(currentDate)) { if (isThisYear(currentDate)) {
monthIndexesToInclude = getMonth(new Date()); monthIndexesToInclude = getMonth(new Date())
} }
return ( return (
<div> <div>
@@ -53,7 +53,7 @@ class Year extends Component {
/> />
<MonthCardGrid> <MonthCardGrid>
{months.long.map((month, index) => { {months.long.map((month, index) => {
const isDisabled = monthIndexesToInclude < index; const isDisabled = monthIndexesToInclude < index
return isDisabled ? ( return isDisabled ? (
<MonthCard key={index} disabled={isDisabled}> <MonthCard key={index} disabled={isDisabled}>
{month} {month}
@@ -66,12 +66,12 @@ class Year extends Component {
> >
<MonthCard>{month}</MonthCard> <MonthCard>{month}</MonthCard>
</Link> </Link>
); )
})} })}
</MonthCardGrid> </MonthCardGrid>
</div> </div>
); )
} }
} }
export default withTheme(Year); export default withTheme(Year)

View File

@@ -1,2 +1,2 @@
import Year from "./Year"; import Year from "./Year"
export default Year; export default Year

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react"
const AuthUserContext = React.createContext(null); const AuthUserContext = React.createContext(null)
export default AuthUserContext; export default AuthUserContext

View File

@@ -1,4 +1,4 @@
import AuthUserContext from "./context"; import AuthUserContext from "./context"
import withAuthentication from "./withAuthentication"; import withAuthentication from "./withAuthentication"
export { AuthUserContext, withAuthentication }; export { AuthUserContext, withAuthentication }

View File

@@ -1,39 +1,39 @@
import React from "react"; import React from "react"
import AuthUserContext from "./context"; import AuthUserContext from "./context"
import { withFirebase } from "../firebase"; import { withFirebase } from "../firebase"
const withAuthentication = Component => { const withAuthentication = Component => {
class WithAuthentication extends React.Component { class WithAuthentication extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props)
this.state = { this.state = {
authUser: JSON.parse(localStorage.getItem("authUser")) authUser: JSON.parse(localStorage.getItem("authUser")),
}; }
} }
componentDidMount() { componentDidMount() {
this.listener = this.props.firebase.auth.onAuthStateChanged( this.listener = this.props.firebase.auth.onAuthStateChanged(
authUser => { authUser => {
localStorage.setItem("authUser", JSON.stringify(authUser)); localStorage.setItem("authUser", JSON.stringify(authUser))
this.setState({ this.setState({
authUser: { authUser: {
uid: authUser.uid, uid: authUser.uid,
email: authUser.email, email: authUser.email,
emailVerified: authUser.emailVerified, emailVerified: authUser.emailVerified,
} },
}); })
}, },
() => { () => {
localStorage.removeItem("authUser"); localStorage.removeItem("authUser")
this.setState({ authUser: null }); this.setState({ authUser: null })
} }
); )
} }
componentWillUnmount() { componentWillUnmount() {
this.listener(); this.listener()
} }
render() { render() {
@@ -41,11 +41,11 @@ const withAuthentication = Component => {
<AuthUserContext.Provider value={this.state.authUser}> <AuthUserContext.Provider value={this.state.authUser}>
<Component authUser={this.state.authUser} {...this.props} /> <Component authUser={this.state.authUser} {...this.props} />
</AuthUserContext.Provider> </AuthUserContext.Provider>
); )
} }
} }
return withFirebase(WithAuthentication); return withFirebase(WithAuthentication)
}; }
export default withAuthentication; export default withAuthentication

View File

@@ -1,19 +1,19 @@
import React from "react"; import React from "react"
import ReactDOM from "react-dom"; import ReactDOM from "react-dom"
import "./index.css"; import "./index.css"
import App from "./App"; import App from "./App"
import * as serviceWorker from "./serviceWorker"; import * as serviceWorker from "./serviceWorker"
import Firebase, { FirebaseContext } from "./components/firebase"; import Firebase, { FirebaseContext } from "./components/firebase"
ReactDOM.render( ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}> <FirebaseContext.Provider value={new Firebase()}>
<App /> <App />
</FirebaseContext.Provider>, </FirebaseContext.Provider>,
document.getElementById("root") document.getElementById("root")
); )
// If you want your app to work offline and load faster, you can change // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls. // unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA // Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.register(); serviceWorker.register()

View File

@@ -18,25 +18,25 @@ const isLocalhost = Boolean(
window.location.hostname.match( window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
) )
); )
export function register(config) { export function register(config) {
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
if (publicUrl.origin !== window.location.origin) { if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin // Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to // from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374 // serve assets; see https://github.com/facebook/create-react-app/issues/2374
return; return
} }
window.addEventListener("load", () => { window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) { if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not. // This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config); checkValidServiceWorker(swUrl, config)
// Add some additional logging to localhost, pointing developers to the // Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation. // service worker/PWA documentation.
@@ -44,13 +44,13 @@ export function register(config) {
console.log( console.log(
"This web app is being served cache-first by a service " + "This web app is being served cache-first by a service " +
"worker. To learn more, visit https://bit.ly/CRA-PWA" "worker. To learn more, visit https://bit.ly/CRA-PWA"
); )
}); })
} else { } else {
// Is not localhost. Just register service worker // Is not localhost. Just register service worker
registerValidSW(swUrl, config); registerValidSW(swUrl, config)
} }
}); })
} }
} }
@@ -59,9 +59,9 @@ function registerValidSW(swUrl, config) {
.register(swUrl) .register(swUrl)
.then(registration => { .then(registration => {
registration.onupdatefound = () => { registration.onupdatefound = () => {
const installingWorker = registration.installing; const installingWorker = registration.installing
if (installingWorker == null) { if (installingWorker == null) {
return; return
} }
installingWorker.onstatechange = () => { installingWorker.onstatechange = () => {
if (installingWorker.state === "installed") { if (installingWorker.state === "installed") {
@@ -72,30 +72,30 @@ function registerValidSW(swUrl, config) {
console.log( console.log(
"New content is available and will be used when all " + "New content is available and will be used when all " +
"tabs for this page are closed. See https://bit.ly/CRA-PWA." "tabs for this page are closed. See https://bit.ly/CRA-PWA."
); )
// Execute callback // Execute callback
if (config && config.onUpdate) { if (config && config.onUpdate) {
config.onUpdate(registration); config.onUpdate(registration)
} }
} else { } else {
// At this point, everything has been precached. // At this point, everything has been precached.
// It's the perfect time to display a // It's the perfect time to display a
// "Content is cached for offline use." message. // "Content is cached for offline use." message.
console.log("Content is cached for offline use."); console.log("Content is cached for offline use.")
// Execute callback // Execute callback
if (config && config.onSuccess) { if (config && config.onSuccess) {
config.onSuccess(registration); config.onSuccess(registration)
} }
} }
} }
}; }
}; }
}) })
.catch(error => { .catch(error => {
console.error("Error during service worker registration:", error); console.error("Error during service worker registration:", error)
}); })
} }
function checkValidServiceWorker(swUrl, config) { function checkValidServiceWorker(swUrl, config) {
@@ -103,7 +103,7 @@ function checkValidServiceWorker(swUrl, config) {
fetch(swUrl) fetch(swUrl)
.then(response => { .then(response => {
// Ensure service worker exists, and that we really are getting a JS file. // Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get("content-type"); const contentType = response.headers.get("content-type")
if ( if (
response.status === 404 || response.status === 404 ||
(contentType != null && contentType.indexOf("javascript") === -1) (contentType != null && contentType.indexOf("javascript") === -1)
@@ -111,25 +111,25 @@ function checkValidServiceWorker(swUrl, config) {
// No service worker found. Probably a different app. Reload the page. // No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => { registration.unregister().then(() => {
window.location.reload(); window.location.reload()
}); })
}); })
} else { } else {
// Service worker found. Proceed as normal. // Service worker found. Proceed as normal.
registerValidSW(swUrl, config); registerValidSW(swUrl, config)
} }
}) })
.catch(() => { .catch(() => {
console.log( console.log(
"No internet connection found. App is running in offline mode." "No internet connection found. App is running in offline mode."
); )
}); })
} }
export function unregister() { export function unregister() {
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then(registration => {
registration.unregister(); registration.unregister()
}); })
} }
} }

View File

@@ -3,5 +3,6 @@ export const SIZES = {
small: "1rem", small: "1rem",
normal: "1.25rem", normal: "1.25rem",
medium: "1.5rem", medium: "1.5rem",
large: "2rem" large: "2rem",
}; maxWidth: "600px",
}

View File

@@ -9,8 +9,8 @@ const theme = {
quarternary: "#EAEAEA", quarternary: "#EAEAEA",
headerBackground: "#FAFBFC", headerBackground: "#FAFBFC",
bodyBackground: "#FFF", bodyBackground: "#FFF",
hover: "hsla(233, 5%, 31%, 0.12)" hover: "hsla(233, 5%, 31%, 0.12)",
} },
}, },
DARK: { DARK: {
name: "Dark", name: "Dark",
@@ -22,9 +22,9 @@ const theme = {
quarternary: "#3E4B62", quarternary: "#3E4B62",
headerBackground: "#272f3d", headerBackground: "#272f3d",
bodyBackground: "#262B34", bodyBackground: "#262B34",
hover: "hsla(233, 100%, 96%, 0.12)" hover: "hsla(233, 100%, 96%, 0.12)",
} },
} },
}; }
export default theme; export default theme

View File

@@ -1,12 +1,12 @@
export const pad = n => (n < 10 ? "0" : "") + n; export const pad = n => (n < 10 ? "0" : "") + n
export const todayUrl = (date = new Date()) => export const todayUrl = (date = new Date()) =>
`/${date.getFullYear()}/${pad(date.getMonth() + 1)}/${pad(date.getDate())}/`; `/${date.getFullYear()}/${pad(date.getMonth() + 1)}/${pad(date.getDate())}/`
export const yearUrl = (date = new Date()) => `/${date.getFullYear()}/`; export const yearUrl = (date = new Date()) => `/${date.getFullYear()}/`
export const months = { export const months = {
long: Array.from({ length: 12 }, (x, index) => long: Array.from({ length: 12 }, (x, index) =>
new Date(0, index).toLocaleDateString("en-US", { month: "long" }) new Date(0, index).toLocaleDateString("en-US", { month: "long" })
) ),
}; }