From 9e63489f15cbb23dc3b373c9adf9634dbedd4479 Mon Sep 17 00:00:00 2001 From: Julien Sulpis Date: Thu, 5 Nov 2020 15:49:01 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20decrease=20time-to-interac?= =?UTF-8?q?tive=20(#162)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The navbar, the buttons to choose the response, and the comment area are now responsive before the Firebase authentication is completed. --- ui/src/index.html | 29 +++-- ui/src/index.ts | 254 ++++++++++++++++++++++------------------ ui/src/styles/style.css | 14 ++- 3 files changed, 170 insertions(+), 127 deletions(-) diff --git a/ui/src/index.html b/ui/src/index.html index 82c28f88..65f483b0 100644 --- a/ui/src/index.html +++ b/ui/src/index.html @@ -44,7 +44,7 @@

A little comment?

- - -
- +
+
-

Stats for agency:

+
`; + + const statsLoader = `
${htmlLoader} Hold on, we're retrieving the data you requested ...
`; + + sendingSectionLoader.innerHTML = htmlLoader; + statsTab.innerHTML = statsLoader; const show = (element: HTMLElement) => { element.classList.remove(hideClass); @@ -89,20 +92,21 @@ window.addEventListener("load", async function () { show(incoming); }; - const displayStatsPage = async (agency?: string) => { - changePageTo(statsPage); - statsTitle.style.display = "none"; - statsTab.innerHTML = htmlLoader; - voteData = await retrieveStatsData(agency); - statsTitle.style.display = ""; - displayStatsData(voteData, agency); - }; - const displayHomePage = () => { hideAllPages(); show(homePage); }; + const renderInitialStatsPage = async () => { + voteData = await retrieveStatsData(); + await fillAgenciesList(); + renderStatsData(voteData); + }; + + const displayStatsPage = async (agency?: string) => { + changePageTo(statsPage); + }; + const retrieveStatsData = async (agency?: string) => { const db = firebase.firestore(); db.settings({ timestampsInSnapshots: true }); @@ -120,7 +124,18 @@ window.addEventListener("load", async function () { })); }; - const fillAgenciesList = (agencyList: Set) => { + const fillAgenciesList = async () => { + const statsCampaignAgency: firebase.firestore.QuerySnapshot = await firebase + .firestore() + .collection(`stats-campaign-agency`) + .get(); + + const agencies = new Set( + statsCampaignAgency.docs.map( + snapshot => (snapshot.data() as StatsData).agency + ) + ); + agencySelector.childNodes.forEach(child => { agencySelector.removeChild(child); }); @@ -129,7 +144,8 @@ window.addEventListener("load", async function () { agencyName.innerText = "Global"; globalElement.setAttribute("value", ""); agencySelector.appendChild(globalElement); - agencyList.forEach(agency => { + + agencies.forEach(agency => { const element = this.document.createElement("option"); element.innerText = agency; element.setAttribute("value", agency); @@ -137,7 +153,7 @@ window.addEventListener("load", async function () { }); }; - const displayStatsData = (voteData: VoteData, agency?: string) => { + const renderStatsData = (voteData: VoteData, agency?: string) => { statsTab.innerHTML = renderTemplate( computeDataFromDataBase( agency ? filterStatsData(voteData, agency) : voteData @@ -153,50 +169,60 @@ window.addEventListener("load", async function () { console.error(err); changePageTo(errorPage); }; - try { - const { session, webAuth } = await authenticateAuth0({ - ...AUTH0_CONFIG, - redirectUri: window.location.href - }); - if (!session) return; // this means a redirect has been issued - if (!session.user.email) { - throw new Error("expected user to have email but it did not"); - } - await authenticateFirebase(session); - userId.innerText = session.user.email; - logoutButton.onclick = async () => { - await signOutFirebase(); - signOutAuth0(webAuth); + const saveResponse = async (response: string, comment?: string) => { + changePageTo(recordingPage); + const payload: Payload = { + vote: response }; + if (comment) { + payload.comment = comment; + } + try { + await castVote(payload); + } catch (err) { + if (err.status === "ALREADY_EXISTS") { + changePageTo(alreadyVotedPage); + } else { + errorOut(err.message); + } + return; + } + changePageTo(thankYouPage); + }; - await enableFirestore(session.user.email); - show(logoutButton); - } catch (err) { - errorOut(err); - return; - } - - async function enableFirestore(userId: string) { - const db = firebase.firestore(); - db.settings({ timestampsInSnapshots: true }); - - const { campaign, alreadyVoted } = await getCurrentCampaignState(); - - statsButton.onclick = () => { - displayStatsPage(); + const initVoteButtonsEventHandlers = () => { + let mood: string; + const buttonMap = [submitGreat, submitNotThatGreat, submitNotGreatAtAll]; + submitGreat.onclick = () => { + buttonMap.forEach(button => button.classList.remove("focusButton")); + submitGreat.classList.add("focusButton"); + mood = "great"; + }; + submitNotThatGreat.onclick = () => { + buttonMap.forEach(button => button.classList.remove("focusButton")); + submitNotThatGreat.classList.add("focusButton"); + mood = "notThatGreat"; + }; + submitNotGreatAtAll.onclick = () => { + buttonMap.forEach(button => button.classList.remove("focusButton")); + submitNotGreatAtAll.classList.add("focusButton"); + mood = "notGreatAtAll"; }; - homeButton.onclick = () => { - if (!campaign) { - changePageTo(noCampaignPage); - return; - } - if (alreadyVoted) { - changePageTo(alreadyVotedPage); + voteButton.onclick = () => { + const comment = commentTextarea.value || undefined; + if (!mood) { + errorDisplay.hidden = false; return; } - displayHomePage(); + saveResponse(mood, comment); + }; + }; + + const initStatsButtonsEventHandlers = () => { + statsButton.onclick = () => { + displayStatsPage(); }; agencySelector.onchange = async () => { @@ -210,22 +236,30 @@ window.addEventListener("load", async function () { agencyName.innerText = newSelectedAgency; selectedAgency = newSelectedAgency; - displayStatsData( + renderStatsData( voteData, selectedAgency === "" ? undefined : selectedAgency ); }; - const statsCampaignAgency: firebase.firestore.QuerySnapshot = await firebase - .firestore() - .collection(`stats-campaign-agency`) - .get(); - fillAgenciesList( - new Set( - statsCampaignAgency.docs.map( - snapshot => (snapshot.data() as StatsData).agency - ) - ) - ); + }; + + const initCampaignAndEmployeeData = async (userId: string) => { + const db = firebase.firestore(); + db.settings({ timestampsInSnapshots: true }); + + const { campaign, alreadyVoted } = await getCurrentCampaignState(); + + homeButton.onclick = () => { + if (!campaign) { + changePageTo(noCampaignPage); + return; + } + if (alreadyVoted) { + changePageTo(alreadyVotedPage); + return; + } + displayHomePage(); + }; if (!campaign) { changePageTo(noCampaignPage); @@ -252,56 +286,50 @@ window.addEventListener("load", async function () { } if (employee.managerEmail) { managerName.innerText = employee.managerEmail; - show(managerNotice); } + }; - const saveResponse = async (response: string, comment?: string) => { - changePageTo(recordingPage); - const payload: Payload = { - vote: response - }; - if (comment) { - payload.comment = comment; - } - try { - await castVote(payload); - } catch (err) { - if (err.status === "ALREADY_EXISTS") { - changePageTo(alreadyVotedPage); - } else { - errorOut(err.message); - } - return; - } - changePageTo(thankYouPage); - }; - let mood: string; - const buttonMap = [submitGreat, submitNotThatGreat, submitNotGreatAtAll]; - submitGreat.onclick = () => { - buttonMap.map(button => button.classList.remove("focusButton")); - submitGreat.classList.add("focusButton"); - mood = "great"; - }; - submitNotThatGreat.onclick = () => { - buttonMap.map(button => button.classList.remove("focusButton")); - submitNotThatGreat.classList.add("focusButton"); - mood = "notThatGreat"; - }; - submitNotGreatAtAll.onclick = () => { - buttonMap.map(button => button.classList.remove("focusButton")); - submitNotGreatAtAll.classList.add("focusButton"); - mood = "notGreatAtAll"; - }; + try { + // 1 - Auth0 authentication + const { session, webAuth } = await authenticateAuth0({ + ...AUTH0_CONFIG, + redirectUri: window.location.href + }); + if (!session) return; // this means a redirect has been issued + if (!session.user.email) { + throw new Error("expected user to have email but it did not"); + } - voteButton.onclick = () => { - const comment = commentTextarea.value || undefined; - if (!mood) { - errorDisplay.hidden = false; - return; - } - saveResponse(mood, comment); + // 2 - Display the main content (Optimistic UI) + userIdElement.innerText = session.user.email; + displayHomePage(); + initVoteButtonsEventHandlers(); + initStatsButtonsEventHandlers(); + + // 3 - Firebase authentication + await authenticateFirebase(session); + + // 4 - Now that the user is fully logged in, they can logout + logoutButton.onclick = async () => { + await signOutFirebase(); + signOutAuth0(webAuth); }; - changePageTo(homePage); + // 5 - Load data for both pages in parallel + initCampaignAndEmployeeData(session.user.email) + .then(() => { + hide(sendingSectionLoader); + show(sendingSection); + }) + .catch(errorOut); + + renderInitialStatsPage() + .then(() => { + show(statsTitle); + }) + .catch(errorOut); + } catch (err) { + errorOut(err); + return; } }); diff --git a/ui/src/styles/style.css b/ui/src/styles/style.css index 4aae30d9..2c79f48a 100644 --- a/ui/src/styles/style.css +++ b/ui/src/styles/style.css @@ -53,7 +53,8 @@ button { padding: 0 32px; } -.page h2 { +.page h2, +.loading-message { align-items: center; color: rgba(0, 0, 0, 0.6); display: flex; @@ -187,7 +188,9 @@ tr:hover td { } .hidden { - display: none; + /* !important required here to ensure all .hidden are hidden ignoring all + other rules of higher specifify */ + display: none !important; } .errorDisplay { @@ -234,6 +237,13 @@ tr:hover td { stroke-linecap: round; } +#sendingSectionLoader:not(.hidden) { + height: 90px; + display: flex; + align-items: center; + justify-content: center; +} + @keyframes rotate { 100% { transform: rotate(360deg);