├── functions ├── .gitignore ├── index.js ├── package.json └── .eslintrc.json ├── src ├── img │ ├── splash.png │ ├── landing-graphic-dark.png │ └── landing-graphic-light.png ├── components │ ├── session │ │ ├── context.js │ │ ├── index.js │ │ └── withAuthentication.js │ ├── firebase │ │ ├── index.js │ │ ├── context.js │ │ └── fire.js │ ├── context │ │ ├── online.js │ │ └── theme.js │ ├── container.js │ ├── SignOut.js │ ├── PrivateRoute.js │ ├── ResendNotice.js │ ├── Footer.js │ ├── Seek.js │ ├── Layout.js │ ├── elements.js │ ├── Icon.js │ ├── Logo.js │ ├── App.js │ └── Navbar.js ├── styles │ ├── constants.js │ └── theme.js ├── utils │ └── date.js ├── pages │ ├── app.js │ ├── index.js │ ├── resetpassword.js │ ├── login.js │ ├── terms.js │ ├── privacy.js │ └── register.js ├── routes │ ├── Year.js │ ├── Welcome.js │ ├── Month.js │ ├── Search.js │ ├── User.js │ ├── Start.js │ └── Day.js ├── data │ └── quotes.json └── serviceWorker.js ├── .prettierrc ├── gatsby-node.js ├── .firebaserc ├── .env.sample ├── firestore.rules ├── gatsby-ssr.js ├── .gitignore ├── firebase.json ├── firestore.indexes.json ├── gatsby-browser.js ├── LICENSE ├── public └── index.html ├── package.json ├── .firebase ├── hosting.YnVpbGQ.cache └── hosting.cHVibGlj.cache ├── gatsby-config.js ├── SETUP.md └── README.md /functions/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /src/img/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillkyle/sol-journal/HEAD/src/img/splash.png -------------------------------------------------------------------------------- /src/img/landing-graphic-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillkyle/sol-journal/HEAD/src/img/landing-graphic-dark.png -------------------------------------------------------------------------------- /src/img/landing-graphic-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillkyle/sol-journal/HEAD/src/img/landing-graphic-light.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "singleQuote": false, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /src/components/session/context.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | const AuthUserContext = React.createContext(null) 4 | 5 | export default AuthUserContext 6 | -------------------------------------------------------------------------------- /src/components/session/index.js: -------------------------------------------------------------------------------- 1 | import AuthUserContext from "./context" 2 | import withAuthentication from "./withAuthentication" 3 | 4 | export { AuthUserContext, withAuthentication } 5 | -------------------------------------------------------------------------------- /src/components/firebase/index.js: -------------------------------------------------------------------------------- 1 | import FirebaseContext, { withFirebase } from "./context" 2 | import Firebase from "./fire" 3 | 4 | export default Firebase 5 | 6 | export { FirebaseContext, withFirebase } 7 | -------------------------------------------------------------------------------- /src/styles/constants.js: -------------------------------------------------------------------------------- 1 | export const SIZES = { 2 | tiny: "0.75rem", 3 | small: "1rem", 4 | normal: "1.25rem", 5 | medium: "1.5rem", 6 | large: "2rem", 7 | maxWidth: "600px", 8 | smallWidth: "300px", 9 | } 10 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | exports.onCreatePage = async ({ page, actions }) => { 2 | const { createPage } = actions 3 | 4 | if (page.path.match(/^\/app/)) { 5 | page.matchPath = "/app/*" 6 | 7 | createPage(page) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/context/online.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | // when navigator is available outside of the build phase, provide it through Context 4 | export const OnlineContext = React.createContext({ 5 | online: typeof window !== "undefined" && navigator && navigator.onLine, 6 | }) 7 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "journal-app-service", 4 | "prod": "sol-journal" 5 | }, 6 | "targets": { 7 | "sol-journal": { 8 | "hosting": { 9 | "soljournal": [ 10 | "soljournal" 11 | ] 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | const functions = require('firebase-functions'); 2 | 3 | // // Create and Deploy Your First Cloud Functions 4 | // // https://firebase.google.com/docs/functions/write-firebase-functions 5 | // 6 | // exports.helloWorld = functions.https.onRequest((request, response) => { 7 | // response.send("Hello from Firebase!"); 8 | // }); 9 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | GATSBY_FIREBASE_API_KEY= 2 | GATSBY_DEV_AUTH_DOMAIN=.firebaseapp.com 3 | GATSBY_DEV_DATABASE_URL=https://.firebaseio.com 4 | GATSBY_DEV_PROJECT_ID= 5 | GATSBY_DEV_STORAGE_BUCKET=.appspot.com 6 | GATSBY_DEV_MESSAGING_SENDER_ID=############ 7 | 8 | GATSBY_CONFIRMATION_EMAIL_REDIRECT=https://.firebaseapp.com -------------------------------------------------------------------------------- /src/components/firebase/context.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | // create context of firebase instance 4 | const FirebaseContext = React.createContext(null) 5 | 6 | export const withFirebase = Component => props => ( 7 | 8 | {firebase => } 9 | 10 | ) 11 | 12 | export default FirebaseContext 13 | -------------------------------------------------------------------------------- /firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | allow read: if request.auth != null 5 | && request.auth.uid == resource.data.userId 6 | && request.auth.token.email_verified; 7 | allow write: if request.auth != null 8 | && request.auth.token.email_verified; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/container.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import styled from "@emotion/styled" 3 | 4 | import { SIZES } from "styles/constants" 5 | 6 | const Container = styled.div` 7 | display: flex; 8 | flex-direction: column; 9 | height: 100%; 10 | margin: 0 auto; 11 | padding: 0 10px; 12 | max-width: ${SIZES.maxWidth}; 13 | min-height: calc(100vh - 60px); 14 | ` 15 | 16 | export default Container 17 | -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { ThemeProvider } from "emotion-theming" 3 | import Firebase, { FirebaseContext } from "./src/components/firebase" 4 | import theme from "./src/styles/theme" 5 | 6 | export const wrapRootElement = ({ element }) => ( 7 | 8 | {element} 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /src/utils/date.js: -------------------------------------------------------------------------------- 1 | export const pad = n => (n < 10 ? "0" : "") + n 2 | 3 | export const todayUrl = (date = new Date()) => 4 | `/${date.getFullYear()}/${pad(date.getMonth() + 1)}/${pad(date.getDate())}/` 5 | 6 | export const yearUrl = (date = new Date()) => `/${date.getFullYear()}/` 7 | 8 | export const months = { 9 | long: Array.from({ length: 12 }, (x, index) => 10 | new Date(0, index).toLocaleDateString("en-US", { month: "long" }) 11 | ), 12 | } 13 | -------------------------------------------------------------------------------- /src/pages/app.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import App from "components/App" 4 | import Layout from "components/Layout" 5 | 6 | /* similar to create-react-app, the App.js is like the 7 | entrypoint to the protected/client only content, 8 | Context providers are moved to gatsby-browser.js 9 | to wrap the root element with necessary providers 10 | and context */ 11 | export default () => ( 12 | 13 | 14 | 15 | ) 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.local 18 | .env.development 19 | .env.development.local 20 | .env.test.local 21 | .env.production 22 | .env.production.local 23 | .env.staging 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | .cache 30 | public 31 | /public -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { SimpleNavbar } from "components/Navbar" 3 | import Index from "routes/Start" 4 | import Layout from "components/Layout" 5 | import Container from "components/container" 6 | 7 | // the landing page is generated like other Gatsby pages 8 | // other unprotected routes like /login, /privacy, etc 9 | // are completely server side rendered by gatsby build 10 | export default () => ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | ) 18 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "hosting": { 7 | "target": "soljournal", 8 | "public": "build", 9 | "ignore": [ 10 | "firebase.json", 11 | "**/.*", 12 | "**/node_modules/**" 13 | ], 14 | "rewrites": [ 15 | { 16 | "source": "**", 17 | "destination": "/index.html" 18 | } 19 | ] 20 | }, 21 | "functions": { 22 | "predeploy": [ 23 | "npm --prefix \"$RESOURCE_DIR\" run lint" 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/SignOut.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { compose } from "recompose" 3 | import { withTheme } from "emotion-theming" 4 | 5 | import { Button } from "components/elements" 6 | 7 | import { withFirebase } from "components/firebase" 8 | 9 | const SignOutButton = ({ firebase, theme }) => ( 10 | 18 | ) 19 | 20 | export default compose( 21 | withTheme, 22 | withFirebase 23 | )(SignOutButton) 24 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "scripts": { 5 | "lint": "eslint .", 6 | "serve": "firebase serve --only functions", 7 | "shell": "firebase functions:shell", 8 | "start": "npm run shell", 9 | "deploy": "firebase deploy --only functions", 10 | "logs": "firebase functions:log" 11 | }, 12 | "dependencies": { 13 | "firebase-admin": "~7.0.0", 14 | "firebase-functions": "^2.2.0" 15 | }, 16 | "devDependencies": { 17 | "eslint": "^5.12.0", 18 | "eslint-plugin-promise": "^4.0.1" 19 | }, 20 | "private": true 21 | } 22 | -------------------------------------------------------------------------------- /firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | // Example: 3 | // 4 | // "indexes": [ 5 | // { 6 | // "collectionGroup": "widgets", 7 | // "queryScope": "COLLECTION", 8 | // "fields": [ 9 | // { "fieldPath": "foo", "arrayConfig": "CONTAINS" }, 10 | // { "fieldPath": "bar", "mode": "DESCENDING" } 11 | // ] 12 | // }, 13 | // 14 | // "fieldOverrides": [ 15 | // { 16 | // "collectionGroup": "widgets", 17 | // "fieldPath": "baz", 18 | // "indexes": [ 19 | // { "order": "ASCENDING", "queryScope": "COLLECTION" } 20 | // ] 21 | // }, 22 | // ] 23 | // ] 24 | "indexes": [], 25 | "fieldOverrides": [] 26 | } -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { ThemeProvider as EmotionThemeProvider } from "emotion-theming" 3 | import Firebase, { FirebaseContext } from "components/firebase" 4 | import theme from "styles/theme" 5 | import ThemeTogglerContext, { ThemeToggler } from "components/context/theme" 6 | 7 | export const wrapRootElement = ({ element }) => { 8 | return ( 9 | 10 | 11 | 12 | {({ themeName }) => ( 13 | 14 | {element} 15 | 16 | )} 17 | 18 | 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/components/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Redirect, Location } from "@reach/router" 3 | import ResendNotice from "components/ResendNotice" 4 | // when a user isn't logged in, force a redirect 5 | const PrivateRoute = ({ 6 | component: Component, 7 | authed, 8 | authUser, 9 | emailVerificationUnnecessary = true, 10 | ...rest 11 | }) => { 12 | return ( 13 | 14 | {({ location }) => 15 | authed === true ? ( 16 | emailVerificationUnnecessary || authUser.emailVerified ? ( 17 | 18 | ) : ( 19 | 20 | ) 21 | ) : ( 22 | 23 | ) 24 | } 25 | 26 | ) 27 | } 28 | 29 | export default PrivateRoute 30 | -------------------------------------------------------------------------------- /src/styles/theme.js: -------------------------------------------------------------------------------- 1 | // standardized role-based design tokens used throughout the whole app 2 | // a name like lightGray doesn't make sense with themese when light 3 | // and dark are possibile 4 | const theme = { 5 | LIGHT: { 6 | name: "LIGHT", 7 | colors: { 8 | logo: "#344157", 9 | primary: "#2E3136", 10 | secondary: "#999", 11 | tertiary: "#C4C4C4", 12 | quarternary: "#EAEAEA", 13 | headerBackground: "#FAFBFC", 14 | bodyBackground: "#FFF", 15 | inputBackground: "#FAFBFC", 16 | hover: "hsla(233, 5%, 31%, 0.12)", 17 | button: "#f2f3f5", 18 | }, 19 | }, 20 | DARK: { 21 | name: "DARK", 22 | colors: { 23 | logo: "#EAEAEA", 24 | primary: "#F3F6F8", 25 | secondary: "#9Ba3B0", 26 | tertiary: "#6F7682", 27 | quarternary: "#3E4B62", 28 | headerBackground: "#272f3d", 29 | bodyBackground: "#262B34", 30 | inputBackground: "#272f3d", 31 | hover: "hsla(233, 100%, 96%, 0.12)", 32 | button: "#464d5d", 33 | }, 34 | }, 35 | } 36 | 37 | export default theme 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kyle Gill 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/ResendNotice.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import styled from "@emotion/styled" 3 | import { withTheme } from "emotion-theming" 4 | import { compose } from "recompose" 5 | 6 | import { withFirebase } from "components/firebase" 7 | import { Button } from "components/elements" 8 | import Logo from "components/Logo" 9 | 10 | const NoticeBlock = styled.footer` 11 | margin-top: 30px; 12 | padding: 30px 0px; 13 | text-align: center; 14 | color: ${props => props.theme.colors.secondary}; 15 | ` 16 | 17 | const ResendNotice = ({ theme, firebase }) => ( 18 | 19 |
20 | 21 |
22 |
23 | Looks like you haven't verified your email, please verify before using the 24 | app 25 |
26 |
27 | 36 |
37 |
38 | ) 39 | 40 | export default compose( 41 | withFirebase, 42 | withTheme 43 | )(ResendNotice) 44 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sol-journal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/core": "^10.0.10", 7 | "@emotion/styled": "^10.0.10", 8 | "babel-plugin-emotion": "^10.0.9", 9 | "date-fns": "^1.30.1", 10 | "emotion-theming": "^10.0.10", 11 | "firebase": "^5.9.0", 12 | "gatsby": "^2.4.3", 13 | "gatsby-image": "^2.1.0", 14 | "gatsby-plugin-create-client-paths": "^2.0.5", 15 | "gatsby-plugin-emotion": "^4.0.6", 16 | "gatsby-plugin-manifest": "^2.1.1", 17 | "gatsby-plugin-module-resolver": "^1.0.3", 18 | "gatsby-plugin-offline": "^2.1.0", 19 | "gatsby-plugin-react-helmet": "^3.0.12", 20 | "gatsby-plugin-sharp": "^2.0.37", 21 | "gatsby-plugin-web-font-loader": "^1.0.4", 22 | "gatsby-source-filesystem": "^2.0.36", 23 | "gatsby-transformer-json": "^2.1.11", 24 | "gatsby-transformer-sharp": "^2.1.19", 25 | "react": "^16.8.4", 26 | "react-dom": "^16.8.4", 27 | "react-feather": "^1.1.6", 28 | "react-helmet": "^5.2.1", 29 | "react-router-dom": "^5.0.0", 30 | "react-spinners": "^0.5.3", 31 | "recompose": "^0.30.0" 32 | }, 33 | "scripts": { 34 | "start": "gatsby develop", 35 | "build": "gatsby build", 36 | "test": "react-scripts test", 37 | "eject": "react-scripts eject" 38 | }, 39 | "eslintConfig": { 40 | "extends": "react-app" 41 | }, 42 | "browserslist": [ 43 | ">0.2%", 44 | "not dead", 45 | "not ie <= 11", 46 | "not op_mini all" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link } from "gatsby" 3 | import { css } from "@emotion/core" 4 | import styled from "@emotion/styled" 5 | import { withTheme } from "emotion-theming" 6 | 7 | import Logo from "components/Logo" 8 | 9 | const FooterBlock = styled.footer` 10 | margin-top: 120px; 11 | padding: 30px 0px; 12 | text-align: center; 13 | color: ${props => props.theme.colors.secondary}; 14 | ` 15 | const linkStyles = css` 16 | cursor: pointer; 17 | text-decoration: none; 18 | margin: 10px; 19 | ` 20 | const FooterLink = styled(Link)` 21 | ${linkStyles} 22 | color: ${props => props.theme.colors.secondary}; 23 | &:hover { 24 | color: ${props => props.theme.colors.tertiary}; 25 | } 26 | ` 27 | const FooterAnchor = styled.a` 28 | ${linkStyles} 29 | color: ${props => props.theme.colors.secondary}; 30 | &:hover { 31 | color: ${props => props.theme.colors.tertiary}; 32 | } 33 | ` 34 | 35 | const Footer = ({ theme }) => ( 36 | 37 |
38 | 39 |
40 |
41 | 46 | View on GitHub 47 | 48 | Terms of Service 49 | Privacy Policy 50 |
51 |
© 2019
52 |
53 | ) 54 | 55 | export default withTheme(Footer) 56 | -------------------------------------------------------------------------------- /src/components/Seek.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import styled from "@emotion/styled" 3 | import { withTheme } from "emotion-theming" 4 | 5 | import Icon from "components/Icon" 6 | import { H1 } from "components/elements" 7 | import { StyledLink as Link } from "components/elements" 8 | 9 | const SeekHeader = styled.header` 10 | display: flex; 11 | flex-direction: row; 12 | justify-content: space-between; 13 | align-items: center; 14 | border-bottom-width: 1px; 15 | border-bottom-style: solid; 16 | border-color: ${props => props.theme.colors.quarternary}; 17 | margin-top: 15px; 18 | ` 19 | const SeekArrows = styled.div` 20 | display: grid; 21 | grid-template-columns: auto auto; 22 | grid-gap: 10px; 23 | ` 24 | 25 | const Seek = ({ title = "", prev = "", next = "", disableNext, theme }) => ( 26 | 27 |

{title}

28 | 29 | 30 | 31 | 32 | {disableNext ? ( 33 | 40 | ) : ( 41 | 42 | 49 | 50 | )} 51 | 52 |
53 | ) 54 | 55 | export default withTheme(Seek) 56 | -------------------------------------------------------------------------------- /src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Helmet } from "react-helmet" 3 | import { Global, css } from "@emotion/core" 4 | import styled from "@emotion/styled" 5 | 6 | import { withTheme } from "emotion-theming" 7 | 8 | const Layout = ({ children, theme }) => ( 9 | <> 10 | 11 | {/* some styles should applied globally via the layout */} 12 | 43 | {children} 44 | 45 | ) 46 | 47 | export default withTheme(Layout) 48 | -------------------------------------------------------------------------------- /.firebase/hosting.YnVpbGQ.cache: -------------------------------------------------------------------------------- 1 | asset-manifest.json,1556898463490,f723129505a2111ca263ef5479bd10b1f0efc470c951b596f13dcf48d8649c7e 2 | manifest.json,1555998067072,20792c911b58103a520ddb6b0f7bcc7ce3bceaa15480cbfd913f8b3620864341 3 | precache-manifest.a096a268b779b4480b9a46727f75551b.js,1556898463490,3ee2e9cfd0185210ebe9c8fe3dd342b86254c667c2e9b51369438728c9f7e324 4 | index.html,1556898463490,d2791f63e8652b46a6a92df716e200ef6670799c079df85344ad44c0732a0338 5 | icon.png,1555998067072,40bb863e95a5ea2c01be6eba0beb6801183746f99d40eb393a6b4776a11d8636 6 | service-worker.js,1556898463490,de05f8df6933f0d1ff232f36ce73e94b0faafe0ac3ce9882fbbacc7fc608fc2d 7 | static/css/main.1716334c.chunk.css,1556898463519,dadd472a021e6f3502d58df7b11455a233a9a25f87ce536e693c18db20036e09 8 | reactfavicon.ico,1555998067072,b72f7455f00e4e58792d2bca892abb068e2213838c0316d6b7a0d6d16acd1955 9 | static/css/main.1716334c.chunk.css.map,1556898463519,c50d028b9046b1664c1a246227bee082eaf7a5f46f8857aad8963283c298dacd 10 | static/js/runtime~main.a8a9905a.js,1556898463519,e1af5f94fdd13901b2e433d0d7607e27c01458151c35b1fe4b7feda2a32b7aa9 11 | splash.png,1555998067072,e06cb28b9a2a8275ce53eb5eead2851f684f537a6a30f0f0bf360b8813fa273f 12 | favicon.ico,1555998067071,229055d54fe1f70f3d835e9d723ea2fef78f2af82ed7ce45efa2f4623c1c1131 13 | static/js/main.a10ab5ea.chunk.js,1556898463491,04608cac454dbaa153b80f00064f3cb33edc8aad4a2710d56a385fd8e86a3bc5 14 | static/js/runtime~main.a8a9905a.js.map,1556898463519,c337bf8b58896da637a6e50ab8cfc779eb1ec42c55f8ec429030a03454a549db 15 | static/js/main.a10ab5ea.chunk.js.map,1556898463521,777ecf9469d772313dec6ecc4b5d7de7425b13cf8529747c21e02254c9251081 16 | static/js/2.3f4eb03d.chunk.js,1556898463520,47a71ca572b94638afd405721c3ef6fb4aef5962dd0db71eb6ff9f284be1cd48 17 | static/js/2.3f4eb03d.chunk.js.map,1556898463521,79d34370558394348a525ac8b13043f85a4b8ba81ecc94a4d7ed54ebb1b33bcd 18 | -------------------------------------------------------------------------------- /src/components/context/theme.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Helmet } from "react-helmet" 3 | import theme from "styles/theme" 4 | 5 | // create an app-wide context for the theme being used as 6 | // well as a function to toggle it back and forth 7 | const ThemeTogglerContext = React.createContext({ 8 | themeName: "LIGHT", 9 | toggle: () => {}, 10 | }) 11 | 12 | class ThemeToggler extends React.Component { 13 | state = { 14 | themeName: 15 | new Date().getHours() >= 7 && new Date().getHours() <= 21 16 | ? "LIGHT" 17 | : "DARK", 18 | } 19 | 20 | componentDidMount() { 21 | // set the body style property on mount so routes don't flash between transitions 22 | const { themeName } = this.state 23 | this.toggle(themeName) 24 | } 25 | 26 | toggle = newThemeName => { 27 | const { themeName } = this.state 28 | const body = document.body 29 | let newTheme 30 | if (newThemeName) { 31 | newTheme = newThemeName 32 | } else { 33 | newTheme = themeName === "LIGHT" ? "DARK" : "LIGHT" 34 | } 35 | body.style.setProperty( 36 | "background-color", 37 | theme[newTheme].colors.bodyBackground 38 | ) 39 | 40 | this.setState({ themeName: newTheme }) 41 | } 42 | 43 | render() { 44 | const { children } = this.props 45 | const { themeName } = this.state 46 | return ( 47 | 53 | 54 | 58 | 59 | {children} 60 | 61 | ) 62 | } 63 | } 64 | 65 | export default ThemeTogglerContext 66 | 67 | export { ThemeToggler } 68 | -------------------------------------------------------------------------------- /src/components/session/withAuthentication.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import AuthUserContext from "./context" 4 | import { withFirebase } from "components/firebase" 5 | 6 | // use context to provide all app components with information about 7 | // the authUser if it's been put in localStorage 8 | const withAuthentication = Component => { 9 | class WithAuthentication extends React.Component { 10 | constructor(props) { 11 | super(props) 12 | 13 | // protect browser API's like localStorage so Gatsby builds don't fail 14 | if (typeof window !== "undefined") { 15 | this.state = { 16 | authUser: JSON.parse(localStorage.getItem("authUser")), 17 | } 18 | } else { 19 | this.state = { authUser: null } 20 | } 21 | } 22 | 23 | componentDidMount() { 24 | this.listener = this.props.firebase.auth.onAuthStateChanged( 25 | authUser => { 26 | // accessing localStorage in componentDidMount is fine in Gatsby 27 | localStorage.setItem("authUser", JSON.stringify(authUser)) 28 | if (authUser) { 29 | this.setState({ 30 | authUser: { 31 | uid: authUser.uid, 32 | email: authUser.email, 33 | emailVerified: authUser.emailVerified, 34 | }, 35 | }) 36 | } 37 | }, 38 | () => { 39 | localStorage.removeItem("authUser") 40 | this.setState({ authUser: null }) 41 | } 42 | ) 43 | } 44 | 45 | componentWillUnmount() { 46 | this.listener() 47 | } 48 | 49 | render() { 50 | return ( 51 | 52 | 53 | 54 | ) 55 | } 56 | } 57 | 58 | return withFirebase(WithAuthentication) 59 | } 60 | 61 | export default withAuthentication 62 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | `gatsby-plugin-react-helmet`, 4 | `gatsby-plugin-emotion`, 5 | // create routes for client side routing 6 | { 7 | resolve: `gatsby-plugin-create-client-paths`, 8 | options: { prefixes: [`/app/*`] }, 9 | }, 10 | // provide fonts from Google fonts 11 | // { 12 | // resolve: `gatsby-plugin-prefetch-google-fonts`, 13 | // options: { 14 | // fonts: [ 15 | // { 16 | // family: `Montserrat`, 17 | // variants: [`400`, `700`], 18 | // }, 19 | // ], 20 | // }, 21 | // }, 22 | { 23 | resolve: 'gatsby-plugin-web-font-loader', 24 | options: { 25 | google: { 26 | families: ['Montserrat:400,700'] 27 | } 28 | } 29 | }, 30 | // plugins for PWA support 31 | `gatsby-plugin-offline`, 32 | { 33 | resolve: `gatsby-plugin-manifest`, 34 | options: { 35 | name: `Sol Journal`, 36 | short_name: `Sol Journal`, 37 | start_url: `/app`, 38 | background_color: `#FFF`, 39 | theme_color: `#FFF`, 40 | display: `standalone`, 41 | icon: `src/img/splash.png`, 42 | }, 43 | }, 44 | // plugins for optimized images 45 | `gatsby-transformer-sharp`, 46 | `gatsby-plugin-sharp`, 47 | { 48 | resolve: `gatsby-source-filesystem`, 49 | options: { 50 | name: `images`, 51 | path: `${__dirname}/src/img`, 52 | }, 53 | }, 54 | // parse data from /src/data as Javascrip objects 55 | `gatsby-transformer-json`, 56 | { 57 | resolve: `gatsby-source-filesystem`, 58 | options: { 59 | path: `./src/data/`, 60 | }, 61 | }, 62 | // easier imports and exports by defining aliases 63 | // for commonly used folders 64 | { 65 | resolve: "gatsby-plugin-module-resolver", 66 | options: { 67 | root: "./src", 68 | aliases: { 69 | components: "./components", 70 | data: "./data", 71 | img: "./img", 72 | routes: "./routes", 73 | styles: "./styles", 74 | utils: "./utils", 75 | }, 76 | }, 77 | }, 78 | ], 79 | } 80 | -------------------------------------------------------------------------------- /src/components/firebase/fire.js: -------------------------------------------------------------------------------- 1 | import app from "firebase/app" 2 | import "firebase/auth" 3 | import "firebase/firestore" 4 | 5 | // store private keys in .env file 6 | // the prefix GATSBY_ is necessary here 7 | const config = { 8 | apiKey: process.env.GATSBY_FIREBASE_API_KEY, 9 | authDomain: process.env.GATSBY_DEV_AUTH_DOMAIN, 10 | databaseURL: process.env.GATSBY_DEV_DATABASE_URL, 11 | projectId: process.env.GATSBY_DEV_PROJECT_ID, 12 | storageBucket: process.env.GATSBY_DEV_STORAGE_BUCKET, 13 | messagingSenderId: process.env.GATSBY_DEV_MESSAGING_SENDER_ID, 14 | } 15 | 16 | class Firebase { 17 | constructor() { 18 | // protect with conditional so gatsby build doesn't have 19 | // issues trying to access the window object 20 | if (typeof window !== "undefined") { 21 | app.initializeApp(config) 22 | this.auth = app.auth() 23 | this.db = app.firestore() 24 | app 25 | .firestore() 26 | .enablePersistence() 27 | .catch(function(err) { 28 | if (err.code === "failed-precondition") { 29 | console.error( 30 | "firestore won't work offline with multiple tabs open" 31 | ) 32 | } else if (err.code === "unimplemented") { 33 | console.error( 34 | "current browser can't take advantage of firestore offline" 35 | ) 36 | } 37 | }) 38 | } 39 | } 40 | 41 | // authentication 42 | // create user in the database 43 | doCreateUserWithEmailAndPassword = (email, password) => 44 | this.auth.createUserWithEmailAndPassword(email, password) 45 | 46 | // login already existing user 47 | doSignInWithEmailAndPassword = (email, password) => 48 | this.auth.signInWithEmailAndPassword(email, password) 49 | 50 | // sign out user 51 | doSignOut = () => { 52 | this.auth.signOut() 53 | window.location.replace("/login") 54 | } 55 | 56 | // send email reset to email provided 57 | resendVerification = () => this.auth.currentUser.sendEmailVerification() 58 | 59 | // send email reset to email provided 60 | doPasswordReset = email => this.auth.sendPasswordResetEmail(email) 61 | 62 | // change password to password provided 63 | doPasswordUpdate = password => this.auth.currentUser.updatePassword(password) 64 | } 65 | 66 | export default Firebase 67 | -------------------------------------------------------------------------------- /src/routes/Year.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react" 2 | import { addYears, subYears, format, isThisYear, getMonth } from "date-fns" 3 | import styled from "@emotion/styled" 4 | import { withTheme } from "emotion-theming" 5 | 6 | import { AppLink as Link } from "components/elements" 7 | import Seek from "components/Seek" 8 | import { months } from "utils/date" 9 | 10 | const MonthCardGrid = styled.div` 11 | display: grid; 12 | grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); 13 | grid-gap: 20px; 14 | margin-top: 20px; 15 | ` 16 | const MonthCard = styled.div` 17 | color: ${props => 18 | props.disabled 19 | ? props.theme.colors.quarternary 20 | : props.theme.colors.secondary}; 21 | border: 1px solid; 22 | border-color: ${props => props.theme.colors.quarternary}; 23 | padding: 40px; 24 | border-radius: 5px; 25 | text-align: center; 26 | user-select: none; 27 | &:hover { 28 | border-color: ${props => !props.disabled && props.theme.colors.tertiary}; 29 | } 30 | ` 31 | 32 | class Year extends Component { 33 | render() { 34 | const { year } = this.props 35 | const currentDate = new Date(year, 0, 1) 36 | 37 | // include all months unless it's this year 38 | let monthIndexesToInclude = 11 39 | if (isThisYear(currentDate)) { 40 | monthIndexesToInclude = getMonth(new Date()) 41 | } 42 | return ( 43 |
44 | = new Date().getFullYear()} 49 | /> 50 | 51 | {months.long.map((month, index) => { 52 | const isDisabled = monthIndexesToInclude < index 53 | return isDisabled ? ( 54 | 55 | {month} 56 | 57 | ) : ( 58 | 63 | {month} 64 | 65 | ) 66 | })} 67 | 68 |
69 | ) 70 | } 71 | } 72 | 73 | export default withTheme(Year) 74 | -------------------------------------------------------------------------------- /src/routes/Welcome.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react" 2 | import { Helmet } from "react-helmet" 3 | import { Link } from "gatsby" 4 | import styled from "@emotion/styled" 5 | import { withTheme } from "emotion-theming" 6 | import { getDayOfYear } from "date-fns" 7 | 8 | import { SIZES } from "styles/constants" 9 | import { Button, P } from "components/elements" 10 | import Footer from "components/Footer" 11 | import { todayUrl } from "utils/date" 12 | 13 | import Quotes from "data/quotes.json" 14 | 15 | const WelcomeGrid = styled.div` 16 | text-align: center; 17 | margin-top: 60px; 18 | line-height: 1.5; 19 | color: ${props => props.theme.colors.primary}; 20 | display: flex; 21 | flex-direction: column; 22 | height: 100%; 23 | ` 24 | 25 | const Quote = styled.blockquote` 26 | margin-top: 30px; 27 | font-family: Montserrat; 28 | font-size: ${SIZES.medium}; 29 | color: ${props => props.theme.colors.primary}; 30 | ` 31 | const QuoteAuthor = styled(P)` 32 | font-style: italic; 33 | ` 34 | 35 | class Welcome extends Component { 36 | render() { 37 | const todaysQuote = Quotes[getDayOfYear(new Date()) % Quotes.length] 38 | const { theme } = this.props 39 | return ( 40 | 41 | 42 | 46 | 47 |
54 | " 55 |
56 | {todaysQuote.quote} 57 | 58 | - {todaysQuote.author} 59 | 60 |
61 |

62 | This your space for wandering thoughts and ideas. Write about 63 | whatever is on your mind. 64 |

65 |
66 |
67 | 68 | 69 | 70 |
71 |