From 5c39e8a628d6d43da8b47ed13e3de3f8affef8c4 Mon Sep 17 00:00:00 2001 From: Ayushya Amitabh Date: Tue, 20 Apr 2021 20:16:21 -0400 Subject: [PATCH] v1.1.0/number values and efficacy --- .gitignore | 6 +- functions/lib/index.js | 270 ++++++++++---------- functions/lib/index.js.map | 2 +- functions/package.json | 2 +- functions/src/index.ts | 366 +++++++++++++-------------- website/public/styles/extralarge.css | 2 +- website/public/styles/extrasmall.css | 37 +++ website/public/styles/large.css | 2 +- website/public/styles/medium.css | 4 + website/src/index.js | 325 ++++++++++++++++-------- 10 files changed, 581 insertions(+), 435 deletions(-) diff --git a/.gitignore b/.gitignore index 28369df..d2e88ee 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ # production */build +*/lib +*/exportDB + # misc .DS_Store @@ -23,5 +26,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* *.cache -*.log -functions/src/backup.json +*/*.log* diff --git a/functions/lib/index.js b/functions/lib/index.js index 884a28d..9115cc6 100644 --- a/functions/lib/index.js +++ b/functions/lib/index.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getPastResult = exports.saveResult = void 0; +exports.getPastResult = exports.saveResult = exports.getStats = void 0; const functions = require("firebase-functions"); const admin = require("firebase-admin"); const firebaseSecret = require("./firebaseSecret.json"); @@ -19,152 +19,120 @@ const params = { admin.initializeApp({ credential: admin.credential.cert(params), }); +const VAX_NAMES = { + PB: "Pfizer-BioNTech", + MD: "Moderna", + JJ: "J&J Janssen", +}; +const VAX_DOSES = { + ONE: "Dose 1", + TWO: "Dose 2", +}; const serializeGaId = (_gaid) => { if (_gaid) { return _gaid.split(".").join("|"); } return ""; }; +const addResultToStats = async (data) => { + const cleanData = Object.assign({}, data); + delete cleanData.name; + delete cleanData.dose; + delete cleanData.age; + delete cleanData.gid; + const updateDoc = { + count: admin.firestore.FieldValue.increment(1), + }; + if (data.name === VAX_NAMES.PB || data.name === VAX_NAMES.MD) { + Object.keys(VAX_DOSES).forEach((doseKey) => { + const dose = VAX_DOSES[doseKey]; + Object.keys(cleanData[dose]).forEach((symptom) => { + if (cleanData[dose][symptom]) { + updateDoc[`${dose}.${data.age}.${symptom.replace("/", "OR")}`] = + admin.firestore.FieldValue.increment(1); + } + }); + }); + } + else { + Object.keys(cleanData).forEach((symptom) => { + updateDoc[`${data.age}.${symptom.replace("/", "OR")}`] = + admin.firestore.FieldValue.increment(1); + }); + } + admin.firestore().collection("stats").doc(data.name).update(updateDoc); +}; +const removeResultFromStats = async (data) => { + const cleanData = Object.assign({}, data); + delete cleanData.name; + delete cleanData.dose; + delete cleanData.age; + delete cleanData.gid; + const updateDoc = { + count: admin.firestore.FieldValue.increment(-1), + }; + if (data.name === VAX_NAMES.PB || data.name === VAX_NAMES.MD) { + Object.keys(VAX_DOSES).forEach((doseKey) => { + const dose = VAX_DOSES[doseKey]; + Object.keys(cleanData[dose]).forEach((symptom) => { + if (cleanData[dose][symptom]) { + updateDoc[`${dose}.${data.age}.${symptom.replace("/", "OR")}`] = + admin.firestore.FieldValue.increment(-1); + } + }); + }); + } + else { + Object.keys(cleanData).forEach((symptom) => { + updateDoc[`${data.age}.${symptom.replace("/", "OR")}`] = + admin.firestore.FieldValue.increment(-1); + }); + } + admin.firestore().collection("stats").doc(data.name).update(updateDoc); +}; +exports.getStats = functions.https.onCall(async () => { + const stats = await admin.firestore().collection("stats").get(); + const docs = stats.docs + .map((docSnap) => ({ name: docSnap.id, result: docSnap.data() })); + const returnData = {}; + docs.forEach((vaxResult) => { + returnData[vaxResult.name] = {}; + returnData[vaxResult.name] = vaxResult.result; + }); + return returnData; +}); exports.saveResult = functions.https.onCall(async (data, context) => { if (!!data && !!context) { const { gid } = data; - const docId = serializeGaId(gid); - const docData = Object.assign({}, data); - delete docData.gid; - const existingData = await admin + // Get existing result + const existingResult = await admin .firestore() .collection("results") - .doc(docId) + .doc(serializeGaId(gid)) .get() - .then((doc) => doc.data()); - if (existingData !== undefined && existingData !== docData && - existingData.name === docData.name) { - // DATA EXISTS FOR USER - // BUT DIFF DATA SENT FOR UPDATE - await admin.firestore().collection("results").doc(docId).set(docData); - delete docData.age; - delete docData.name; - delete docData.dose; - const updateDoc = {}; - Object.keys(docData).forEach((symptom) => { - // IF WAS TRUE, NOW FALSE = -1 - // IF WAS TRUE, NOW TRUE = 0 - // IF WAS FALSE, NOW TRUE = +1 - // IF WAS FALSE, NOW FALSE = 0 - let incrementValue = 0; - if (existingData[symptom] === true && docData[symptom] === false) { - incrementValue = -1; - } - else if (existingData[symptom] === false && - docData[symptom] === true) { - incrementValue = 1; - } - if (data.dose) { - updateDoc[`${data.dose}.${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(incrementValue); - } - else { - updateDoc[`${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(incrementValue); - } - }); - admin - .firestore() - .collection("stats") - .doc(data.name) - .update(updateDoc); + .then((docSnap) => docSnap.data()); + // Update user result + const cleanData = Object.assign({}, data); + delete cleanData.gid; + await admin + .firestore() + .collection("results") + .doc(serializeGaId(gid)) + .set(cleanData); + // No existing result + if (existingResult === undefined) { + await addResultToStats(data); } - else if (existingData !== undefined && existingData !== docData && - existingData.name !== docData.name) { - // UNDO DATA FOR OLD VAX - // DECREMENT VALUES FOR EACH SYMPTOM ON OLD VAX - const undoDoc = { - count: admin.firestore.FieldValue.increment(-1), - }; - Object.keys(existingData).forEach((symptom) => { - if (symptom !== "name" && symptom !== "dose" && - symptom !== "age") { - if (existingData[symptom] === true) { - if (existingData.dose) { - undoDoc[`${existingData.dose}.${existingData.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(-1); - } - else { - undoDoc[`${existingData.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(-1); - } - } - } - }); - admin - .firestore() - .collection("stats") - .doc(existingData.name) - .update(undoDoc); - // PROCESS NEW DATA FOR USE - // SAVE USER DATA - await admin.firestore().collection("results").doc(docId).set(docData); - delete docData.age; - delete docData.name; - delete docData.dose; - // INCREMENT TOTAL RESPONSE COUNT - const updateDoc = { - count: admin.firestore.FieldValue.increment(1), - }; - // INCREMENT VALUES FOR EACH SYMPTOM - Object.keys(docData).forEach((symptom) => { - if (docData[symptom] === true) { - if (data.dose) { - updateDoc[`${data.dose}.${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(1); - } - else { - updateDoc[`${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(1); - } - } - }); - admin - .firestore() - .collection("stats") - .doc(data.name) - .update(updateDoc); - } - else if (existingData === undefined) { - // DATA DOESN'T EXIST FOR USER - // SAVE USER DATA - await admin.firestore().collection("results").doc(docId).set(docData); - delete docData.age; - delete docData.name; - delete docData.dose; - // INCREMENT TOTAL RESPONSE COUNT - const updateDoc = { - count: admin.firestore.FieldValue.increment(1), - }; - // INCREMENT VALUES FOR EACH SYMPTOM - Object.keys(docData).forEach((symptom) => { - if (docData[symptom] === true) { - if (data.dose) { - updateDoc[`${data.dose}.${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(1); - } - else { - updateDoc[`${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(1); - } - } - }); - admin - .firestore() - .collection("stats") - .doc(data.name) - .update(updateDoc); + else { + await removeResultFromStats(existingResult); + await addResultToStats(data); } } }); exports.getPastResult = functions.https.onCall(async (data, context) => { if (!!data && !!context) { const { gid } = data; + let hasToUpdateDb = false; const existingData = await admin .firestore() .collection("results") @@ -172,11 +140,49 @@ exports.getPastResult = functions.https.onCall(async (data, context) => { .get() .then((doc) => doc.data()); if (existingData) { - if (existingData["NAUSEA / VOMITING"]) { - existingData["NAUSEA OR VOMITING"] = - existingData["NAUSEA / VOMITING"]; + const returnData = Object.assign({}, existingData); + if (existingData.name === VAX_NAMES.PB || + existingData.name === VAX_NAMES.MD) { + Object.keys(VAX_DOSES).forEach((doseKey) => { + const dose = VAX_DOSES[doseKey]; + if (!existingData[dose]) { + hasToUpdateDb = true; + const temp = {}; + Object.keys(existingData).forEach((symptom) => { + if (symptom !== "name" && + symptom !== "age" && + symptom !== "dose") { + if (existingData.dose === dose) { + temp[symptom] = existingData[symptom]; + delete returnData[symptom]; + } + else { + temp[symptom] = false; + } + } + }); + returnData[dose] = temp; + } + if (returnData[dose]["NAUSEA / VOMITING"]) { + returnData["NAUSEA OR VOMITING"] = + returnData["NAUSEA / VOMITING"]; + } + }); } - return existingData; + else { + if (returnData["NAUSEA / VOMITING"]) { + returnData["NAUSEA OR VOMITING"] = + returnData["NAUSEA / VOMITING"]; + } + } + if (hasToUpdateDb) { + await admin + .firestore() + .collection("results") + .doc(serializeGaId(gid)) + .set(returnData); + } + return returnData; } } return undefined; diff --git a/functions/lib/index.js.map b/functions/lib/index.js.map index 042d860..505cf89 100644 --- a/functions/lib/index.js.map +++ b/functions/lib/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/package.json b/functions/package.json index e6e9276..95007b0 100644 --- a/functions/package.json +++ b/functions/package.json @@ -3,7 +3,7 @@ "scripts": { "lint": "eslint --ext .js,.ts .", "build": "tsc", - "serve": "npm run devenv && npm run build && $env:NODE_ENV=DEV && firebase emulators:start", + "serve": "npm run devenv && npm run build && firebase emulators:start --import ./exportDB", "devenv": "SET NODE_ENV=dev", "shell": "npm run build && firebase functions:shell", "start": "npm run shell", diff --git a/functions/src/index.ts b/functions/src/index.ts index 9bc71a3..894c60c 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -19,14 +19,15 @@ admin.initializeApp({ credential: admin.credential.cert(params), }); -type SideEffect = Record; -type DoseToSideEffect = Record; -type DBResult = { - name : string; - age : string; - "J&J Janssen"? : SideEffect; - "Moderna"? : DoseToSideEffect; - "Pfizer-BioNTech"? : DoseToSideEffect; +const VAX_NAMES : Record = { + PB: "Pfizer-BioNTech", + MD: "Moderna", + JJ: "J&J Janssen", +}; + +const VAX_DOSES : Record = { + ONE: "Dose 1", + TWO: "Dose 2", }; type SaveResultRequestData = { @@ -44,34 +45,93 @@ const serializeGaId = (_gaid: string): string => { } return ""; }; - -const requestDataToDbData = (data: SaveResultRequestData) : DBResult => { - console.log(data); - return {name: "a", age: "d"}; -}; - -const addResultToStats = async (data : DBResult) => { - console.log(data); -}; - -const removeResultFromStats = async (data : DBResult) => { - console.log(data); -}; - -const setResultInDb = async (data: SaveResultRequestData) => { - const {name, age, dose} = data; - const result : DBResult = {name, age}; - const rawResult : Record = {}; - Object.keys(data).filter(k => data[k] === true || data[k] === false).forEach(k => { - rawResult[k] = data[k]; - }); - if (!!dose.replace(/\s/g, "")) { - result[name][dose][age] = +const addResultToStats = async (data : SaveResultRequestData) => { + const cleanData : Record = + Object.assign({}, data); + delete cleanData.name; + delete cleanData.dose; + delete cleanData.age; + delete cleanData.gid; + const updateDoc : Record = { + count: admin.firestore.FieldValue.increment(1), + }; + if (data.name === VAX_NAMES.PB || data.name === VAX_NAMES.MD) { + Object.keys(VAX_DOSES).forEach((doseKey : string) => { + const dose : string = VAX_DOSES[doseKey]; + Object.keys(cleanData[dose]).forEach((symptom : string) => { + if (cleanData[dose][symptom]) { + let updateDocSymptom = symptom.replace("/", "OR"); + if (symptom === "DIARRHEA") { + updateDocSymptom = "DIRRHEA"; + } + updateDoc[`${dose}.${data.age}.${updateDocSymptom}`] = + admin.firestore.FieldValue.increment(1); + } + }); + }); + } else { + Object.keys(cleanData).forEach((symptom : string) => { + let updateDocSymptom = symptom.replace("/", "OR"); + if (symptom === "DIARRHEA") { + updateDocSymptom = "DIRRHEA"; + } + updateDoc[`${data.age}.${updateDocSymptom}`] = + admin.firestore.FieldValue.increment(1); + }); } - await addResultToStats(requestDataToDbData(data)); - return result; + admin.firestore().collection("stats").doc(data.name).update(updateDoc); }; +const removeResultFromStats = async (data : FirebaseFirestore.DocumentData) => { + const cleanData : Record = + Object.assign({}, data); + delete cleanData.name; + delete cleanData.dose; + delete cleanData.age; + delete cleanData.gid; + const updateDoc : Record = { + count: admin.firestore.FieldValue.increment(-1), + }; + if (data.name === VAX_NAMES.PB || data.name === VAX_NAMES.MD) { + Object.keys(VAX_DOSES).forEach((doseKey : string) => { + const dose : string = VAX_DOSES[doseKey]; + Object.keys(cleanData[dose]).forEach((symptom : string) => { + if (cleanData[dose][symptom]) { + let updateDocSymptom = symptom.replace("/", "OR"); + if (symptom === "DIARRHEA") { + updateDocSymptom = "DIRRHEA"; + } + updateDoc[`${dose}.${data.age}.${updateDocSymptom}`] = + admin.firestore.FieldValue.increment(-1); + } + }); + }); + } else { + Object.keys(cleanData).forEach((symptom : string) => { + let updateDocSymptom = symptom.replace("/", "OR"); + if (symptom === "DIARRHEA") { + updateDocSymptom = "DIRRHEA"; + } + updateDoc[`${data.age}.${updateDocSymptom}`] = + admin.firestore.FieldValue.increment(-1); + }); + } + admin.firestore().collection("stats").doc(data.name).update(updateDoc); +}; + +export const getStats = functions.https.onCall( + async () => { + const stats = await admin.firestore().collection("stats").get(); + const docs = stats.docs + .map((docSnap) => ({name: docSnap.id, result: docSnap.data()})); + const returnData : Record = {}; + docs.forEach((vaxResult) => { + returnData[vaxResult.name] = {}; + returnData[vaxResult.name] = vaxResult.result; + }); + return returnData; + }); + export const saveResult = functions.https.onCall( async (data: SaveResultRequestData, context) => { if (!!data && !!context) { @@ -83,175 +143,87 @@ export const saveResult = functions.https.onCall( .doc(serializeGaId(gid)) .get() .then((docSnap) => docSnap.data()); - // No existing result - if (existingResult === undefined) { - // Set result - setResultInDb(data); - } else { - const {dose, name, age} = data; - if (existingResult.name === name && // Same vax - existingResult.age === age && // Same age group - !!dose.replace(/\s/g, "") && // Has dose sub category - !existingResult[name][age][dose]) { // Dose not existing - setResultInDb(data); - } else { - await removeResultFromStats(existingResult); - } - } - } - - if (!!data && !!context) { - const {gid} = data; - const docId: string = serializeGaId(gid); - const docData: Record = - Object.assign({}, data); - delete docData.gid; - const existingData = await admin + // Update user result + const cleanData : Record = + Object.assign({}, data); + delete cleanData.gid; + await admin .firestore() .collection("results") - .doc(docId) - .get() - .then((doc) => doc.data()); - if (existingData !== undefined && existingData !== docData && - existingData.name === docData.name) { - // DATA EXISTS FOR USER - // BUT DIFF DATA SENT FOR UPDATE - await admin.firestore().collection("results").doc(docId).set(docData); - delete docData.age; - delete docData.name; - delete docData.dose; - const updateDoc : Record = {}; - Object.keys(docData).forEach((symptom : string) => { - // IF WAS TRUE, NOW FALSE = -1 - // IF WAS TRUE, NOW TRUE = 0 - // IF WAS FALSE, NOW TRUE = +1 - // IF WAS FALSE, NOW FALSE = 0 - let incrementValue = 0; - if (existingData[symptom] === true && docData[symptom] === false) { - incrementValue = -1; - } else if (existingData[symptom] === false && - docData[symptom] === true) { - incrementValue = 1; - } - if (data.dose) { - updateDoc[ - `${data.dose}.${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(incrementValue); - } else { - updateDoc[`${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(incrementValue); - } - }); - admin - .firestore() - .collection("stats") - .doc(data.name) - .update(updateDoc); - } else if (existingData !== undefined && existingData !== docData && - existingData.name !== docData.name) { - // UNDO DATA FOR OLD VAX - // DECREMENT VALUES FOR EACH SYMPTOM ON OLD VAX - const undoDoc : Record = { - count: admin.firestore.FieldValue.increment(-1), - }; - Object.keys(existingData).forEach((symptom : string) => { - if (symptom !== "name" && symptom !== "dose" && - symptom !== "age" ) { - if (existingData[symptom] === true) { - if (existingData.dose) { - undoDoc[ - `${existingData.dose}.${existingData.age}.${ - symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(-1); - } else { - undoDoc[`${existingData.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(-1); - } - } - } - }); - admin - .firestore() - .collection("stats") - .doc(existingData.name) - .update(undoDoc); - // PROCESS NEW DATA FOR USE - // SAVE USER DATA - await admin.firestore().collection("results").doc(docId).set(docData); - delete docData.age; - delete docData.name; - delete docData.dose; - // INCREMENT TOTAL RESPONSE COUNT - const updateDoc : Record = { - count: admin.firestore.FieldValue.increment(1), - }; - // INCREMENT VALUES FOR EACH SYMPTOM - Object.keys(docData).forEach((symptom : string) => { - if (docData[symptom] === true) { - if (data.dose) { - updateDoc[ - `${data.dose}.${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(1); - } else { - updateDoc[`${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(1); - } - } - }); - admin - .firestore() - .collection("stats") - .doc(data.name) - .update(updateDoc); - } else if (existingData === undefined) { - // DATA DOESN'T EXIST FOR USER - // SAVE USER DATA - await admin.firestore().collection("results").doc(docId).set(docData); - delete docData.age; - delete docData.name; - delete docData.dose; - // INCREMENT TOTAL RESPONSE COUNT - const updateDoc : Record = { - count: admin.firestore.FieldValue.increment(1), - }; - // INCREMENT VALUES FOR EACH SYMPTOM - Object.keys(docData).forEach((symptom : string) => { - if (docData[symptom] === true) { - if (data.dose) { - updateDoc[ - `${data.dose}.${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(1); - } else { - updateDoc[`${data.age}.${symptom.replace("/", "OR")}`] = - admin.firestore.FieldValue.increment(1); - } - } - }); - admin - .firestore() - .collection("stats") - .doc(data.name) - .update(updateDoc); + .doc(serializeGaId(gid)) + .set(cleanData); + // No existing result + if (existingResult === undefined) { + await addResultToStats(data); + } else { + await removeResultFromStats(existingResult); + await addResultToStats(data); } } }); -export const getPastResult = functions.https.onCall(async (data : {gid:string}, context) => { - if (!!data && !!context) { - const {gid} = data; - const existingData : admin.firestore.DocumentData | undefined = await admin - .firestore() - .collection("results") - .doc(serializeGaId(gid)) - .get() - .then((doc) => doc.data()); - if (existingData) { - if (existingData["NAUSEA / VOMITING"]) { - existingData["NAUSEA OR VOMITING"] = - existingData["NAUSEA / VOMITING"]; +export const getPastResult = + functions.https.onCall(async (data : {gid:string}, context) => { + if (!!data && !!context) { + const {gid} = data; + let hasToUpdateDb = false; + const existingData : admin.firestore.DocumentData | undefined = + await admin + .firestore() + .collection("results") + .doc(serializeGaId(gid)) + .get() + .then((doc) => doc.data()); + if (existingData) { + const returnData : admin.firestore.DocumentData | undefined = + Object.assign({}, existingData); + if (existingData.name === VAX_NAMES.PB || + existingData.name === VAX_NAMES.MD) { + Object.keys(VAX_DOSES).forEach((doseKey : string) => { + const dose : string = VAX_DOSES[doseKey]; + if (!existingData[dose]) { + hasToUpdateDb = true; + const temp : Record = {}; + Object.keys(existingData).forEach((symptom) => { + if (symptom !== "name" && + symptom !== "age" && + symptom !== "dose") { + if (existingData.dose === dose) { + temp[symptom] = existingData[symptom]; + delete returnData[symptom]; + } else { + temp[symptom] = false; + } + } + }); + returnData[dose] = temp; + } + if (returnData[dose]["NAUSEA / VOMITING"]) { + returnData["NAUSEA OR VOMITING"] = + returnData["NAUSEA / VOMITING"]; + } + if (returnData["DIRRHEA"]) { + returnData["DIARRHEA"] = returnData["DIRRHEA"]; + } + }); + } else { + if (returnData["NAUSEA / VOMITING"]) { + returnData["NAUSEA OR VOMITING"] = + returnData["NAUSEA / VOMITING"]; + } + if (returnData["DIRRHEA"]) { + returnData["DIARRHEA"] = returnData["DIRRHEA"]; + } + } + if (hasToUpdateDb) { + await admin + .firestore() + .collection("results") + .doc(serializeGaId(gid)) + .set(returnData); + } + return returnData; } - return existingData; } - } - return undefined; -}); + return undefined; + }); diff --git a/website/public/styles/extralarge.css b/website/public/styles/extralarge.css index 718ed37..5300208 100644 --- a/website/public/styles/extralarge.css +++ b/website/public/styles/extralarge.css @@ -1,5 +1,5 @@ .input-popup.expanded { - height: 40vh; + height: 50vh; } .loading-bar-container { diff --git a/website/public/styles/extrasmall.css b/website/public/styles/extrasmall.css index 3e0acbc..5ee2f36 100644 --- a/website/public/styles/extrasmall.css +++ b/website/public/styles/extrasmall.css @@ -331,6 +331,43 @@ footer .by { .input-popup-submit.submitted { background-color: #55c16c; } +.input-popup-submit.errored { + background-color: #c43d34; +} + +.input-popup.expanded::-webkit-scrollbar { + width: 1.5rem; + background-color: var(--color-tertiary); + border-radius: 0 4rem 4rem 0; +} +.input-popup.expanded::-webkit-scrollbar-thumb { + width: 1rem; + background-color: var(--color-dim); + border-radius: 0 4rem 4rem 0; +} + +.tip-container { + display: flex; + flex-direction: row; + align-items: center; + padding: 2rem 4rem; + padding-left: 2rem; + border-radius: 1rem; + margin-bottom: 2rem; + border: 1px solid var(--color-secondary); +} +.tip-icon { + font-size: 3rem; +} +.tip { + padding-left: 2rem; +} +.tip .overline { + font-size: 1.4rem; +} +.tip .subheader { + font-size: 3.2rem; +} @keyframes loading { 0% { diff --git a/website/public/styles/large.css b/website/public/styles/large.css index fe78b89..2de2562 100644 --- a/website/public/styles/large.css +++ b/website/public/styles/large.css @@ -22,7 +22,7 @@ footer { height: 10rem; } .input-popup.expanded { - height: 50vh; + height: 55vh; } .loading-bar-container { diff --git a/website/public/styles/medium.css b/website/public/styles/medium.css index 330d556..9a26ba7 100644 --- a/website/public/styles/medium.css +++ b/website/public/styles/medium.css @@ -103,3 +103,7 @@ footer { .loading-bar-container { max-width: 50%; } + +.tip-container { + width: fit-content; +} \ No newline at end of file diff --git a/website/src/index.js b/website/src/index.js index 786a466..95ec570 100644 --- a/website/src/index.js +++ b/website/src/index.js @@ -20,6 +20,7 @@ if (window.location.host.startsWith("localhost")) { const fb_functions = firebase.functions(); const saveResult = fb_functions.httpsCallable("saveResult"); const getPastResult = fb_functions.httpsCallable("getPastResult"); +const getStats = fb_functions.httpsCallable("getStats"); const VAX_NAMES = { PB: "Pfizer-BioNTech", @@ -47,61 +48,76 @@ const VAX_AGES = { }, }; +const VAX_EFFICACY = { + "Pfizer-BioNTech": { + "18 - 55": 95.3, + "56 & Older": 93.8, + }, + Moderna: { + "18 - 64": 95.6, + "65 & Older": 86.4, + }, + "J&J Janssen": { + "18 - 59": 66.1, + "60 & Older": 66.2, + }, +} + const PB_DOSE1_YOUNGER_RESULT = { - "PAIN AT SITE": "8", - "RED AT SITE": "1", - "SWELLING AT SITE": "1", - FATIGUE: "5", - HEADACHE: "4", - "NEED PAIN MED": "3", - "MUSCLE PAIN": "2", - CHILLS: "1", - DIRRHEA: "1", - "JOINT PAIN": "1", - FEVER: "<1", - VOMITING: "<1", + "PAIN AT SITE": 1904, + "RED AT SITE": 104, + "SWELLING AT SITE": 132, + FATIGUE: 1085, + HEADACHE: 959, + "NEED PAIN MED":638, + "MUSCLE PAIN": 487, + CHILLS: 321, + DIARRHEA: 255, + "JOINT PAIN": 251, + FEVER: 85, + VOMITING: 28, }; const PB_DOSE2_YOUNGER_RESULT = { - "PAIN AT SITE": "8", - "RED AT SITE": "1", - "SWELLING AT SITE": "1", - FATIGUE: "6", - HEADACHE: "5", - "NEED PAIN MED": "5", - "MUSCLE PAIN": "4", - CHILLS: "4", - "JOINT PAIN": "2", - FEVER: "2", - DIRRHEA: "1", - VOMITING: "<1", + "PAIN AT SITE": 1632, + "RED AT SITE": 123, + "SWELLING AT SITE": 132, + FATIGUE: 1247, + HEADACHE: 1085, + "NEED PAIN MED": 945, + "MUSCLE PAIN": 783, + CHILLS: 737, + "JOINT PAIN": 459, + FEVER: 331, + DIARRHEA: 219, + VOMITING: 40, }; const PB_DOSE1_OLDER_RESULT = { - "PAIN AT SITE": "7", - "RED AT SITE": "1", - "SWELLING AT SITE": "1", - FATIGUE: "3", - HEADACHE: "3", - "NEED PAIN MED": "2", - "MUSCLE PAIN": "1", - CHILLS: "1", - DIRRHEA: "1", - "JOINT PAIN": "1", - FEVER: "<1", - VOMITING: "<1", + "PAIN AT SITE": 1282, + "RED AT SITE": 85, + "SWELLING AT SITE": 118, + FATIGUE: 615, + HEADACHE: 454, + "NEED PAIN MED": 358, + "MUSCLE PAIN": 251, + CHILLS: 113, + DIARRHEA: 147, + "JOINT PAIN": 155, + FEVER: 26, + VOMITING: 9, }; const PB_DOSE2_OLDER_RESULT = { - "PAIN AT SITE": "7", - "RED AT SITE": "1", - "SWELLING AT SITE": "1", - FATIGUE: "5", - HEADACHE: "4", - "NEED PAIN MED": "4", - "MUSCLE PAIN": "3", - CHILLS: "2", - "JOINT PAIN": "2", - DIRRHEA: "1", - FEVER: "1", - VOMITING: "<1", + "PAIN AT SITE": 1098, + "RED AT SITE": 120, + "SWELLING AT SITE": 124, + FATIGUE: 839, + HEADACHE: 647, + "NEED PAIN MED": 625, + "MUSCLE PAIN": 477, + CHILLS: 377, + "JOINT PAIN": 313, + DIARRHEA: 137, + FEVER: 181, + VOMITING: 11, }; const MD_DOSE1_YOUNGER_RESULT = { "PAIN AT SITE": "9", @@ -174,6 +190,33 @@ const JJ_OLDER_RESULT = { FEVER: "<1", }; +const VAX_COUNTS = { + "Pfizer-BioNTech": { + "Dose 1": { + "18 - 55": 2291, + "56 & Older": 1802, + }, + "Dose 2": { + "18 - 55": 2098, + "56 & Older": 1660, + }, + }, + Moderna: { + "Dose 1": { + "18 - 64": 1, + "65 & Older": 1, + }, + "Dose 2": { + "18 - 64": 1, + "65 & Older": 1, + }, + }, + "J&J Janssen": { + "18 - 59": 1, + "60 & Older": 1, + }, +}; + const VAX_RESULTS = { "Pfizer-BioNTech": { "Dose 1": { @@ -210,7 +253,7 @@ const ICONS = { "NEED PAIN MED": "💊", "MUSCLE PAIN": "😫", CHILLS: "🥶", - DIRRHEA: "💩", + DIARRHEA: "💩", "JOINT PAIN": "🦵", FEVER: "🤒", VOMITING: "🤮", @@ -227,6 +270,8 @@ class App extends React.Component { gid: "", showInputPopup: false, answers: {}, + fetchedResult : {}, + stats: {}, loading: true, }; } @@ -276,11 +321,7 @@ class App extends React.Component { const { data } = v; if (!!data) { const { name, age, dose } = data; - this.setState({ name, age, dose, showInputPopup: true }); - delete data.name; - delete data.age; - delete data.dose; - this.setState({ answers: data, loading: false }); + this.setState({ name, age, dose, showInputPopup: true, fetchedResult: data, answers: data, loading: false }); const inputPopupSubmit = document.querySelector( ".input-popup-submit" ); @@ -302,7 +343,13 @@ class App extends React.Component { } else { this.setState({ loading: false }); } + + getStats().then(v => this.setState({ stats: v.data })); } + shouldShowDoses = () => { + const { name } = this.state; + return name && (name === VAX_NAMES.PB || name === VAX_NAMES.MD); + }; shouldShowAgeGroup = () => { const { name, dose } = this.state; switch (name) { @@ -373,25 +420,55 @@ class App extends React.Component { document.querySelector("#share-result").innerHTML = "Share ↗"; }, 1500); }; - updateAnswers = (symptom) => { + updateAnswers = (symptom, dose) => { const { answers } = this.state; const newAnswers = Object.assign({}, answers); - if (answers[symptom]) { - newAnswers[symptom] = !answers[symptom]; + if (!!dose) { + if (!newAnswers[dose]) { + newAnswers[dose] = {}; + } + if (newAnswers[dose][symptom]) { + newAnswers[dose][symptom] = !newAnswers[dose][symptom]; + } else { + newAnswers[dose][symptom] = true; + } } else { - newAnswers[symptom] = true; + if (newAnswers[symptom]) { + newAnswers[symptom] = !newAnswers[symptom]; + } else { + newAnswers[symptom] = true; + } } this.setState({ answers: newAnswers }); }; submitAnswer = () => { document.querySelector(".input-popup-submit").innerHTML = "SENDING..."; const { name, dose, age, answers, gid } = this.state; - const result = Object.assign({}, this.getResults()); + const result = this.getResults(); const symptoms = {}; - Object.keys(result).forEach((symptom) => { - symptoms[symptom] = false; - }); - const submitAnswers = Object.assign({}, symptoms, answers, { + delete answers.name; + delete answers.dose; + delete answers.age; + if(!!dose) { + symptoms[VAX_DOSES.ONE] = {}; + symptoms[VAX_DOSES.TWO] = {}; + Object.keys(VAX_DOSES).forEach((dose) => { + Object.keys(result).forEach((symptom) => { + symptoms[VAX_DOSES[dose]][symptom] = false; + if (answers[VAX_DOSES[dose]] && answers[VAX_DOSES[dose]][symptom]) { + symptoms[VAX_DOSES[dose]][symptom] = true; + } + }); + }); + } else { + Object.keys(result).forEach((symptom) => { + symptoms[symptom] = false; + if (answers[symptom]) { + symptoms[symptom] = true; + } + }); + } + const submitAnswers = Object.assign({}, symptoms, { name, dose, age, @@ -399,7 +476,12 @@ class App extends React.Component { }); saveResult(submitAnswers) .then(() => { + delete submitAnswers.gid; + this.setState({ fetchedResult : submitAnswers }); const inputPopupSubmit = document.querySelector(".input-popup-submit"); + if (inputPopupSubmit.classList.contains("errored")) { + inputPopupSubmit.classList.remove("errored"); + } if (!inputPopupSubmit.classList.contains("submitted")) { inputPopupSubmit.classList.add("submitted"); } @@ -408,10 +490,57 @@ class App extends React.Component { inputPopupSubmit.innerHTML = "UPDATE ANSWER"; }, 1500); }) - .catch((err) => console.error("ERROR Saving Answer", err)); + .catch((err) => { + const inputPopupSubmit = document.querySelector(".input-popup-submit"); + if (!inputPopupSubmit.classList.contains("errored")) { + inputPopupSubmit.classList.add("errored"); + } + if (inputPopupSubmit.classList.contains("submitted")) { + inputPopupSubmit.classList.remove("submitted"); + } + inputPopupSubmit.innerHTML = "UH OH...UNABLE TO SEND"; + setTimeout(() => { + inputPopupSubmit.innerHTML = "TRY AGAIN"; + }, 1500); + console.error("ERROR Saving Answer", err); + }); + }; + getAnswerOptions = (answerDose) => { + const { answers, dose } = this.state; + const result = this.getResults(); + return
+ {!!answerDose ? ( +
+ {answerDose} +
+ ) : null} +

