From 9cccf5902f3a46a7eadaf51faab0cae6df23d8b9 Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 3 May 2024 13:52:19 -0700 Subject: [PATCH] Implement check_page --- server/vb/components/base_page.py | 6 +- server/vb/components/check_page.py | 212 +++++++++++++++++++++++++++++ server/vb/views.py | 5 +- 3 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 server/vb/components/check_page.py diff --git a/server/vb/components/base_page.py b/server/vb/components/base_page.py index c2a3e59..3bdf524 100644 --- a/server/vb/components/base_page.py +++ b/server/vb/components/base_page.py @@ -53,6 +53,8 @@ def base_page( extra_head: h.Node | None = None, title: str = "VoterBowl", bg_color: str = "#cdff64", + show_faq: bool = True, + show_footer: bool = True, ) -> h.Element: """Render the generic structure for all pages on voterbowl.org.""" return h.html(lang="en")[ @@ -75,7 +77,7 @@ def base_page( ], h.body[ children, - h.div(".faq")[h.div(".container")[faq(school=None)]], - footer(), + h.div(".faq")[h.div(".container")[faq(school=None)]] if show_faq else None, + footer() if show_footer else None, ], ] diff --git a/server/vb/components/check_page.py b/server/vb/components/check_page.py new file mode 100644 index 0000000..fe7c9ff --- /dev/null +++ b/server/vb/components/check_page.py @@ -0,0 +1,212 @@ +import htpy as h +from django.templatetags.static import static +from markupsafe import Markup + +from ..models import Contest, School +from .base_page import base_page +from .countdown import countdown +from .logo import school_logo + +_STYLE = """ +me { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +me main { + width: 100%; + text-align: center; + padding: 0.5rem 0; +} + +me main img { + height: 150px; + margin-bottom: -1.75rem; +} + +me main p { + font-weight: 378; + font-size: 20px; + line-height: 130%; +} + +me main h2 { + font-weight: 500; + font-size: 36px; + line-height: 120%; + text-transform: uppercase; +} + +me .faq { + width: 100%; + color: white; + padding: 2rem 0; +} + +me .button-holder { + display: flex; + justify-content: center; + margin: 1.5rem 0; +} + +me .form { + width: 100%; + background-color: white; + padding: 2rem 0; +} + +me .urgency { + flex-direction: column; + gap: 1rem; +} + +@media screen and (min-width: 768px) { + me main { + padding: 2rem 0; + } + + me main img { + height: 150px; + margin: 1.5rem 0; + } + + me .urgency { + flex-direction: row; + gap: 2rem; + } +} + +me main { + position: relative; + color: {main_color}; + background-color: {main_bg_color}; +} + +me main a { + color: {main_color}; + transition: opacity 0.2s; +} + +me main a:hover { + opacity: 0.7; + transition: opacity 0.2s; +} + +me main .urgency { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +me main .fireworks { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + overflow: hidden; +} + +me main .separate { + padding-left: 1rem; +} + +me main img { + display: block; +} + +@media screen and (min-width: 768px) { + me main .urgency { + flex-direction: row; + } +} +""" + +_SCRIPT = h.script[ + Markup(""" +(function(self) { + /** + * Finalize a verify and, possibly, mint a new gift card if all is well. + * + * @param {string} firstName + * @param {string} lastName + * @param {string} email + */ + const finishVerify = (firstName, lastName, email) => { + htmx.ajax("POST", "./finish/", { + target: self.querySelector(".urgency"), + values: { + first_name: firstName, + last_name: lastName, + email: email + } + }); + }; + + window.addEventListener('VoteAmericaEvent', (event) => { + const { + data + } = event.detail; + if (data?.tool === "verify" && data?.event === "action-finish") { + setTimeout(() => { + finishVerify(data.first_name, data.last_name, data.email); + }, 500); + } + }); +})(me());""") +] + + +def _style(main_color: str, main_bg_color: str) -> h.Element: + return h.style[ + _STYLE.replace("{main_color}", main_color).replace( + "{main_bg_color}", main_bg_color + ) + ] + + +def check_page(school: School, current_contest: Contest | None) -> h.Element: + """Render a school-specific 'check voter registration' form page.""" + extra_head = [ + h.script(src=static("js/fireworks.js")), + h.script(src="https://cdn.voteamerica.com/embed/tools.js", _async=True), + ] + return base_page( + title=f"Voter Bowl x {school.name}", + bg_color=school.logo.bg_color, + extra_head=extra_head, + show_faq=False, + show_footer=False, + )[ + h.div[ + _style( + main_color=school.logo.bg_text_color, main_bg_color=school.logo.bg_color + ), + h.main[ + h.div(".container")[ + h.div(".urgency")[ + school_logo(school), + countdown(current_contest) + if current_contest + else h.div(".separate")[ + h.p["Check your voter registraiton status below."] + ], + ] + ], + h.div(".fireworks"), + ], + h.div(".form")[ + h.div(".container")[ + h.div( + ".voteamerica-embed", + data_subscriber="voterbowl", + data_tool="verify", + data_edition="college", + ) + ] + ], + ] + ] diff --git a/server/vb/views.py b/server/vb/views.py index bddd906..82e20ba 100644 --- a/server/vb/views.py +++ b/server/vb/views.py @@ -9,6 +9,7 @@ from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_GET, require_POST +from .components.check_page import check_page from .components.home_page import home_page from .components.school_page import school_page from .models import Contest, EmailValidationLink, School @@ -66,9 +67,7 @@ def check(request: HttpRequest, slug: str) -> HttpResponse: """ school = get_object_or_404(School, slug=slug) current_contest = school.contests.current() - return render( - request, "check.dhtml", {"school": school, "current_contest": current_contest} - ) + return HttpResponse(check_page(school, current_contest)) class FinishCheckForm(forms.Form):