chore: add comments, setup instructions
This commit is contained in:
29
README.md
29
README.md
@@ -8,7 +8,10 @@
|
||||
Sol Journal is a simple, minimal, journaling platform that works offline and across all devices. It can be self-hosted through Firebase and then installed as a PWA, on mobile devices for easy access on a phone, or on Desktops.
|
||||
|
||||
<p align="center">
|
||||
<img alt="preview of page" src="https://raw.githubusercontent.com/gillkyle/images/master/hero-mixed.png" />
|
||||
<img
|
||||
alt="preview of page"
|
||||
src="https://raw.githubusercontent.com/gillkyle/images/master/hero-mixed.png"
|
||||
/>
|
||||
</p>
|
||||
|
||||
## Introduction
|
||||
@@ -31,13 +34,25 @@ Sol Journal uses firebase to support offline functionality and authentication, m
|
||||
In the spirit of minimalism, key features are what are in place for a quick, lightweight journaling experience that can work across devices, including:
|
||||
|
||||
- 🔥 Authentication: Cloud firestore persists registered users to a users document and saved journal entries to an entries document
|
||||
- 🎨 Dark Theme: the `src/styles/theme.js` file contains a set of colors and default styles that are applied to components with Emotion. A default light and dark theme are already in the file
|
||||
- 🎨 Theming: the `src/styles/theme.js` file contains a set of colors and default styles that are applied to components with Emotion. A default light and dark theme are already in the file
|
||||
- 🔍 Search: full-text search of a user's entries stored in Firestore for quick access to past entries
|
||||
- 🖥 Mobile Friendly: designed to look great on mobile as well as desktop, with easy navigation on both
|
||||
- 💡 PWA: being a progressive web app makes it installable from Chrome/Safari on desktop, or be added to the homescreen on iOS/Android
|
||||
- 🔌 Offline Support: read/write when you're offline and let the updates happen when your connection is restored
|
||||
- 🗄 Export: backup all of your entries at any time to save your data
|
||||
|
||||
## Project setup
|
||||
|
||||
Files are organized into these folders:
|
||||
|
||||
`/components`: user interface pieces to construct the design and layout of the site
|
||||
`/data`: local data transformed by gatsby to become queryable by Gatsby's GraphQL data layer
|
||||
`/img`: images used by places like landing pages that are optimized by gatsby-image and then queryable in the GraphQL layer
|
||||
`/pages`: public pages that can be seen by unauthenticated users and are completely server side rendered by Gatsby during `gatsby build`
|
||||
`/routes`: private, client only routes only visible to authenticated users that are used by the app section of the journal
|
||||
`/styles`: role based design tokens and theme definitions
|
||||
`/util`: simple utility functions, for things like formatting dates
|
||||
|
||||
## Developing
|
||||
|
||||
Clone the project:
|
||||
@@ -74,10 +89,10 @@ GATSBY_CONFIRMATION_EMAIL_REDIRECT=https://<name>.firebaseapp.com
|
||||
Navigate into the project directory, and then launch the site with this command:
|
||||
|
||||
```bash
|
||||
yarn develop
|
||||
gatsby develop
|
||||
```
|
||||
|
||||
The site will be opened up in your default browser on http://localhost:3000
|
||||
The site will be opened up in your default browser on http://localhost:8000
|
||||
|
||||
Edit code in the `/src`, save your changes, and they'll reload instantly in the browser.
|
||||
|
||||
@@ -86,10 +101,12 @@ Edit code in the `/src`, save your changes, and they'll reload instantly in the
|
||||
To create an optimized build of the site run this command:
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
gatsby build
|
||||
```
|
||||
|
||||
A `/build` folder will be assembled that can be deployed with this command:
|
||||
A `/build` folder will be assembled that can be deployed to any static file hosting service like Netlify or surge.
|
||||
|
||||
It can be deployed to firebase with this command:
|
||||
|
||||
```bash
|
||||
firebase deploy
|
||||
|
||||
30
SETUP.md
30
SETUP.md
@@ -2,7 +2,6 @@
|
||||
|
||||
Setting up Sol Journal requires a free Firebase account since many of the features rely on built in Firebase capabilities to allow functionality offline. The free plan gives you 50,000 reads and 20,000 writes per day, meaning you can easily run a personal version for yourself.
|
||||
|
||||
|
||||
## Setting up Firebase
|
||||
|
||||
You will need a Google account to create a Firebase project, then navigate to the Firebase Console: https://console.firebase.google.com/
|
||||
@@ -71,7 +70,7 @@ In order to prevent would be hackers or nefarious folks from messing with data i
|
||||
|
||||
##### Configuration
|
||||
|
||||
## Code Setup
|
||||
## Setting up Code
|
||||
|
||||
Having node installed is a prerequistie, you can follow instructions to get it setup yourself.
|
||||
|
||||
@@ -88,3 +87,30 @@ firebase login
|
||||
```
|
||||
|
||||
Follow the prompted instructions to connect your account.
|
||||
|
||||
Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/gillkyle/sol-journal
|
||||
```
|
||||
|
||||
Then change the name of the `.env.sample` file to `.env` (or `.env.production`/`.env.development` if you would like to run the app in different environments) and fill it in, mapping your own keys from your Firebase project to the corresponding names in the file.
|
||||
|
||||
```env
|
||||
GATSBY_FIREBASE_API_KEY=<BUNCHofRandomNumbersAndChars>
|
||||
GATSBY_DEV_AUTH_DOMAIN=<name>.firebaseapp.com
|
||||
GATSBY_DEV_DATABASE_URL=https://<name>.firebaseio.com
|
||||
GATSBY_DEV_PROJECT_ID=<name>
|
||||
GATSBY_DEV_STORAGE_BUCKET=<name>.appspot.com
|
||||
GATSBY_DEV_MESSAGING_SENDER_ID=############
|
||||
|
||||
GATSBY_CONFIRMATION_EMAIL_REDIRECT=https://<name>.firebaseapp.com
|
||||
```
|
||||
|
||||
Then run the build command to generate an optimized build in the public directory.
|
||||
|
||||
```bash
|
||||
gatsby build
|
||||
```
|
||||
|
||||
You can upload your `/public` folder to S3, or run the surge command inside the directory to deploy it to a free subdomain.
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
`gatsby-plugin-react-helmet`,
|
||||
{
|
||||
resolve: `gatsby-plugin-emotion`,
|
||||
},
|
||||
`gatsby-plugin-emotion`,
|
||||
// create routes for client side routing
|
||||
{
|
||||
resolve: `gatsby-plugin-create-client-paths`,
|
||||
@@ -53,7 +51,8 @@ module.exports = {
|
||||
path: `./src/data/`,
|
||||
},
|
||||
},
|
||||
// easier imports and exports
|
||||
// easier imports and exports by defining aliases
|
||||
// for commonly used folders
|
||||
{
|
||||
resolve: "gatsby-plugin-module-resolver",
|
||||
options: {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { withTheme } from "emotion-theming"
|
||||
const Layout = ({ children, theme }) => (
|
||||
<>
|
||||
<Helmet title="Sol Journal" />
|
||||
{/* some styles should applied globally via the layout */}
|
||||
<Global
|
||||
styles={css`
|
||||
* {
|
||||
|
||||
@@ -111,6 +111,7 @@ const Navbar = ({ authUser, theme, toggleTheme }) => (
|
||||
</Header>
|
||||
)
|
||||
|
||||
// on langing page and simple pages outside the app, simplify link options
|
||||
const SimpleNav = ({ authUser, theme }) => (
|
||||
<Header>
|
||||
<Nav>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react"
|
||||
import { Redirect, Location } from "@reach/router"
|
||||
|
||||
// when a user isn't logged in, force a redirect
|
||||
const PrivateRoute = ({ component: Component, authed, ...rest }) => {
|
||||
return (
|
||||
<Location>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react"
|
||||
|
||||
// when navigator is available outside of the build phase, provide it through Context
|
||||
export const OnlineContext = React.createContext({
|
||||
online: typeof window !== "undefined" && navigator && navigator.onLine,
|
||||
})
|
||||
|
||||
@@ -2,6 +2,8 @@ import React from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import theme from "styles/theme"
|
||||
|
||||
// create an app-wide context for the theme being used as
|
||||
// well as a function to toggle it back and forth
|
||||
const ThemeTogglerContext = React.createContext({
|
||||
themeName: "LIGHT",
|
||||
toggle: () => {},
|
||||
|
||||
@@ -71,6 +71,7 @@ export const P = styled.p`
|
||||
color: ${props => props.theme.colors.secondary};
|
||||
`
|
||||
|
||||
// prepend links used within the app section with app
|
||||
export const AppLink = props => <Link {...props} to={"/app" + props.to} />
|
||||
|
||||
export const StyledLink = withTheme(styled(AppLink)`
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react"
|
||||
|
||||
// create context of firebase instance
|
||||
const FirebaseContext = React.createContext(null)
|
||||
|
||||
export const withFirebase = Component => props => (
|
||||
|
||||
@@ -2,6 +2,8 @@ import app from "firebase/app"
|
||||
import "firebase/auth"
|
||||
import "firebase/firestore"
|
||||
|
||||
// store private keys in .env file
|
||||
// the prefix GATSBY_ is necessary here
|
||||
const config = {
|
||||
apiKey: process.env.GATSBY_FIREBASE_API_KEY,
|
||||
authDomain: process.env.GATSBY_DEV_AUTH_DOMAIN,
|
||||
@@ -13,6 +15,8 @@ const config = {
|
||||
|
||||
class Firebase {
|
||||
constructor() {
|
||||
// protect with conditional so gatsby build doesn't have
|
||||
// issues trying to access the window object
|
||||
if (typeof window !== "undefined") {
|
||||
app.initializeApp(config)
|
||||
this.auth = app.auth()
|
||||
@@ -34,20 +38,25 @@ class Firebase {
|
||||
}
|
||||
}
|
||||
|
||||
// Auth
|
||||
// authentication
|
||||
// create user in the database
|
||||
doCreateUserWithEmailAndPassword = (email, password) =>
|
||||
this.auth.createUserWithEmailAndPassword(email, password)
|
||||
|
||||
// login already existing user
|
||||
doSignInWithEmailAndPassword = (email, password) =>
|
||||
this.auth.signInWithEmailAndPassword(email, password)
|
||||
|
||||
// sign out user
|
||||
doSignOut = () => {
|
||||
this.auth.signOut()
|
||||
window.location.replace("/login")
|
||||
}
|
||||
|
||||
// send email reset to email provided
|
||||
doPasswordReset = email => this.auth.sendPasswordResetEmail(email)
|
||||
|
||||
// change password to password provided
|
||||
doPasswordUpdate = password => this.auth.currentUser.updatePassword(password)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,14 @@ import React from "react"
|
||||
import AuthUserContext from "./context"
|
||||
import { withFirebase } from "components/firebase"
|
||||
|
||||
// use context to provide all app components with information about
|
||||
// the authUser if it's been put in localStorage
|
||||
const withAuthentication = Component => {
|
||||
class WithAuthentication extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
// protect browser API's like localStorage so Gatsby builds don't fail
|
||||
if (typeof window !== "undefined") {
|
||||
this.state = {
|
||||
authUser: JSON.parse(localStorage.getItem("authUser")),
|
||||
@@ -20,6 +23,7 @@ const withAuthentication = Component => {
|
||||
componentDidMount() {
|
||||
this.listener = this.props.firebase.auth.onAuthStateChanged(
|
||||
authUser => {
|
||||
// accessing localStorage in componentDidMount is fine in Gatsby
|
||||
localStorage.setItem("authUser", JSON.stringify(authUser))
|
||||
if (authUser) {
|
||||
this.setState({
|
||||
|
||||
@@ -3,6 +3,11 @@ import React from "react"
|
||||
import App from "../App"
|
||||
import Layout from "components/Layout"
|
||||
|
||||
/* similar to create-react-app, the App.js is like the
|
||||
entrypoint to the protected/client only content,
|
||||
Context providers are moved to gatsby-browser.js
|
||||
to wrap the root element with necessary providers
|
||||
and context */
|
||||
export default () => (
|
||||
<Layout>
|
||||
<App />
|
||||
|
||||
@@ -4,6 +4,9 @@ import Index from "routes/Start"
|
||||
import Layout from "components/Layout"
|
||||
import Container from "components/container"
|
||||
|
||||
// the landing page is generated like other Gatsby pages
|
||||
// other unprotected routes like /login, /privacy, etc
|
||||
// are completely server side rendered by gatsby build
|
||||
export default () => (
|
||||
<Layout>
|
||||
<SimpleNavbar />
|
||||
|
||||
@@ -116,6 +116,7 @@ class Day extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// check if a new route was hit, to save the entry and load the next one
|
||||
if (this.props.uri !== prevProps.uri) {
|
||||
const [
|
||||
,
|
||||
|
||||
@@ -79,6 +79,8 @@ class Search extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// all entries for a user are fetched this scales poorly but is much
|
||||
// simpler than an API key and setting up an index for Algolia
|
||||
getEntries = async _ => {
|
||||
const { firebase, authUser } = this.props
|
||||
const entriesRef = await firebase.db
|
||||
@@ -86,13 +88,7 @@ class Search extends Component {
|
||||
.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 })
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// standardized role-based design tokens used throughout the whole app
|
||||
// a name like lightGray doesn't make sense with themese when light
|
||||
// and dark are possibile
|
||||
const theme = {
|
||||
LIGHT: {
|
||||
name: "LIGHT",
|
||||
|
||||
Reference in New Issue
Block a user