Added redux. Moved theme loading/setting to redux actions. Added automatic theme loading based on localStorage value.
This commit is contained in:
parent
765418d3d2
commit
7199e296b8
14 changed files with 203 additions and 39 deletions
51
client/package-lock.json
generated
51
client/package-lock.json
generated
|
@ -2290,6 +2290,15 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz",
|
||||
"integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA=="
|
||||
},
|
||||
"@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@types/html-minifier-terser": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz",
|
||||
|
@ -2388,6 +2397,17 @@
|
|||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-redux": {
|
||||
"version": "7.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz",
|
||||
"integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==",
|
||||
"requires": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/react-router": {
|
||||
"version": "5.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.14.tgz",
|
||||
|
@ -12541,6 +12561,19 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz",
|
||||
"integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"react-refresh": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
|
||||
|
@ -12773,6 +12806,24 @@
|
|||
"strip-indent": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"redux": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.0.tgz",
|
||||
"integrity": "sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"redux-devtools-extension": {
|
||||
"version": "2.13.9",
|
||||
"resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz",
|
||||
"integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A=="
|
||||
},
|
||||
"redux-thunk": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
|
||||
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
|
||||
},
|
||||
"regenerate": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
|
|
|
@ -12,11 +12,16 @@
|
|||
"@types/node": "^12.20.12",
|
||||
"@types/react": "^17.0.5",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"redux": "^4.1.0",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"typescript": "^4.2.4",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
.SettingsButton {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
background-color: var(--color-accent);
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0.25;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.SettingsButton:hover {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
|
@ -1,23 +1,30 @@
|
|||
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
|
||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||
import { setTheme } from './store/actions';
|
||||
|
||||
// Redux
|
||||
import store from './store/store';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import classes from './App.module.css';
|
||||
|
||||
import Home from './components/Home/Home';
|
||||
import Apps from './components/Apps/Apps';
|
||||
import Settings from './components/Settings/Settings';
|
||||
|
||||
import Icon from './components/UI/Icon/Icon';
|
||||
if (localStorage.theme) {
|
||||
store.dispatch(setTheme(localStorage.theme));
|
||||
}
|
||||
|
||||
const App = (): JSX.Element => {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route exact path='/' component={Apps} />
|
||||
<Route exact path='/settings' component={Settings} />
|
||||
</Switch>
|
||||
<Link to='/settings' className={classes.SettingsButton}>
|
||||
<Icon icon='mdiCog' />
|
||||
</Link>
|
||||
</BrowserRouter>
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route exact path='/' component={Home} />
|
||||
<Route exact path='/settings' component={Settings} />
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
19
client/src/components/Home/Home.module.css
Normal file
19
client/src/components/Home/Home.module.css
Normal file
|
@ -0,0 +1,19 @@
|
|||
.SettingsButton {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
background-color: var(--color-accent);
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0.25;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.SettingsButton:hover {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
29
client/src/components/Home/Home.tsx
Normal file
29
client/src/components/Home/Home.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Link } from 'react-router-dom';
|
||||
import Icon from '../UI/Icon/Icon';
|
||||
|
||||
import classes from './Home.module.css';
|
||||
import { Container } from '../UI/Layout/Layout';
|
||||
import Headline from '../UI/Headline/Headline';
|
||||
|
||||
const Home = (): JSX.Element => {
|
||||
const dateAndTime = (): string => {
|
||||
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
|
||||
const date = new Date();
|
||||
|
||||
|
||||
return `${days[date.getDay()]}, ${date.getDate()} of ${months[date.getMonth()]} ${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Headline title='Home' subtitle={dateAndTime()} />
|
||||
<Link to='/settings' className={classes.SettingsButton}>
|
||||
<Icon icon='mdiCog' />
|
||||
</Link>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home;
|
|
@ -3,7 +3,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.ThemePreview:hover {
|
||||
|
@ -13,6 +13,7 @@
|
|||
.ThemePreview p {
|
||||
text-transform: capitalize;
|
||||
margin: 8px 0;
|
||||
color: var(--color-primary);
|
||||
/* align-self: flex-start; */
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useEffect, Fragment } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import classes from './Themer.module.css';
|
||||
|
||||
|
@ -8,7 +9,13 @@ import ThemePreview from './ThemePreview';
|
|||
import { Container } from '../UI/Layout/Layout';
|
||||
import Headline from '../UI/Headline/Headline';
|
||||
|
||||
const Themer = (): JSX.Element => {
|
||||
import { setTheme } from '../../store/actions';
|
||||
|
||||
interface ComponentProps {
|
||||
setTheme: Function;
|
||||
}
|
||||
|
||||
const Themer = (props: ComponentProps): JSX.Element => {
|
||||
useEffect((): void => {
|
||||
if (localStorage.theme) {
|
||||
applyTheme(localStorage.theme);
|
||||
|
@ -27,19 +34,20 @@ const Themer = (): JSX.Element => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Fragment>
|
||||
<div>
|
||||
<Headline
|
||||
{/* <Headline
|
||||
title='Themes'
|
||||
subtitle='Select new theme by clicking on it'
|
||||
/>
|
||||
/> */}
|
||||
<div className={classes.ThemerGrid}>
|
||||
{themes.map((theme: Theme): JSX.Element => <ThemePreview theme={theme} applyTheme={applyTheme} />)}
|
||||
{themes.map((theme: Theme): JSX.Element => <ThemePreview theme={theme} applyTheme={props.setTheme} />)}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</Fragment>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default Themer;
|
||||
|
||||
export default connect(null, { setTheme })(Themer);
|
1
client/src/store/actions/actionTypes.ts
Normal file
1
client/src/store/actions/actionTypes.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const SET_THEME = 'SET_THEME';
|
2
client/src/store/actions/index.ts
Normal file
2
client/src/store/actions/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './theme';
|
||||
export * from './actionTypes';
|
24
client/src/store/actions/theme.ts
Normal file
24
client/src/store/actions/theme.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { Dispatch } from 'redux';
|
||||
import { themes } from '../../components/Themer/themes.json';
|
||||
import { Theme } from '../../interfaces/Theme';
|
||||
import { SET_THEME } from './actionTypes';
|
||||
|
||||
export const setTheme = (themeName: string) => (dispatch: Dispatch) => {
|
||||
const theme = themes.find((theme: Theme) => theme.name === themeName);
|
||||
|
||||
if (theme) {
|
||||
localStorage.setItem('theme', themeName);
|
||||
loadTheme(theme);
|
||||
|
||||
dispatch({
|
||||
type: SET_THEME,
|
||||
payload: themeName
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const loadTheme = (theme: Theme) => {
|
||||
for (const [key, value] of Object.entries(theme.colors)) {
|
||||
document.body.style.setProperty(`--color-${key}`, value);
|
||||
}
|
||||
}
|
9
client/src/store/reducers/index.ts
Normal file
9
client/src/store/reducers/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { combineReducers } from 'redux';
|
||||
|
||||
import themeReducer from './theme';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
theme: themeReducer
|
||||
})
|
||||
|
||||
export default rootReducer;
|
18
client/src/store/reducers/theme.ts
Normal file
18
client/src/store/reducers/theme.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { SET_THEME } from '../actions/actionTypes';
|
||||
|
||||
const initialState = {
|
||||
theme: 'blues'
|
||||
}
|
||||
|
||||
const themeReducer = (state = initialState, action: any) => {
|
||||
switch (action.type) {
|
||||
case SET_THEME:
|
||||
return {
|
||||
theme: action.payload
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default themeReducer;
|
9
client/src/store/store.ts
Normal file
9
client/src/store/store.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { createStore, applyMiddleware } from 'redux';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||
import thunk from 'redux-thunk';
|
||||
import rootReducer from './reducers';
|
||||
const initialState = {};
|
||||
|
||||
const store = createStore(rootReducer, initialState, composeWithDevTools(applyMiddleware(thunk)));
|
||||
|
||||
export default store;
|
Loading…
Reference in a new issue