v1.1.0/number values and efficacy

This commit is contained in:
2021-04-20 20:16:21 -04:00
parent 2eabb6807f
commit 5c39e8a628
10 changed files with 581 additions and 435 deletions
+4 -2
View File
@@ -11,6 +11,9 @@
# production # production
*/build */build
*/lib
*/exportDB
# misc # misc
.DS_Store .DS_Store
@@ -23,5 +26,4 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
*.cache *.cache
*.log */*.log*
functions/src/backup.json
+138 -132
View File
@@ -1,6 +1,6 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); 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 functions = require("firebase-functions");
const admin = require("firebase-admin"); const admin = require("firebase-admin");
const firebaseSecret = require("./firebaseSecret.json"); const firebaseSecret = require("./firebaseSecret.json");
@@ -19,152 +19,120 @@ const params = {
admin.initializeApp({ admin.initializeApp({
credential: admin.credential.cert(params), 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) => { const serializeGaId = (_gaid) => {
if (_gaid) { if (_gaid) {
return _gaid.split(".").join("|"); return _gaid.split(".").join("|");
} }
return ""; 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) => { exports.saveResult = functions.https.onCall(async (data, context) => {
if (!!data && !!context) { if (!!data && !!context) {
const { gid } = data; const { gid } = data;
const docId = serializeGaId(gid); // Get existing result
const docData = Object.assign({}, data); const existingResult = await admin
delete docData.gid;
const existingData = await admin
.firestore() .firestore()
.collection("results") .collection("results")
.doc(docId) .doc(serializeGaId(gid))
.get() .get()
.then((doc) => doc.data()); .then((docSnap) => docSnap.data());
if (existingData !== undefined && existingData !== docData && // Update user result
existingData.name === docData.name) { const cleanData = Object.assign({}, data);
// DATA EXISTS FOR USER delete cleanData.gid;
// BUT DIFF DATA SENT FOR UPDATE await admin
await admin.firestore().collection("results").doc(docId).set(docData); .firestore()
delete docData.age; .collection("results")
delete docData.name; .doc(serializeGaId(gid))
delete docData.dose; .set(cleanData);
const updateDoc = {}; // No existing result
Object.keys(docData).forEach((symptom) => { if (existingResult === undefined) {
// IF WAS TRUE, NOW FALSE = -1 await addResultToStats(data);
// 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 && else {
existingData.name !== docData.name) { await removeResultFromStats(existingResult);
// UNDO DATA FOR OLD VAX await addResultToStats(data);
// 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);
} }
} }
}); });
exports.getPastResult = functions.https.onCall(async (data, context) => { exports.getPastResult = functions.https.onCall(async (data, context) => {
if (!!data && !!context) { if (!!data && !!context) {
const { gid } = data; const { gid } = data;
let hasToUpdateDb = false;
const existingData = await admin const existingData = await admin
.firestore() .firestore()
.collection("results") .collection("results")
@@ -172,11 +140,49 @@ exports.getPastResult = functions.https.onCall(async (data, context) => {
.get() .get()
.then((doc) => doc.data()); .then((doc) => doc.data());
if (existingData) { if (existingData) {
if (existingData["NAUSEA / VOMITING"]) { const returnData = Object.assign({}, existingData);
existingData["NAUSEA OR VOMITING"] = if (existingData.name === VAX_NAMES.PB ||
existingData["NAUSEA / VOMITING"]; 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; return undefined;
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -3,7 +3,7 @@
"scripts": { "scripts": {
"lint": "eslint --ext .js,.ts .", "lint": "eslint --ext .js,.ts .",
"build": "tsc", "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", "devenv": "SET NODE_ENV=dev",
"shell": "npm run build && firebase functions:shell", "shell": "npm run build && firebase functions:shell",
"start": "npm run shell", "start": "npm run shell",
+169 -197
View File
@@ -19,14 +19,15 @@ admin.initializeApp({
credential: admin.credential.cert(params), credential: admin.credential.cert(params),
}); });
type SideEffect = Record<string, boolean>; const VAX_NAMES : Record<string, string> = {
type DoseToSideEffect = Record<string, SideEffect>; PB: "Pfizer-BioNTech",
type DBResult = { MD: "Moderna",
name : string; JJ: "J&J Janssen",
age : string; };
"J&J Janssen"? : SideEffect;
"Moderna"? : DoseToSideEffect; const VAX_DOSES : Record<string, string> = {
"Pfizer-BioNTech"? : DoseToSideEffect; ONE: "Dose 1",
TWO: "Dose 2",
}; };
type SaveResultRequestData = { type SaveResultRequestData = {
@@ -44,34 +45,93 @@ const serializeGaId = (_gaid: string): string => {
} }
return ""; return "";
}; };
const addResultToStats = async (data : SaveResultRequestData) => {
const requestDataToDbData = (data: SaveResultRequestData) : DBResult => { const cleanData : Record<string, any> =
console.log(data); Object.assign({}, data);
return {name: "a", age: "d"}; delete cleanData.name;
}; delete cleanData.dose;
delete cleanData.age;
const addResultToStats = async (data : DBResult) => { delete cleanData.gid;
console.log(data); const updateDoc : Record<string, admin.firestore.FieldValue> = {
}; count: admin.firestore.FieldValue.increment(1),
};
const removeResultFromStats = async (data : DBResult) => { if (data.name === VAX_NAMES.PB || data.name === VAX_NAMES.MD) {
console.log(data); Object.keys(VAX_DOSES).forEach((doseKey : string) => {
}; const dose : string = VAX_DOSES[doseKey];
Object.keys(cleanData[dose]).forEach((symptom : string) => {
const setResultInDb = async (data: SaveResultRequestData) => { if (cleanData[dose][symptom]) {
const {name, age, dose} = data; let updateDocSymptom = symptom.replace("/", "OR");
const result : DBResult = {name, age}; if (symptom === "DIARRHEA") {
const rawResult : Record<string, boolean> = {}; updateDocSymptom = "DIRRHEA";
Object.keys(data).filter(k => data[k] === true || data[k] === false).forEach(k => { }
rawResult[k] = data[k]; updateDoc[`${dose}.${data.age}.${updateDocSymptom}`] =
}); admin.firestore.FieldValue.increment(1);
if (!!dose.replace(/\s/g, "")) { }
result[name][dose][age] = });
});
} 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)); admin.firestore().collection("stats").doc(data.name).update(updateDoc);
return result;
}; };
const removeResultFromStats = async (data : FirebaseFirestore.DocumentData) => {
const cleanData : Record<string, any> =
Object.assign({}, data);
delete cleanData.name;
delete cleanData.dose;
delete cleanData.age;
delete cleanData.gid;
const updateDoc : Record<string, admin.firestore.FieldValue> = {
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<string, unknown> = {};
docs.forEach((vaxResult) => {
returnData[vaxResult.name] = {};
returnData[vaxResult.name] = vaxResult.result;
});
return returnData;
});
export const saveResult = functions.https.onCall( export const saveResult = functions.https.onCall(
async (data: SaveResultRequestData, context) => { async (data: SaveResultRequestData, context) => {
if (!!data && !!context) { if (!!data && !!context) {
@@ -83,175 +143,87 @@ export const saveResult = functions.https.onCall(
.doc(serializeGaId(gid)) .doc(serializeGaId(gid))
.get() .get()
.then((docSnap) => docSnap.data()); .then((docSnap) => docSnap.data());
// No existing result // Update user result
if (existingResult === undefined) { const cleanData : Record<string, any> =
// Set result Object.assign({}, data);
setResultInDb(data); delete cleanData.gid;
} else { await admin
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<string, string | boolean> =
Object.assign({}, data);
delete docData.gid;
const existingData = await admin
.firestore() .firestore()
.collection("results") .collection("results")
.doc(docId) .doc(serializeGaId(gid))
.get() .set(cleanData);
.then((doc) => doc.data()); // No existing result
if (existingData !== undefined && existingData !== docData && if (existingResult === undefined) {
existingData.name === docData.name) { await addResultToStats(data);
// DATA EXISTS FOR USER } else {
// BUT DIFF DATA SENT FOR UPDATE await removeResultFromStats(existingResult);
await admin.firestore().collection("results").doc(docId).set(docData); await addResultToStats(data);
delete docData.age;
delete docData.name;
delete docData.dose;
const updateDoc : Record<string, admin.firestore.FieldValue> = {};
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<string, admin.firestore.FieldValue> = {
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<string, admin.firestore.FieldValue> = {
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<string, admin.firestore.FieldValue> = {
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);
} }
} }
}); });
export const getPastResult = functions.https.onCall(async (data : {gid:string}, context) => { export const getPastResult =
if (!!data && !!context) { functions.https.onCall(async (data : {gid:string}, context) => {
const {gid} = data; if (!!data && !!context) {
const existingData : admin.firestore.DocumentData | undefined = await admin const {gid} = data;
.firestore() let hasToUpdateDb = false;
.collection("results") const existingData : admin.firestore.DocumentData | undefined =
.doc(serializeGaId(gid)) await admin
.get() .firestore()
.then((doc) => doc.data()); .collection("results")
if (existingData) { .doc(serializeGaId(gid))
if (existingData["NAUSEA / VOMITING"]) { .get()
existingData["NAUSEA OR VOMITING"] = .then((doc) => doc.data());
existingData["NAUSEA / VOMITING"]; 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<string, unknown> = {};
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; });
});
+1 -1
View File
@@ -1,5 +1,5 @@
.input-popup.expanded { .input-popup.expanded {
height: 40vh; height: 50vh;
} }
.loading-bar-container { .loading-bar-container {
+37
View File
@@ -331,6 +331,43 @@ footer .by {
.input-popup-submit.submitted { .input-popup-submit.submitted {
background-color: #55c16c; 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 { @keyframes loading {
0% { 0% {
+1 -1
View File
@@ -22,7 +22,7 @@ footer {
height: 10rem; height: 10rem;
} }
.input-popup.expanded { .input-popup.expanded {
height: 50vh; height: 55vh;
} }
.loading-bar-container { .loading-bar-container {
+4
View File
@@ -103,3 +103,7 @@ footer {
.loading-bar-container { .loading-bar-container {
max-width: 50%; max-width: 50%;
} }
.tip-container {
width: fit-content;
}
+225 -100
View File
@@ -20,6 +20,7 @@ if (window.location.host.startsWith("localhost")) {
const fb_functions = firebase.functions(); const fb_functions = firebase.functions();
const saveResult = fb_functions.httpsCallable("saveResult"); const saveResult = fb_functions.httpsCallable("saveResult");
const getPastResult = fb_functions.httpsCallable("getPastResult"); const getPastResult = fb_functions.httpsCallable("getPastResult");
const getStats = fb_functions.httpsCallable("getStats");
const VAX_NAMES = { const VAX_NAMES = {
PB: "Pfizer-BioNTech", 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 = { const PB_DOSE1_YOUNGER_RESULT = {
"PAIN AT SITE": "8", "PAIN AT SITE": 1904,
"RED AT SITE": "1", "RED AT SITE": 104,
"SWELLING AT SITE": "1", "SWELLING AT SITE": 132,
FATIGUE: "5", FATIGUE: 1085,
HEADACHE: "4", HEADACHE: 959,
"NEED PAIN MED": "3", "NEED PAIN MED":638,
"MUSCLE PAIN": "2", "MUSCLE PAIN": 487,
CHILLS: "1", CHILLS: 321,
DIRRHEA: "1", DIARRHEA: 255,
"JOINT PAIN": "1", "JOINT PAIN": 251,
FEVER: "<1", FEVER: 85,
VOMITING: "<1", VOMITING: 28,
}; };
const PB_DOSE2_YOUNGER_RESULT = { const PB_DOSE2_YOUNGER_RESULT = {
"PAIN AT SITE": "8", "PAIN AT SITE": 1632,
"RED AT SITE": "1", "RED AT SITE": 123,
"SWELLING AT SITE": "1", "SWELLING AT SITE": 132,
FATIGUE: "6", FATIGUE: 1247,
HEADACHE: "5", HEADACHE: 1085,
"NEED PAIN MED": "5", "NEED PAIN MED": 945,
"MUSCLE PAIN": "4", "MUSCLE PAIN": 783,
CHILLS: "4", CHILLS: 737,
"JOINT PAIN": "2", "JOINT PAIN": 459,
FEVER: "2", FEVER: 331,
DIRRHEA: "1", DIARRHEA: 219,
VOMITING: "<1", VOMITING: 40,
}; };
const PB_DOSE1_OLDER_RESULT = { const PB_DOSE1_OLDER_RESULT = {
"PAIN AT SITE": "7", "PAIN AT SITE": 1282,
"RED AT SITE": "1", "RED AT SITE": 85,
"SWELLING AT SITE": "1", "SWELLING AT SITE": 118,
FATIGUE: "3", FATIGUE: 615,
HEADACHE: "3", HEADACHE: 454,
"NEED PAIN MED": "2", "NEED PAIN MED": 358,
"MUSCLE PAIN": "1", "MUSCLE PAIN": 251,
CHILLS: "1", CHILLS: 113,
DIRRHEA: "1", DIARRHEA: 147,
"JOINT PAIN": "1", "JOINT PAIN": 155,
FEVER: "<1", FEVER: 26,
VOMITING: "<1", VOMITING: 9,
}; };
const PB_DOSE2_OLDER_RESULT = { const PB_DOSE2_OLDER_RESULT = {
"PAIN AT SITE": "7", "PAIN AT SITE": 1098,
"RED AT SITE": "1", "RED AT SITE": 120,
"SWELLING AT SITE": "1", "SWELLING AT SITE": 124,
FATIGUE: "5", FATIGUE: 839,
HEADACHE: "4", HEADACHE: 647,
"NEED PAIN MED": "4", "NEED PAIN MED": 625,
"MUSCLE PAIN": "3", "MUSCLE PAIN": 477,
CHILLS: "2", CHILLS: 377,
"JOINT PAIN": "2", "JOINT PAIN": 313,
DIRRHEA: "1", DIARRHEA: 137,
FEVER: "1", FEVER: 181,
VOMITING: "<1", VOMITING: 11,
}; };
const MD_DOSE1_YOUNGER_RESULT = { const MD_DOSE1_YOUNGER_RESULT = {
"PAIN AT SITE": "9", "PAIN AT SITE": "9",
@@ -174,6 +190,33 @@ const JJ_OLDER_RESULT = {
FEVER: "<1", 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 = { const VAX_RESULTS = {
"Pfizer-BioNTech": { "Pfizer-BioNTech": {
"Dose 1": { "Dose 1": {
@@ -210,7 +253,7 @@ const ICONS = {
"NEED PAIN MED": "💊", "NEED PAIN MED": "💊",
"MUSCLE PAIN": "😫", "MUSCLE PAIN": "😫",
CHILLS: "🥶", CHILLS: "🥶",
DIRRHEA: "💩", DIARRHEA: "💩",
"JOINT PAIN": "🦵", "JOINT PAIN": "🦵",
FEVER: "🤒", FEVER: "🤒",
VOMITING: "🤮", VOMITING: "🤮",
@@ -227,6 +270,8 @@ class App extends React.Component {
gid: "", gid: "",
showInputPopup: false, showInputPopup: false,
answers: {}, answers: {},
fetchedResult : {},
stats: {},
loading: true, loading: true,
}; };
} }
@@ -276,11 +321,7 @@ class App extends React.Component {
const { data } = v; const { data } = v;
if (!!data) { if (!!data) {
const { name, age, dose } = data; const { name, age, dose } = data;
this.setState({ name, age, dose, showInputPopup: true }); this.setState({ name, age, dose, showInputPopup: true, fetchedResult: data, answers: data, loading: false });
delete data.name;
delete data.age;
delete data.dose;
this.setState({ answers: data, loading: false });
const inputPopupSubmit = document.querySelector( const inputPopupSubmit = document.querySelector(
".input-popup-submit" ".input-popup-submit"
); );
@@ -302,7 +343,13 @@ class App extends React.Component {
} else { } else {
this.setState({ loading: false }); 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 = () => { shouldShowAgeGroup = () => {
const { name, dose } = this.state; const { name, dose } = this.state;
switch (name) { switch (name) {
@@ -373,25 +420,55 @@ class App extends React.Component {
document.querySelector("#share-result").innerHTML = "Share ↗"; document.querySelector("#share-result").innerHTML = "Share ↗";
}, 1500); }, 1500);
}; };
updateAnswers = (symptom) => { updateAnswers = (symptom, dose) => {
const { answers } = this.state; const { answers } = this.state;
const newAnswers = Object.assign({}, answers); const newAnswers = Object.assign({}, answers);
if (answers[symptom]) { if (!!dose) {
newAnswers[symptom] = !answers[symptom]; if (!newAnswers[dose]) {
newAnswers[dose] = {};
}
if (newAnswers[dose][symptom]) {
newAnswers[dose][symptom] = !newAnswers[dose][symptom];
} else {
newAnswers[dose][symptom] = true;
}
} else { } else {
newAnswers[symptom] = true; if (newAnswers[symptom]) {
newAnswers[symptom] = !newAnswers[symptom];
} else {
newAnswers[symptom] = true;
}
} }
this.setState({ answers: newAnswers }); this.setState({ answers: newAnswers });
}; };
submitAnswer = () => { submitAnswer = () => {
document.querySelector(".input-popup-submit").innerHTML = "SENDING..."; document.querySelector(".input-popup-submit").innerHTML = "SENDING...";
const { name, dose, age, answers, gid } = this.state; const { name, dose, age, answers, gid } = this.state;
const result = Object.assign({}, this.getResults()); const result = this.getResults();
const symptoms = {}; const symptoms = {};
Object.keys(result).forEach((symptom) => { delete answers.name;
symptoms[symptom] = false; delete answers.dose;
}); delete answers.age;
const submitAnswers = Object.assign({}, symptoms, answers, { 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, name,
dose, dose,
age, age,
@@ -399,7 +476,12 @@ class App extends React.Component {
}); });
saveResult(submitAnswers) saveResult(submitAnswers)
.then(() => { .then(() => {
delete submitAnswers.gid;
this.setState({ fetchedResult : submitAnswers });
const inputPopupSubmit = document.querySelector(".input-popup-submit"); const inputPopupSubmit = document.querySelector(".input-popup-submit");
if (inputPopupSubmit.classList.contains("errored")) {
inputPopupSubmit.classList.remove("errored");
}
if (!inputPopupSubmit.classList.contains("submitted")) { if (!inputPopupSubmit.classList.contains("submitted")) {
inputPopupSubmit.classList.add("submitted"); inputPopupSubmit.classList.add("submitted");
} }
@@ -408,10 +490,57 @@ class App extends React.Component {
inputPopupSubmit.innerHTML = "UPDATE ANSWER"; inputPopupSubmit.innerHTML = "UPDATE ANSWER";
}, 1500); }, 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 <div className="question-container" key={`answer-dose-${answerDose}`}>
{!!answerDose ? (
<h6 className="overline" data-font="sans">
{answerDose}
</h6>
) : null}
<p className="question" data-font="sans-italic">
{ ((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)" }
</p>
<div className="answers-container">
{Object.keys(result).map((symptom) => (
<button
key={`ans-${answerDose}-${symptom}`}
data-font="sans-bold"
className={`answer${
!!answerDose
? (answers[answerDose] && answers[answerDose][symptom])
? " selected"
: ""
: answers[symptom]
? " selected"
: ""
}`}
onClick={() => this.updateAnswers(symptom, answerDose)}
>
{symptom}
</button>
))}
</div>
</div>;
}; };
render() { render() {
const { name, dose, age, showInputPopup, answers, loading } = this.state; const { name, dose, age, showInputPopup, loading, fetchedResult } = this.state;
let result = {}; let result = {};
let resultAtSite = {}; let resultAtSite = {};
if (this.shouldShowResults()) { if (this.shouldShowResults()) {
@@ -463,7 +592,7 @@ class App extends React.Component {
name: v, name: v,
dose: "", dose: "",
age: "", age: "",
answers: {}, answers: v === fetchedResult.name ? fetchedResult : {},
showInputPopup: false, showInputPopup: false,
}) })
} }
@@ -478,7 +607,7 @@ class App extends React.Component {
</div> </div>
</div> </div>
) : null} ) : null}
{name && (name === VAX_NAMES.PB || name === VAX_NAMES.MD) ? ( {this.shouldShowDoses() ? (
<div className="question-container"> <div className="question-container">
<p className="question" data-font="sans-italic"> <p className="question" data-font="sans-italic">
Select Dose Select Dose
@@ -491,7 +620,6 @@ class App extends React.Component {
this.setState({ this.setState({
dose: v, dose: v,
age: "", age: "",
answers: {},
showInputPopup: false, showInputPopup: false,
}) })
} }
@@ -519,7 +647,6 @@ class App extends React.Component {
onClick={() => onClick={() =>
this.setState({ this.setState({
age: v, age: v,
answers: {},
showInputPopup: false, showInputPopup: false,
}) })
} }
@@ -553,6 +680,27 @@ class App extends React.Component {
</div> </div>
) : null} ) : null}
{this.shouldShowResults() ? (
<div className="tip-container">
<div className="tip-icon">&#9432;</div>
<div className="tip">
<h6 style={{marginBottom: '1.2rem'}} className="overline" data-font="sans">
NET EFFICACY
</h6>
<h6 className="overline" data-font="sans-italic">
{name} was effective for
</h6>
<h3 className="subheader" data-font="sans-bold">
{VAX_EFFICACY[name][age]}%
</h3>
<h6 className="overline" data-font="sans-italic">
of people who took it, in the {age} age group
</h6>
</div>
</div>
) : null
}
{this.shouldShowResults() ? ( {this.shouldShowResults() ? (
<div className="symptom-container"> <div className="symptom-container">
{Object.keys(resultAtSite).map((symptom) => ( {Object.keys(resultAtSite).map((symptom) => (
@@ -579,6 +727,7 @@ class App extends React.Component {
))} ))}
</div> </div>
) : null} ) : null}
</main> </main>
{this.shouldShowResults() ? ( {this.shouldShowResults() ? (
<div className={`input-popup${showInputPopup ? " expanded" : ""}`}> <div className={`input-popup${showInputPopup ? " expanded" : ""}`}>
@@ -606,41 +755,17 @@ class App extends React.Component {
</div> </div>
<div className="input-popup-body"> <div className="input-popup-body">
<p className="question" data-font="sans"> <p className="question" data-font="sans">
I am in age group <strong>{age}</strong> and got I am in age group <strong>{age}</strong> and got the
<strong>{!!dose ? ` ${dose} ` : ""}</strong>
{!!dose ? `of` : ""} the
<strong> {name}</strong> vaccine. I experienced: <strong> {name}</strong> vaccine. I experienced:
</p> </p>
<div className="question-container"> {this.shouldShowDoses()
<p className="question" data-font="sans-italic"> ? Object.keys(VAX_DOSES).map((dose) =>
(Select all side effects that you experienced) this.getAnswerOptions(VAX_DOSES[dose])
</p> )
<div className="answers-container"> : this.getAnswerOptions()}
{Object.keys(resultAtSite).map((symptom) => (
<button
key={`ans-${symptom}`}
data-font="sans-bold"
className={`answer${answers[symptom] ? " selected" : ""}`}
onClick={() => this.updateAnswers(symptom)}
>
{symptom}
</button>
))}
{Object.keys(result).map((symptom) => (
<button
key={`ans-${symptom}`}
data-font="sans-bold"
className={`answer${answers[symptom] ? " selected" : ""}`}
onClick={() => this.updateAnswers(symptom)}
>
{symptom}
</button>
))}
</div>
</div>
<div className="question-container"> <div className="question-container">
<p className="question" data-font="sans"> <p className="question" data-font="sans">
You can comeback and update your experience later! You can comeback and update your experience at any time!
</p> </p>
<p className="question" data-font="sans-italic"> <p className="question" data-font="sans-italic">
(Just use the same device) (Just use the same device)