diff --git a/.gitignore b/.gitignore index 417c6ce..d47c336 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ -.vscode/ \ No newline at end of file +.vscode/ +.netlify diff --git a/package-lock.json b/package-lock.json index 39adea6..1c97132 100644 --- a/package-lock.json +++ b/package-lock.json @@ -392,6 +392,30 @@ "warning": "^4.0.1" } }, + "@material-ui/icons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-3.0.1.tgz", + "integrity": "sha512-1kNcxYiIT1x8iDPEAlgmKrfRTIV8UyK6fLVcZ9kMHIKGWft9I451V5mvSrbCjbf7MX1TbLWzZjph0aVCRf9MqQ==", + "requires": { + "@babel/runtime": "7.0.0", + "recompose": "^0.29.0" + }, + "dependencies": { + "recompose": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.29.0.tgz", + "integrity": "sha512-J/qLXNU4W+AeHCDR70ajW8eMd1uroqZaECTj6qqDLPMILz3y0EzpYlvrnxKB9DnqcngWrtGwjXY9JeXaW9kS1A==", + "requires": { + "@babel/runtime": "^7.0.0", + "change-emitter": "^0.1.2", + "fbjs": "^0.8.1", + "hoist-non-react-statics": "^2.3.1", + "react-lifecycles-compat": "^3.0.2", + "symbol-observable": "^1.0.4" + } + } + } + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -3027,6 +3051,16 @@ "sha.js": "^2.4.8" } }, + "create-react-class": { + "version": "15.6.3", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", + "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -3543,6 +3577,40 @@ } } }, + "disqus-react": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/disqus-react/-/disqus-react-1.0.5.tgz", + "integrity": "sha1-go6hhr0kHF4qbf66tRNeUqLZHRE=", + "requires": { + "react": "^15.6.1", + "react-dom": "^15.6.1" + }, + "dependencies": { + "react": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz", + "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=", + "requires": { + "create-react-class": "^15.6.0", + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.10" + } + }, + "react-dom": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz", + "integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=", + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.10" + } + } + } + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -6076,8 +6144,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -6429,8 +6496,7 @@ }, "safe-buffer": { "version": "5.1.1", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -6477,7 +6543,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6516,13 +6581,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.2", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -9529,6 +9592,11 @@ "integrity": "sha512-9DITV2YEMcw7XojdfvGl3gDD8J9QjZTJ7ZOUuSAkP+F3T6rDbzMJuPktxptsdHYEvZcmXrCD3LMOhdSAEq6zKA==", "optional": true }, + "moment": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -11871,6 +11939,11 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-quiz-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/react-quiz-component/-/react-quiz-component-0.2.0.tgz", + "integrity": "sha512-oWUos0A4NtYNBNoSqAbjjpZC9ndEuw5SebYRDhX2EUv4I41iqWWVNMbY2Pu7qT6I7MuELfWHrpV+5p0XwaChaQ==" + }, "react-router": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", diff --git a/package.json b/package.json index e2ef165..51de5d0 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,16 @@ "private": true, "dependencies": { "@material-ui/core": "^3.1.0", + "@material-ui/icons": "^3.0.1", + "disqus-react": "^1.0.5", "firebase": "^5.5.2", "firebase-admin": "^6.0.0", "flamelink": "^0.19.2", "google-maps-react": "^2.0.2", + "moment": "^2.22.2", "react": "^16.5.1", "react-dom": "^16.5.1", + "react-quiz-component": "0.2.0", "react-router": "^4.3.1", "react-router-dom": "^4.3.1", "react-scripts": "1.1.5" diff --git a/public/index.html b/public/index.html index ebb2c8a..d949bd5 100644 --- a/public/index.html +++ b/public/index.html @@ -1,6 +1,17 @@ + + + + + @@ -30,15 +41,16 @@ You need to enable JavaScript to run this app.
- + + diff --git a/public/quizimages/question1.jpg b/public/quizimages/question1.jpg new file mode 100644 index 0000000..8ce9a50 Binary files /dev/null and b/public/quizimages/question1.jpg differ diff --git a/public/quizimages/question2.jpg b/public/quizimages/question2.jpg new file mode 100644 index 0000000..8f6d931 Binary files /dev/null and b/public/quizimages/question2.jpg differ diff --git a/public/quizimages/question3.jpg b/public/quizimages/question3.jpg new file mode 100644 index 0000000..c518c5e Binary files /dev/null and b/public/quizimages/question3.jpg differ diff --git a/public/quizimages/question4.jpg b/public/quizimages/question4.jpg new file mode 100644 index 0000000..0e52c59 Binary files /dev/null and b/public/quizimages/question4.jpg differ diff --git a/public/quizimages/question5.jpg b/public/quizimages/question5.jpg new file mode 100644 index 0000000..c49d92f Binary files /dev/null and b/public/quizimages/question5.jpg differ diff --git a/src/App.css b/src/App.css index 9dd2624..b5b1e22 100644 --- a/src/App.css +++ b/src/App.css @@ -2,11 +2,49 @@ body { margin: 0; } -.report-google-map-container > div { - height: 92% !important; - width: 50% !important; +@media (min-width: 600px) { + .sighting-google-map-container > div { + width: calc(100% - 240px) !important; + height: calc(100% - 64px) !important; + } } -.sighting-google-map-container > div { - height: 92% !important; +@media (min-width: 960px) { + .report-google-map-container > div { + width: calc(100% - 50% - 120px) !important; + } +} + +#disqus_thread { + width: 89% !important; + margin: 0 auto; +} + +.sighting-list { + height: calc(50vh - 64px); + overflow-y: scroll; +} + +@media (min-width: 960px) { + .sighting-list { + height: calc(100vh - 64px); + overflow-y: scroll; + } +} + +.sighting-details-content { + width: 89%; + margin: 330px auto 0 auto; +} + +.sighting-detail-google-map-container > div { + width: 100% !important; + height: 300px !important; +} + +@media (min-width: 960px) { + .sighting-detail-google-map-container > div { + width: calc(100% - 50% - 120px) !important; + height: 300px !important; + } } \ No newline at end of file diff --git a/src/components/Flamelink.js b/src/components/Flamelink.js index fd3187d..3958057 100644 --- a/src/components/Flamelink.js +++ b/src/components/Flamelink.js @@ -2,7 +2,6 @@ import { Component } from 'react'; class Flamelink extends Component { render() { - return(null); } } diff --git a/src/components/Main.js b/src/components/Main.js index 2259d17..0332dbe 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -1,75 +1,209 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; +import Drawer from '@material-ui/core/Drawer'; import AppBar from '@material-ui/core/AppBar'; -import Tabs from '@material-ui/core/Tabs'; -import Tab from '@material-ui/core/Tab'; +import Toolbar from '@material-ui/core/Toolbar'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; import Typography from '@material-ui/core/Typography'; +import IconButton from '@material-ui/core/IconButton'; +import Hidden from '@material-ui/core/Hidden'; +import Divider from '@material-ui/core/Divider'; +import MenuIcon from '@material-ui/icons/Menu'; +import HomeIcon from '@material-ui/icons/Home'; +import AssignmentIcon from '@material-ui/icons/Assignment'; +import MapIcon from '@material-ui/icons/Map'; +import ListIcon from '@material-ui/icons/List'; +import SlideshowIcon from '@material-ui/icons/Slideshow'; import Home from '../pages/Home'; import ViewMap from '../pages/ViewMap'; -import Quiz from '../pages/Quiz'; +import Quiz from '../pages/QuizPage'; import SightingList from '../pages/SightingList'; import Report from '../pages/Report'; -import Info from '../pages/Info'; +import CssBaseline from '@material-ui/core/CssBaseline'; +import ExpandLess from '@material-ui/icons/ExpandLess'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import Collapse from '@material-ui/core/Collapse'; -function TabContainer(props) { - return ( - - {props.children} - - ); -} - -TabContainer.propTypes = { - children: PropTypes.node.isRequired, -}; +const drawerWidth = 240; const styles = theme => ({ root: { + display: 'flex', + }, + drawer: { + [theme.breakpoints.up('sm')]: { + width: drawerWidth, + flexShrink: 0, + }, + }, + nested: { + paddingLeft: theme.spacing.unit * 4, + }, + appBar: { + marginLeft: drawerWidth, + [theme.breakpoints.up('sm')]: { + width: `calc(100% - ${drawerWidth}px)`, + }, + }, + menuButton: { + marginRight: 20, + [theme.breakpoints.up('sm')]: { + display: 'none', + }, + }, + toolbar: theme.mixins.toolbar, + drawerPaper: { + width: drawerWidth, + }, + content: { flexGrow: 1, - backgroundColor: theme.palette.background.paper, + width: '60%' }, }); -class SimpleTabs extends React.Component { +class ResponsiveDrawer extends React.Component { state = { - value: 0, + mobileOpen: false, + key: 'Home', + open: false }; - handleChange = (event, value) => { - this.setState({ value }); - }; + handleDrawerToggle = () => { + this.setState(state => ({ mobileOpen: !state.mobileOpen })); + } + + handleClick = () => { + this.setState(state => ({ open: !state.open })); + } + + nav = (text) => { + this.setState({ + key: text + }); + } render() { - const { classes } = this.props; - const { value } = this.state; + const { classes, theme } = this.props; - return ( -
- - - - - - - - - - - {value === 0 && } - {value === 1 && } - {value === 2 && } - {value === 3 && } - {value === 4 && } - {value === 5 && } -
- ); + const drawer = ( +
+
+ + + this.nav('Home')}> + + + + this.nav('Report')}> + + + + this.nav('Map')}> + + + + this.nav('List')}> + + + + + + + + + {this.state.open ? : } + + + + this.nav('Easy-Quiz')}> + + + this.nav('Medium-Quiz')}> + + + this.nav('Hard-Quiz')}> + + + + + + +
+ ); + + return ( +
+ + + + + + + + Marten Tracker + + + + +
+
+ {this.state.key === 'Home' && } + {this.state.key === 'Report' && } + {this.state.key === 'Map' && } + {this.state.key === 'List' && } + {this.state.key === 'Easy-Quiz' && } + {this.state.key === 'Medium-Quiz' && } + {this.state.key === 'Hard-Quiz' && } +
+
+ ); } } -SimpleTabs.propTypes = { +ResponsiveDrawer.propTypes = { classes: PropTypes.object.isRequired, + // Injected by the documentation to work in an iframe. + // You won't need it on your project. + container: PropTypes.object, + theme: PropTypes.object.isRequired, }; -export default withStyles(styles)(SimpleTabs); - +export default withStyles(styles, { withTheme: true })(ResponsiveDrawer); \ No newline at end of file diff --git a/src/components/QuizGame.js b/src/components/QuizGame.js new file mode 100644 index 0000000..88f8bbe --- /dev/null +++ b/src/components/QuizGame.js @@ -0,0 +1,297 @@ +import React, { Fragment } from 'react'; +import Grid from '@material-ui/core/Grid'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Quiz from 'react-quiz-component'; +import { Typography } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import RefreshIcon from '@material-ui/icons/Refresh'; + +// Style for the tabs. +const styles = theme => ({ + root: { + flexGrow: 1, + backgroundColor: theme.palette.background.paper, + }, + button: { + margin: theme.spacing.unit, + }, + rightIcon: { + marginLeft: theme.spacing.unit, + }, +}); + +class QuizGame extends React.Component { + + /** + * Shuffles a given array. + * @param {*} array The array passed in. + */ + shuffleArray = array => { + let shuffled = array; + + var j, x, i; + + for (i = shuffled.length - 1; i > 0; i--) { + j = Math.floor(Math.random() * (i + 1)); + x = shuffled[i]; + shuffled[i] = shuffled[j]; + shuffled[j] = x; + } + + return shuffled; + } + + reset = () => { + this.setState({ + difficulty: this.pickDifficulty(this.props.difficulty), + key: Math.random() + }); + } + + easy = { + "quizTitle": "Trail Cam Quiz: Easy", + "questions": [ + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Black bear", + "Common wombat", + "Raccoon", + "White-tailed deer" + ], + "correctAnswer": "1" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American beaver", + "Muskrat", + "Porcupine", + "Woodchuck" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American badger", + "Raccoon", + "Striped skunk", + "Virginia opossum" + ], + "correctAnswer": "2" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Eastern fox squirrel", + "Eastern gray squirrel", + "Red squirrel", + "Southern flying squirrel" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American Crow", + "Black Vulture", + "Turkey Vulture", + "Northern Raven" + ], + "correctAnswer": "3" + }, + ] + } + + medium = { + "quizTitle": "Trail Cam Quiz: Medium", + "questions": [ + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Black bear", + "Common wombat", + "Raccoon", + "White-tailed deer" + ], + "correctAnswer": "1" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American beaver", + "Muskrat", + "Porcupine", + "Woodchuck" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American badger", + "Raccoon", + "Striped skunk", + "Virginia opossum" + ], + "correctAnswer": "2" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Eastern fox squirrel", + "Eastern gray squirrel", + "Red squirrel", + "Southern flying squirrel" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American Crow", + "Black Vulture", + "Turkey Vulture", + "Northern Raven" + ], + "correctAnswer": "3" + }, + ] + } + + hard = { + "quizTitle": "Trail Cam Quiz: Hard", + "questions": [ + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Black bear", + "Common wombat", + "Raccoon", + "White-tailed deer" + ], + "correctAnswer": "1" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American beaver", + "Muskrat", + "Porcupine", + "Woodchuck" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American badger", + "Raccoon", + "Striped skunk", + "Virginia opossum" + ], + "correctAnswer": "2" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Eastern fox squirrel", + "Eastern gray squirrel", + "Red squirrel", + "Southern flying squirrel" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American Crow", + "Black Vulture", + "Turkey Vulture", + "Northern Raven" + ], + "correctAnswer": "3" + }, + ] + } + + /** + * This function returns the + * quiz data based on the difficulty + * level passed into it. + * @param {*} difficulty The difficulty setting passed in. + */ + pickDifficulty = difficulty => { + let level; + + switch (difficulty) { + case 'Easy': + this.easy.questions = this.shuffleArray(this.easy.questions); + level = this.easy; + break; + case 'Medium': + this.medium.questions = this.shuffleArray(this.medium.questions); + level = this.medium; + break; + case 'Hard': + this.hard.questions = this.shuffleArray(this.hard.questions); + level = this.hard; + break; + default: + break; + } + + return level; + } + + // The state of the component. + state = { + difficulty: this.pickDifficulty(this.props.difficulty), + key: Math.random() + }; + + // Renders the quiz component. + render() { + const { classes } = this.props; + + return ( + // Tabs +
+ + + + + + + + +
+ ); + } +} + +QuizGame.propTypes = { + classes: PropTypes.object.isRequired +}; + +export default withStyles(styles)(QuizGame); \ No newline at end of file diff --git a/src/components/ReportForm.js b/src/components/ReportForm.js index ac72000..9057b89 100644 --- a/src/components/ReportForm.js +++ b/src/components/ReportForm.js @@ -1,12 +1,18 @@ -import React, {Fragment} from 'react'; +import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import Grid from '@material-ui/core/Grid'; import { withStyles } from '@material-ui/core/styles'; import MenuItem from '@material-ui/core/MenuItem'; import TextField from '@material-ui/core/TextField'; +import CheckCircleIcon from '@material-ui/icons/CheckCircle'; +import Snackbar from '@material-ui/core/Snackbar'; +import IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; import Button from '@material-ui/core/Button'; import firebase from '../firebase.js'; import GoogleMap from '../components/ReportMap'; +import Modal from '@material-ui/core/Modal'; +import Typography from '@material-ui/core/Typography'; /** * Styles that the different @@ -14,80 +20,162 @@ import GoogleMap from '../components/ReportMap'; * in. Mostly used for spacing. */ const styles = theme => ({ - container: { - display: 'flex', - flexWrap: 'wrap', - }, - textField: { - marginLeft: theme.spacing.unit * 2, - marginRight: theme.spacing.unit, - marginTop: theme.spacing.unit * 2, - flexBasis: 280, - width: '90%' - }, - button: { - marginLeft: theme.spacing.unit * 2, - marginRight: theme.spacing.unit, - marginTop: theme.spacing.unit * 2, - }, - dense: { - marginTop: 30, - }, - menu: { - width: 200, - }, + container: { + display: 'flex', + flexWrap: 'wrap' + }, + textField: { + marginLeft: theme.spacing.unit * 2, + marginRight: theme.spacing.unit, + marginTop: theme.spacing.unit * 2, + flexBasis: 280, + width: '90%' + }, + button: { + marginLeft: theme.spacing.unit * 2, + marginRight: theme.spacing.unit, + marginTop: theme.spacing.unit * 2, + }, + dense: { + marginTop: 30, + }, + close: { + padding: theme.spacing.unit / 2, + }, + icon: { + fontSize: 20, + marginRight: theme.spacing.unit, + }, + message: { + display: 'flex', + alignItems: 'center', + }, + menu: { + width: 200, + }, + paper: { + position: 'absolute', + width: theme.spacing.unit * 50, + backgroundColor: theme.palette.background.paper, + boxShadow: theme.shadows[5], + padding: theme.spacing.unit * 4, + } }); -/** - * Function for formatting the - * date as a string that - * Material UI can use. We'll - * also store the date like - * this string in the database. - * @param {*} date, Date passed in. - */ -function formatDate(date) { - var d = new Date(date), - month = '' + (d.getMonth() + 1), - day = '' + d.getDate(), - year = d.getFullYear(); - - if (month.length < 2) month = '0' + month; - if (day.length < 2) day = '0' + day; - - return [year, month, day].join('-'); -} - /** * Types of sightings. Label is what is * viewed in the application, value is * what is stored in the database. */ const sightingTypes = [ - { - value: 'visual', - label: 'Visual', - }, - { - value: 'roadkill', - label: 'Roadkill', - }, - { - value: 'trapped', - label: 'Trapped', - }, - { - value: 'viewed_tracks', - label: 'Viewed Tracks', - }, - { - value: 'photo', - label: 'Photo', - }, - { - value: 'other', - label: 'Other', - }, + { + value: 'visual', + label: 'Visual', + }, + { + value: 'roadkill', + label: 'Roadkill', + }, + { + value: 'trapped', + label: 'Trapped', + }, + { + value: 'viewed_tracks', + label: 'Viewed Tracks', + }, + { + value: 'photo', + label: 'Photo', + }, + { + value: 'other', + label: 'Other', + }, +]; + +/** + * Types of sightings. Label is what is + * viewed in the application, value is + * what is stored in the database. +*/ +const monthTypes = [ + { + value: '01', + label: 'January', + }, + { + value: '02', + label: 'February', + }, + { + value: '03', + label: 'March', + }, + { + value: '04', + label: 'April', + }, + { + value: '05', + label: 'May', + }, + { + value: '06', + label: 'June', + }, + { + value: '07', + label: 'July', + }, + { + value: '08', + label: 'August', + }, + { + value: '09', + label: 'September', + }, + { + value: '10', + label: 'October', + }, + { + value: '11', + label: 'November', + }, + { + value: '12', + label: 'December', + }, +]; + +/** + * Types of sightings. Label is what is + * viewed in the application, value is + * what is stored in the database. +*/ +const timeTypes = [ + { + value: 'unknown', + label: 'Unknown', + }, + { + value: 'morning', + label: 'Morning', + }, + { + value: 'midday', + label: 'Midday', + }, + { + value: 'evening', + label: 'Evening', + }, + { + value: 'night', + label: 'Night', + }, ]; /** @@ -97,246 +185,378 @@ const sightingTypes = [ */ const confidenceLevels = [ { - value: '1', - label: '1 - Strongly unconfident', + value: '1', + label: '1 - Strongly disagree', }, { - value: '2', - label: '2 - Unconfident', + value: '2', + label: '2 - Disagree', }, { - value: '3', - label: '3 - Somewhat confident', + value: '3', + label: '3 - Neutral', }, { - value: '4', - label: '4 - Confident', + value: '4', + label: '4 - Agree', }, { - value: '5', - label: '5 - Very confident', + value: '5', + label: '5 - Strongly agree', }, - ]; +]; /** * The form component. */ class ReportForm extends React.Component { - /** - * Component contructor. Currently - * only used to bind event - * handlers. - */ - constructor() { - super(); - this.handleSubmit = this.handleSubmit.bind(this); - } - - /** - * State of form components. - */ - state = { - date: formatDate(new Date()), - time: '00:00', - type: 'visual', - confidence: '1', - desc: '', - lat: '', - lng: '' - }; - - /** - * Handles state change in form - * components. - */ - handleChange = name => event => { - this.setState({ - [name]: event.target.value, - }); - }; - - /* - * Get the coordinates - * - */ - getCoordinates = (lat,lng) => { - let latitude = lat; - let longitude = lng; - - this.setState({ - lat: latitude, - lng: longitude - }); - } - - /** - * Event listener for form. - * When the form is submitted, - * this function passes the - * data along to Firebase. - */ - handleSubmit(e) { - e.preventDefault(); - const sightingsRef = firebase.database().ref('sightings'); - const sighting = { - type: this.state.type, - confidence: this.state.confidence, - date: this.state.date, - time: this.state.time, - desc: this.state.desc, - lat: this.state.lat, - lng: this.state.lng + /** + * Component contructor. Currently + * only used to bind event + * handlers. + */ + constructor() { + super(); + this.handleSubmit = this.handleSubmit.bind(this); } - sightingsRef.push(sighting); - this.setState({ - date: formatDate(new Date()), - time: '00:00', - type: 'visual', - confidence: '1', - desc: '', - lat: '', - lng: '' - }); - }; - - - /** - * The render method for this component. - */ - render() { - const { classes } = this.props; /** - * The actual form. + * Function for formatting the + * year as a string that + * Material UI can use. + * @param {*} date, Date passed in. + */ + getYear = date => { + var d = new Date(date), + year = d.getFullYear(); + + return year; + } + + /** + * Function for formatting the + * month as a string that + * Material UI can use. + * @param {*} date, Date passed in. + */ + getMonth = date => { + var d = new Date(date), + month = d.getMonth() + 1; + + month = month.toString(); + + if (month.length === 1) { + month = "0" + month; + } + + return month; + } + + /** + * State of form components. */ - return ( - -
- - - - - - {sightingTypes.map(option => ( - - {option.label} - - ))} - - + state = { + month: this.getMonth(new Date()), + year: this.getYear(new Date()), + time: 'unknown', + type: 'visual', + confidence: '1', + desc: '', + lat: '', + lng: '', + open: false, + openModal: false, + hasModalOpened: false + }; - - - {confidenceLevels.map(option => ( - - {option.label} - - ))} - - + handleModalOpen = () => !this.state.hasModalOpened ? this.setState({ openModal: true, hasModalOpened: true }) : null; - - - + handleModalClose = () => { + this.setState({ openModal: false }); + }; - - - + getModalStyle = () => { + return { + top: `25%`, + left: `75%`, + transform: `translate(-25%, -75%)`, + }; + } - - + /** + * Handles state change in form + * components. + */ + handleChange = name => event => { + this.setState({ + [name]: event.target.value, + }); + }; + + /** + * Handles closing the toast. + */ + handleClose = (event, reason) => { + if (reason === 'clickaway') { + return; + } + + this.setState({ open: false }); + }; + + /* + * Get the coordinates + * + */ + getCoordinates = (lat, lng) => { + let latitude = lat; + let longitude = lng; + + this.setState({ + lat: latitude, + lng: longitude + }); + } + + /** + * Event listener for form. + * When the form is submitted, + * this function passes the + * data along to Firebase. + */ + handleSubmit(e) { + e.preventDefault(); + const sightingsRef = firebase.database().ref('sightings'); + const sighting = { + type: this.state.type, + confidence: this.state.confidence, + date: this.state.year + '-' + this.state.month, + time: this.state.time, + desc: this.state.desc, + lat: this.state.lat, + lng: this.state.lng + } + sightingsRef.push(sighting); + this.setState({ + year: this.getYear(new Date()), + month: this.getMonth(new Date()), + time: 'unknown', + type: 'visual', + confidence: '1', + desc: '', + lat: '', + lng: '', + open: true + }); + }; + + + /** + * The render method for this component. + */ + render() { + const { classes } = this.props; + + /** + * The actual form. + */ + return ( + + + + + + + + {sightingTypes.map(option => ( + + {option.label} + + ))} + + + + + + {confidenceLevels.map(option => ( + + {option.label} + + ))} + + + + + + {timeTypes.map(option => ( + + {option.label} + + ))} + + + + + + {monthTypes.map(option => ( + + {option.label} + + ))} + + + + + + + + + + + + + - - - - - - - + + + +
+ + Need a little help? + + + Click on the map to drop a pin! + +
+
+
-
- -
- ); - } + + Report received.} + action={[ + + + , + ]} + /> + + ); + } } ReportForm.propTypes = { - classes: PropTypes.object.isRequired, + classes: PropTypes.object.isRequired, }; export default withStyles(styles)(ReportForm); \ No newline at end of file diff --git a/src/components/ReportMap.js b/src/components/ReportMap.js index 882a49f..f4848ee 100644 --- a/src/components/ReportMap.js +++ b/src/components/ReportMap.js @@ -9,7 +9,7 @@ const API_KEY = 'AIzaSyAZ_0J01bA6wCbIPK4UBq2RUBC-hIqG4mM'; const mapStyles = { width: '100%', height: '100%' -} +}; export class MapContainer extends Component { @@ -18,22 +18,22 @@ export class MapContainer extends Component { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition((position) => { this.setState({ - myLatLng: { - lat: position.coords.latitude, - lng: position.coords.longitude - } + myLatLng: { + lat: position.coords.latitude, + lng: position.coords.longitude } + } ); }) } else { // If browser doesn't support geolocation or if user does not allow it, // center map on Grand Rapids, Michigan this.setState({ - myLatLng: { - lat: 42.9634, - lng: 85.6681 - } + myLatLng: { + lat: 42.9634, + lng: 85.6681 } + } ); } } @@ -52,7 +52,7 @@ export class MapContainer extends Component { showingInfoWindow: true }); } - + // When the user clicks on the map, if a info window is visible then close it // and 'unactive' that marker onMapClick = (props, map, e) => { @@ -68,13 +68,13 @@ export class MapContainer extends Component { lat: e.latLng.lat(), lng: e.latLng.lng() } - }) + }); let lat = e.latLng.lat(); - let lng = e.latLng.lng(); + let lng = e.latLng.lng(); if (this.props.onClick) { - this.props.onClick(lat,lng); + this.props.onClick(lat, lng); } } @@ -98,36 +98,36 @@ export class MapContainer extends Component { return ( // Render the Google Map, Marker, and InfoWindow components -
+
+ style={mapStyles} + google={this.props.google} + initialCenter={this.state.myLatLng} + center={this.state.myLatLng} + defaultZoom={15} + onClick={this.onMapClick} > - - + marker={this.state.activeMarker} + visible={this.state.showingInfoWindow} > - - { this.state.selectedPlace.title } + + {this.state.selectedPlace.title} - - { this.state.selectedPlace.name } + + {this.state.selectedPlace.name} @@ -138,6 +138,4 @@ export class MapContainer extends Component { } // Send the Google Map API Key with the MapContainer component -export default GoogleApiWrapper({ - apiKey: (API_KEY) -})(MapContainer) \ No newline at end of file +export default GoogleApiWrapper({ apiKey: (API_KEY) })(MapContainer); \ No newline at end of file diff --git a/src/components/SightingDetail.js b/src/components/SightingDetail.js new file mode 100644 index 0000000..01618b8 --- /dev/null +++ b/src/components/SightingDetail.js @@ -0,0 +1,160 @@ +import React, { Component, Fragment } from 'react'; +import Disqus from 'disqus-react'; +import moment from 'moment'; +import SightingDetailMap from './SightingDetailMap'; + +/** + * Types of sightings. Label is what is + * viewed in the application, value is + * what is stored in the database. + */ + const sightingTypes = [ + { + value: 'visual', + label: 'Visual', + }, + { + value: 'roadkill', + label: 'Roadkill', + }, + { + value: 'trapped', + label: 'Trapped', + }, + { + value: 'viewed_tracks', + label: 'Viewed Tracks', + }, + { + value: 'photo', + label: 'Photo', + }, + { + value: 'other', + label: 'Other', + }, +]; + +/** + * Types of sightings. Label is what is + * viewed in the application, value is + * what is stored in the database. +*/ +const timeTypes = [ + { + value: 'unknown', + label: 'Unknown', + }, + { + value: 'morning', + label: 'Morning', + }, + { + value: 'midday', + label: 'Midday', + }, + { + value: 'evening', + label: 'Evening', + }, + { + value: 'night', + label: 'Night', + }, +]; + +/** + * Levels of confidence. Label is what is + * viewed in the application, value is + * what is stored in the database. +*/ +const confidenceLevels = [ + { + value: '1', + label: '1 - Strongly disagree', + }, + { + value: '2', + label: '2 - Disagree', + }, + { + value: '3', + label: '3 - Neutral', + }, + { + value: '4', + label: '4 - Agree', + }, + { + value: '5', + label: '5 - Strongly agree', + }, +]; + +class SightingDetail extends Component { + + /** + * Gets formatted type value. + */ + getType = item => { + for (var i = 0; i < sightingTypes.length; i++) { + if (sightingTypes[i].value === item) { + return sightingTypes[i].label; + } + } + } + + + /** + * Gets formatted time value. + */ + getTime = item => { + for (var i = 0; i < timeTypes.length; i++) { + if (timeTypes[i].value === item) { + return timeTypes[i].label; + } + } + } + + /** + * Gets formatted confidence value. + */ + getConfidence = item => { + for (var i = 0; i < confidenceLevels.length; i++) { + if (confidenceLevels[i].value === item) { + return confidenceLevels[i].label; + } + } + + } + + formatDate = date => { + return (moment(date, "YYYY-MM").format("MMMM YYYY").toString()); + } + + render() { + const disqusShortname = 'https-marten-application-netlify-com'; + const disqusConfig = { + url: `http://localhost:3000/${this.props.detail.id}`, + identifier: this.props.detail.id, + title: this.props.detail.id + }; + + return ( + + +
+

Type: {this.getType(this.props.detail.type)}

+

When: {this.formatDate(this.props.detail.date)}, {this.getTime(this.props.detail.time)}

+

Where: {this.props.detail.lat} degrees N, and {this.props.detail.lng} degrees E

+

I am confident of my sighting: {this.getConfidence(this.props.detail.confidence)}

+
+

{`${this.props.detail.desc}`}

+
+ +
+ ); + } +} + +export default SightingDetail; \ No newline at end of file diff --git a/src/components/SightingDetailMap.js b/src/components/SightingDetailMap.js new file mode 100644 index 0000000..775e7ee --- /dev/null +++ b/src/components/SightingDetailMap.js @@ -0,0 +1,36 @@ +import React, { Component } from 'react'; +import { Map, Marker, GoogleApiWrapper } from 'google-maps-react'; + +// Google Maps API Key +const API_KEY = 'AIzaSyAZ_0J01bA6wCbIPK4UBq2RUBC-hIqG4mM'; + +// Map container styles +const mapStyles = { + width: '100%', + height: '100%' +}; + +export class MapContainer extends Component { + + render() { + return ( + // Render the Google Map, Marker, and InfoWindow components +
+ + + + +
+ ); + } +} + +// Send the Google Map API Key with the MapContainer component +export default GoogleApiWrapper({ apiKey: (API_KEY) })(MapContainer); \ No newline at end of file diff --git a/src/components/SightingMap.js b/src/components/SightingMap.js index 6bfdbe2..ee69ed6 100644 --- a/src/components/SightingMap.js +++ b/src/components/SightingMap.js @@ -1,5 +1,6 @@ import React, { Component, Fragment } from 'react'; import { Map, InfoWindow, Marker, GoogleApiWrapper } from 'google-maps-react'; +import moment from 'moment'; import Typography from '@material-ui/core/Typography'; import firebase from '../firebase.js'; @@ -10,7 +11,95 @@ const API_KEY = 'AIzaSyAZ_0J01bA6wCbIPK4UBq2RUBC-hIqG4mM'; const mapStyles = { width: '100%', height: '100%' -} +}; + +/** + * Types of sightings. Label is what is + * viewed in the application, value is + * what is stored in the database. + */ +const sightingTypes = [ + { + value: 'visual', + label: 'Visual', + }, + { + value: 'roadkill', + label: 'Roadkill', + }, + { + value: 'trapped', + label: 'Trapped', + }, + { + value: 'viewed_tracks', + label: 'Viewed Tracks', + }, + { + value: 'photo', + label: 'Photo', + }, + { + value: 'other', + label: 'Other', + }, +]; + +/** + * Types of sightings. Label is what is + * viewed in the application, value is + * what is stored in the database. +*/ +const timeTypes = [ + { + value: 'unknown', + label: 'Unknown', + }, + { + value: 'morning', + label: 'Morning', + }, + { + value: 'midday', + label: 'Midday', + }, + { + value: 'evening', + label: 'Evening', + }, + { + value: 'night', + label: 'Night', + }, +]; + +/** + * Levels of confidence. Label is what is + * viewed in the application, value is + * what is stored in the database. +*/ +const confidenceLevels = [ + { + value: '1', + label: '1 - Strongly disagree', + }, + { + value: '2', + label: '2 - Disagree', + }, + { + value: '3', + label: '3 - Neutral', + }, + { + value: '4', + label: '4 - Agree', + }, + { + value: '5', + label: '5 - Strongly agree', + }, +]; export class MapContainer extends Component { @@ -19,26 +108,59 @@ export class MapContainer extends Component { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition((position) => { this.setState({ - myLatLng: { - lat: position.coords.latitude, - lng: position.coords.longitude - } + myLatLng: { + lat: position.coords.latitude, + lng: position.coords.longitude } - ); - }) + }); + }); } else { // If browser doesn't support geolocation or if user does not allow it, // center map on Grand Rapids, Michigan this.setState({ - myLatLng: { - lat: 42.9634, - lng: 85.6681 - } + myLatLng: { + lat: 42.9634, + lng: 85.6681 } - ); + }); } } + /** + * Gets formatted type value. + */ + getType = item => { + for (var i = 0; i < sightingTypes.length; i++) { + if (sightingTypes[i].value === item) { + return sightingTypes[i].label; + } + } + } + + + /** + * Gets formatted time value. + */ + getTime = item => { + for (var i = 0; i < timeTypes.length; i++) { + if (timeTypes[i].value === item) { + return timeTypes[i].label; + } + } + } + + /** + * Gets formatted confidence value. + */ + getConfidence = item => { + for (var i = 0; i < confidenceLevels.length; i++) { + if (confidenceLevels[i].value === item) { + return confidenceLevels[i].label; + } + } + + } + // When the component has mounted to the DOM, get the user's location componentDidMount() { this.getLocation(); @@ -104,9 +226,12 @@ export class MapContainer extends Component { default: break } - return pinIcon } + + formatDate = date => { + return (moment(date, "YYYY-MM").format("MMMM YYYY").toString()); + } // Set the state of the component to contain user coordinates and initial // marker and info window information @@ -119,42 +244,43 @@ export class MapContainer extends Component { activeMarker: {}, selectedPlace: {}, sightings: [] - } + }; render() { const {google} = this.props; return ( // Render the Google Map, Marker, and InfoWindow components -
+
+ style={mapStyles} + google={this.props.google} + initialCenter={this.state.myLatLng} + center={this.state.myLatLng} + defaultZoom={15} + onClick={this.onMapClick} + > - - { this.state.sightings.map((sighting) => { - - let pinIcon = this.sightingIcon(sighting.type) + {this.state.sightings.map((sighting) => { + let pinIcon = this.sightingIcon(sighting.type) + return ( Date: {this.formatDate(sighting.date)}} + time={Time: {this.getTime(sighting.time)}} + confidence={I am confident of my sighting: {this.getConfidence(sighting.confidence)}} + description={Description: {sighting.desc}} icon={{ url: pinIcon, anchor: new google.maps.Point(32,32), @@ -165,24 +291,24 @@ export class MapContainer extends Component { })} + marker={this.state.activeMarker} + visible={this.state.showingInfoWindow} > - - { this.state.selectedPlace.type } + + {this.state.selectedPlace.type} - - { this.state.selectedPlace.confidence } + + {this.state.selectedPlace.date} - - { this.state.selectedPlace.date } + + {this.state.selectedPlace.time} - - { this.state.selectedPlace.time } + + {this.state.selectedPlace.confidence} - - { this.state.selectedPlace.description } + + {this.state.selectedPlace.description} @@ -193,6 +319,4 @@ export class MapContainer extends Component { } // Send the Google Map API Key with the MapContainer component -export default GoogleApiWrapper({ - apiKey: (API_KEY) -})(MapContainer) \ No newline at end of file +export default GoogleApiWrapper({ apiKey: (API_KEY) })(MapContainer); \ No newline at end of file diff --git a/src/components/ViewSightings.js b/src/components/ViewSightings.js index 822cf8e..ec5d3c2 100644 --- a/src/components/ViewSightings.js +++ b/src/components/ViewSightings.js @@ -1,232 +1,95 @@ -import React, {Fragment} from 'react'; -import PropTypes from 'prop-types'; +import React, { Component, Fragment } from 'react'; import Grid from '@material-ui/core/Grid'; -import TextField from '@material-ui/core/TextField' -import Button from '@material-ui/core/Button' -import Paper from '@material-ui/core/Paper' -import { withStyles } from '@material-ui/core/styles'; -import Typography from '@material-ui/core/Typography'; import firebase from '../firebase.js'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; +import SightingDetail from './SightingDetail'; -const styles = theme => ({ - root: { - ...theme.mixins.gutters(), - paddingTop: theme.spacing.unit * 2, - paddingBottom: theme.spacing.unit * 2, - }, - container: { - display: 'flex', - flexWrap: 'wrap', - }, - textField: { - marginLeft: theme.spacing.unit * 2, - marginRight: theme.spacing.unit * 2, - marginTop: theme.spacing.unit * 2, - flexBasis: 280, - }, - button: { - marginLeft: theme.spacing.unit * 3, - marginRight: theme.spacing.unit * 3, - marginTop: theme.spacing.unit * 3, - }, - paper: { - marginLeft: theme.spacing.unit * 2, - marginRight: theme.spacing.unit, - marginTop: theme.spacing.unit * 2, - }, - }); +class ViewSightings extends Component { - /** - * Types of sightings. Label is what is - * viewed in the application, value is - * what is stored in the database. - */ - const sightingTypes = [ - { - value: 'visual', - label: 'Visual', - }, - { - value: 'roadkill', - label: 'Roadkill', - }, - { - value: 'trapped', - label: 'Trapped', - }, - { - value: 'viewed_tracks', - label: 'Viewed Tracks', - }, - { - value: 'photo', - label: 'Photo', - }, - { - value: 'other', - label: 'Other', - }, - ]; - - /** - * Levels of confidence. Label is what is - * viewed in the application, value is - * what is stored in the database. - */ - const confidenceLevels = [ - { - value: '1', - label: '1 - Strongly unconfident', - }, - { - value: '2', - label: '2 - Unconfident', - }, - { - value: '3', - label: '3 - Somewhat confident', - }, - { - value: '4', - label: '4 - Confident', - }, - { - value: '5', - label: '5 - Very confident', - }, - ]; + componentDidMount() { + const sightingsRef = firebase.database().ref('sightings'); - /** - * Gets formatted confidence value. - */ - function getConfidence(item) { - for (var i = 0; i < confidenceLevels.length; i++) { - if (confidenceLevels[i].value === item) { - return confidenceLevels[i].label; + sightingsRef.on('value', (snapshot) => { + let sightings = snapshot.val(); + let newState = []; + + for (let sighting in sightings) { + newState.unshift({ + id: sighting, + lat: sightings[sighting].lat, + lng: sightings[sighting].lng, + desc: sightings[sighting].desc, + type: sightings[sighting].type, + confidence: sightings[sighting].confidence, + date: sightings[sighting].date, + time: sightings[sighting].time + }); } - } - + this.setState({ + sightings: newState + }); + }); } - /** - * Gets formatted type value. - */ - function getType(item) { - for (var i = 0; i < sightingTypes.length; i++) { - if (sightingTypes[i].value === item) { - return sightingTypes[i].label; - } - } - } - -class ViewSightings extends React.Component { - constructor(props){ - super(props); - - this.handleSubmit = this.handleSubmit.bind(this); + getDetail = (id, lat, lng, desc, type, confidence, date, time) => { + this.setState({ + selectedSighting: { + id: id, + lat: lat, + lng: lng, + desc: desc, + type: type, + confidence: confidence, + date: date, + time: time + }, + clicked: true + }) } state = { - id: '', - type: 'N/A', - confidence: 'N/A', - date: 'N/A', - time: 'N/A', - desc: 'N/A', - lat: 'N/A', - lng: 'N/A' - }; - - - /** - * Handles state change. - */ - handleChange = name => event => { - this.setState({ - [name]: event.target.value, - }); + sightings: [], + selectedSighting: { + id: null, + lat: null, + lng: null, + desc: null, + type: null, + confidence: null, + date: null, + time: null + }, + clicked: false }; - /** - * Handles submit on search. - */ - handleSubmit(e){ - e.preventDefault(); - const itemSighting = firebase.database().ref("sightings/" + this.state.id); - itemSighting.once("value").then((snapshot) => { - // Die if there's no data for that ID. - if (!snapshot.exists()) { - return; - } - - let data = snapshot.val(); - - this.setState({ - date: data.date, - time: data.time, - type: getType(data.type), - confidence: getConfidence(data.confidence), - desc: data.desc, - lat: data.lat, - lng: data.lng - }); - }); - }; - - render(){ - const { classes } = this.props; + render() { return ( - /** - * The below houses the search - * and submit button along with - * the sighting information - * it pulls. - */ - -
- - + + + + + { + this.state.sightings.map((sighting) => { + return ( + this.getDetail(sighting.id, sighting.lat, sighting.lng, sighting.desc, sighting.type, sighting.confidence, sighting.date, sighting.time)}> + + + ); + }) + } + + - - - - - - - - Sighting - - - Type: {this.state.type} {
} - Confidence: {this.state.confidence} {
} - Date: {this.state.date} {
} - Time: {this.state.time} {
} - Latitude: {this.state.lat} {
} - Longitude: {this.state.lng} {
} - Description: {this.state.desc} -
-
+ + {this.state.clicked === true && }
- ) - + ); } - } -ViewSightings.propTypes = { - classes: PropTypes.object.isRequired, - }; - -export default withStyles(styles)(ViewSightings); \ No newline at end of file +export default ViewSightings; \ No newline at end of file diff --git a/src/firebase.js b/src/firebase.js index 8d01e36..6e6b6b2 100644 --- a/src/firebase.js +++ b/src/firebase.js @@ -1,5 +1,6 @@ import firebase from 'firebase/app'; import 'firebase/database'; +import 'firebase/storage'; const config = { apiKey: "AIzaSyAYf9AbeYwLY892NRiQfn0AMtG9xIFAJbo", diff --git a/src/pages/Quiz.js b/src/pages/Quiz.js deleted file mode 100644 index 28b9f15..0000000 --- a/src/pages/Quiz.js +++ /dev/null @@ -1,14 +0,0 @@ -import React, { Component } from 'react'; -import Typography from '@material-ui/core/Typography'; - -class Quiz extends Component { - render() { - return ( - - Quiz - - ); - } -} - -export default Quiz; diff --git a/src/pages/QuizPage.js b/src/pages/QuizPage.js new file mode 100644 index 0000000..9422b7b --- /dev/null +++ b/src/pages/QuizPage.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react'; +import QuizGame from '../components/QuizGame'; + + +class QuizPage extends Component { + render() { + return ( + + ); + } +} + +export default QuizPage; diff --git a/src/pages/SightingList.js b/src/pages/SightingList.js index 9d035b9..0540527 100644 --- a/src/pages/SightingList.js +++ b/src/pages/SightingList.js @@ -1,13 +1,10 @@ import React, { Component } from 'react'; import ViewSightings from '../components/ViewSightings.js'; -import Typography from '@material-ui/core/Typography'; class Sighting extends Component { render() { return ( - - - + ); } }