From 78acede8ab854e6bab8ee2c6aedd6fffde03364a Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 10 May 2021 19:02:16 +0200 Subject: [PATCH] Apps actions and reducer --- client/package-lock.json | 8 +++ client/package.json | 1 + client/src/App.tsx | 1 + .../src/components/Apps/AppCard/AppCard.tsx | 24 +++++-- client/src/components/Apps/Apps.tsx | 69 +++++++++++++------ .../components/UI/Spinner/Spinner.module.css | 54 +++++++++++++++ client/src/components/UI/Spinner/Spinner.tsx | 11 +++ client/src/index.css | 3 +- client/src/interfaces/App.ts | 8 +++ client/src/interfaces/index.ts | 2 + client/src/store/actions/actionTypes.ts | 4 ++ client/src/store/actions/app.ts | 25 +++++++ client/src/store/actions/index.ts | 1 + client/src/store/reducers/app.ts | 42 +++++++++++ client/src/store/reducers/index.ts | 4 +- 15 files changed, 230 insertions(+), 27 deletions(-) create mode 100644 client/src/components/UI/Spinner/Spinner.module.css create mode 100644 client/src/components/UI/Spinner/Spinner.tsx create mode 100644 client/src/interfaces/App.ts create mode 100644 client/src/interfaces/index.ts create mode 100644 client/src/store/actions/app.ts create mode 100644 client/src/store/reducers/app.ts diff --git a/client/package-lock.json b/client/package-lock.json index 6b4a88b..fe13e61 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -3142,6 +3142,14 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.2.0.tgz", "integrity": "sha512-1uIESzroqpaTzt9uX48HO+6gfnKu3RwvWdCcWSrX4csMInJfCo1yvKPNXCwXFRpJqRW25tiASb6No0YH57PXqg==" }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", diff --git a/client/package.json b/client/package.json index 0ed0b77..8f7d7a9 100644 --- a/client/package.json +++ b/client/package.json @@ -14,6 +14,7 @@ "@types/react-dom": "^17.0.3", "@types/react-redux": "^7.1.16", "@types/react-router-dom": "^5.1.7", + "axios": "^0.21.1", "react": "^17.0.2", "react-dom": "^17.0.2", "react-redux": "^7.2.4", diff --git a/client/src/App.tsx b/client/src/App.tsx index ec52781..b5fd09d 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -22,6 +22,7 @@ const App = (): JSX.Element => { + diff --git a/client/src/components/Apps/AppCard/AppCard.tsx b/client/src/components/Apps/AppCard/AppCard.tsx index ec4518a..4821d89 100644 --- a/client/src/components/Apps/AppCard/AppCard.tsx +++ b/client/src/components/Apps/AppCard/AppCard.tsx @@ -1,15 +1,31 @@ import classes from './AppCard.module.css'; import Icon from '../../UI/Icon/Icon'; -const AppCard = (): JSX.Element => { +import { App } from '../../../interfaces'; + +interface ComponentProps { + app: App; +} + +const AppCard = (props: ComponentProps): JSX.Element => { + const iconParser = (mdiName: string): string => { + let parsedName = mdiName + .split('-') + .map((word: string) => `${word[0].toUpperCase()}${word.slice(1)}`) + .join(''); + parsedName = `mdi${parsedName}`; + + return parsedName; + } + return (
- +
-
plex
- plex.example.com +
{props.app.name}
+ {props.app.url}
) diff --git a/client/src/components/Apps/Apps.tsx b/client/src/components/Apps/Apps.tsx index 8084b25..30cc147 100644 --- a/client/src/components/Apps/Apps.tsx +++ b/client/src/components/Apps/Apps.tsx @@ -1,32 +1,59 @@ -import { Fragment } from 'react'; +import { Fragment, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +// Redux +import { connect } from 'react-redux'; +import { getApps } from '../../store/actions'; + +// Typescript +import { App } from '../../interfaces'; + +// CSS import classes from './Apps.module.css'; +// UI import { Container } from '../UI/Layout/Layout'; import Headline from '../UI/Headlines/Headline/Headline'; +import Spinner from '../UI/Spinner/Spinner'; + +// Subcomponents import AppCard from './AppCard/AppCard'; -const Apps = (): JSX.Element => { +interface ComponentProps { + getApps: Function; + apps: App[]; + loading: boolean; +} + +const Apps = (props: ComponentProps): JSX.Element => { + useEffect(() => { + props.getApps() + }, [props.getApps]); + return ( -
- - - - - - - - - - - - - - - - -
+ + Go back} + /> + +
+ {props.loading + ? 'loading' + : props.apps.map((app: App): JSX.Element => { + return + }) + } +
+
) } -export default Apps; \ No newline at end of file +const mapStateToProps = (state: any) => { + return { + apps: state.app.apps, + loading: state.app.loading + } +} + +export default connect(mapStateToProps, { getApps })(Apps); \ No newline at end of file diff --git a/client/src/components/UI/Spinner/Spinner.module.css b/client/src/components/UI/Spinner/Spinner.module.css new file mode 100644 index 0000000..abf8f4e --- /dev/null +++ b/client/src/components/UI/Spinner/Spinner.module.css @@ -0,0 +1,54 @@ +.Spinner, +.Spinner:before, +.Spinner:after { + background: var(--color-primary); + animation: load1 1s infinite ease-in-out; + width: 1em; + height: 4em; +} + +.Spinner { + color: var(--color-primary); + text-indent: -9999em; + margin: 88px auto; + position: relative; + font-size: 11px; + transform: translateZ(0); + animation-delay: -0.16s; +} + +.Spinner:before, +.Spinner:after { + position: absolute; + top: 0; + content: ''; +} + +.Spinner:before { + left: -1.5em; + animation-delay: -0.32s; +} + +.Spinner:after { + left: 1.5em; +} + +@keyframes load1 { + 0%, + 80%, + 100% { + box-shadow: 0 0; + height: 4em; + } + 40% { + box-shadow: 0 -2em; + height: 5em; + } +} + +.SpinnerWrapper { + height: 150px; + display: flex; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/client/src/components/UI/Spinner/Spinner.tsx b/client/src/components/UI/Spinner/Spinner.tsx new file mode 100644 index 0000000..a081c51 --- /dev/null +++ b/client/src/components/UI/Spinner/Spinner.tsx @@ -0,0 +1,11 @@ +import classes from './Spinner.module.css'; + +const Spinner = (): JSX.Element => { + return ( +
+
Loading...
+
+ ) +} + +export default Spinner; \ No newline at end of file diff --git a/client/src/index.css b/client/src/index.css index a08e967..24c1877 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -2,7 +2,7 @@ margin: 0; padding: 0; box-sizing: border-box; - transition: all 0.3s; + /* transition: all 0.3s; */ user-select: none; } @@ -12,6 +12,7 @@ body { --color-accent: #6677EB; background-color: var(--color-background); + transition: background-color 0.3s; /* font weights light 300 regular 400 diff --git a/client/src/interfaces/App.ts b/client/src/interfaces/App.ts new file mode 100644 index 0000000..401b4d2 --- /dev/null +++ b/client/src/interfaces/App.ts @@ -0,0 +1,8 @@ +export interface App { + id: number; + name: string; + url: string; + icon: string; + createdAt: Date; + updatedAt: Date; +} \ No newline at end of file diff --git a/client/src/interfaces/index.ts b/client/src/interfaces/index.ts new file mode 100644 index 0000000..c8c9f06 --- /dev/null +++ b/client/src/interfaces/index.ts @@ -0,0 +1,2 @@ +export * from './App'; +export * from './Theme'; \ No newline at end of file diff --git a/client/src/store/actions/actionTypes.ts b/client/src/store/actions/actionTypes.ts index 1446098..fa7f158 100644 --- a/client/src/store/actions/actionTypes.ts +++ b/client/src/store/actions/actionTypes.ts @@ -1 +1,5 @@ export const SET_THEME = 'SET_THEME'; + +export const GET_APPS = 'GET_APPS'; +export const GET_APPS_SUCCESS = 'GET_APPS_SUCCESS'; +export const GET_APPS_ERROR = 'GET_APPS_ERROR'; \ No newline at end of file diff --git a/client/src/store/actions/app.ts b/client/src/store/actions/app.ts new file mode 100644 index 0000000..5623c5b --- /dev/null +++ b/client/src/store/actions/app.ts @@ -0,0 +1,25 @@ +import axios from 'axios'; +import { Dispatch } from 'redux'; +import { + GET_APPS, + GET_APPS_SUCCESS, + GET_APPS_ERROR +} from './actionTypes'; + +export const getApps = () => async (dispatch: Dispatch) => { + dispatch({ type: GET_APPS }); + + try { + const res = await axios.get('/api/apps'); + + dispatch({ + type: GET_APPS_SUCCESS, + payload: res.data.data + }) + } catch (err) { + dispatch({ + type: GET_APPS_ERROR, + payload: err.data.data + }) + } +} \ No newline at end of file diff --git a/client/src/store/actions/index.ts b/client/src/store/actions/index.ts index 158a6ed..d9fc5a8 100644 --- a/client/src/store/actions/index.ts +++ b/client/src/store/actions/index.ts @@ -1,2 +1,3 @@ export * from './theme'; +export * from './app'; export * from './actionTypes'; \ No newline at end of file diff --git a/client/src/store/reducers/app.ts b/client/src/store/reducers/app.ts new file mode 100644 index 0000000..6591086 --- /dev/null +++ b/client/src/store/reducers/app.ts @@ -0,0 +1,42 @@ +import { + GET_APPS, + GET_APPS_SUCCESS, + GET_APPS_ERROR +} from '../actions/actionTypes'; + +import { App } from '../../interfaces/App'; + +interface State { + loading: boolean; + apps: App[]; +} + +const initialState: State = { + loading: true, + apps: [] +} + +const getApps = (state: State, action: any): State => { + return { + ...state, + loading: true + } +} + +const getAppsSuccess = (state: State, action: any): State => { + return { + ...state, + loading: false, + apps: action.payload + } +} + +const appReducer = (state = initialState, action: any) => { + switch (action.type) { + case GET_APPS: return getApps(state, action); + case GET_APPS_SUCCESS: return getAppsSuccess(state, action); + default: return state; + } +} + +export default appReducer; \ No newline at end of file diff --git a/client/src/store/reducers/index.ts b/client/src/store/reducers/index.ts index e091dac..5061e34 100644 --- a/client/src/store/reducers/index.ts +++ b/client/src/store/reducers/index.ts @@ -1,9 +1,11 @@ import { combineReducers } from 'redux'; import themeReducer from './theme'; +import appReducer from './app'; const rootReducer = combineReducers({ - theme: themeReducer + theme: themeReducer, + app: appReducer }) export default rootReducer; \ No newline at end of file