Browse Source

Multiple changes to App related components. Created form to add new Apps, outsourced AppGrid component

unknown 4 years ago
parent
commit
7e540587a5

+ 4 - 1
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 {

+ 10 - 3
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 (
-    <div className={classes.AppCard}>
+    <a href={`http://${props.app.url}`} target='blank' className={classes.AppCard}>
       <div className={classes.AppCardIcon}>
         <Icon icon={iconParser(props.app.icon)} />
       </div>
       <div className={classes.AppCardDetails}>
         <h5>{props.app.name}</h5>
-        <a href="/">{props.app.url}</a>
+        <span>{props.app.url}</span>
       </div>
-    </div>
+    </a>
   )
 }
 

+ 49 - 0
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);
+}

+ 99 - 0
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<NewApp>({
+    name: '',
+    url: '',
+    icon: ''
+  });
+
+  const _modalHandler = () => {
+    props.modalHandler();
+  }
+
+  const inputChangeHandler = (e: ChangeEvent<HTMLInputElement>): void => {
+    setFormData({
+      ...formData,
+      [e.target.name]: e.target.value
+    })
+  }
+
+  const formSubmitHandler = (e: SyntheticEvent): void => {
+    e.preventDefault();
+    props.addApp(formData);
+    setFormData({
+      name: '',
+      url: '',
+      icon: ''
+    })
+  }
+
+  return (
+    <div className={classes.AppForm}>
+      <div className={classes.AppFormIcon} onClick={_modalHandler}>
+        <Icon icon='mdiClose' />
+      </div>
+      <form onSubmit={(e) => formSubmitHandler(e)}>
+        <div className={classes.InputGroup}>
+          <label htmlFor='name'>App Name</label>
+          <input
+            type='text'
+            name='name'
+            id='name'
+            placeholder='Bookstack'
+            required
+            value={formData.name}
+            onChange={(e) => inputChangeHandler(e)}
+          />
+        </div>
+        <div className={classes.InputGroup}>
+          <label htmlFor='url'>App URL</label>
+          <input
+            type='text'
+            name='url'
+            id='url'
+            placeholder='bookstack.example.com'
+            required
+            value={formData.url}
+            onChange={(e) => inputChangeHandler(e)}
+          />
+          <span>Use URL without protocol</span>
+        </div>
+        <div className={classes.InputGroup}>
+          <label htmlFor='icon'>App Icon</label>
+          <input
+            type='text'
+            name='icon'
+            id='icon'
+            placeholder='book-open-outline'
+            required
+            value={formData.icon}
+            onChange={(e) => inputChangeHandler(e)}
+          />
+          <span>
+            Use icon name from MDI. 
+            <a
+              href='https://materialdesignicons.com/'
+              target='blank'>
+              {' '}Click here for reference
+            </a>
+          </span>
+        </div>
+        <button type="submit">add</button>
+      </form>
+    </div>
+  )
+}
+
+export default connect(null, { addApp })(AppForm);

+ 28 - 0
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. */

+ 25 - 0
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 = (
+    <div className={classes.AppGrid}>
+      {props.apps.map((app: App): JSX.Element => {
+        return <AppCard
+          key={app.id}
+          app={app}
+        />
+      })}
+    </div>
+  );
+
+  return apps;
+}
+
+export default AppGrid;

+ 4 - 28
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. */
+.ActionsContainer {
+  display: flex;
+  align-items: center;
+}

+ 56 - 12
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 (
     <Container>
+      <Modal isOpen={modalIsOpen}>
+        <AppForm modalHandler={toggleModal} />
+      </Modal>
+
       <Headline
-        title='Pinned Apps'
-        subtitle={<Link to='/'>Go back</Link>}
+        title='All Apps'
+        subtitle={(<Link to='/'>Go back</Link>)}
       />
-      <Headline title='All Apps' />
+      
+      <div className={classes.ActionsContainer}>
+        <ActionButton
+          name='Add'
+          icon='mdiPlusBox'
+          handler={toggleModal}
+        />
+        <ActionButton
+          name='Edit'
+          icon='mdiPencil'
+          handler={toggleEdit}
+        />
+      </div>
+
       <div className={classes.Apps}>
         {props.loading
           ? 'loading'
-          : props.apps.map((app: App): JSX.Element => {
-            return <AppCard key={app.id} app={app} />
-          })
+          : (!isInEdit
+              ? <AppGrid apps={props.apps} />
+              : <AppTable />)
         }
       </div>
     </Container>
@@ -56,4 +100,4 @@ const mapStateToProps = (state: GlobalState) => {
   }
 }
 
-export default connect(mapStateToProps, { getApps })(Apps);
+export default connect(mapStateToProps, { getApps, pinApp, addApp })(Apps);

+ 1 - 1
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,