+ { ((dose === VAX_DOSES.ONE) && (answerDose === VAX_DOSES.TWO)) ? "(You can comeback later to tell us about Dose 2)":"(Select all side effects that you experienced)" } +

+
+ {Object.keys(result).map((symptom) => ( + + ))} +
+
; }; render() { - const { name, dose, age, showInputPopup, answers, loading } = this.state; + const { name, dose, age, showInputPopup, loading, fetchedResult } = this.state; let result = {}; let resultAtSite = {}; if (this.shouldShowResults()) { @@ -463,7 +592,7 @@ class App extends React.Component { name: v, dose: "", age: "", - answers: {}, + answers: v === fetchedResult.name ? fetchedResult : {}, showInputPopup: false, }) } @@ -478,7 +607,7 @@ class App extends React.Component { ) : null} - {name && (name === VAX_NAMES.PB || name === VAX_NAMES.MD) ? ( + {this.shouldShowDoses() ? (

Select Dose @@ -491,7 +620,6 @@ class App extends React.Component { this.setState({ dose: v, age: "", - answers: {}, showInputPopup: false, }) } @@ -519,7 +647,6 @@ class App extends React.Component { onClick={() => this.setState({ age: v, - answers: {}, showInputPopup: false, }) } @@ -553,6 +680,27 @@ class App extends React.Component {

) : null} + {this.shouldShowResults() ? ( +
+
ⓘ
+
+
+ NET EFFICACY +
+
+ {name} was effective for +
+

+ {VAX_EFFICACY[name][age]}% +

+
+ of people who took it, in the {age} age group +
+
+
+ ) : null + } + {this.shouldShowResults() ? (
{Object.keys(resultAtSite).map((symptom) => ( @@ -579,6 +727,7 @@ class App extends React.Component { ))}
) : null} + {this.shouldShowResults() ? (
@@ -606,41 +755,17 @@ class App extends React.Component {

- I am in age group {age} and got - {!!dose ? ` ${dose} ` : ""} - {!!dose ? `of` : ""} the + I am in age group {age} and got the {name} vaccine. I experienced:

-
-

- (Select all side effects that you experienced) -

-
- {Object.keys(resultAtSite).map((symptom) => ( - - ))} - {Object.keys(result).map((symptom) => ( - - ))} -
-
+ {this.shouldShowDoses() + ? Object.keys(VAX_DOSES).map((dose) => + this.getAnswerOptions(VAX_DOSES[dose]) + ) + : this.getAnswerOptions()}

- You can comeback and update your experience later! + You can comeback and update your experience at any time!

(Just use the same device)