diff --git a/package-lock.json b/package-lock.json
index d72a10c..53b161a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1186,6 +1186,12 @@
"minimist": "^1.2.0"
}
},
+ "@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
+ "dev": true
+ },
"@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -1857,6 +1863,79 @@
}
}
},
+ "@material-ui/core": {
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz",
+ "integrity": "sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.4.4",
+ "@material-ui/styles": "^4.10.0",
+ "@material-ui/system": "^4.9.14",
+ "@material-ui/types": "^5.1.0",
+ "@material-ui/utils": "^4.10.2",
+ "@types/react-transition-group": "^4.2.0",
+ "clsx": "^1.0.4",
+ "hoist-non-react-statics": "^3.3.2",
+ "popper.js": "1.16.1-lts",
+ "prop-types": "^15.7.2",
+ "react-is": "^16.8.0",
+ "react-transition-group": "^4.4.0"
+ }
+ },
+ "@material-ui/styles": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz",
+ "integrity": "sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.4.4",
+ "@emotion/hash": "^0.8.0",
+ "@material-ui/types": "^5.1.0",
+ "@material-ui/utils": "^4.9.6",
+ "clsx": "^1.0.4",
+ "csstype": "^2.5.2",
+ "hoist-non-react-statics": "^3.3.2",
+ "jss": "^10.0.3",
+ "jss-plugin-camel-case": "^10.0.3",
+ "jss-plugin-default-unit": "^10.0.3",
+ "jss-plugin-global": "^10.0.3",
+ "jss-plugin-nested": "^10.0.3",
+ "jss-plugin-props-sort": "^10.0.3",
+ "jss-plugin-rule-value-function": "^10.0.3",
+ "jss-plugin-vendor-prefixer": "^10.0.3",
+ "prop-types": "^15.7.2"
+ }
+ },
+ "@material-ui/system": {
+ "version": "4.9.14",
+ "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.14.tgz",
+ "integrity": "sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.4.4",
+ "@material-ui/utils": "^4.9.6",
+ "csstype": "^2.5.2",
+ "prop-types": "^15.7.2"
+ }
+ },
+ "@material-ui/types": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz",
+ "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==",
+ "dev": true
+ },
+ "@material-ui/utils": {
+ "version": "4.10.2",
+ "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.10.2.tgz",
+ "integrity": "sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.4.4",
+ "prop-types": "^15.7.2",
+ "react-is": "^16.8.0"
+ }
+ },
"@sinonjs/commons": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz",
@@ -2002,6 +2081,31 @@
"integrity": "sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ==",
"dev": true
},
+ "@types/prop-types": {
+ "version": "15.7.3",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
+ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
+ "dev": true
+ },
+ "@types/react": {
+ "version": "16.9.43",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.43.tgz",
+ "integrity": "sha512-PxshAFcnJqIWYpJbLPriClH53Z2WlJcVZE+NP2etUtWQs2s7yIMj3/LDKZT/5CHJ/F62iyjVCDu2H3jHEXIxSg==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "csstype": "^2.2.0"
+ }
+ },
+ "@types/react-transition-group": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz",
+ "integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@@ -3459,6 +3563,12 @@
"shallow-clone": "^3.0.0"
}
},
+ "clsx": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
+ "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==",
+ "dev": true
+ },
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -3835,6 +3945,16 @@
}
}
},
+ "css-vendor": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
+ "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.8.3",
+ "is-in-browser": "^1.0.2"
+ }
+ },
"css-what": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
@@ -3870,6 +3990,12 @@
}
}
},
+ "csstype": {
+ "version": "2.6.11",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.11.tgz",
+ "integrity": "sha512-l8YyEC9NBkSm783PFTvh0FmJy7s5pFKrDp49ZL7zBGX3fWkO+N4EEyan1qqp8cwPLDcD0OSdyY6hAMoxp34JFw==",
+ "dev": true
+ },
"currently-unhandled": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@@ -4159,6 +4285,16 @@
"utila": "~0.4"
}
},
+ "dom-helpers": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz",
+ "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^2.6.7"
+ }
+ },
"dom-serializer": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
@@ -6044,6 +6180,12 @@
"integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==",
"dev": true
},
+ "hyphenate-style-name": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
+ "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==",
+ "dev": true
+ },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -6510,6 +6652,12 @@
"is-extglob": "^2.1.1"
}
},
+ "is-in-browser": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
+ "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=",
+ "dev": true
+ },
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
@@ -8452,6 +8600,92 @@
"verror": "1.10.0"
}
},
+ "jss": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss/-/jss-10.3.0.tgz",
+ "integrity": "sha512-B5sTRW9B6uHaUVzSo9YiMEOEp3UX8lWevU0Fsv+xtRnsShmgCfIYX44bTH8bPJe6LQKqEXku3ulKuHLbxBS97Q==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "csstype": "^2.6.5",
+ "is-in-browser": "^1.1.3",
+ "tiny-warning": "^1.0.2"
+ }
+ },
+ "jss-plugin-camel-case": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.3.0.tgz",
+ "integrity": "sha512-tadWRi/SLWqLK3EUZEdDNJL71F3ST93Zrl9JYMjV0QDqKPAl0Liue81q7m/nFUpnSTXczbKDy4wq8rI8o7WFqA==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "hyphenate-style-name": "^1.0.3",
+ "jss": "^10.3.0"
+ }
+ },
+ "jss-plugin-default-unit": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.3.0.tgz",
+ "integrity": "sha512-tT5KkIXAsZOSS9WDSe8m8lEHIjoEOj4Pr0WrG0WZZsMXZ1mVLFCSsD2jdWarQWDaRNyMj/I4d7czRRObhOxSuw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "^10.3.0"
+ }
+ },
+ "jss-plugin-global": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.3.0.tgz",
+ "integrity": "sha512-etYTG/y3qIR/vxZnKY+J3wXwObyBDNhBiB3l/EW9/pE3WHE//BZdK8LFvQcrCO48sZW1Z6paHo6klxUPP7WbzA==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "^10.3.0"
+ }
+ },
+ "jss-plugin-nested": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.3.0.tgz",
+ "integrity": "sha512-qWiEkoXNEkkZ+FZrWmUGpf+zBsnEOmKXhkjNX85/ZfWhH9dfGxUCKuJFuOWFM+rjQfxV4csfesq4hY0jk8Qt0w==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "^10.3.0",
+ "tiny-warning": "^1.0.2"
+ }
+ },
+ "jss-plugin-props-sort": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.3.0.tgz",
+ "integrity": "sha512-boetORqL/lfd7BWeFD3K+IyPqyIC+l3CRrdZr+NPq7Noqp+xyg/0MR7QisgzpxCEulk+j2CRcEUoZsvgPC4nTg==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "^10.3.0"
+ }
+ },
+ "jss-plugin-rule-value-function": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.3.0.tgz",
+ "integrity": "sha512-7WiMrKIHH3rwxTuJki9+7nY11r1UXqaUZRhHvqTD4/ZE+SVhvtD5Tx21ivNxotwUSleucA/8boX+NF21oXzr5Q==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "^10.3.0",
+ "tiny-warning": "^1.0.2"
+ }
+ },
+ "jss-plugin-vendor-prefixer": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.3.0.tgz",
+ "integrity": "sha512-sZQbrcZyP5V0ADjCLwUA1spVWoaZvM7XZ+2fSeieZFBj31cRsnV7X70FFDerMHeiHAXKWzYek+67nMDjhrZAVQ==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "css-vendor": "^2.0.8",
+ "jss": "^10.3.0"
+ }
+ },
"jsx-ast-utils": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz",
@@ -9354,6 +9588,16 @@
"sort-keys": "^1.0.0"
}
},
+ "notistack": {
+ "version": "0.9.17",
+ "resolved": "https://registry.npmjs.org/notistack/-/notistack-0.9.17.tgz",
+ "integrity": "sha512-nypTN6sEe+q98wMaxF/UwatA1yAq948+bZOo9JKYR+tU65DW0ipWyx8DseJ3UJYvb6VDD+Fqo83qwayQ46bEEA==",
+ "dev": true,
+ "requires": {
+ "clsx": "^1.1.0",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -9975,6 +10219,12 @@
"find-up": "^2.1.0"
}
},
+ "popper.js": {
+ "version": "1.16.1-lts",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
+ "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==",
+ "dev": true
+ },
"portfinder": {
"version": "1.0.26",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
@@ -10446,6 +10696,18 @@
"tiny-warning": "^1.0.0"
}
},
+ "react-transition-group": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
+ "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ }
+ },
"read-pkg": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
diff --git a/package.json b/package.json
index 685f952..a13a65e 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"@babel/plugin-transform-runtime": "^7.10.3",
"@babel/preset-env": "^7.10.3",
"@babel/preset-react": "^7.10.1",
+ "@material-ui/core": "^4.11.0",
"babel-eslint": "^10.0.3",
"babel-loader": "^8.1.0",
"babel-plugin-module-resolver": "^3.2.0",
@@ -42,6 +43,7 @@
"jest": "^26.0.1",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.14.1",
+ "notistack": "^0.9.17",
"prop-types": "^15.7.2",
"sass-loader": "^8.0.2",
"webpack": "^4.43.0",
diff --git a/src/_content/main.scss b/src/_content/main.scss
index 8980002..63b09be 100644
--- a/src/_content/main.scss
+++ b/src/_content/main.scss
@@ -20,6 +20,23 @@ table {
th, td {
padding: 0.25rem;
border: 1px solid black;
+
+ .textLink {
+ border-style: none;
+ cursor: pointer;
+ display: inline;
+ perspective-origin: 0 0;
+ text-align: start;
+ text-decoration: underline;
+ color: blue;
+ font-size: 1em;
+ font-weight: normal;
+ font-style: normal;
+ font-stretch: normal;
+ line-height: normal;
+ letter-spacing: normal;
+ background: rgba(0, 0, 0, 0);
+ }
}
th {
@@ -38,4 +55,8 @@ img {
width: 100px;
height: 100px;
display: block;
+}
+
+.form-grid {
+ display: grid;
}
\ No newline at end of file
diff --git a/src/app/components/buttons/add.jsx b/src/app/components/buttons/add.jsx
new file mode 100644
index 0000000..52702f8
--- /dev/null
+++ b/src/app/components/buttons/add.jsx
@@ -0,0 +1,53 @@
+import React, { useCallback } from 'react';
+import { Button } from '@material-ui/core';
+import axios from 'axios';
+import { useSnackbar } from 'notistack';
+import { func, shape, string, bool } from 'prop-types';
+
+const AddButton = ({ setSubmitting, newRecord, disabled, dispatch }) => {
+ const { enqueueSnackbar, closeSnackbar } = useSnackbar();
+
+ const submit = useCallback((event) => {
+ if (event) event.preventDefault();
+ closeSnackbar();
+ setSubmitting(true);
+ axios.post('/api', newRecord)
+ .then((res) => {
+ if (res) {
+ dispatch({ type: 'reset' });
+ enqueueSnackbar('Successfully Added. Add another?');
+ } else {
+ enqueueSnackbar('Oops! Something gone wrong');
+ }
+ })
+ .catch(() => {
+ enqueueSnackbar('Oops! Something gone wrong');
+ });
+ setSubmitting(false);
+ }, [closeSnackbar, setSubmitting, newRecord, dispatch, enqueueSnackbar]);
+
+ return (
+
+ );
+};
+
+AddButton.propTypes = {
+ setSubmitting: func.isRequired,
+ newRecord: shape({
+ title: string,
+ frequency: string,
+ url: string,
+ imageUrl: string,
+ }).isRequired,
+ disabled: bool.isRequired,
+ dispatch: func.isRequired,
+};
+
+export default AddButton;
diff --git a/src/app/components/buttons/delete.jsx b/src/app/components/buttons/delete.jsx
new file mode 100644
index 0000000..09b95f2
--- /dev/null
+++ b/src/app/components/buttons/delete.jsx
@@ -0,0 +1,58 @@
+import React, { useState, useCallback } from 'react';
+import axios from 'axios';
+import { Button, Dialog, DialogActions, DialogTitle } from '@material-ui/core';
+import { string } from 'prop-types';
+import { useSnackbar } from 'notistack';
+import { useHistory } from 'react-router-dom';
+
+const DeleteButton = ({ id }) => {
+ const history = useHistory();
+ const [open, setOpen] = useState(false);
+ const { enqueueSnackbar, closeSnackbar } = useSnackbar();
+
+ const handleOpen = () => setOpen(true);
+ const handleClose = () => setOpen(false);
+
+ const deleteRecord = useCallback((event) => {
+ if (event) event.preventDefault();
+ closeSnackbar();
+ axios.delete(`/api/${id}`)
+ .then((res) => {
+ if (res) {
+ enqueueSnackbar('Successfully removed the record');
+ history.push({ pathname: '/' });
+ } else {
+ enqueueSnackbar('Oh oh, something went horribly wrong');
+ }
+ })
+ .catch(() => {
+ enqueueSnackbar('Oh oh, something went horribly wrong');
+ });
+ setOpen(false);
+ }, [closeSnackbar, enqueueSnackbar, history, id]);
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+DeleteButton.propTypes = { id: string.isRequired };
+
+export default DeleteButton;
diff --git a/src/app/components/buttons/textLink.jsx b/src/app/components/buttons/textLink.jsx
new file mode 100644
index 0000000..c38d159
--- /dev/null
+++ b/src/app/components/buttons/textLink.jsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import { Button } from '@material-ui/core';
+import { func, string } from 'prop-types';
+
+const TextLink = ({ text, onClick }) => (
+
+);
+
+TextLink.propTypes = {
+ text: string.isRequired,
+ onClick: func.isRequired,
+};
+
+export default TextLink;
diff --git a/src/app/components/buttons/update.jsx b/src/app/components/buttons/update.jsx
new file mode 100644
index 0000000..8d28394
--- /dev/null
+++ b/src/app/components/buttons/update.jsx
@@ -0,0 +1,54 @@
+import React, { useCallback } from 'react';
+import { Button } from '@material-ui/core';
+import axios from 'axios';
+import { useSnackbar } from 'notistack';
+import { func, shape, string, bool } from 'prop-types';
+
+const UpdateButton = ({ setSubmitting, record, disabled }) => {
+ const { enqueueSnackbar, closeSnackbar } = useSnackbar();
+
+ const submit = useCallback((event) => {
+ if (event) event.preventDefault();
+ closeSnackbar();
+ setSubmitting(true);
+ const { id, title, frequency, url, imageUrl } = record;
+
+ axios.patch(`/api/${id}?title=${title}&&frequency=${frequency}&&url=${url}&&imageUrl=${imageUrl}`)
+ .then((res) => {
+ if (res) {
+ enqueueSnackbar('Successfully Updated');
+ } else {
+ enqueueSnackbar('Oops! Something gone wrong');
+ }
+ })
+ .catch(() => {
+ enqueueSnackbar('Oops! Something gone wrong');
+ });
+ setSubmitting(false);
+ }, [closeSnackbar, setSubmitting, record, enqueueSnackbar]);
+
+ return (
+
+ );
+};
+
+UpdateButton.propTypes = {
+ setSubmitting: func.isRequired,
+ record: shape({
+ id: string,
+ title: string,
+ frequency: string,
+ url: string,
+ imageUrl: string,
+ }).isRequired,
+ disabled: bool.isRequired,
+};
+
+export default UpdateButton;
diff --git a/src/app/components/fm.jsx b/src/app/components/fm.jsx
new file mode 100644
index 0000000..7f2d2d4
--- /dev/null
+++ b/src/app/components/fm.jsx
@@ -0,0 +1,102 @@
+import React, { useState, useReducer, useEffect } from 'react';
+import { TextField } from '@material-ui/core';
+import { useLocation } from 'react-router-dom';
+import Delete from './buttons/delete';
+import AddButton from './buttons/add';
+import UpdateButton from './buttons/update';
+
+const FM = () => {
+ const location = useLocation();
+ const { record = {}, edit } = location;
+
+ const initialState = {
+ title: record.title || '',
+ url: record.url || '',
+ frequency: record.frequency || '',
+ imageUrl: record.imageUrl || '',
+ };
+
+ const reducer = (state, action) => {
+ const { data } = action;
+ switch (action.type) {
+ case 'reset':
+ return initialState;
+ case 'update':
+ return { ...state, [data.name]: data.value };
+ default:
+ return state;
+ }
+ };
+
+ const [state, dispatch] = useReducer(reducer, initialState);
+ const [submitting, setSubmitting] = useState(false);
+ const [formError, setFormError] = useState('');
+
+ const handleChange = (name, value) => {
+ dispatch({ type: 'update', data: { name, value } });
+ };
+
+ useEffect(() => {
+ const errorCount = Object.keys(state).reduce((acc, key) => {
+ if (state[key] === '') {
+ return acc + 1;
+ }
+ return acc;
+ }, 0);
+ if (errorCount > 0) {
+ setFormError('Please add the required information');
+ } else {
+ setFormError('');
+ }
+ }, [state]);
+
+ return (
+
+
{formError}
+
handleChange('title', e.target.value)}
+ />
+ handleChange('frequency', e.target.value)}
+ />
+ handleChange('url', e.target.value)}
+ />
+ handleChange('imageUrl', e.target.value)}
+ />
+ {!edit && (
+
+ )}
+ {edit && (
+ <>
+
+
+ >
+ )}
+
+ );
+};
+
+export default FM;
diff --git a/src/app/components/navbar.jsx b/src/app/components/navbar.jsx
index 4091f14..a5b3e1c 100644
--- a/src/app/components/navbar.jsx
+++ b/src/app/components/navbar.jsx
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
const Navbar = () => (
);
diff --git a/src/app/components/searchPage.jsx b/src/app/components/searchPage.jsx
index 0e18ea4..533025c 100644
--- a/src/app/components/searchPage.jsx
+++ b/src/app/components/searchPage.jsx
@@ -1,9 +1,13 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
+import { LinearProgress } from '@material-ui/core';
+import { useHistory } from 'react-router-dom';
+import TextLink from './buttons/textLink';
const SearchPage = () => {
const [fmList, setFMList] = useState([]);
const [loading, setLoading] = useState(true);
+ const history = useHistory();
const sortFMByTitle = (titleA, titleB) => {
if (titleA < titleB) return -1;
@@ -12,9 +16,6 @@ const SearchPage = () => {
};
useEffect(() => {
- // TODO
- // MOVE THIS TO ITS CUSTOM HOOK
- // ADD TEST
const fetchData = async () => {
const result = await axios('/api');
const { data } = result;
@@ -28,27 +29,39 @@ const SearchPage = () => {
fetchData();
}, []);
+ const onClick = (fm) => {
+ history.push({
+ pathname: '/fm',
+ record: fm,
+ edit: true,
+ });
+ };
+
return (
-
-
-
- | Station |
- Frequency |
- Image |
-
-
-
- {!loading && fmList.length > 0
- ? fmList.map(fm => (
-
- | {fm.title} |
- {fm.frequency} |
-  |
-
- ))
- : Still loading....
}
-
-
+ <>
+ {!loading && fmList.length > 0
+ ? (
+
+
+
+ | Station |
+ Frequency |
+ Image |
+
+
+
+ {fmList.map(fm => (
+ onClick(fm)}>
+ | onClick(fm)} /> |
+ {fm.frequency} |
+  |
+
+ ))}
+
+
+ )
+ : }
+ >
);
};
diff --git a/src/app/index.jsx b/src/app/index.jsx
index 931953a..879cf0c 100644
--- a/src/app/index.jsx
+++ b/src/app/index.jsx
@@ -1,6 +1,8 @@
import React from 'react';
+import { SnackbarProvider } from 'notistack';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import SearchPage from './components/searchPage';
+import FMStation from './components/fm';
import Navbar from './components/navbar';
const App = () => (
@@ -8,8 +10,15 @@ const App = () => (
+
);
-export default App;
+const WrappedApp = () => (
+
+
+
+);
+
+export default WrappedApp;