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 (
)
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 (
+
+ )
+}
+
+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