From ff4a941dd86a8c69822eb769e5a6f33b6816eadd Mon Sep 17 00:00:00 2001 From: Joshua Seigler Date: Wed, 19 Feb 2020 02:00:21 -0500 Subject: [PATCH] feat: sync redux to localstorage --- .gitignore | 12 +++---- components/addCard.js | 41 +++++++++++++++++------- components/card.js | 29 ++++++++++++++--- components/cardMover.js | 20 ++++++++++++ components/clock.js | 41 ------------------------ components/column.js | 63 ++++++++++++++++++++++++++++++------- components/counter.js | 36 --------------------- next.config.js | 9 ++++++ pages/index.js | 69 +++++++++++++++++++++++++---------------- store.js | 59 ++++++++++++++++++++++++++++------- 10 files changed, 230 insertions(+), 149 deletions(-) create mode 100644 components/cardMover.js delete mode 100644 components/clock.js delete mode 100644 components/counter.js create mode 100644 next.config.js diff --git a/.gitignore b/.gitignore index 3f68239..85b8d78 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,17 @@ # dependencies -/node_modules -/.pnp +node_modules +.pnp .pnp.js # testing -/coverage +coverage/ # next.js -/.next/ -/out/ +.next/ +out/ # production -/build +build/ # misc .DS_Store diff --git a/components/addCard.js b/components/addCard.js index 72a05d0..039f6da 100644 --- a/components/addCard.js +++ b/components/addCard.js @@ -1,19 +1,38 @@ import React from 'react'; import { useDispatch } from 'react-redux' -import { withRedux } from '../lib/redux'; +import { withRedux } from 'lib/redux'; -const addCard = ({ columnIndex }) => { +const addCard = ({ column }) => { const dispatch = useDispatch(); - return { - const userText = window.prompt('New card text:', '(no text)'); - dispatch({ type: 'ADD_CARD', payload: { - column: columnIndex, - text: userText - } }); - } - }>+ Add a card; + return <> + { + const userText = window.prompt('New card text:', ''); + if (userText !== null) { + dispatch({ type: 'ADD_CARD', payload: { + column, + text: userText + }}); + } + } + }>+ Add a card + + ; } export default withRedux(addCard); diff --git a/components/card.js b/components/card.js index 2161084..d20e796 100644 --- a/components/card.js +++ b/components/card.js @@ -1,7 +1,26 @@ -import React from 'react' +import React from 'react'; +import CardMover from 'components/cardMover'; -export default ({ children }) => { - return
- I am a card -
+export default ({ text = 'No text', moveLeft, moveRight }) => { + return <> +
+ { moveLeft && } +
+ { text } +
+ { moveRight && } +
+ + ; } diff --git a/components/cardMover.js b/components/cardMover.js new file mode 100644 index 0000000..ed50e40 --- /dev/null +++ b/components/cardMover.js @@ -0,0 +1,20 @@ +import React from 'react'; + +export default ({ direction, action }) => { + return <> + + { direction === 'left' ? '〈' : '〉' } + + + ; +} diff --git a/components/clock.js b/components/clock.js deleted file mode 100644 index e23d3e7..0000000 --- a/components/clock.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react' -import { useSelector, shallowEqual } from 'react-redux' - -const useClock = () => { - return useSelector( - state => ({ - lastUpdate: state.lastUpdate, - light: state.light, - }), - shallowEqual - ) -} - -const formatTime = time => { - // cut off except hh:mm:ss - return new Date(time).toJSON().slice(11, 19) -} - -const Clock = () => { - const { lastUpdate, light } = useClock() - return ( -
- {formatTime(lastUpdate)} - -
- ) -} - -export default Clock diff --git a/components/column.js b/components/column.js index e488d79..d9b599c 100644 --- a/components/column.js +++ b/components/column.js @@ -1,23 +1,64 @@ import React from 'react'; -import { useSelector, shallowEqual } from 'react-redux'; -import { withRedux } from '../lib/redux'; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; +import { withRedux } from 'lib/redux'; -import Card from './card'; -import AddCard from './addCard'; +import Card from 'components/card'; +import AddCard from 'components/addCard'; -const column = ({ name, index, headerColor }) => { - const cards = useSelector(state => state.columns[index].cards); +const column = ({ name, id, headerColor }) => { + const dispatch = useDispatch(); + const cards = useSelector(state => state.cards.filter( + c => (c.column === id) + ), shallowEqual); + const colIds = useSelector(state => state.columns.map(c => c.id)); + const myIndex = colIds.findIndex(x => x === id); + const neighbors = [null, ...colIds, null]; + const prevColId = neighbors[myIndex+1-1]; + const nextColId = neighbors[myIndex+1+1]; - return
-

{name}

- { cards.map((c, cardIndex) => ) } - + const moverFactory = (id, column) => { + if (column) { + return () => { + dispatch({ + type: 'MOVE_CARD', + payload: { + id, + column + } + }); + } + } else { + return null; + } + } + + return
+

{name}

+ { cards.map(({ text, id }) => ) } + -
+ } export default withRedux(column); diff --git a/components/counter.js b/components/counter.js deleted file mode 100644 index a161c68..0000000 --- a/components/counter.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react' -import { useSelector, useDispatch } from 'react-redux' - -const useCounter = () => { - const count = useSelector(state => state.count) - const dispatch = useDispatch() - const increment = () => - dispatch({ - type: 'INCREMENT', - }) - const decrement = () => - dispatch({ - type: 'DECREMENT', - }) - const reset = () => - dispatch({ - type: 'RESET', - }) - return { count, increment, decrement, reset } -} - -const Counter = () => { - const { count, increment, decrement, reset } = useCounter() - return ( -
-

- Count: {count} -

- - - -
- ) -} - -export default Counter diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..e67618f --- /dev/null +++ b/next.config.js @@ -0,0 +1,9 @@ +const path = require('path'); + +module.exports = { + webpack(config, options) { + config.resolve.alias['components'] = path.join(__dirname, 'components') + config.resolve.alias['lib'] = path.join(__dirname, 'lib') + return config + }, +}; diff --git a/pages/index.js b/pages/index.js index 78e965a..2dc20c9 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,44 +1,59 @@ -import React from 'react' -import { useDispatch } from 'react-redux' -import { useSelector, shallowEqual } from 'react-redux' -import { withRedux } from '../lib/redux' -import useInterval from '../lib/useInterval' +import React, { useEffect, useState } from 'react'; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; +import { withRedux } from 'lib/redux'; -import Column from '../components/column'; -import Card from '../components/card'; +import Column from 'components/column'; + +const rehydrateStore = () => { + if (localStorage.getItem('triplebyte-react-spa') === null) { + return null; + } else { + return JSON.parse(localStorage.getItem('triplebyte-react-spa')); + } +}; const IndexPage = () => { + const [loaded, setLoaded] = useState(false); + const dispatch = useDispatch(); const columns = useSelector(state => state.columns, shallowEqual); + + useEffect(() => { + dispatch({ type: 'RESET', payload: rehydrateStore() }); + setLoaded(true); + }, []); + if (!loaded) return null; return (
- {columns.map((c, index) => )} + {columns.map((c, index, array) => ( + + ))}
) } -IndexPage.getInitialProps = ({ reduxStore }) => { - // // Tick the time once, so we'll have a - // // valid time before first render - // const { dispatch } = reduxStore - // dispatch({ - // type: 'TICK', - // light: typeof window === 'object', - // lastUpdate: Date.now(), - // }) - - return {} -} - -export default withRedux(IndexPage) +export default withRedux(IndexPage); diff --git a/store.js b/store.js index 1dfbb2e..28f3bb1 100644 --- a/store.js +++ b/store.js @@ -1,31 +1,66 @@ -import { createStore, applyMiddleware } from 'redux' -import { composeWithDevTools } from 'redux-devtools-extension' +import { compose, createStore, applyMiddleware } from 'redux'; +import { composeWithDevTools } from 'redux-devtools-extension'; const initialState = { columns: [ - { name: 'Backlog', headerColor: '#8E6E95', cards: [{}, {}] }, - { name: 'In Progress', headerColor: '#8E6E95', headerColor: '#39A59C', cards: [{}, {}] }, - { name: 'Ready for Review', headerColor: '#344759', cards: [{}, {}] }, - { name: 'Completed', headerColor: '#E8741E', cards: [{}, {}] }, - ] -} + { name: 'Backlog', headerColor: '#8E6E95', id: 'backlog' }, + { name: 'In Progress', headerColor: '#8E6E95', headerColor: '#39A59C', id: 'in-progress' }, + { name: 'Ready for Review', headerColor: '#344759', id: 'ready-for-review' }, + { name: 'Completed', headerColor: '#E8741E', id: 'completed' }, + ], + cards: [ + ], +}; const reducer = (state = initialState, {type, payload}) => { switch (type) { case 'ADD_CARD': - newCards = [...state.columns[action.payload.column].cards, { text: payload.text }]; return { ...state, + cards: state.cards.concat( { + id: `${Date.now()}-${Math.random()}`, + text: payload.text, + column: payload.column, + }), }; + case 'MOVE_CARD': + const cardIndex = state.cards.findIndex(c => c.id === payload.id); + const newCards = state.cards.slice(0); + newCards.splice(cardIndex, 1, { + ...state.cards[cardIndex], + column: payload.column, + }); + return { + ...state, + cards: newCards, + }; + case 'RESET': + if (payload) { + return payload; + } else { + return initialState; + } default: return state } -} +}; + +const localStorageMiddleware = ({getState}) => { + return (next) => (action) => { + const result = next(action); + localStorage.setItem('triplebyte-react-spa', JSON.stringify( + getState() + )); + return result; + }; +}; export const initializeStore = (preloadedState = initialState) => { return createStore( reducer, preloadedState, - composeWithDevTools(applyMiddleware()) + composeWithDevTools(applyMiddleware( + localStorageMiddleware + )) ) -} +};