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 ( + <> + + + Are you sure you want to delete this record? + + + + + + + ); +}; + +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 ( - - - - - - - - - - {!loading && fmList.length > 0 - ? fmList.map(fm => ( - - - - - - )) - :
Still loading....
} - -
StationFrequencyImage
{fm.title}{fm.frequency}{fm.title}
+ <> + {!loading && fmList.length > 0 + ? ( + + + + + + + + + + {fmList.map(fm => ( + onClick(fm)}> + + + + + ))} + +
StationFrequencyImage
onClick(fm)} />{fm.frequency}{fm.title}
+ ) + : } + ); }; 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;