From 5e7cb72b8216b9adb47e52e54bd2265eef27f351 Mon Sep 17 00:00:00 2001 From: unknown <pawel999@icloud.com> Date: Sun, 13 Jun 2021 23:21:35 +0200 Subject: [PATCH] Reworked OtherSettings to work with global config state. Fixed bug with certain settings not being synchronized --- client/src/App.tsx | 4 - .../src/components/Apps/AppForm/AppForm.tsx | 4 +- .../Bookmarks/BookmarkForm/BookmarkForm.tsx | 4 +- client/src/components/Home/Home.tsx | 43 ++++++--- .../Settings/OtherSettings/OtherSettings.tsx | 95 ++++++++++--------- .../WeatherSettings/WeatherSettings.tsx | 40 ++++---- .../Widgets/WeatherWidget/WeatherWidget.tsx | 2 +- client/src/interfaces/Forms.ts | 7 ++ client/src/store/actions/config.ts | 10 +- client/src/utility/searchConfig.ts | 2 +- utils/initialConfig.json | 4 + 11 files changed, 124 insertions(+), 91 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index d58f4ad..7210f3d 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -18,10 +18,6 @@ if (localStorage.theme) { store.dispatch<any>(setTheme(localStorage.theme)); } -if (localStorage.customTitle) { - document.title = localStorage.customTitle; -} - const App = (): JSX.Element => { return ( <Provider store={store}> diff --git a/client/src/components/Apps/AppForm/AppForm.tsx b/client/src/components/Apps/AppForm/AppForm.tsx index cffeb19..e9c7beb 100644 --- a/client/src/components/Apps/AppForm/AppForm.tsx +++ b/client/src/components/Apps/AppForm/AppForm.tsx @@ -100,10 +100,10 @@ const AppForm = (props: ComponentProps): JSX.Element => { /> <span> <a - href='https://github.com/pawelmalak/flame#supported-URL-formats-for-applications-and-bookmarks' + href='https://github.com/pawelmalak/flame#supported-url-formats-for-applications-and-bookmarks' target='_blank' rel='noreferrer' - > + > {' '}Check supported URL formats </a> </span> diff --git a/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx b/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx index 9341565..eb83013 100644 --- a/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx +++ b/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx @@ -186,10 +186,10 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => { /> <span> <a - href='https://github.com/pawelmalak/flame#supported-URL-formats-for-applications-and-bookmarks' + href='https://github.com/pawelmalak/flame#supported-url-formats-for-applications-and-bookmarks' target='_blank' rel='noreferrer' - > + > {' '}Check supported URL formats </a> </span> diff --git a/client/src/components/Home/Home.tsx b/client/src/components/Home/Home.tsx index ab41ac2..854c04f 100644 --- a/client/src/components/Home/Home.tsx +++ b/client/src/components/Home/Home.tsx @@ -27,6 +27,9 @@ import WeatherWidget from '../Widgets/WeatherWidget/WeatherWidget'; import { greeter } from './functions/greeter'; import { dateTime } from './functions/dateTime'; +// Utils +import { searchConfig } from '../../utility'; + interface ComponentProps { getApps: Function; getCategories: Function; @@ -67,26 +70,36 @@ const Home = (props: ComponentProps): JSX.Element => { // Refresh greeter and time useEffect(() => { - const interval = setInterval(() => { - setHeader({ - dateTime: dateTime(), - greeting: greeter() - }) - }, 1000); + let interval: any; + + // Start interval only when hideHeader is false + if (searchConfig('hideHeader', 0) !== 1) { + interval = setInterval(() => { + setHeader({ + dateTime: dateTime(), + greeting: greeter() + }) + }, 1000); + } return () => clearInterval(interval); }, []) - + return ( <Container> - <header className={classes.Header}> - <p>{header.dateTime}</p> - <Link to='/settings' className={classes.SettingsLink}>Go to Settings</Link> - <span className={classes.HeaderMain}> - <h1>{header.greeting}</h1> - <WeatherWidget /> - </span> - </header> + {searchConfig('hideHeader', 0) !== 1 + ? ( + <header className={classes.Header}> + <p>{header.dateTime}</p> + <Link to='/settings' className={classes.SettingsLink}>Go to Settings</Link> + <span className={classes.HeaderMain}> + <h1>{header.greeting}</h1> + <WeatherWidget /> + </span> + </header> + ) + : <div></div> + } <SectionHeadline title='Applications' link='/applications' /> {appsLoading diff --git a/client/src/components/Settings/OtherSettings/OtherSettings.tsx b/client/src/components/Settings/OtherSettings/OtherSettings.tsx index 7a6090e..5df8be7 100644 --- a/client/src/components/Settings/OtherSettings/OtherSettings.tsx +++ b/client/src/components/Settings/OtherSettings/OtherSettings.tsx @@ -1,69 +1,56 @@ import { useState, useEffect, ChangeEvent, FormEvent } from 'react'; -import axios from 'axios'; -import { connect } from 'react-redux'; +// Redux +import { connect } from 'react-redux'; +import { createNotification, updateConfig } from '../../../store/actions'; + +// Typescript +import { GlobalState, NewNotification, SettingsForm } from '../../../interfaces'; + +// UI import InputGroup from '../../UI/Forms/InputGroup/InputGroup'; import Button from '../../UI/Buttons/Button/Button'; -import { createNotification } from '../../../store/actions'; -import { ApiResponse, Config, NewNotification } from '../../../interfaces'; -interface FormState { - customTitle: string; - pinAppsByDefault: number; - pinCategoriesByDefault: number; -} +// Utils +import { searchConfig } from '../../../utility'; interface ComponentProps { createNotification: (notification: NewNotification) => void; + updateConfig: (formData: SettingsForm) => void; + loading: boolean; } const OtherSettings = (props: ComponentProps): JSX.Element => { - const [formData, setFormData] = useState<FormState>({ + // Initial state + const [formData, setFormData] = useState<SettingsForm>({ customTitle: document.title, - pinAppsByDefault: 0, - pinCategoriesByDefault: 0 + pinAppsByDefault: 1, + pinCategoriesByDefault: 1, + hideHeader: 0 }) - // get initial config + // Get config useEffect(() => { - axios.get<ApiResponse<Config[]>>('/api/config?keys=customTitle,pinAppsByDefault,pinCategoriesByDefault') - .then(data => { - let tmpFormData = { ...formData }; + setFormData({ + customTitle: searchConfig('customTitle', 'Flame'), + pinAppsByDefault: searchConfig('pinAppsByDefault', 1), + pinCategoriesByDefault: searchConfig('pinCategoriesByDefault', 1), + hideHeader: searchConfig('hideHeader', 0) + }) + }, [props.loading]); - data.data.data.forEach((config: Config) => { - let value: string | number = config.value; - if (config.valueType === 'number') { - value = parseFloat(value); - } - - tmpFormData = { - ...tmpFormData, - [config.key]: value - } - }) - - setFormData(tmpFormData); - }) - .catch(err => console.log(err)); - }, []) - - const formSubmitHandler = (e: FormEvent) => { + // Form handler + const formSubmitHandler = async (e: FormEvent) => { e.preventDefault(); - axios.put<ApiResponse<{}>>('/api/config', formData) - .then(() => { - props.createNotification({ - title: 'Success', - message: 'Settings updated' - }) - }) - .catch((err) => console.log(err)); + // Save settings + await props.updateConfig(formData); // update local page title - localStorage.setItem('customTitle', formData.customTitle); document.title = formData.customTitle; } + // Input handler const inputChangeHandler = (e: ChangeEvent<HTMLInputElement | HTMLSelectElement>, isNumber?: boolean) => { let value: string | number = e.target.value; @@ -80,7 +67,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { return ( <form onSubmit={(e) => formSubmitHandler(e)}> <InputGroup> - <label htmlFor='customTitle'>Custom Page Title</label> + <label htmlFor='customTitle'>Custom page title</label> <input type='text' id='customTitle' @@ -114,9 +101,27 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { <option value={0}>False</option> </select> </InputGroup> + <InputGroup> + <label htmlFor='hideHeader'>Hide greeting and date</label> + <select + id='hideHeader' + name='hideHeader' + value={formData.hideHeader} + onChange={(e) => inputChangeHandler(e, true)} + > + <option value={1}>True</option> + <option value={0}>False</option> + </select> + </InputGroup> <Button>Save changes</Button> </form> ) } -export default connect(null, { createNotification })(OtherSettings); \ No newline at end of file +const mapStateToProps = (state: GlobalState) => { + return { + loading: state.config.loading + } +} + +export default connect(mapStateToProps, { createNotification, updateConfig })(OtherSettings); \ No newline at end of file diff --git a/client/src/components/Settings/WeatherSettings/WeatherSettings.tsx b/client/src/components/Settings/WeatherSettings/WeatherSettings.tsx index 6f14cfc..912aced 100644 --- a/client/src/components/Settings/WeatherSettings/WeatherSettings.tsx +++ b/client/src/components/Settings/WeatherSettings/WeatherSettings.tsx @@ -22,6 +22,7 @@ interface ComponentProps { } const WeatherSettings = (props: ComponentProps): JSX.Element => { + // Initial state const [formData, setFormData] = useState<WeatherForm>({ WEATHER_API_KEY: '', lat: 0, @@ -29,19 +30,7 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => { isCelsius: 1 }) - const inputChangeHandler = (e: ChangeEvent<HTMLInputElement | HTMLSelectElement>, isNumber?: boolean) => { - let value: string | number = e.target.value; - - if (isNumber) { - value = parseFloat(value); - } - - setFormData({ - ...formData, - [e.target.name]: value - }) - } - + // Get config useEffect(() => { setFormData({ WEATHER_API_KEY: searchConfig('WEATHER_API_KEY', ''), @@ -51,6 +40,7 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => { }) }, [props.loading]); + // Form handler const formSubmitHandler = async (e: FormEvent) => { e.preventDefault(); @@ -58,7 +48,7 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => { if ((formData.lat || formData.long) && !formData.WEATHER_API_KEY) { props.createNotification({ title: 'Warning', - message: 'API Key is missing. Weather Module will NOT work' + message: 'API key is missing. Weather Module will NOT work' }) } @@ -81,10 +71,24 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => { }); } + // Input handler + const inputChangeHandler = (e: ChangeEvent<HTMLInputElement | HTMLSelectElement>, isNumber?: boolean) => { + let value: string | number = e.target.value; + + if (isNumber) { + value = parseFloat(value); + } + + setFormData({ + ...formData, + [e.target.name]: value + }) + } + return ( <form onSubmit={(e) => formSubmitHandler(e)}> <InputGroup> - <label htmlFor='WEATHER_API_KEY'>API Key</label> + <label htmlFor='WEATHER_API_KEY'>API key</label> <input type='text' id='WEATHER_API_KEY' @@ -104,7 +108,7 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => { </span> </InputGroup> <InputGroup> - <label htmlFor='lat'>Location Latitude</label> + <label htmlFor='lat'>Location latitude</label> <input type='number' id='lat' @@ -123,7 +127,7 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => { </span> </InputGroup> <InputGroup> - <label htmlFor='long'>Location Longitude</label> + <label htmlFor='long'>Location longitude</label> <input type='number' id='long' @@ -134,7 +138,7 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => { /> </InputGroup> <InputGroup> - <label htmlFor='isCelsius'>Temperature Unit</label> + <label htmlFor='isCelsius'>Temperature unit</label> <select id='isCelsius' name='isCelsius' diff --git a/client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx b/client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx index 8a0e142..edf6fee 100644 --- a/client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx +++ b/client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx @@ -68,7 +68,7 @@ const WeatherWidget = (props: ComponentProps): JSX.Element => { return ( <div className={classes.WeatherWidget}> - {isLoading || props.configLoading || searchConfig('WEATHER_API_KEY', '') && + {(isLoading || props.configLoading || searchConfig('WEATHER_API_KEY', '')) && (weather.id > 0 && (<Fragment> <div className={classes.WeatherIcon}> diff --git a/client/src/interfaces/Forms.ts b/client/src/interfaces/Forms.ts index 6ce2d42..52f62c8 100644 --- a/client/src/interfaces/Forms.ts +++ b/client/src/interfaces/Forms.ts @@ -3,4 +3,11 @@ export interface WeatherForm { lat: number; long: number; isCelsius: number; +} + +export interface SettingsForm { + customTitle: string; + pinAppsByDefault: number; + pinCategoriesByDefault: number; + hideHeader: number; } \ No newline at end of file diff --git a/client/src/store/actions/config.ts b/client/src/store/actions/config.ts index e65415d..a14e21e 100644 --- a/client/src/store/actions/config.ts +++ b/client/src/store/actions/config.ts @@ -1,8 +1,9 @@ import axios from 'axios'; import { Dispatch } from 'redux'; import { ActionTypes } from './actionTypes'; -import { Config, ApiResponse, WeatherForm } from '../../interfaces'; +import { Config, ApiResponse } from '../../interfaces'; import { CreateNotificationAction } from './notification'; +import { searchConfig } from '../../utility'; export interface GetConfigAction { type: ActionTypes.getConfig; @@ -12,11 +13,14 @@ export interface GetConfigAction { export const getConfig = () => async (dispatch: Dispatch) => { try { const res = await axios.get<ApiResponse<Config[]>>('/api/config'); - + dispatch<GetConfigAction>({ type: ActionTypes.getConfig, payload: res.data.data }) + + // Set custom page title if set + document.title = searchConfig('customTitle', 'Flame'); } catch (err) { console.log(err) } @@ -27,7 +31,7 @@ export interface UpdateConfigAction { payload: Config[]; } -export const updateConfig = (formData: WeatherForm) => async (dispatch: Dispatch) => { +export const updateConfig = (formData: any) => async (dispatch: Dispatch) => { try { const res = await axios.put<ApiResponse<Config[]>>('/api/config', formData); dispatch<CreateNotificationAction>({ diff --git a/client/src/utility/searchConfig.ts b/client/src/utility/searchConfig.ts index 0f8ec23..fe4db57 100644 --- a/client/src/utility/searchConfig.ts +++ b/client/src/utility/searchConfig.ts @@ -5,7 +5,7 @@ import { store } from '../store/store'; * @param key Config pair key to search * @param _default Value to return if key is not found */ -export const searchConfig = (key: string, _default: any)=> { +export const searchConfig = (key: string, _default: any) => { const state = store.getState(); const pair = state.config.config.find(p => p.key === key); diff --git a/utils/initialConfig.json b/utils/initialConfig.json index eae974a..eface5d 100644 --- a/utils/initialConfig.json +++ b/utils/initialConfig.json @@ -27,6 +27,10 @@ { "key": "pinCategoriesByDefault", "value": true + }, + { + "key": "hideHeader", + "value": false } ] } \ No newline at end of file