feat: offline detection

This commit is contained in:
Kyle Gill
2019-04-21 21:26:05 -06:00
parent daeaa9649d
commit 4cf34cdb7f
3 changed files with 93 additions and 54 deletions

View File

@@ -18,6 +18,7 @@ import Register from "./components/screens/Register"
import Start from "./components/screens/Start" import Start from "./components/screens/Start"
import PrivateRoute from "./components/PrivateRoute" import PrivateRoute from "./components/PrivateRoute"
import { OnlineContext } from "./components/context/online"
import { withAuthentication } from "./components/session" import { withAuthentication } from "./components/session"
import { withFirebase } from "./components/firebase" import { withFirebase } from "./components/firebase"
@@ -42,6 +43,17 @@ class App extends Component {
new Date().getHours() >= 7 && new Date().getHours() <= 21 new Date().getHours() >= 7 && new Date().getHours() <= 21
? "LIGHT" ? "LIGHT"
: "DARK", : "DARK",
online: navigator.onLine,
}
componentDidMount() {
window.addEventListener('online', () => {
this.setState({ online: true })
});
window.addEventListener('offline', () => {
this.setState({ online: false })
});
} }
onChangeTheme = () => { onChangeTheme = () => {
@@ -69,43 +81,45 @@ class App extends Component {
} }
render() { render() {
const { selectedTheme, authUser } = this.state const { selectedTheme, authUser, online } = this.state
const { authUser: propAuthUser } = this.props const { authUser: propAuthUser } = this.props
const authed = !!propAuthUser || !!authUser const authed = !!propAuthUser || !!authUser
const currentTheme = theme[selectedTheme] const currentTheme = theme[selectedTheme]
return ( return (
<ThemeProvider theme={currentTheme}> <ThemeProvider theme={currentTheme}>
<Router> <OnlineContext.Provider value={online}>
<FullscreenLayout> <Router>
<Navbar toggleTheme={this.onChangeTheme} /> <FullscreenLayout>
<RouteLayout> <Navbar toggleTheme={this.onChangeTheme} />
<PrivateRoute <RouteLayout>
authed={authed} <PrivateRoute
path="/:year(\d+)" authed={authed}
component={Year} path="/:year(\d+)"
exact component={Year}
/> exact
<PrivateRoute />
authed={authed} <PrivateRoute
path="/:year(\d+)/:month(0[1-9]|1[0-2]+)" authed={authed}
component={Month} path="/:year(\d+)/:month(0[1-9]|1[0-2]+)"
exact component={Month}
/> exact
<PrivateRoute />
authed={authed} <PrivateRoute
path="/:year(\d+)/:month(0[1-9]|1[0-2]+)/:day(\d+)" authed={authed}
component={Day} path="/:year(\d+)/:month(0[1-9]|1[0-2]+)/:day(\d+)"
exact component={Day}
/> exact
<Route path="/user" component={User} exact /> />
<Route path="/login" component={Login} exact /> <Route path="/user" component={User} exact />
<Route path="/search" component={Search} exact /> <Route path="/login" component={Login} exact />
<Route path="/register" component={Register} exact /> <Route path="/search" component={Search} exact />
<Route path="/" component={Start} exact /> <Route path="/register" component={Register} exact />
</RouteLayout> <Route path="/" component={Start} exact />
</FullscreenLayout> </RouteLayout>
</Router> </FullscreenLayout>
</Router>
</OnlineContext.Provider>
</ThemeProvider> </ThemeProvider>
) )
} }

View File

@@ -0,0 +1,5 @@
import React from 'react'
export const OnlineContext = React.createContext({
online: navigator.onLine,
});

View File

@@ -8,6 +8,7 @@ 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 { OnlineContext } from "../../context/online"
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"
@@ -23,6 +24,11 @@ const EntryHeading = styled.div`
justify-content: space-between; justify-content: space-between;
margin-top: ${SIZES.medium}; margin-top: ${SIZES.medium};
` `
const EntryInfo = styled.div`
display: flex;
flex-direction: row;
align-items: center;
`
const JournalHeading = styled.h2` const JournalHeading = styled.h2`
font-weight: 700; font-weight: 700;
font-size: ${SIZES.tiny}; font-size: ${SIZES.tiny};
@@ -37,6 +43,13 @@ const SavedMessaged = styled.div`
font-size: ${SIZES.tiny}; font-size: ${SIZES.tiny};
user-select: none; user-select: none;
` `
const OfflineNotice = styled.div`
padding: 5px;
color: ${props => props.theme.colors.secondary};
border: 1px solid;
border-color: ${props => props.theme.colors.tertiary};
border-radius: 3px;
`
const JournalEntryArea = styled.textarea` const JournalEntryArea = styled.textarea`
font-family: sans-serif; font-family: sans-serif;
flex-grow: 0.85; flex-grow: 0.85;
@@ -95,6 +108,8 @@ class Day extends React.Component {
timeout = 0 timeout = 0
retrievedFromServer = false retrievedFromServer = false
static contextType = OnlineContext
componentDidMount() { componentDidMount() {
const { const {
history, history,
@@ -137,6 +152,7 @@ class Day extends React.Component {
}) })
.catch(err => { .catch(err => {
console.warn("entry not found in cache") console.warn("entry not found in cache")
this.setState({ loading: false})
// 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()) {
@@ -184,10 +200,7 @@ class Day extends React.Component {
saveText = (text, year, month, day) => { saveText = (text, year, month, day) => {
this.setState({ saving: true }) this.setState({ saving: true })
const { const { firebase, authUser } = this.props
firebase,
authUser,
} = this.props
firebase.db firebase.db
.collection("entries") .collection("entries")
.doc(`${year}${month}${day}-${authUser.uid}`) .doc(`${year}${month}${day}-${authUser.uid}`)
@@ -215,6 +228,7 @@ class Day extends React.Component {
}, },
theme, theme,
} = this.props } = this.props
const online = this.context
const { text, loading, saving, lastSavedAt, lastEditedAt } = this.state const { text, loading, saving, lastSavedAt, lastEditedAt } = this.state
const currentDay = new Date(year, month - 1, day) const currentDay = new Date(year, month - 1, day)
if (!currentDay) return if (!currentDay) return
@@ -222,7 +236,10 @@ class Day extends React.Component {
return ( return (
<> <>
<Prompt when={!hasSavedChanges} message="You have unsaved changes, are you sure you want to leave?" /> <Prompt
when={!hasSavedChanges}
message="You have unsaved changes, are you sure you want to leave?"
/>
<Seek <Seek
title={format(currentDay, "YYYY MMM DD - dddd")} title={format(currentDay, "YYYY MMM DD - dddd")}
prev={format(subDays(currentDay, 1), "/YYYY/MM/DD")} prev={format(subDays(currentDay, 1), "/YYYY/MM/DD")}
@@ -231,24 +248,27 @@ class Day extends React.Component {
/> />
<EntryHeading> <EntryHeading>
<JournalHeading>RECORD THOUGHTS ABOUT YOUR DAY</JournalHeading> <JournalHeading>RECORD THOUGHTS ABOUT YOUR DAY</JournalHeading>
<SavedMessaged> <EntryInfo>
{saving ? ( <SavedMessaged>
<> {saving ? (
Saving <>
<LoadingSpinner Saving
color={theme.colors.quarternary} <LoadingSpinner
size={5} color={theme.colors.quarternary}
css={css` size={5}
animation: 1s ease-in; css={css`
`} animation: 1s ease-in;
/> `}
</> />
) : hasSavedChanges ? ( </>
`Last saved at ${format(lastSavedAt, "h:mma")}` ) : hasSavedChanges ? (
) : ( `Last saved at ${format(lastSavedAt, "h:mma")}`
"Unsaved changes" ) : (
)} "Unsaved changes"
</SavedMessaged> )}
</SavedMessaged>
{!online && <OfflineNotice>Offline</OfflineNotice>}
</EntryInfo>
</EntryHeading> </EntryHeading>
{loading ? ( {loading ? (
<div style={{ marginTop: 10 }}> <div style={{ marginTop: 10 }}>