Apps actions and reducer

This commit is contained in:
unknown 2021-05-10 19:02:16 +02:00
parent 2acc3b72ec
commit 78acede8ab
15 changed files with 230 additions and 27 deletions

View file

@ -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",

View file

@ -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",

View file

@ -22,6 +22,7 @@ const App = (): JSX.Element => {
<Switch>
<Route exact path='/' component={Home} />
<Route path='/settings' component={Settings} />
<Route path='/apps' component={Apps} />
</Switch>
</BrowserRouter>
</Provider>

View file

@ -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 (
<div className={classes.AppCard}>
<div className={classes.AppCardIcon}>
<Icon icon='mdiBookOpenBlankVariant' />
<Icon icon={iconParser(props.app.icon)} />
</div>
<div className={classes.AppCardDetails}>
<h5>plex</h5>
<a href="/">plex.example.com</a>
<h5>{props.app.name}</h5>
<a href="/">{props.app.url}</a>
</div>
</div>
)

View file

@ -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 (
<div className={classes.Apps}>
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
<AppCard />
</div>
<Container>
<Headline
title='Pinned Apps'
subtitle={<Link to='/'>Go back</Link>}
/>
<Headline title='All Apps' />
<div className={classes.Apps}>
{props.loading
? 'loading'
: props.apps.map((app: App): JSX.Element => {
return <AppCard key={app.id} app={app} />
})
}
</div>
</Container>
)
}
export default Apps;
const mapStateToProps = (state: any) => {
return {
apps: state.app.apps,
loading: state.app.loading
}
}
export default connect(mapStateToProps, { getApps })(Apps);

View file

@ -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;
}

View file

@ -0,0 +1,11 @@
import classes from './Spinner.module.css';
const Spinner = (): JSX.Element => {
return (
<div className={classes.SpinnerWrapper}>
<div className={classes.Spinner}>Loading...</div>
</div>
)
}
export default Spinner;

View file

@ -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

View file

@ -0,0 +1,8 @@
export interface App {
id: number;
name: string;
url: string;
icon: string;
createdAt: Date;
updatedAt: Date;
}

View file

@ -0,0 +1,2 @@
export * from './App';
export * from './Theme';

View file

@ -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';

View file

@ -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
})
}
}

View file

@ -1,2 +1,3 @@
export * from './theme';
export * from './app';
export * from './actionTypes';

View file

@ -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;

View file

@ -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;