Compare commits
10
Commits
3055bf4521
...
main-admin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
329265aabb | ||
|
|
87e4916db6 | ||
|
|
21b65cadea | ||
|
|
06b18c14cd | ||
|
|
732bd6887c | ||
|
|
abb00ef27b | ||
|
|
36674febe4 | ||
|
|
50f1f53669 | ||
|
|
5c39e8a628 | ||
|
|
2eabb6807f |
+5
-3
@@ -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
@@ -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": {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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": [
|
||||||
|
|||||||
@@ -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,5 +1,5 @@
|
|||||||
.input-popup.expanded {
|
.input-popup.expanded {
|
||||||
height: 40vh;
|
height: 50vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-bar-container {
|
.loading-bar-container {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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&Me
|
||||||
|
</h6>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{this.shouldShowResults() ? (
|
||||||
|
<div className="tip-container">
|
||||||
|
{/* <div className="tip-icon">ⓘ</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 {
|
|||||||
▼
|
▼
|
||||||
</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user