From 89fff3f9164438bb1af80339662f5805ac847566 Mon Sep 17 00:00:00 2001 From: Adam Baldwin <66842209+adamb9@users.noreply.github.com> Date: Thu, 26 May 2022 13:22:25 +0100 Subject: [PATCH] Goal-Setting Semester 2 2022 Changes. Various changes made throughout semester 2 of the 2021/22 academic year. See README.md for more info and instructions. --- README.md | 42 ++++++++++++++++ src/components/BlockContent/BlockContent.js | 24 ++++++--- src/components/BlockContent/BlockPage.js | 11 +++-- src/components/GroupContent/GroupContent.js | 33 ++++++++----- src/components/HomeContent/HomeContent.js | 55 +++++++++++++++++---- src/components/QuizContent/QuizContent.js | 10 ++-- src/components/SignUpDialog/SignUpDialog.js | 36 +++++++++++++- src/services/authentication.js | 2 +- 8 files changed, 172 insertions(+), 41 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b645d8 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# AcademicSurvey.ie + +**React.js** project designed as part of an online platform that enables students to participate in academic surveys. + +This is written in JavaScript with Firebase used for much of the backend functionality including data storage, authentication, and hosting. To view the live version, see [https://academicsurvey.ie/](https://academicsurvey.ie/) + +**Currently running on** +* React v16.12.0 +* Firebase v7.8.1 + + +### Helpful Guides: +* [Intro to React](https://reactjs.org/tutorial/tutorial.html) +* [React Deployments](https://create-react-app.dev/docs/deployment/) +* [Understanding Firebase Projects](https://firebase.google.com/docs/projects/learn-more) +* [Firebase Authentication](https://firebase.google.com/docs/auth) +* [Cloud Firestore](https://firebase.google.com/docs/firestore) +* [Firebase Hosting](https://firebase.google.com/docs/hosting) +* [Firebase Security Rules](https://firebase.google.com/docs/rules) + + +### Building +There are a number of IDEs that you can choose from but I'd highly recommend Visual Studio Code. It's widely used, well-documented, and has a large selection of useful plugins and tools. + +To run this locally, simply use the command `npm start`. This should start the project and open it in your default web browser on `http://localhost:3000/` + +In order to build and deploy the web app, you need to first run `npm run build`. +After this, you can run `firebase deploy` and this should create a new deployment once your environment is set up correctly. Please see the documentation on [React Deployments](https://create-react-app.dev/docs/deployment/) and [Firebase Hosting](https://firebase.google.com/docs/hosting) for an in-depth guide. + + +### Goal-Setting Group Assignment & Redirect +As of the time of writing (May 2022), all new sign-ups are assigned to either the goal-setting or control group. This will have no affect on any studies currently. It will not interfere or limit which studies can be carried out unless you change code to make it do so. + +This was done to allow automatic group assignment to be carried out and to redirect participants to the correct quiz based on their group. To enable this redirect, see `fetchGroup()` and the commented out code in `render()` in `HomeContent.js`. Make sure to change the routes in `fetchGroup()` to match the new URLs for the studies which you intend on redirect the participants to. + + +### Recording Participants' Webcams +A previous study required the use of participants' webcams for the duration of the study. This was not required in all situations so it was disabled. If you wish to re-enable it, there are a number of commented out sections of code in `BlockContent.js`, `GroupContent.js` and `QuizContent.js`. + + +### Important +Please make sure to thoroughly test out any surveys/quizzes that you make before sending them out. There have been a number of issues that have previously popped up when testing that may have never been noticed. These might be small or else they may end up breaking an entire section of your study. \ No newline at end of file diff --git a/src/components/BlockContent/BlockContent.js b/src/components/BlockContent/BlockContent.js index 1a07106..f36ee5d 100644 --- a/src/components/BlockContent/BlockContent.js +++ b/src/components/BlockContent/BlockContent.js @@ -43,7 +43,9 @@ function BlockContent(props) { let gr = window.location.toString().split("/blockGroup/")[1] let group = gr.replace(/_/g, ' '); const groupDocumentReference = firestore.collection("blockGroups"); - const existingResults = firestore.collection("userID").doc(localStorage.getItem("hash")); + + //const existingResults = firestore.collection("userID").doc(localStorage.getItem("hash")); + const classes = useStyles(); const defaultState = { @@ -64,7 +66,7 @@ function BlockContent(props) { } } - async function setURL(uploadURL) { + /*async function setURL(uploadURL) { let userDoc = (await existingResults.get()).data(); if (userDoc.result === undefined) @@ -76,7 +78,7 @@ function BlockContent(props) { userDoc.result[gr]["video_url"] = uploadURL; existingResults.update(userDoc); } - + function stopRecording() { const tempFunc = async () => { await recorder.stopRecording(async function () { @@ -85,14 +87,16 @@ function BlockContent(props) { let ref = storageRef.child(uploadURL); let snapshot = await ref.put(blob); let downloadURL = await snapshot.ref.getDownloadURL(); - setURL(downloadURL); + //setURL(downloadURL); }); }; tempFunc(); } + */ useEffect(() => { + /* //Set up recorder navigator.mediaDevices .getUserMedia({ @@ -105,10 +109,14 @@ function BlockContent(props) { mimeType: "video/webm", })) }); + */ async function getGroupDetails() { const bg = await groupDocumentReference.get(); - let completedList = await getCompletedList(gr); + let completedList = []; + if(auth.currentUser !== null ) { + completedList = await getCompletedList(gr); + } let newState; bg.forEach((doc) => { @@ -140,19 +148,19 @@ function BlockContent(props) { {state.title.replace(/_/g, ' ')} - {state.video_url != undefined && } + {/*state.video_url != undefined && */} {state.description}
- {state.video_url != undefined && + {/*state.video_url != undefined && { recordFunction() }} stopRecording={() => { stopRecording() }} - width={"100%"} />} + width={"100%"} />*/} diff --git a/src/components/BlockContent/BlockPage.js b/src/components/BlockContent/BlockPage.js index 02ed5ad..64aedd0 100644 --- a/src/components/BlockContent/BlockPage.js +++ b/src/components/BlockContent/BlockPage.js @@ -3,7 +3,7 @@ import { Breadcrumbs, Link, Typography } from "@material-ui/core"; import { Link as RouterLink } from 'react-router-dom'; import { MemoryRouter as Router } from 'react-router'; import Box from "@material-ui/core/Box"; -import { firestore } from "../../firebase"; +import { auth, firestore } from "../../firebase"; import Card from "@material-ui/core/Card"; import Divider from "@material-ui/core/Divider"; import makeStyles from "@material-ui/core/styles/makeStyles"; @@ -96,14 +96,19 @@ function BlockPage(props) { const fetchBlock = async () => { let blockGroups = await groupDocumentReference.get(); - let completeList = await getCompletedList(props.location.state.blockGroupTitle, props.location.state.blockTitle); + let completeList = []; + if(auth.currentUser !== null) { + completeList = await getCompletedList(props.location.state.blockGroupTitle, props.location.state.blockTitle); + } blockGroups.forEach((doc) => { let blockGroup = doc.data(); if (blockGroup.title === props.location.state.blockGroupTitle.replace(/_/g, ' ')) { blockGroup.blockList.forEach(block => { if (block.title === props.location.state.blockTitle) { - setCompleted(props.location.state.blockGroupTitle, block.title, completeList, block.content); + if(auth.currentUser !== null) { + setCompleted(props.location.state.blockGroupTitle, block.title, completeList, block.content); + } const setBlockContent = async () => { if(block.random) block.content = await setOrder(block.content, props.location.state.blockGroupTitle, block.title); setState({ ...block, group: props.location.state.blockGroupTitle, completedList: completeList }) diff --git a/src/components/GroupContent/GroupContent.js b/src/components/GroupContent/GroupContent.js index 9fdde2a..2e457d0 100644 --- a/src/components/GroupContent/GroupContent.js +++ b/src/components/GroupContent/GroupContent.js @@ -45,8 +45,10 @@ var storageRef = storage.ref(); function GroupContent(props) { const { blockGroupTitle, blockTitle, quizGroupTitle } = { ...props.location.state } const groupDocumentReference = firestore.collection("quizGroups").doc(quizGroupTitle); - const existingResults = firestore.collection("userID").doc(localStorage.getItem("hash")); - + let existingResults = null; + if(auth.currentUser !== null && localStorage.getItem("hash") !== null){ + existingResults = firestore.collection("userID").doc(localStorage.getItem("hash")); + } const classes = useStyles(); const defaultState = { @@ -95,7 +97,7 @@ function GroupContent(props) { let ref = storageRef.child(uploadURL); let snapshot = await ref.put(blob); let downloadURL = await snapshot.ref.getDownloadURL(); - setURL(downloadURL); + //setURL(downloadURL); }); }; @@ -104,7 +106,7 @@ function GroupContent(props) { useEffect(() => { //Set up recorder - navigator.mediaDevices + /*navigator.mediaDevices .getUserMedia({ video: true, audio: true, @@ -115,24 +117,29 @@ function GroupContent(props) { mimeType: "video/webm", })) }); + */ //Fetch details on the quizGroup to be loaded async function getGroupDetails() { let doc = await groupDocumentReference.get(); let data = doc.data(); - let uData = (await existingResults.get()).data(); + let uData = undefined; let completedQuiz = []; - - if (uData.results !== undefined && uData.results[blockGroupTitle] != undefined && uData.results[blockGroupTitle][blockTitle] != undefined && uData.results[blockGroupTitle][blockTitle][quizGroupTitle] !== undefined) - completedQuiz = Object.keys(uData.results[blockGroupTitle][blockTitle][quizGroupTitle]); - let isCompleted = false; - if (completedQuiz.length === Object.keys(data.content).length) { - setCompleted(blockGroupTitle, blockTitle, quizGroupTitle, uData); - isCompleted = true; - } + + if (auth.currentUser !== null) { + uData = (await existingResults.get()).data(); + + if (uData.results !== undefined && uData.results[blockGroupTitle] != undefined && uData.results[blockGroupTitle][blockTitle] != undefined && uData.results[blockGroupTitle][blockTitle][quizGroupTitle] !== undefined) + completedQuiz = Object.keys(uData.results[blockGroupTitle][blockTitle][quizGroupTitle]); + + if (completedQuiz.length === Object.keys(data.content).length) { + setCompleted(blockGroupTitle, blockTitle, quizGroupTitle, uData); + isCompleted = true; + } + } let newState = { title: data.title, diff --git a/src/components/HomeContent/HomeContent.js b/src/components/HomeContent/HomeContent.js index d957c7a..f643430 100644 --- a/src/components/HomeContent/HomeContent.js +++ b/src/components/HomeContent/HomeContent.js @@ -15,6 +15,33 @@ class HomeContent extends Component { }; } + //fetchGroup can be used for a goal-setting study redirect provided that the participants are assigned to a group. + //New sign-ups are assigned to a group in SignUpDialog.js + //Change the URLs/Routes below to redirect to the correct quizzes + fetchGroup = () => { + let group; + let ref; + + ref = firestore.collection("userID").doc(localStorage.getItem('hash')).get().then( (doc) => { + if (doc.exists) { + group = doc.data()['group']; + if(group == "goal-setting") { + //CHANGE THIS ROUTE IF YOU WANT THE REDIRECT TO WORK!!! + this.props.history.push("/blockGroup/Goal-Setting-2022"); + } + else if (group == "control"){ + //CHANGE THIS ROUTE IF YOU WANT THE REDIRECT TO WORK!!! + this.props.history.push("/blockGroup/Self-Reflection-2022"); + } + //console.log(group); + } else { + group = ""; + console.log("Error getting group!"); + } + }) + return group; + } + quizDocumentsReference = firestore.collection("quizs"); signInWithEmailLink = () => { @@ -79,6 +106,14 @@ class HomeContent extends Component { if (user) { return this.SignedInContent(); } + + //Uncomment the lines below to enable the goal-setting redirect + /* + if(user !== null && auth.currentUser !== null && localStorage.getItem("hash") !== null) { + this.fetchGroup(); + return null; + } + */ return ( { - // let resList = []; - // querySnapshot.forEach((doc, index) => { - // resList.push( - // - // ); - // }); - // - // this.setState({quizList: resList}); - // }); + this.quizDocumentsReference.get().then(querySnapshot => { + let resList = []; + querySnapshot.forEach((doc, index) => { + resList.push( + + ); + }); + + this.setState({quizList: resList}); + }); } } diff --git a/src/components/QuizContent/QuizContent.js b/src/components/QuizContent/QuizContent.js index 063ce33..857873b 100644 --- a/src/components/QuizContent/QuizContent.js +++ b/src/components/QuizContent/QuizContent.js @@ -163,7 +163,7 @@ class QuizContent extends Component { await that.recorder.startRecording(); } - navigator.mediaDevices + /*navigator.mediaDevices .getUserMedia({ video: true, audio: true, @@ -177,6 +177,7 @@ class QuizContent extends Component { }); }); + */ } resetState() { @@ -586,7 +587,7 @@ class QuizContent extends Component { {this.state.data.title !== undefined && {this.state.data.title.replace(/_/g, ' ')} } - {this.state.description_recording && + {/*this.state.description_recording &&
{(this.state.media_url == null || this.state.media_url == undefined) && @@ -601,10 +602,9 @@ class QuizContent extends Component { this.stopRecording(); }}> {(this.state.recording !== "description") ? "Begin Recording" : "Stop Recording"} - } + */}
- } - + ({ closeButton: { @@ -65,7 +68,16 @@ const initialState = { class SignUpDialog extends Component { constructor(props) { super(props); - + //Used for automatically assigning people to goal-setting or control groups + //See code related to signup below for more group assignment details + counterCollectionReference.doc('counter').get().then( (doc) => { + if (doc.exists) { + counter = doc.data()['count']; + console.log(counter); + } else { + console.log("Error getting count"); + } + }); this.state = initialState; } @@ -111,7 +123,29 @@ class SignUpDialog extends Component { collectionReference.doc(hash).set({email: "hidden"}); + }) + .then(value => { + //Group Assignment + //If 'count' is even then the person is assigned to the goal-setting group. + //If 'count' is odd then the person is assigned to the control group. + //The group wont do anything unless you use it elsewhere. + //See the commented out code in HomeContent for a group redirect solution. + let group = ""; + if(counter % 2 === 0) { + group = "goal-setting"; + } + else { + group = "control"; + } + + localStorage.setItem('group', group); + let hash = localStorage.getItem('hash'); + collectionReference.doc(hash).set({group: group}); + counter = counter +1; + counterCollectionReference.doc('counter').set({count: counter}); + this.props.dialogProps.onClose(); + }) .catch(reason => { const code = reason.code; diff --git a/src/services/authentication.js b/src/services/authentication.js index 31ac1e1..4bdfe81 100644 --- a/src/services/authentication.js +++ b/src/services/authentication.js @@ -445,7 +445,7 @@ authentication.signOut = () => { .signOut() .then(value => { analytics.logEvent("sign_out"); - + localStorage.clear(); resolve(value); }) .catch(reason => {