Compare commits

..
10 Commits
Author SHA1 Message Date
ayushya 329265aabb v1.1.1/release styling fixes, add links 2021-04-22 23:47:22 -04:00
ayushya 87e4916db6 v1.1.1/release
fix stylings
2021-04-22 23:06:11 -04:00
ayushya 21b65cadea v1.1.0/release 2021-04-22 17:04:51 -04:00
ayushya 06b18c14cd v1.1.0/release
-fix dose logic
-fix popup icon style
2021-04-22 12:11:23 -04:00
ayushya 732bd6887c v1.1.0/release functions
(fix lint, build, ignore script)
2021-04-21 23:10:39 -04:00
ayushya abb00ef27b v1.1.0/release 2021-04-21 13:04:00 -04:00
ayushya 36674febe4 V1.1.0/add compat functions, script to update, data source select 2021-04-21 00:00:42 -04:00
ayushya 50f1f53669 v1.1.0/remove lib and secret 2021-04-20 21:31:38 -04:00
ayushya 5c39e8a628 v1.1.0/number values and efficacy 2021-04-20 20:16:21 -04:00
ayushya 2eabb6807f v1.1.0/server side second dose 2021-04-19 22:41:34 -04:00
17 changed files with 1271 additions and 549 deletions
+5 -3
View File
@@ -11,6 +11,10 @@
# production # production
*/build */build
*/lib
*/exportDB
firebaseSecret.json
# misc # misc
.DS_Store .DS_Store
@@ -23,6 +27,4 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
*.cache *.cache
*.log */*.log*
functions/lib/
functions/src/backup.json
+7 -1
View File
@@ -50,16 +50,22 @@
"npm --prefix \"$RESOURCE_DIR\" run lint", "npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build" "npm --prefix \"$RESOURCE_DIR\" run build"
], ],
"source": "functions" "source": "functions",
"ignore": [
"functions/scripts"
]
}, },
"emulators": { "emulators": {
"functions": { "functions": {
"host": "192.168.86.46",
"port": 5001 "port": 5001
}, },
"firestore": { "firestore": {
"host": "192.168.86.46",
"port": 8080 "port": 8080
}, },
"hosting": { "hosting": {
"host": "192.168.86.46",
"port": 5000 "port": 5000
}, },
"ui": { "ui": {
+1
View File
@@ -20,6 +20,7 @@ module.exports = {
}, },
ignorePatterns: [ ignorePatterns: [
"/lib/**/*", // Ignore built files. "/lib/**/*", // Ignore built files.
"/scripts/**/*", // Ignore scripts
], ],
plugins: ["@typescript-eslint", "import"], plugins: ["@typescript-eslint", "import"],
rules: { rules: {
-12
View File
@@ -1,12 +0,0 @@
{
"type": "service_account",
"project_id": "vaxandme",
"private_key_id": "878a8ed429f84ef5fb42555a34bd71d53ba612b2",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDnYRdGS8aHi8aY\nDmcIPlPVN0HSDMjg046n1LmR1f1eRS/28m+C94pP6Pn2WvzRJIGK/8XKXARfAiPD\noVfUaXUAPD0DVbgHMn6/1pvCCF0wwlbXZNsIjDg9vLQiGvnsNcgoDXOhfliqw0ca\nTx3oUdgPCzOzkWw4wGMZaJrNK4w0jwuffNgA746jCG3RX9vo/m+aucerYqPPlSfs\nAjJpmpXXCwdHNx0EnEpGQc3bdnbN53hikEJ+J2xCESqaEmTryi1vXDv+i+YCrZn6\nLD+P1etLvtoU0pKMMCcVLk7VmCPMT8F2RB0L21aFmGLuy8fpBDMIYXtjMp+FbKBf\nAne0zKX1AgMBAAECggEAcn4nQ7gsWZdPYp/B2SDqX9fTgKql3iahcSgA2YrPsM3A\nscx+ZIq0TN2GjyKv9pe/aHYvO/azjQXkh6H3cZrrc/3a3SMWCK2ybaA2+9kisAWC\nblwwTHcCP354jmXV5M4qKLObU77PBlTSAtFChdq19J/A6ircJ+r/Pqv16e+SFdD/\n4ie64DR5Uxj9vUN8WFrfDfapwxwnGLgJCOrS7fecC7xDzi5iLR9mZD1ak2a8B+B6\nwNrzGafVz285B0fVtYbXiEGNElbPBd/hvioK0zsHdYxZYmiaZvyWqABejRk6jW6R\n6OTy7fCk6h7LG23+uh9Ruh8jKEE9nX6g/vYaKOqv0wKBgQD6nKvshaCQD4G5s5gK\nwQFYUFJbmMd547BKtzaFXKzUcldZaL9da4AhINoxu70uCl/nDJmTACEcifU/juiO\nM0XsZ0kufoomW2DyxVKBshBoF/OJoEUngDiCDdJFMUsa6zeiZHVi9uFmTY4c4nf8\nozJxxpcbmQRXEdv9kafBEw83DwKBgQDsWpC/t7QCqCytXWqnkVM5QIvrIxXqIHGR\nzHkS6bmUYv2IF6ynzCgAbnRNDh0X2PIvBk6gJDfyC9lN9LvLurn/CVpEK85Q6hRy\nVO4BegYEaFOfDueiAB1RXvGbEvchZp7+DpN2tgi2lRbuEDPWcySFb7YVkyHo/aVO\nfDHjbSmyuwKBgQDB7ORY1Rpm/6jZozB++dTpmtXwDSwmlNsBJf7peco/Zu9Sv2x9\nhMcUW5CcNXNxohON/cEE8G+u3s5JTXOFPgXYpiAJ+185yTIfCjQLKlfpxMbuc3ev\n1ldjxAA3DKrafLODVQtE9ld6y36xuUkx1LMfOZYKhADIw4ghEknmbLI0XwKBgQCJ\njHtI16QPF8hIbGExe8WvP/0trpGGW7M8Yn7jntm9nY5msUBttsZg2ecdIOWVh1AK\nReVkFS/p0TN5xr+z5MtxvQOnQMclF9UmZH7OpTDzR7PCkW4EPYTSYirBUNw5t/s3\nZ0wyfeW8PC/IA+3nE8NhVI3nKGVHrrWRY321GUZ3GQKBgQDzLC96HVGbIQgfGRhW\nu1mcgNKFdQcy8Yu8ffttnuIIPGljwWwLpfbROD+Fe0CtqJ4XXL0Js7XfoPYoxMMH\nc16GysIG9/TYioBhBVfyf94eekylVjdYO0aY7d5Zjw1EuO/LVdPMlSZuosv3q10n\nyB55dJWP6pkR6tLqeNCuJ8+9qQ==\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-meacv@vaxandme.iam.gserviceaccount.com",
"client_id": "110649794888712776390",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-meacv%40vaxandme.iam.gserviceaccount.com"
}
-184
View File
@@ -1,184 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPastResult = exports.saveResult = void 0;
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const firebaseSecret = require("./firebaseSecret.json");
const params = {
type: firebaseSecret.type,
projectId: firebaseSecret.project_id,
privateKeyId: firebaseSecret.private_key_id,
privateKey: firebaseSecret.private_key,
clientEmail: firebaseSecret.client_email,
clientId: firebaseSecret.client_id,
authUri: firebaseSecret.auth_uri,
tokenUri: firebaseSecret.token_uri,
authProviderX509CertUrl: firebaseSecret.auth_provider_x509_cert_url,
clientC509CertUrl: firebaseSecret.client_x509_cert_url,
};
admin.initializeApp({
credential: admin.credential.cert(params),
});
const serializeGaId = (_gaid) => {
if (_gaid) {
return _gaid.split(".").join("|");
}
return "";
};
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
.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 = {};
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);
}
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);
}
}
});
exports.getPastResult = functions.https.onCall(async (data, context) => {
if (!!data && !!context) {
const { gid } = data;
const existingData = 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"];
}
return existingData;
}
}
return undefined;
});
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
+2 -3
View File
@@ -1,10 +1,9 @@
{ {
"name": "functions", "name": "vaxandme-api",
"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 build && firebase emulators:start --import ./exportDB",
"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",
"deploy": "firebase deploy --only functions", "deploy": "firebase deploy --only functions",
+185
View File
@@ -0,0 +1,185 @@
const admin = require("firebase-admin");
const firebaseSecret = require("../src/firebaseSecret.json");
admin.initializeApp({
credential: admin.credential.cert(firebaseSecret),
});
const VAX_NAMES = {
PB: "Pfizer-BioNTech",
MD: "Moderna",
JJ: "J&J Janssen",
};
const VAX_DOSES = {
ONE: "Dose 1",
TWO: "Dose 2",
};
const VAX_AGES = {
"Pfizer-BioNTech": {
YOUNGER: "18 - 55",
OLDER: "56 & Older",
},
"Moderna": {
YOUNGER: "18 - 64",
OLDER: "65 & Older",
},
"J&J Janssen": {
YOUNGER: "18 - 59",
OLDER: "60 & Older",
},
};
// PFIZER COUNTS
admin.firestore().collection("results")
.where("name", "==", VAX_NAMES.PB)
.where("dose", "in", [VAX_DOSES.ONE, VAX_DOSES.TWO])
.where("age", "==", VAX_AGES[VAX_NAMES.PB].YOUNGER)
.get()
.then((docs) => {
console.log("PB_ONE_YOUNGER", docs.docs.length);
admin.firestore()
.collection("stats")
.doc(VAX_NAMES.PB)
.update({
[`${VAX_DOSES.ONE}.${VAX_AGES[VAX_NAMES.PB].YOUNGER}.count`]:
docs.docs.length,
});
});
admin.firestore().collection("results")
.where("name", "==", VAX_NAMES.PB)
.where("dose", "==", VAX_DOSES.TWO)
.where("age", "==", VAX_AGES[VAX_NAMES.PB].YOUNGER)
.get()
.then((docs) => {
console.log("PB_TWO_YOUNGER", docs.docs.length);
admin.firestore()
.collection("stats")
.doc(VAX_NAMES.PB)
.update({
[`${VAX_DOSES.TWO}.${VAX_AGES[VAX_NAMES.PB].YOUNGER}.count`]:
docs.docs.length,
});
});
admin.firestore().collection("results")
.where("name", "==", VAX_NAMES.PB)
.where("dose", "in", [VAX_DOSES.ONE, VAX_DOSES.TWO])
.where("age", "==", VAX_AGES[VAX_NAMES.PB].OLDER)
.get()
.then((docs) => {
console.log("PB_ONE_OLDER", docs.docs.length);
admin.firestore()
.collection("stats")
.doc(VAX_NAMES.PB)
.update({
[`${VAX_DOSES.ONE}.${VAX_AGES[VAX_NAMES.PB].OLDER}.count`]:
docs.docs.length,
});
});
admin.firestore().collection("results")
.where("name", "==", VAX_NAMES.PB)
.where("dose", "==", VAX_DOSES.TWO)
.where("age", "==", VAX_AGES[VAX_NAMES.PB].OLDER)
.get()
.then((docs) => {
console.log("PB_TWO_OLDER", docs.docs.length);
admin.firestore()
.collection("stats")
.doc(VAX_NAMES.PB)
.update({
[`${VAX_DOSES.TWO}.${VAX_AGES[VAX_NAMES.PB].OLDER}.count`]:
docs.docs.length,
});
});
// MODERNA COUNTS
admin.firestore().collection("results")
.where("name", "==", VAX_NAMES.MD)
.where("dose", "in", [VAX_DOSES.ONE, VAX_DOSES.TWO])
.where("age", "==", VAX_AGES[VAX_NAMES.MD].YOUNGER)
.get()
.then((docs) => {
console.log("MD_ONE_YOUNGER", docs.docs.length);
admin.firestore()
.collection("stats")
.doc(VAX_NAMES.MD)
.update({
[`${VAX_DOSES.ONE}.${VAX_AGES[VAX_NAMES.MD].YOUNGER}.count`]:
docs.docs.length,
});
});
admin.firestore().collection("results")
.where("name", "==", VAX_NAMES.MD)
.where("dose", "==", VAX_DOSES.TWO)
.where("age", "==", VAX_AGES[VAX_NAMES.MD].YOUNGER)
.get()
.then((docs) => {
console.log("MD_TWO_YOUNGER", docs.docs.length);
admin.firestore()
.collection("stats")
.doc(VAX_NAMES.MD)
.update({
[`${VAX_DOSES.TWO}.${VAX_AGES[VAX_NAMES.MD].YOUNGER}.count`]:
docs.docs.length,
});
});
admin.firestore().collection("results")
.where("name", "==", VAX_NAMES.MD)
.where("dose", "in", [VAX_DOSES.ONE, VAX_DOSES.TWO])
.where("age", "==", VAX_AGES[VAX_NAMES.MD].OLDER)
.get()
.then((docs) => {
console.log("MD_ONE_OLDER", docs.docs.length);
admin.firestore()
.collection("stats")
.doc(VAX_NAMES.MD)
.update({
[`${VAX_DOSES.ONE}.${VAX_AGES[VAX_NAMES.MD].OLDER}.count`]:
docs.docs.length,
});
});
admin.firestore().collection("results")
.where("name", "==", VAX_NAMES.MD)
.where("dose", "==", VAX_DOSES.TWO)
.where("age", "==", VAX_AGES[VAX_NAMES.MD].OLDER)
.get()
.then((docs) => {
console.log("MD_TWO_OLDER", docs.docs.length);
admin.firestore()
.collection("stats")
.doc(VAX_NAMES.MD)
.update({
[`${VAX_DOSES.TWO}.${VAX_AGES[VAX_NAMES.MD].OLDER}.count`]:
docs.docs.length,
});
});
// J&J COUNTS
admin.firestore().collection("results")
.where("name", "==", VAX_NAMES.JJ)
.where("age", "==", VAX_AGES[VAX_NAMES.JJ].YOUNGER)
.get()
.then((docs) => {
console.log("JJ_YOUNGER", docs.docs.length);
admin.firestore()
.collection("stats")
.doc(VAX_NAMES.JJ)
.update({
[`${VAX_AGES[VAX_NAMES.JJ].YOUNGER}.count`]:
docs.docs.length,
});
});
admin.firestore().collection("results")
.where("name", "==", VAX_NAMES.JJ)
.where("age", "==", VAX_AGES[VAX_NAMES.JJ].OLDER)
.get()
.then((docs) => {
console.log("JJ_OLDER", docs.docs.length);
admin.firestore()
.collection("stats")
.doc(VAX_NAMES.JJ)
.update({
[`${VAX_AGES[VAX_NAMES.JJ].OLDER}.count`]:
docs.docs.length,
});
});
-12
View File
@@ -1,12 +0,0 @@
{
"type": "service_account",
"project_id": "vaxandme",
"private_key_id": "878a8ed429f84ef5fb42555a34bd71d53ba612b2",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDnYRdGS8aHi8aY\nDmcIPlPVN0HSDMjg046n1LmR1f1eRS/28m+C94pP6Pn2WvzRJIGK/8XKXARfAiPD\noVfUaXUAPD0DVbgHMn6/1pvCCF0wwlbXZNsIjDg9vLQiGvnsNcgoDXOhfliqw0ca\nTx3oUdgPCzOzkWw4wGMZaJrNK4w0jwuffNgA746jCG3RX9vo/m+aucerYqPPlSfs\nAjJpmpXXCwdHNx0EnEpGQc3bdnbN53hikEJ+J2xCESqaEmTryi1vXDv+i+YCrZn6\nLD+P1etLvtoU0pKMMCcVLk7VmCPMT8F2RB0L21aFmGLuy8fpBDMIYXtjMp+FbKBf\nAne0zKX1AgMBAAECggEAcn4nQ7gsWZdPYp/B2SDqX9fTgKql3iahcSgA2YrPsM3A\nscx+ZIq0TN2GjyKv9pe/aHYvO/azjQXkh6H3cZrrc/3a3SMWCK2ybaA2+9kisAWC\nblwwTHcCP354jmXV5M4qKLObU77PBlTSAtFChdq19J/A6ircJ+r/Pqv16e+SFdD/\n4ie64DR5Uxj9vUN8WFrfDfapwxwnGLgJCOrS7fecC7xDzi5iLR9mZD1ak2a8B+B6\nwNrzGafVz285B0fVtYbXiEGNElbPBd/hvioK0zsHdYxZYmiaZvyWqABejRk6jW6R\n6OTy7fCk6h7LG23+uh9Ruh8jKEE9nX6g/vYaKOqv0wKBgQD6nKvshaCQD4G5s5gK\nwQFYUFJbmMd547BKtzaFXKzUcldZaL9da4AhINoxu70uCl/nDJmTACEcifU/juiO\nM0XsZ0kufoomW2DyxVKBshBoF/OJoEUngDiCDdJFMUsa6zeiZHVi9uFmTY4c4nf8\nozJxxpcbmQRXEdv9kafBEw83DwKBgQDsWpC/t7QCqCytXWqnkVM5QIvrIxXqIHGR\nzHkS6bmUYv2IF6ynzCgAbnRNDh0X2PIvBk6gJDfyC9lN9LvLurn/CVpEK85Q6hRy\nVO4BegYEaFOfDueiAB1RXvGbEvchZp7+DpN2tgi2lRbuEDPWcySFb7YVkyHo/aVO\nfDHjbSmyuwKBgQDB7ORY1Rpm/6jZozB++dTpmtXwDSwmlNsBJf7peco/Zu9Sv2x9\nhMcUW5CcNXNxohON/cEE8G+u3s5JTXOFPgXYpiAJ+185yTIfCjQLKlfpxMbuc3ev\n1ldjxAA3DKrafLODVQtE9ld6y36xuUkx1LMfOZYKhADIw4ghEknmbLI0XwKBgQCJ\njHtI16QPF8hIbGExe8WvP/0trpGGW7M8Yn7jntm9nY5msUBttsZg2ecdIOWVh1AK\nReVkFS/p0TN5xr+z5MtxvQOnQMclF9UmZH7OpTDzR7PCkW4EPYTSYirBUNw5t/s3\nZ0wyfeW8PC/IA+3nE8NhVI3nKGVHrrWRY321GUZ3GQKBgQDzLC96HVGbIQgfGRhW\nu1mcgNKFdQcy8Yu8ffttnuIIPGljwWwLpfbROD+Fe0CtqJ4XXL0Js7XfoPYoxMMH\nc16GysIG9/TYioBhBVfyf94eekylVjdYO0aY7d5Zjw1EuO/LVdPMlSZuosv3q10n\nyB55dJWP6pkR6tLqeNCuJ8+9qQ==\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-meacv@vaxandme.iam.gserviceaccount.com",
"client_id": "110649794888712776390",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-meacv%40vaxandme.iam.gserviceaccount.com"
}
+383 -92
View File
@@ -19,53 +19,328 @@ admin.initializeApp({
credential: admin.credential.cert(params), credential: admin.credential.cert(params),
}); });
interface RequestData { const VAX_NAMES: Record<string, string> = {
gid: string; PB: "Pfizer-BioNTech",
} MD: "Moderna",
JJ: "J&J Janssen",
};
interface SaveResultRequestData extends RequestData { const VAX_DOSES: Record<string, string> = {
name: string; ONE: "Dose 1",
age: string; TWO: "Dose 2",
dose: string; };
[x: string]: string;
}
const serializeGaId = (_gaid: string): string => { const VAX_AGES: Record<string, Record<string, string>> = {
"Pfizer-BioNTech": {
YOUNGER: "18 - 55",
OLDER: "56 & Older",
},
"Moderna": {
YOUNGER: "18 - 64",
OLDER: "65 & Older",
},
"J&J Janssen": {
YOUNGER: "18 - 59",
OLDER: "60 & Older",
},
};
type SaveResultRequestData = {
name?: string;
age?: string;
dose?: string;
gid?: string;
} & {
[x: string]: boolean;
} & {
[y: string]: Record<string, boolean>;
};
const serializeGaId = (_gaid: string | undefined): string => {
if (_gaid) { if (_gaid) {
return _gaid.split(".").join("|"); return _gaid.split(".").join("|");
} }
return ""; return "";
}; };
if (process.env.NODE_ENV && process.env.NODE_ENV === "dev") { const addResultToStats = async (data: SaveResultRequestData) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires const cleanData: SaveResultRequestData = Object.assign({}, data);
const backup = require("./backup.json"); delete cleanData.name;
const BackupResults : Record<string, Record<string, delete cleanData.dose;
boolean | Record<string, unknown> | string>> = delete cleanData.age;
backup.results; 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) {
if (data.dose === VAX_DOSES.ONE) {
updateDoc[
`${VAX_DOSES.ONE}.${data.age}.count`
] = admin.firestore.FieldValue.increment(1);
} else if (data.dose === VAX_DOSES.TWO) {
updateDoc[
`${VAX_DOSES.ONE}.${data.age}.count`
] = admin.firestore.FieldValue.increment(1);
updateDoc[
`${VAX_DOSES.TWO}.${data.age}.count`
] = admin.firestore.FieldValue.increment(1);
}
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 {
updateDoc[`${data.age}.count`] = admin.firestore.FieldValue.increment(1);
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);
});
}
if (data.name) {
admin.firestore().collection("stats").doc(data.name).update(updateDoc);
}
};
const BackupStats : Record<string, const removeResultFromStats = async (data: FirebaseFirestore.DocumentData) => {
Record<string, unknown> | const cleanData: SaveResultRequestData = Object.assign({}, data);
Record<string, number> | delete cleanData.name;
Record<string, Record<string, number> | delete cleanData.dose;
Record<string, Record<string, Record<string, number>>>>> = delete cleanData.age;
backup.stats; 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);
};
Object.keys(BackupResults).forEach((key : string) => { /**
admin.firestore().collection("results").doc(key).set(BackupResults[key]); * FUNCTIONS FROM V1.1.0 TO CURRENT
*/
export const getStatsV1 = 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) => {
if (vaxResult.name === VAX_NAMES.PB || vaxResult.name === VAX_NAMES.MD) {
Object.keys(VAX_DOSES).forEach((doseKey) => {
const dose: string = VAX_DOSES[doseKey];
Object.keys(VAX_AGES[vaxResult.name]).forEach((ageKey) => {
const age: string = VAX_AGES[vaxResult.name][ageKey];
if (vaxResult.result[dose][age]["NAUSEA / VOMITING"] >= 0) {
if (vaxResult.result[dose][age]["NAUSEA OR VOMITING"] >= 0) {
vaxResult.result[dose][age]["NAUSEA OR VOMITING"] +=
vaxResult.result[dose][age]["NAUSEA / VOMITING"];
} else {
vaxResult.result[dose][age]["NAUSEA OR VOMITING"] =
vaxResult.result[dose][age]["NAUSEA / VOMITING"];
}
}
if (vaxResult.result[dose][age]["DIRRHEA"] >= 0) {
if (vaxResult.result[dose][age]["DIARRHEA"] >= 0) {
vaxResult.result[dose][age]["DIARRHEA"] +=
vaxResult.result[dose][age]["DIRRHEA"];
} else {
vaxResult.result[dose][age]["DIARRHEA"] =
vaxResult.result[dose][age]["DIRRHEA"];
}
}
});
});
} else {
Object.keys(VAX_AGES[vaxResult.name]).forEach((ageKey) => {
const age: string = VAX_AGES[vaxResult.name][ageKey];
if (vaxResult.result[age]["NAUSEA / VOMITING"] >= 0) {
if (vaxResult.result[age]["NAUSEA OR VOMITING"] >= 0) {
vaxResult.result[age]["NAUSEA OR VOMITING"] +=
vaxResult.result[age]["NAUSEA / VOMITING"];
} else {
vaxResult.result[age]["NAUSEA OR VOMITING"] =
vaxResult.result[age]["NAUSEA / VOMITING"];
}
}
if (vaxResult.result[age]["DIRRHEA"] >= 0) {
if (vaxResult.result[age]["DIARRHEA"] >= 0) {
vaxResult.result[age]["DIARRHEA"] +=
vaxResult.result[age]["DIRRHEA"];
} else {
vaxResult.result[age]["DIARRHEA"] =
vaxResult.result[age]["DIRRHEA"];
}
}
});
}
returnData[vaxResult.name] = {};
returnData[vaxResult.name] = vaxResult.result;
}); });
Object.keys(BackupStats).forEach((key : string) => { return returnData;
admin.firestore().collection("stats").doc(key).set(BackupStats[key]); });
});
}
export const saveResultV1 = functions.https.onCall(
async (data: SaveResultRequestData, context) => {
if (!!data && !!context) {
if (!!data.name && !!data.age && !!data.gid) {
const {gid} = data;
// Get existing result
const existingResult = await admin
.firestore()
.collection("results")
.doc(serializeGaId(gid))
.get()
.then((docSnap) => docSnap.data());
// Update user result
const cleanData: SaveResultRequestData = Object.assign({}, data);
delete cleanData.gid;
if (cleanData.dose && cleanData.dose === VAX_DOSES.ONE) {
Object.keys(cleanData[VAX_DOSES.TWO]).forEach((symptom) => {
if (cleanData[VAX_DOSES.TWO][symptom] === true) {
cleanData.dose = VAX_DOSES.TWO;
data.dose = VAX_DOSES.TWO;
}
});
}
await admin
.firestore()
.collection("results")
.doc(serializeGaId(gid))
.set(cleanData);
// No existing result
if (existingResult === undefined) {
await addResultToStats(data);
} else {
await removeResultFromStats(existingResult);
await addResultToStats(data);
}
}
}
}
);
export const getPastResultV1 = 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<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[dose]["NAUSEA OR VOMITING"] =
returnData[dose]["NAUSEA / VOMITING"];
}
if (returnData[dose]["DIRRHEA"]) {
returnData[dose]["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 undefined;
}
);
/**
* FUNCTIONS FROM V1.0.0 TO V1.0.1
*/
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) {
const {gid} = data; const {gid} = data;
const docId: string = serializeGaId(gid); const docId: string = serializeGaId(gid);
const docData: Record<string, string | boolean> = const docData: Record<string, string | boolean> =
Object.assign({}, data); Object.assign({}, data);
delete docData.gid; delete docData.gid;
const existingData = await admin const existingData = await admin
.firestore() .firestore()
@@ -73,60 +348,70 @@ export const saveResult = functions.https.onCall(
.doc(docId) .doc(docId)
.get() .get()
.then((doc) => doc.data()); .then((doc) => doc.data());
if (existingData !== undefined && existingData !== docData && if (
existingData.name === docData.name) { existingData !== undefined &&
// DATA EXISTS FOR USER existingData !== docData &&
// BUT DIFF DATA SENT FOR UPDATE existingData.name === docData.name
) {
// DATA EXISTS FOR USER
// BUT DIFF DATA SENT FOR UPDATE
await admin.firestore().collection("results").doc(docId).set(docData); await admin.firestore().collection("results").doc(docId).set(docData);
delete docData.age; delete docData.age;
delete docData.name; delete docData.name;
delete docData.dose; delete docData.dose;
const updateDoc : Record<string, admin.firestore.FieldValue> = {}; const updateDoc: Record<string, admin.firestore.FieldValue> = {};
Object.keys(docData).forEach((symptom : string) => { Object.keys(docData).forEach((symptom: string) => {
// IF WAS TRUE, NOW FALSE = -1 // IF WAS TRUE, NOW FALSE = -1
// IF WAS TRUE, NOW TRUE = 0 // IF WAS TRUE, NOW TRUE = 0
// IF WAS FALSE, NOW TRUE = +1 // IF WAS FALSE, NOW TRUE = +1
// IF WAS FALSE, NOW FALSE = 0 // IF WAS FALSE, NOW FALSE = 0
let incrementValue = 0; let incrementValue = 0;
if (existingData[symptom] === true && docData[symptom] === false) { if (existingData[symptom] === true && docData[symptom] === false) {
incrementValue = -1; incrementValue = -1;
} else if (existingData[symptom] === false && } else if (
docData[symptom] === true) { existingData[symptom] === false &&
docData[symptom] === true
) {
incrementValue = 1; incrementValue = 1;
} }
if (data.dose) { if (data.dose) {
updateDoc[ updateDoc[
`${data.dose}.${data.age}.${symptom.replace("/", "OR")}`] = `${data.dose}.${data.age}.${symptom.replace("/", "OR")}`
admin.firestore.FieldValue.increment(incrementValue); ] = admin.firestore.FieldValue.increment(incrementValue);
} else { } else {
updateDoc[`${data.age}.${symptom.replace("/", "OR")}`] = updateDoc[
admin.firestore.FieldValue.increment(incrementValue); `${data.age}.${symptom.replace("/", "OR")}`
] = admin.firestore.FieldValue.increment(incrementValue);
} }
}); });
admin if (data.name) {
.firestore() admin
.collection("stats") .firestore()
.doc(data.name) .collection("stats")
.update(updateDoc); .doc(data.name)
} else if (existingData !== undefined && existingData !== docData && .update(updateDoc);
existingData.name !== docData.name) { }
// UNDO DATA FOR OLD VAX } else if (
// DECREMENT VALUES FOR EACH SYMPTOM ON OLD VAX existingData !== undefined &&
const undoDoc : Record<string, admin.firestore.FieldValue> = { 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), count: admin.firestore.FieldValue.increment(-1),
}; };
Object.keys(existingData).forEach((symptom : string) => { Object.keys(existingData).forEach((symptom: string) => {
if (symptom !== "name" && symptom !== "dose" && if (symptom !== "name" && symptom !== "dose" && symptom !== "age") {
symptom !== "age" ) {
if (existingData[symptom] === true) { if (existingData[symptom] === true) {
if (existingData.dose) { if (existingData.dose) {
undoDoc[ undoDoc[`${existingData.dose}.${existingData.age}.${
`${existingData.dose}.${existingData.age}.${ symptom.replace("/", "OR")}`
symptom.replace("/", "OR")}`] = ] = admin.firestore.FieldValue.increment(-1);
admin.firestore.FieldValue.increment(-1);
} else { } else {
undoDoc[`${existingData.age}.${symptom.replace("/", "OR")}`] = undoDoc[
admin.firestore.FieldValue.increment(-1); `${existingData.age}.${symptom.replace("/", "OR")}`
] = admin.firestore.FieldValue.increment(-1);
} }
} }
} }
@@ -143,64 +428,71 @@ export const saveResult = functions.https.onCall(
delete docData.name; delete docData.name;
delete docData.dose; delete docData.dose;
// INCREMENT TOTAL RESPONSE COUNT // INCREMENT TOTAL RESPONSE COUNT
const updateDoc : Record<string, admin.firestore.FieldValue> = { const updateDoc: Record<string, admin.firestore.FieldValue> = {
count: admin.firestore.FieldValue.increment(1), count: admin.firestore.FieldValue.increment(1),
}; };
// INCREMENT VALUES FOR EACH SYMPTOM // INCREMENT VALUES FOR EACH SYMPTOM
Object.keys(docData).forEach((symptom : string) => { Object.keys(docData).forEach((symptom: string) => {
if (docData[symptom] === true) { if (docData[symptom] === true) {
if (data.dose) { if (data.dose) {
updateDoc[ updateDoc[
`${data.dose}.${data.age}.${symptom.replace("/", "OR")}`] = `${data.dose}.${data.age}.${symptom.replace("/", "OR")}`
admin.firestore.FieldValue.increment(1); ] = admin.firestore.FieldValue.increment(1);
} else { } else {
updateDoc[`${data.age}.${symptom.replace("/", "OR")}`] = updateDoc[
admin.firestore.FieldValue.increment(1); `${data.age}.${symptom.replace("/", "OR")}`
] = admin.firestore.FieldValue.increment(1);
} }
} }
}); });
admin if (data.name) {
.firestore() admin
.collection("stats") .firestore()
.doc(data.name) .collection("stats")
.update(updateDoc); .doc(data.name)
.update(updateDoc);
}
} else if (existingData === undefined) { } else if (existingData === undefined) {
// DATA DOESN'T EXIST FOR USER // DATA DOESN'T EXIST FOR USER
// SAVE USER DATA // SAVE USER DATA
await admin.firestore().collection("results").doc(docId).set(docData); await admin.firestore().collection("results").doc(docId).set(docData);
delete docData.age; delete docData.age;
delete docData.name; delete docData.name;
delete docData.dose; delete docData.dose;
// INCREMENT TOTAL RESPONSE COUNT // INCREMENT TOTAL RESPONSE COUNT
const updateDoc : Record<string, admin.firestore.FieldValue> = { const updateDoc: Record<string, admin.firestore.FieldValue> = {
count: admin.firestore.FieldValue.increment(1), count: admin.firestore.FieldValue.increment(1),
}; };
// INCREMENT VALUES FOR EACH SYMPTOM // INCREMENT VALUES FOR EACH SYMPTOM
Object.keys(docData).forEach((symptom : string) => { Object.keys(docData).forEach((symptom: string) => {
if (docData[symptom] === true) { if (docData[symptom] === true) {
if (data.dose) { if (data.dose) {
updateDoc[ updateDoc[
`${data.dose}.${data.age}.${symptom.replace("/", "OR")}`] = `${data.dose}.${data.age}.${symptom.replace("/", "OR")}`
admin.firestore.FieldValue.increment(1); ] = admin.firestore.FieldValue.increment(1);
} else { } else {
updateDoc[`${data.age}.${symptom.replace("/", "OR")}`] = updateDoc[
admin.firestore.FieldValue.increment(1); `${data.age}.${symptom.replace("/", "OR")}`
] = admin.firestore.FieldValue.increment(1);
} }
} }
}); });
admin if (data.name) {
.firestore() admin
.collection("stats") .firestore()
.doc(data.name) .collection("stats")
.update(updateDoc); .doc(data.name)
.update(updateDoc);
}
} }
} }
}); }
);
export const getPastResult = functions.https.onCall(async (data, context) => { export const getPastResult = functions.https.onCall(async (data, context) => {
if (!!data && !!context) { if (!!data && !!context) {
const {gid} = data; const {gid} = data;
const existingData : admin.firestore.DocumentData | undefined = await admin const existingData: admin.firestore.DocumentData | undefined = await admin
.firestore() .firestore()
.collection("results") .collection("results")
.doc(serializeGaId(gid)) .doc(serializeGaId(gid))
@@ -208,8 +500,7 @@ export const getPastResult = functions.https.onCall(async (data, context) => {
.then((doc) => doc.data()); .then((doc) => doc.data());
if (existingData) { if (existingData) {
if (existingData["NAUSEA / VOMITING"]) { if (existingData["NAUSEA / VOMITING"]) {
existingData["NAUSEA OR VOMITING"] = existingData["NAUSEA OR VOMITING"] = existingData["NAUSEA / VOMITING"];
existingData["NAUSEA / VOMITING"];
} }
return existingData; return existingData;
} }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "vaxandme", "name": "vaxandme",
"version": "0.1.0", "version": "1.1.1",
"private": true, "private": true,
"dependencies": { "dependencies": {
"firebase": "^8.4.1", "firebase": "^8.4.1",
@@ -15,7 +15,7 @@
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"deploy": "firebase deploy --only hosting", "deploy": "firebase deploy --only hosting",
"serve" : "firebase serve --only hosting" "serve": "firebase serve --only hosting"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
+11 -4
View File
@@ -117,16 +117,23 @@
<div id="root"></div> <div id="root"></div>
<footer data-font="sans"> <footer data-font="sans">
<div class="link-container"> <div class="link-container">
<div> <div id="data-sources">
<h6 class="overline" data-font="sans">FDA Data Sources</h6> <h6 class="overline" data-font="sans">FDA Data Sources</h6>
<a href="https://www.fda.gov/media/144416/download">PFizer-BioNTech</a> <a id="fda-pfizer" href="https://www.fda.gov/media/144416/download">PFizer-BioNTech</a>
<a href="https://www.fda.gov/media/144637/download">Moderna</a> <a id="fda-moderna" href="https://www.fda.gov/media/144637/download">Moderna</a>
<a href="https://www.fda.gov/media/146304/download">J&J Janssen</a> <a id="fda-j&j" href="https://www.fda.gov/media/146304/download">J&J Janssen</a>
</div> </div>
<div> <div>
<h6 class="overline" data-font="sans">Links</h6> <h6 class="overline" data-font="sans">Links</h6>
<a href="%PUBLIC_URL%/vaxandme_chart.pdf">Download Chart</a> <a href="%PUBLIC_URL%/vaxandme_chart.pdf">Download Chart</a>
<a href="/">Home</a> <a href="/">Home</a>
<a
target="_blank"
href="https://www.gavi.org/vaccineswork/what-difference-between-efficacy-and-effectiveness"
rel="noreferrer"
>
What is efficacy? ↗
</a>
</div> </div>
<div> <div>
<h6 class="overline" data-font="sans">Legal</h6> <h6 class="overline" data-font="sans">Legal</h6>
+1 -1
View File
@@ -1,5 +1,5 @@
.input-popup.expanded { .input-popup.expanded {
height: 40vh; height: 50vh;
} }
.loading-bar-container { .loading-bar-container {
+93 -2
View File
@@ -8,6 +8,7 @@
html { html {
font-size: 10px; font-size: 10px;
scroll-behavior: smooth;
} }
body { body {
display: flex; display: flex;
@@ -295,9 +296,9 @@ footer .by {
} }
.input-popup-header-icon { .input-popup-header-icon {
cursor: pointer; cursor: pointer;
background-color: var(--color-secondary); background-color: var(--color-primary);
outline: none; outline: none;
color: var(--color-primary); color: var(--color-secondary);
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 10rem; border-radius: 10rem;
height: 5rem; height: 5rem;
@@ -331,6 +332,50 @@ 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;
padding: 2rem 0;
margin: 2rem 0;
border-radius: 1rem;
margin-bottom: 2rem;
}
.tip-icon {
font-size: 3rem;
}
.tip .overline {
font-size: 1.6rem;
}
.tip .subheader {
font-size: 2rem;
letter-spacing: 0.2rem;
margin-top: 0.4rem;
}
.tip .header {
font-size: 6rem;
letter-spacing: 0.5rem;
}
.tip-container .overline[data-button] {
margin-top: 1rem;
}
.tip-container .overline[data-button]:first-of-type {
margin-top: 2rem;
}
@keyframes loading { @keyframes loading {
0% { 0% {
@@ -355,6 +400,52 @@ footer .by {
animation: 2s ease-out 0s infinite loading; animation: 2s ease-out 0s infinite loading;
} }
.result-header-container {
display: flex;
align-items: center;
margin-top: 5rem;
flex-direction: column;
margin-bottom: 2rem;
border: 1px solid var(--color-secondary);
border-radius: 1rem;
padding: 3rem 2rem;
}
.result-header-container .symptom-container {
margin: 0;
flex: 1;
width: 100%;
}
.result-header-select {
width: 100%;
margin-top: 2rem;
}
.result-header-select .overline {
font-size: 1.6rem;
margin-bottom: 0.75rem;
}
.result-header-select .overline.errored {
margin-bottom: 0;
margin-top: 0.75rem;
color: #c43d34;
}
.result-header-select .overline.success {
margin-bottom: 0;
margin-top: 0.75rem;
}
.result-header-select select {
background-color: var(--color-tertiary);
border-radius: 1rem;
padding: 1rem 2rem;
outline: none;
width: 100%;
}
@media (hover: hover) and (pointer: fine) { @media (hover: hover) and (pointer: fine) {
.answer:hover, .answer:hover,
.answer.unselected:hover { .answer.unselected:hover {
+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 {
+30
View File
@@ -103,3 +103,33 @@ footer {
.loading-bar-container { .loading-bar-container {
max-width: 50%; max-width: 50%;
} }
.tip-container .overline[data-button] {
margin-top: 2rem;
margin-left: 0;
}
.tip-container .overline[data-button]:last-of-type {
margin-left: 1.2rem;
}
.tip {
padding-left: 2rem;
}
.result-header-container {
flex-direction: row;
}
.result-header-container .symptom-container {
width: fit-content;
}
.result-header-select {
margin-top: 0rem;
width: fit-content;
padding-left: 3rem;
border-left: 1px solid var(--color-secondary);
}
.result-header-select select {
width: fit-content;
}
+550 -231
View File
@@ -14,12 +14,15 @@ const FIREBASE_CONFIG = {
}; };
firebase.initializeApp(FIREBASE_CONFIG); firebase.initializeApp(FIREBASE_CONFIG);
if (window.location.host.startsWith("localhost")) {
firebase.functions().useEmulator("localhost", 5001); if (window.location.host !== "www.vaxand.me") {
firebase.functions().useEmulator(window.location.host.split(":")[0], 5001);
} }
const fb_functions = firebase.functions(); const fb_functions = firebase.functions();
const saveResult = fb_functions.httpsCallable("saveResult"); const saveResult = fb_functions.httpsCallable("saveResultV1");
const getPastResult = fb_functions.httpsCallable("getPastResult"); const getPastResult = fb_functions.httpsCallable("getPastResultV1");
const getStats = fb_functions.httpsCallable("getStatsV1");
const VAX_NAMES = { const VAX_NAMES = {
PB: "Pfizer-BioNTech", PB: "Pfizer-BioNTech",
@@ -47,131 +50,173 @@ 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": 9908,
"RED AT SITE": "<1", "RED AT SITE": 344,
"SWELLING AT SITE": "<1", "SWELLING AT SITE": 2089,
HEADACHE: "4", HEADACHE: 4030,
FATIGUE: "4", FATIGUE: 4384,
"MUSCLE PAIN": "2", "MUSCLE PAIN": 2699,
"JOINT PAIN": "2", "JOINT PAIN": 1893,
"NAUSEA OR VOMITING": "1", "NAUSEA OR VOMITING": 1068,
FEVER: "1", FEVER: 105,
CHILLS: "1", CHILLS: 1051,
}; };
const MD_DOSE2_YOUNGER_RESULT = { const MD_DOSE2_YOUNGER_RESULT = {
"PAIN AT SITE": "9", "PAIN AT SITE": 9873,
"RED AT SITE": "1", "RED AT SITE": 982,
"SWELLING AT SITE": "1", "SWELLING AT SITE": 3164,
FATIGUE: "7", FATIGUE: 7430,
HEADACHE: "6", HEADACHE: 6898,
"JOINT PAIN": "5", "JOINT PAIN": 4993,
CHILLS: "5", CHILLS: 5341,
FEVER: "2", FEVER: 1908,
"NAUSEA OR VOMITING": "2", "NAUSEA OR VOMITING": 2348,
"MUSCLE PAIN": "1", "MUSCLE PAIN": 6769,
}; };
const MD_DOSE1_OLDER_RESULT = { const MD_DOSE1_OLDER_RESULT = {
"PAIN AT SITE": "7", "PAIN AT SITE": 2782,
"RED AT SITE": "<1", "RED AT SITE": 86,
"SWELLING AT SITE": "<1", "SWELLING AT SITE": 396,
HEADACHE: "3", HEADACHE: 921,
FATIGUE: "3", FATIGUE: 1251,
"MUSCLE PAIN": "2", "MUSCLE PAIN": 742,
"JOINT PAIN": "2", "JOINT PAIN": 618,
"NAUSEA OR VOMITING": "1", "NAUSEA OR VOMITING": 194,
CHILLS: "1", CHILLS: 202,
FEVER: "<1", FEVER: 10,
}; };
const MD_DOSE2_OLDER_RESULT = { const MD_DOSE2_OLDER_RESULT = {
"PAIN AT SITE": "8", "PAIN AT SITE": 3070,
"RED AT SITE": "1", "RED AT SITE": 275,
"SWELLING AT SITE": "1", "SWELLING AT SITE": 715,
FATIGUE: "6", FATIGUE: 2152,
HEADACHE: "5", HEADACHE: 1704,
"MUSCLE PAIN": "5", "MUSCLE PAIN": 1739,
"JOINT PAIN": "4", "JOINT PAIN": 1291,
CHILLS: "3", CHILLS: 1141,
"NAUSEA OR VOMITING": "1", "NAUSEA OR VOMITING": 437,
FEVER: "1", FEVER: 370,
}; };
const JJ_YOUNGER_RESULT = { const JJ_YOUNGER_RESULT = {
"PAIN AT SITE": "6", "PAIN AT SITE": 1193,
"RED AT SITE": "1", "RED AT SITE": 184,
"SWELLING AT SITE": "1", "SWELLING AT SITE": 142,
HEADACHE: "4", HEADACHE: 905,
FATIGUE: "4", FATIGUE: 891,
"MUSCLE PAIN": "4", "MUSCLE PAIN": 796,
"NEED PAIN MED": "3", "NEED PAIN MED": 538,
"NAUSEA OR VOMITING": "2", "NAUSEA OR VOMITING": 315,
FEVER: "1", FEVER: 261,
}; };
const JJ_OLDER_RESULT = { const JJ_OLDER_RESULT = {
"PAIN AT SITE": "3", "PAIN AT SITE": 439,
"RED AT SITE": "<1", "RED AT SITE": 61,
"SWELLING AT SITE": "<1", "SWELLING AT SITE": 36,
HEADACHE: "3", HEADACHE: 401,
FATIGUE: "3", FATIGUE: 392,
"MUSCLE PAIN": "2", "MUSCLE PAIN": 317,
"NAUSEA OR VOMITING": "1", "NAUSEA OR VOMITING": 162,
"NEED PAIN MED": "1", "NEED PAIN MED": 130,
FEVER: "<1", FEVER: 41,
};
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": 11406,
"65 & Older": 3762,
},
"Dose 2": {
"18 - 64": 10985,
"65 & Older": 3692,
},
},
"J&J Janssen": {
"18 - 59": 2036,
"60 & Older": 1320,
},
}; };
const VAX_RESULTS = { const VAX_RESULTS = {
@@ -210,13 +255,19 @@ const ICONS = {
"NEED PAIN MED": "💊", "NEED PAIN MED": "💊",
"MUSCLE PAIN": "😫", "MUSCLE PAIN": "😫",
CHILLS: "🥶", CHILLS: "🥶",
DIRRHEA: "💩", DIARRHEA: "💩",
"JOINT PAIN": "🦵", "JOINT PAIN": "🦵",
FEVER: "🤒", FEVER: "🤒",
VOMITING: "🤮", VOMITING: "🤮",
"NAUSEA OR VOMITING": "🤮", "NAUSEA OR VOMITING": "🤮",
}; };
const DATA_CHOICES = {
VNM: "Vax&Me Only",
VNM_FDA: "Vax&Me + FDA",
FDA: "FDA Only",
};
class App extends React.Component { class App extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -227,7 +278,11 @@ class App extends React.Component {
gid: "", gid: "",
showInputPopup: false, showInputPopup: false,
answers: {}, answers: {},
fetchedResult: {},
stats: {},
loading: true, loading: true,
dataSource: DATA_CHOICES.FDA,
loadingMessage: "Gettings stats...",
}; };
} }
componentDidMount() { componentDidMount() {
@@ -263,48 +318,78 @@ class App extends React.Component {
} }
} }
if (!window.localStorage.getItem("hasExistingAnswer")) { getStats()
this.setState({ loading: false }); .then((v) => {
} // GET & SET STATS
this.setState({
if (window.gtag) { stats: v.data,
window.gtag("get", "G-7VZCY223NG", "client_id", (client_id) => { loadingMessage: "Checking for existing answers...",
if (!!client_id) { dataSource: DATA_CHOICES.VNM_FDA,
this.setState({ gid: client_id }); });
getPastResult({ gid: client_id }) // HAS GID?
.then((v) => { if (window.gtag) {
const { data } = v; // GET GID
if (!!data) { window.gtag("get", "G-7VZCY223NG", "client_id", (client_id) => {
const { name, age, dose } = data; // GOT GID?
this.setState({ name, age, dose, showInputPopup: true }); if (!!client_id) {
delete data.name; // SAVE GID
delete data.age; this.setState({ gid: client_id });
delete data.dose; // GET PAST ANSWER FOR GID
this.setState({ answers: data, loading: false }); getPastResult({ gid: client_id })
const inputPopupSubmit = document.querySelector( .then((v) => {
".input-popup-submit" const { data } = v;
); if (!!data) {
if (inputPopupSubmit) { const { name, age, dose } = data;
inputPopupSubmit.classList.add("submitted"); this.setState({
inputPopupSubmit.innerHTML = "UPDATE ANSWER"; name,
} age,
window.localStorage.setItem("hasExistingAnswer", true); dose,
} else { showInputPopup: true,
this.setState({ loading: false }); fetchedResult: data,
} answers: data,
}) loading: false,
.catch((err) => { });
console.error("ERROR Getting Past Answer", err); const inputPopupSubmit = document.querySelector(
".input-popup-submit"
);
if (inputPopupSubmit) {
inputPopupSubmit.classList.add("submitted");
inputPopupSubmit.innerHTML = "UPDATE ANSWER";
}
window.localStorage.setItem("hasExistingAnswer", true);
} else {
this.setState({ loading: false });
}
})
.catch((err) => {
console.error("ERROR Getting Past Answer", err);
this.setState({ loading: false });
});
} else {
this.setState({ loading: false }); this.setState({ loading: false });
}); }
});
} else {
this.setState({ loading: false });
} }
})
.catch((err) => {
console.error("ERROR GETTING STATS", err);
this.setState({ loading: false });
}); });
} else {
this.setState({ loading: false });
}
} }
shouldShowDoses = () => {
const { name, loading } = this.state;
if (loading) {
return false;
}
return name && (name === VAX_NAMES.PB || name === VAX_NAMES.MD);
};
shouldShowAgeGroup = () => { shouldShowAgeGroup = () => {
const { name, dose } = this.state; const { name, dose, loading } = this.state;
if (loading) {
return false;
}
switch (name) { switch (name) {
case VAX_NAMES.JJ: case VAX_NAMES.JJ:
return true; return true;
@@ -316,7 +401,10 @@ class App extends React.Component {
} }
}; };
shouldShowResults = () => { shouldShowResults = () => {
const { name, dose, age } = this.state; const { name, dose, age, loading } = this.state;
if (loading) {
return false;
}
switch (name) { switch (name) {
case VAX_NAMES.JJ: case VAX_NAMES.JJ:
return !!age; return !!age;
@@ -328,7 +416,10 @@ class App extends React.Component {
} }
}; };
getResults = () => { getResults = () => {
const { name, dose, age } = this.state; const { name, dose, age, loading } = this.state;
if (loading) {
return null;
}
switch (name) { switch (name) {
case VAX_NAMES.JJ: case VAX_NAMES.JJ:
return VAX_RESULTS[name][age]; return VAX_RESULTS[name][age];
@@ -373,25 +464,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 +520,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 +534,161 @@ 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">
{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>
);
};
getStatForSymptom = (symptom) => {
const { dataSource, name, dose, age, stats } = this.state;
switch (dataSource) {
case DATA_CHOICES.FDA:
if (!!dose) {
return Math.ceil(
(VAX_RESULTS[name][dose][age][symptom] /
VAX_COUNTS[name][dose][age]) *
10
);
} else {
return Math.ceil(
(VAX_RESULTS[name][age][symptom] / VAX_COUNTS[name][age]) * 10
);
}
case DATA_CHOICES.VNM:
if (Object.keys(stats).length > 0) {
if (!!dose) {
return Math.ceil(
(stats[name][dose][age][symptom] / stats[name][dose][age].count) *
10
);
} else {
return Math.ceil(
(stats[name][age][symptom] / stats[name][age].count) * 10
);
}
} else {
return 0;
}
case DATA_CHOICES.VNM_FDA:
let symptomCount = 0;
let totalCount = 0;
if (Object.keys(stats).length > 0) {
if (!!dose) {
symptomCount =
VAX_RESULTS[name][dose][age][symptom] +
stats[name][dose][age][symptom];
totalCount =
VAX_COUNTS[name][dose][age] + stats[name][dose][age].count;
} else {
symptomCount =
VAX_RESULTS[name][age][symptom] + stats[name][age][symptom];
totalCount = VAX_COUNTS[name][age] + stats[name][age].count;
}
} else {
symptomCount = VAX_RESULTS[name][dose][age][symptom];
totalCount = VAX_COUNTS[name][dose][age];
}
return Math.ceil((symptomCount / totalCount) * 10);
default:
return 1;
}
};
getSampleSize = () => {
const { dataSource, name, dose, age, stats } = this.state;
switch (dataSource) {
case DATA_CHOICES.FDA:
if (!!dose) {
return VAX_COUNTS[name][dose][age];
} else {
return VAX_COUNTS[name][age];
}
case DATA_CHOICES.VNM:
if (Object.keys(stats).length > 0) {
if (!!dose) {
return stats[name][dose][age].count;
} else {
return stats[name][age].count;
}
} else {
return 0;
}
case DATA_CHOICES.VNM_FDA:
let totalCount = 0;
if (Object.keys(stats).length > 0) {
if (!!dose) {
totalCount =
VAX_COUNTS[name][dose][age] + stats[name][dose][age].count;
} else {
totalCount = VAX_COUNTS[name][age] + stats[name][age].count;
}
} else {
totalCount = VAX_COUNTS[name][dose][age];
}
return totalCount;
default:
return 0;
}
}; };
render() { render() {
const { name, dose, age, showInputPopup, answers, loading } = this.state; const {
name,
dose,
age,
showInputPopup,
loading,
fetchedResult,
dataSource,
loadingMessage,
stats,
} = this.state;
let result = {}; let result = {};
let resultAtSite = {}; let resultAtSite = {};
if (this.shouldShowResults()) { if (this.shouldShowResults()) {
@@ -442,7 +719,7 @@ class App extends React.Component {
{loading ? ( {loading ? (
<div className="loading-container"> <div className="loading-container">
<p className="question" data-font="sans"> <p className="question" data-font="sans">
Checking for existing answers... {loadingMessage}
</p> </p>
<div className="loading-bar-container"> <div className="loading-bar-container">
<div className="loading-bar"></div> <div className="loading-bar"></div>
@@ -451,7 +728,7 @@ class App extends React.Component {
) : null} ) : null}
{!loading ? ( {!loading ? (
<div className="question-container"> <div className="question-container">
<p className="question" data-font="sans-italic"> <p className="question" data-font="sans">
Select Vaccine Select Vaccine
</p> </p>
<div className="answers-container"> <div className="answers-container">
@@ -463,7 +740,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,9 +755,9 @@ 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">
Select Dose Select Dose
</p> </p>
<div className="answers-container"> <div className="answers-container">
@@ -491,7 +768,6 @@ class App extends React.Component {
this.setState({ this.setState({
dose: v, dose: v,
age: "", age: "",
answers: {},
showInputPopup: false, showInputPopup: false,
}) })
} }
@@ -509,7 +785,7 @@ class App extends React.Component {
{this.shouldShowAgeGroup() ? ( {this.shouldShowAgeGroup() ? (
<div className="question-container"> <div className="question-container">
<p className="question" data-font="sans-italic"> <p className="question" data-font="sans">
Select Age Group Select Age Group
</p> </p>
<div className="answers-container"> <div className="answers-container">
@@ -519,7 +795,6 @@ class App extends React.Component {
onClick={() => onClick={() =>
this.setState({ this.setState({
age: v, age: v,
answers: {},
showInputPopup: false, showInputPopup: false,
}) })
} }
@@ -536,20 +811,84 @@ class App extends React.Component {
) : null} ) : null}
{this.shouldShowResults() ? ( {this.shouldShowResults() ? (
<div className="symptom-container subheader"> <div className="result-header-container">
<h3 data-font="sans-bold">Here's what to expect.</h3> <div className="symptom-container subheader">
<h6 <h3 data-font="sans-bold">Here's what to expect.</h3>
id="share-result" <h6
onClick={this.copyShareLink} id="share-result"
className="overline" onClick={this.copyShareLink}
data-font="sans" className="overline"
data-button data-font="sans"
> data-button
Share ↗ >
</h6> Share ↗
<h6 className="overline" data-font="sans" data-button> </h6>
<a href="/vaxandme_chart.pdf">Download Chart ↗</a> <h6 className="overline" data-font="sans" data-button>
</h6> <a href="/vaxandme_chart.pdf">Download Chart ↗</a>
</h6>
</div>
<div className="result-header-select">
<h6 className="overline" data-font="sans">
Use data from:
</h6>
<select
disabled={Object.keys(stats).length < 1}
onChange={(e) =>
this.setState({ dataSource: e.target.value })
}
value={dataSource}
>
{Object.keys(DATA_CHOICES).map((dataKey) => (
<option key={dataKey}>{DATA_CHOICES[dataKey]}</option>
))}
</select>
<h6 className="overline success" data-font="sans">
Based on
{` ${this.getSampleSize()
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",")} `}
individual experiences
</h6>
{Object.keys(stats).length < 1 ? (
<h6 className="overline errored" data-font="sans">
Failed to get stats from Vax&amp;Me
</h6>
) : null}
</div>
</div>
) : null}
{this.shouldShowResults() ? (
<div className="tip-container">
{/* <div className="tip-icon">&#9432;</div> */}
<div className="tip">
<h6 className="subheader" data-font="sans">
NET EFFICACY
</h6>
<h3 className="header" data-font="sans-bold">
{VAX_EFFICACY[name][age]}%
</h3>
<h6
className="overline"
style={{ wordBreak: "break-word" }}
data-font="sans"
>
of vaccine trial participants who took the <u>{name}</u>{" "}
vaccine (in the <u>{age}</u> age group) did not get COVID-19
</h6>
<h6 className="overline" data-font="sans" data-button>
<a href="#data-sources">See Data Sources ↗</a>
</h6>
<h6 className="overline" data-font="sans" data-button>
<a
target="_blank"
href="https://www.gavi.org/vaccineswork/what-difference-between-efficacy-and-effectiveness"
rel="noreferrer"
>
What is efficacy? ↗
</a>
</h6>
</div>
</div> </div>
) : null} ) : null}
@@ -562,7 +901,7 @@ class App extends React.Component {
{symptom} {symptom}
</p> </p>
<p data-font="sans-bold" className="symptom-chance"> <p data-font="sans-bold" className="symptom-chance">
{resultAtSite[symptom]}/10 {this.getStatForSymptom(symptom)}/10
</p> </p>
</div> </div>
))} ))}
@@ -573,7 +912,7 @@ class App extends React.Component {
{symptom} {symptom}
</p> </p>
<p data-font="sans-bold" className="symptom-chance"> <p data-font="sans-bold" className="symptom-chance">
{result[symptom]}/10 {this.getStatForSymptom(symptom)}/10
</p> </p>
</div> </div>
))} ))}
@@ -604,56 +943,36 @@ class App extends React.Component {
&#9660; &#9660;
</button> </button>
</div> </div>
<div className="input-popup-body"> {showInputPopup ? (
<p className="question" data-font="sans"> <div className="input-popup-body">
I am in age group <strong>{age}</strong> and got <p className="question" data-font="sans">
<strong>{!!dose ? ` ${dose} ` : ""}</strong> I am in age group <strong>{age}</strong> and got the
{!!dose ? `of` : ""} the <strong> {name}</strong> vaccine. I experienced:
<strong> {name}</strong> vaccine. I experienced:
</p>
<div className="question-container">
<p className="question" data-font="sans-italic">
(Select all side effects that you experienced)
</p> </p>
<div className="answers-container"> {this.shouldShowDoses()
{Object.keys(resultAtSite).map((symptom) => ( ? Object.keys(VAX_DOSES).map((dose) =>
<button this.getAnswerOptions(VAX_DOSES[dose])
key={`ans-${symptom}`} )
data-font="sans-bold" : this.getAnswerOptions()}
className={`answer${answers[symptom] ? " selected" : ""}`} <div className="question-container">
onClick={() => this.updateAnswers(symptom)} <p className="question" data-font="sans">
> You can comeback and update your experience at any time!
{symptom} </p>
</button> <p className="question" data-font="sans">
))} (Just use the same device)
{Object.keys(result).map((symptom) => ( </p>
<button
key={`ans-${symptom}`}
data-font="sans-bold"
className={`answer${answers[symptom] ? " selected" : ""}`}
onClick={() => this.updateAnswers(symptom)}
>
{symptom}
</button>
))}
</div> </div>
</div> </div>
<div className="question-container"> ) : null}
<p className="question" data-font="sans"> {showInputPopup ? (
You can comeback and update your experience later! <div
</p> onClick={this.submitAnswer}
<p className="question" data-font="sans-italic"> className="input-popup-submit"
(Just use the same device) data-font="sans-bold"
</p> >
SUBMIT
</div> </div>
</div> ) : null}
<div
onClick={this.submitAnswer}
className="input-popup-submit"
data-font="sans-bold"
>
SUBMIT
</div>
</div> </div>
) : null} ) : null}
</div> </div>