diff --git a/client/src/components/Apps/AppCard/AppCard.module.css b/client/src/components/Apps/AppCard/AppCard.module.css
index 05dce47..bba98be 100644
--- a/client/src/components/Apps/AppCard/AppCard.module.css
+++ b/client/src/components/Apps/AppCard/AppCard.module.css
@@ -9,7 +9,10 @@
}
.AppCardIcon {
+ /* height: 64px; */
width: 40px;
+ height: 40px;
+ margin-right: 0.5em;
}
.AppCardDetails {
@@ -23,7 +26,7 @@
font-size: 1em;
font-weight: 500;
color: var(--color-primary);
- margin-bottom: -8px;
+ margin-bottom: -4px;
}
.AppCardDetails span {
diff --git a/client/src/components/Apps/AppCard/AppCard.tsx b/client/src/components/Apps/AppCard/AppCard.tsx
index 4821d89..b927c5c 100644
--- a/client/src/components/Apps/AppCard/AppCard.tsx
+++ b/client/src/components/Apps/AppCard/AppCard.tsx
@@ -1,3 +1,5 @@
+import { Link } from 'react-router-dom';
+
import classes from './AppCard.module.css';
import Icon from '../../UI/Icon/Icon';
@@ -5,6 +7,7 @@ import { App } from '../../../interfaces';
interface ComponentProps {
app: App;
+ pinHandler?: Function;
}
const AppCard = (props: ComponentProps): JSX.Element => {
@@ -18,16 +21,20 @@ const AppCard = (props: ComponentProps): JSX.Element => {
return parsedName;
}
+ const redirectHandler = (url: string): void => {
+ window.open(url);
+ }
+
return (
-
+
)
}
diff --git a/client/src/components/Apps/AppForm/AppForm.module.css b/client/src/components/Apps/AppForm/AppForm.module.css
new file mode 100644
index 0000000..7a451ea
--- /dev/null
+++ b/client/src/components/Apps/AppForm/AppForm.module.css
@@ -0,0 +1,49 @@
+.AppForm {
+ background-color: var(--color-background);
+ color: var(--color-primary);
+ border-radius: 6px;
+ width: 60%;
+ position: relative;
+ /* height: 50vh; */
+ padding: 50px 50px;
+}
+
+.AppFormIcon {
+ width: 40px;
+ position: absolute;
+ right: 5px;
+ top: 5px;
+}
+
+.AppFormIcon:hover {
+ cursor: pointer;
+}
+
+.InputGroup {
+ margin-bottom: 15px;
+}
+
+.InputGroup label,
+.InputGroup span,
+.InputGroup input {
+ display: block;
+}
+
+.InputGroup input {
+ margin: 8px 0;
+ width: 100%;
+ border: none;
+ border-radius: 4px;
+ padding: 10px;
+ background-color: var(--color-primary);
+ color: var(--color-background);
+}
+
+.InputGroup span {
+ font-size: 12px;
+ color: var(--color-primary)
+}
+
+.InputGroup span a {
+ color: var(--color-accent);
+}
\ No newline at end of file
diff --git a/client/src/components/Apps/AppForm/AppForm.tsx b/client/src/components/Apps/AppForm/AppForm.tsx
new file mode 100644
index 0000000..6e822f9
--- /dev/null
+++ b/client/src/components/Apps/AppForm/AppForm.tsx
@@ -0,0 +1,99 @@
+import { useState, ChangeEvent, SyntheticEvent } from 'react';
+import { connect } from 'react-redux';
+import { addApp } from '../../../store/actions';
+import { NewApp } from '../../../interfaces/App';
+
+import classes from './AppForm.module.css';
+import Icon from '../../UI/Icon/Icon';
+
+interface ComponentProps {
+ modalHandler: Function;
+ addApp: (formData: NewApp) => any;
+}
+
+const AppForm = (props: ComponentProps): JSX.Element => {
+ const [formData, setFormData] = useState({
+ name: '',
+ url: '',
+ icon: ''
+ });
+
+ const _modalHandler = () => {
+ props.modalHandler();
+ }
+
+ const inputChangeHandler = (e: ChangeEvent): void => {
+ setFormData({
+ ...formData,
+ [e.target.name]: e.target.value
+ })
+ }
+
+ const formSubmitHandler = (e: SyntheticEvent): void => {
+ e.preventDefault();
+ props.addApp(formData);
+ setFormData({
+ name: '',
+ url: '',
+ icon: ''
+ })
+ }
+
+ return (
+
+ )
+}
+
+export default connect(null, { addApp })(AppForm);
\ No newline at end of file
diff --git a/client/src/components/Apps/AppGrid/AppGrid.module.css b/client/src/components/Apps/AppGrid/AppGrid.module.css
new file mode 100644
index 0000000..e8ee751
--- /dev/null
+++ b/client/src/components/Apps/AppGrid/AppGrid.module.css
@@ -0,0 +1,28 @@
+.AppGrid {
+ display: grid;
+ grid-template-columns: repeat(1, 1fr);
+}
+
+@media (min-width: 430px) {
+ .AppGrid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (min-width: 670px) {
+ .AppGrid {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
+@media (min-width: 900px) {
+ .AppGrid {
+ grid-template-columns: repeat(4, 1fr);
+ }
+}
+
+/* 320px — 480px: Mobile devices.
+481px — 768px: iPads, Tablets.
+769px — 1024px: Small screens, laptops.
+1025px — 1200px: Desktops, large screens.
+1201px and more — Extra large screens, TV. */
\ No newline at end of file
diff --git a/client/src/components/Apps/AppGrid/AppGrid.tsx b/client/src/components/Apps/AppGrid/AppGrid.tsx
new file mode 100644
index 0000000..f61e402
--- /dev/null
+++ b/client/src/components/Apps/AppGrid/AppGrid.tsx
@@ -0,0 +1,25 @@
+import classes from './AppGrid.module.css';
+import { App } from '../../../interfaces/App';
+
+import AppCard from '../AppCard/AppCard';
+
+interface ComponentProps {
+ apps: App[];
+}
+
+const AppGrid = (props: ComponentProps): JSX.Element => {
+ const apps = (
+
+ {props.apps.map((app: App): JSX.Element => {
+ return
+ })}
+
+ );
+
+ return apps;
+}
+
+export default AppGrid;
\ No newline at end of file
diff --git a/client/src/components/Apps/Apps.module.css b/client/src/components/Apps/Apps.module.css
index 302fb5b..09810e1 100644
--- a/client/src/components/Apps/Apps.module.css
+++ b/client/src/components/Apps/Apps.module.css
@@ -1,28 +1,4 @@
-.Apps {
- display: grid;
- grid-template-columns: repeat(1, 1fr);
-}
-
-@media (min-width: 430px) {
- .Apps {
- grid-template-columns: repeat(2, 1fr);
- }
-}
-
-@media (min-width: 670px) {
- .Apps {
- grid-template-columns: repeat(3, 1fr);
- }
-}
-
-@media (min-width: 900px) {
- .Apps {
- grid-template-columns: repeat(4, 1fr);
- }
-}
-
-/* 320px — 480px: Mobile devices.
-481px — 768px: iPads, Tablets.
-769px — 1024px: Small screens, laptops.
-1025px — 1200px: Desktops, large screens.
-1201px and more — Extra large screens, TV. */
\ No newline at end of file
+.ActionsContainer {
+ display: flex;
+ align-items: center;
+}
\ No newline at end of file
diff --git a/client/src/components/Apps/Apps.tsx b/client/src/components/Apps/Apps.tsx
index a2aa91f..0bb6d64 100644
--- a/client/src/components/Apps/Apps.tsx
+++ b/client/src/components/Apps/Apps.tsx
@@ -1,12 +1,12 @@
-import { Fragment, useEffect } from 'react';
+import { Fragment, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
// Redux
import { connect } from 'react-redux';
-import { getApps } from '../../store/actions';
+import { getApps, pinApp, addApp } from '../../store/actions';
// Typescript
-import { App, GlobalState } from '../../interfaces';
+import { App, GlobalState, NewApp } from '../../interfaces';
// CSS
import classes from './Apps.module.css';
@@ -15,34 +15,78 @@ import classes from './Apps.module.css';
import { Container } from '../UI/Layout/Layout';
import Headline from '../UI/Headlines/Headline/Headline';
import Spinner from '../UI/Spinner/Spinner';
+import ActionButton from '../UI/Buttons/ActionButton/ActionButton';
+import Modal from '../UI/Modal/Modal';
// Subcomponents
-import AppCard from './AppCard/AppCard';
+import AppGrid from './AppGrid/AppGrid';
+import AppForm from './AppForm/AppForm';
+import AppTable from './AppTable/AppTable';
+import Test from '../Test';
interface ComponentProps {
getApps: Function;
+ pinApp: (id: number, isPinned: boolean) => any;
+ addApp: (formData: NewApp) => any;
apps: App[];
loading: boolean;
}
const Apps = (props: ComponentProps): JSX.Element => {
+ const [modalIsOpen, setModalIsOpen] = useState(false);
+ const [isInEdit, setIsInEdit] = useState(false);
+
useEffect(() => {
- props.getApps()
+ props.getApps();
+ // props.addApp({
+ // name: 'Plex',
+ // url: '192.168.0.128',
+ // icon: 'cat'
+ // })
}, [props.getApps]);
+ const pinAppHandler = (id: number, state: boolean): void => {
+ props.pinApp(id, state);
+ }
+
+ const toggleModal = (): void => {
+ setModalIsOpen(!modalIsOpen);
+ }
+
+ const toggleEdit = (): void => {
+ setIsInEdit(!isInEdit);
+ }
+
return (
+
+
+
+
Go back}
+ title='All Apps'
+ subtitle={(Go back)}
/>
-
+
+
+
{props.loading
? 'loading'
- : props.apps.map((app: App): JSX.Element => {
- return
- })
+ : (!isInEdit
+ ?
+ :
)
}
@@ -56,4 +100,4 @@ const mapStateToProps = (state: GlobalState) => {
}
}
-export default connect(mapStateToProps, { getApps })(Apps);
\ No newline at end of file
+export default connect(mapStateToProps, { getApps, pinApp, addApp })(Apps);
\ No newline at end of file
diff --git a/client/src/store/reducers/app.ts b/client/src/store/reducers/app.ts
index f8d9d7b..06678ca 100644
--- a/client/src/store/reducers/app.ts
+++ b/client/src/store/reducers/app.ts
@@ -52,7 +52,7 @@ const pinApp = (state: State, action: Action): State => {
}
const addAppSuccess = (state: State, action: Action): State => {
- const tmpApps = [...state.apps, ...action.payload];
+ const tmpApps = [...state.apps, action.payload];
return {
...state,