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