Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
24 changes: 16 additions & 8 deletions src/components/BlockContent/BlockContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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)
Expand All @@ -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 () {
Expand All @@ -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({
Expand All @@ -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) => {
Expand Down Expand Up @@ -140,19 +148,19 @@ function BlockContent(props) {
<Typography variant="h5" component="h2">
{state.title.replace(/_/g, ' ')}
</Typography>
{state.video_url != undefined && <TestVideo />}
{/*state.video_url != undefined && <TestVideo />*/}
</Box>
<Typography variant="body1" component="pre" style={{ whiteSpace: "pre-wrap" }}>
{state.description}
</Typography>
<br />
{state.video_url != undefined &&
{/*state.video_url != undefined &&
<CustomVideoComponent
recordingEnabled={true}
mediaURL={state.video_url}
record={() => { recordFunction() }}
stopRecording={() => { stopRecording() }}
width={"100%"} />}
width={"100%"} />*/}
</Box>
</Card>
</Box>
Expand Down
11 changes: 8 additions & 3 deletions src/components/BlockContent/BlockPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 })
Expand Down
33 changes: 20 additions & 13 deletions src/components/GroupContent/GroupContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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);
});
};

Expand All @@ -104,7 +106,7 @@ function GroupContent(props) {

useEffect(() => {
//Set up recorder
navigator.mediaDevices
/*navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
Expand All @@ -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,
Expand Down
55 changes: 45 additions & 10 deletions src/components/HomeContent/HomeContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -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 (
<EmptyState
Expand All @@ -99,16 +134,16 @@ class HomeContent extends Component {
componentDidMount() {
this.signInWithEmailLink();

// this.quizDocumentsReference.get().then(querySnapshot => {
// let resList = [];
// querySnapshot.forEach((doc, index) => {
// resList.push(
// <QuizCard key={index + new Date()} title={doc.data().title}/>
// );
// });
//
// this.setState({quizList: resList});
// });
this.quizDocumentsReference.get().then(querySnapshot => {
let resList = [];
querySnapshot.forEach((doc, index) => {
resList.push(
<QuizCard key={index + new Date()} title={doc.data().title}/>
);
});

this.setState({quizList: resList});
});
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/components/QuizContent/QuizContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ class QuizContent extends Component {
await that.recorder.startRecording();
}

navigator.mediaDevices
/*navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
Expand All @@ -177,6 +177,7 @@ class QuizContent extends Component {
});

});
*/
}

resetState() {
Expand Down Expand Up @@ -586,7 +587,7 @@ class QuizContent extends Component {
{this.state.data.title !== undefined && <Typography variant="h5" component="h2">
{this.state.data.title.replace(/_/g, ' ')}
</Typography>}
{this.state.description_recording &&
{/*this.state.description_recording &&
<div style={{ display: "flex", columnCount: 2, columnGap: "10px" }}>
<TestVideo />
{(this.state.media_url == null || this.state.media_url == undefined) &&
Expand All @@ -601,10 +602,9 @@ class QuizContent extends Component {
this.stopRecording();
}}>
{(this.state.recording !== "description") ? "Begin Recording" : "Stop Recording"}
</Button>}
</Button>*/}
</div>
}
</div>

<Typography
variant="body1"
component="pre"
Expand Down
Loading