Parcourir la source

[web] Minor api hook changes. Settings for theme&layout

Innokentiy Enikeev il y a 4 ans
Parent
commit
9422e54a20
5 fichiers modifiés avec 61 ajouts et 43 suppressions
  1. 19 20
      web/src/Api.js
  2. 28 7
      web/src/App.js
  3. 7 13
      web/src/Login.js
  4. 2 1
      web/src/Player.js
  5. 5 2
      web/src/UserInfo.js

+ 19 - 20
web/src/Api.js

@@ -1,13 +1,13 @@
-import {useEffect, useState, useReducer} from 'react';
+import {useEffect, useState} from 'react';
 import {getQueryString} from './utils';
 
 const domain = '';
 
-export function saveToken(token) {
+export function storeToken(token) {
   localStorage.setItem('token', token);
 }
 
-export function getToken() {
+export function loadToken() {
   return localStorage.getItem('token');
 }
 
@@ -37,40 +37,39 @@ export function getTracks(album_id) {
   return {url: `/api/album/${album_id}/tracks`};
 }
 
-const fetchReducer = (s, a) => {
-  switch (a.t) {
-  case 'init': return {...s, loading: true, error: null};
-  case 'success': return {...s, loading: false, error: null, data: a.p};
-  case 'fail': return {...s, loading: false, error: a.p};
+export const initialState = (initialData) => {return {loading: false, error: null, data: initialData}}
+export function fetchReducer(s, a) {
+  switch (a.type) {
+  case 'FETCH_INIT': return {...s, loading: true, error: null};
+  case 'FETCH_SUCCESS': return {...s, loading: false, error: null, data: a.payload};
+  case 'FETCH_FAIL': return {...s, loading: false, error: a.payload};
   default: throw new Error();
   }
-};
+}
 
-export const useFetch = (initialUrl, initialData) => {
-  const INIT = {loading: false, error: null, data: initialData};
+export const useFetch = (initialUrl, dispatch, typePrefix) => {
   const [url, setUrl] = useState(initialUrl);
-  const [state, dispatch] = useReducer(fetchReducer, INIT);
+  const prefix = typePrefix || 'FETCH';
 
   useEffect(() => {
     if (!url) return;
     let mounted = true;
     const fetchData = async () => {
-      dispatch({t:'init'});
+      dispatch({type:`${prefix}_INIT`});
       try {
         const data = await request(url.url, url.options);
-        mounted && dispatch({t:'success', p: data});
+        mounted && dispatch({type:`${prefix}_SUCCESS`, payload: data});
       } catch (err) {
-        mounted && dispatch({t:'fail', p: err});
+        mounted && dispatch({type:`${prefix}_FAIL`, payload: err});
       }
     };
     fetchData();
     return () => { mounted = false; };
-  }, [url]);
+  }, [url,prefix,dispatch]);
 
-  return [state, setUrl];
+  return setUrl;
 };
 
-
 export function request(url, options) {
   // performs api calls sending the required authentication headers
   const headers = {
@@ -80,8 +79,8 @@ export function request(url, options) {
 
   // Setting Authorization header
   // Authorization: Bearer xxxxxxx.xxxxxxxx.xxxxxx
-  if (getToken()) {
-    headers['Authorization'] = 'Bearer ' + getToken();
+  if (loadToken()) {
+    headers['Authorization'] = 'Bearer ' + loadToken();
   }
 
   return fetch(`${domain}${url}`, {

+ 28 - 7
web/src/App.js

@@ -1,20 +1,41 @@
-import {useState} from 'react';
+import {useReducer} from 'react';
 import './App.css';
 import logo from './chad-logo-256.png';
+import {storeToken, loadToken} from './Api';
 import Login from './Login';
-import {getToken} from './Api';
 import UserInfo from './UserInfo';
 import Player from './Player';
 
-function App() {
-  const [token, setToken] = useState(getToken());
+function storeSettings(settings) {
+  localStorage.setItem(JSON.stringify(settings));
+  return settings;
+}
+function loadSettings() {
+  const settings = localStorage.getItem('settings');
+  return (settings && JSON.parse(settings)) || {login:{}, theme:'light', layout:'two-col'};
+}
 
-  if(!token) {
-    return <Login setToken={setToken} />
+function settingsReducer(state, action) {
+  switch(action.type) {
+  case 'LOGIN_SUCCESS':
+    storeToken(action.payload);
+    return {...state, login: {loading: false, error: null}};
+  case 'LOGIN_INIT':    return {...state, login: {loading: true, error: null}};
+  case 'LOGIN_FAIL':    return {...state, login: {loading: false, error: action.payload}};
+  case 'LAYOUT_SET':    return storeSettings({...state, layout: action.payload});
+  case 'THEME_SET':     return storeSettings({...state, theme: action.payload});
+  default: throw new Error(`Bad action ${action}`);
+  }
+}
+
+function App() {
+  const [settings, dispatcher] = useReducer(settingsReducer, loadSettings());
+  if(!loadToken()) {
+    return <Login state={settings} dispatcher={dispatcher} />
   }
 
   return (
-    <div className="App">
+    <div className="App" data-theme={settings.theme} data-layout={settings.layout}>
       <header className="App-header">
         <img src={logo} className="App-logo" alt="Chad music" />
         <h1 className="App-title">Chad Music</h1>

+ 7 - 13
web/src/Login.js

@@ -1,22 +1,16 @@
 import './Login.css';
 import TelegramLoginButton from './TelegramLoginButton';
-import {login, useFetch, saveToken} from './Api';
+import {login, useFetch} from './Api';
 
-export default function Login({setToken}) {
-  const [{loading, error, data}, setUrl] = useFetch();
-  if (data) {
-    setTimeout(()=>{
-      saveToken(data.token);
-      setToken(data.token);
-    });
-  }
+export default function Login({state, dispatch}) {
+  const setUrl = useFetch(null, dispatch, 'LOGIN');
   const doLogin = (info) => setUrl(login(info));
 
   return (
-      <div className="Login">
-        {error && <div className="Login-error">{error.message}</div>}
-        {loading && <div>Loading...</div>}
+    <div className="Login">
+      {state.login.error && <div className="Login-error">{state.login.error.message}</div>}
+      {state.login.loading && <div>Loading...</div>}
       <TelegramLoginButton botName="ChadPartners_bot" dataOnAuth={doLogin} />
-      </div>
+    </div>
   );
 }

+ 2 - 1
web/src/Player.js

@@ -29,7 +29,8 @@ function playTrack(track, status) {
       artist: track.artist,
       album: track.album,
       cover: track.cover
-    }
+    },
+    error: NO_ERROR
   }
 }
 

+ 5 - 2
web/src/UserInfo.js

@@ -1,9 +1,12 @@
+import {useReducer} from 'react';
 import './UserInfo.css';
 import logo from './chad-logo-256.png';
-import {useFetch, getUser} from './Api';
+import {useFetch, initialState, fetchReducer, getUser} from './Api';
 
 function UserInfo() {
-  const [{loading, error, data}] = useFetch(getUser());
+  const [{loading, error, data}, dispatch] = useReducer(fetchReducer, initialState());
+  useFetch(getUser(), dispatch);
+
   const msg = data ? `${data.first_name} ${data.last_name}`
         : (error ? `${error.message}` : (loading ? 'Loading' : ''));
   const className = data ? 'UserInfo-name' : (error ? 'UserInfo-error' : (loading ? 'UserInfo-loading': ''));