Skip to content

Commit

Permalink
Add "verify registration" buttons + forms to the site (#4)
Browse files Browse the repository at this point in the history
We now always display two buttons, one for "Verify registration" and one for "Register to vote". In cases where either state will do, we just have generic buttons; in cases where we're explicitly suggesting a state, the buttons name the state by abbreviation.

This adds two new events that go to both Facebook and Google:

VerifyStart and VerifyFinish. VerifyStart happens after the user submits the initial intake form. VerifyFinish generally speaking happens right after (see https://docs.voteamerica.com/software/events/#action-finish-event-4)


* Separate out the form and verification pages.

* Plumb new 'verify' events through.

* About as good as I can make it, I suppose.
  • Loading branch information
davepeck authored Mar 18, 2024
1 parent d2b267c commit 81f1988
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 122 deletions.
118 changes: 87 additions & 31 deletions src/components/More.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,36 +208,46 @@ const SelectStates: React.FC<{

/** Props to the register-to-vote button. */
interface RegisterToVoteButtonProps {
state: State;
handler: RegistrationHandler;
behavior: "register" | "verify";

/** The specific state to register in. */
state: State;

/** If true, this was a preferred state. */
chosen?: boolean;

/** If true, hide the state name in the button. */
hideState?: boolean;

className?: string;
}

/** Renders a single analytics-tracked "register to vote" button. */
const RegisterToVoteButton: React.FC<RegisterToVoteButtonProps> = ({
state,
handler,
behavior,
chosen,
hideState,
className,
}) => {
const url = bestRegistrationUrl(state);

const handleRegistrationClick = useCallback(
(e: React.MouseEvent, state: State, url: string, chosen?: boolean) => {
(e: React.MouseEvent, state: State, chosen?: boolean) => {
e.preventDefault();
fireClickRegisterEvent({ state, handler });

switch (handler) {
case "direct":
const url = bestRegistrationUrl(state);
if (!url) return;
window.open(url, "_blank");
break;

case "voteamerica":
const path = behavior === "register" ? "/va-register" : "/va-verify";
window.document.location.href =
"/va-register/?state=" +
`${path}/?state=` +
state +
(chosen ? `&chosen=${STATE_NAMES[state]}` : "");
break;
Expand All @@ -253,23 +263,27 @@ const RegisterToVoteButton: React.FC<RegisterToVoteButtonProps> = ({
[handler]
);

if (!url) return null;

return (
<a
className={clsx(
"inline-block bg-point rounded-md px-6 py-4 text-white text-xl font-extrabold md:hover:bg-hover transition-colors duration-200 mb-2",
className
)}
href={bestRegistrationUrl(state)!}
href="#"
target="_blank"
onClick={(e) => handleRegistrationClick(e, state, url, chosen)}
onClick={(e) => handleRegistrationClick(e, state, chosen)}
aria-label={`Follow this link to register to vote in ${STATE_NAMES[state]}`}
>
Register to vote in{" "}
<span className="font-cabinet inline-block w-8 min-w-8 max-w-8">
{state.toUpperCase()}
</span>
{behavior === "register" ? "Register to vote" : "Verify registration"}
{!hideState && (
<>
{" "}
in{" "}
<span className="font-cabinet inline-block w-8 min-w-8 max-w-8">
{state.toUpperCase()}
</span>
</>
)}
</a>
);
};
Expand Down Expand Up @@ -422,7 +436,65 @@ const DescribeSelection: React.FC<{
result: StateSelectionResult;
handler: RegistrationHandler;
}> = ({ result, handler }) => {
const { selection, homeState, schoolState } = result;
const { selection, homeState } = result;

let buttons;
if (selection === "toss-up") {
// Show one generic register button + one generic verify button
buttons = (
<>
<RegisterToVoteButton
hideState
state={homeState}
handler={handler}
behavior="verify"
/>
<RegisterToVoteButton
hideState
state={homeState}
handler={handler}
behavior="register"
className="ml-4"
/>
</>
);
} else if (selection === "same") {
// Show one state-specific register button and one state-specific verify
buttons = (
<>
<RegisterToVoteButton
state={homeState}
handler={handler}
behavior="verify"
/>
<RegisterToVoteButton
state={homeState}
handler={handler}
behavior="register"
className="ml-4"
/>
</>
);
} else {
// Show one state-specific register button and one state-specific verify
buttons = (
<>
<RegisterToVoteButton
state={selectedState(result)}
handler={handler}
behavior="verify"
chosen={true}
/>
<RegisterToVoteButton
state={selectedState(result)}
handler={handler}
behavior="register"
chosen={true}
className="ml-4"
/>
</>
);
}

return (
<div>
Expand All @@ -435,23 +507,7 @@ const DescribeSelection: React.FC<{
<ShareButton />
</div>
<div className="flex-1 flex flex-row flex-wrap justify-end -mb-2">
{selection !== "school" && bestRegistrationUrl(homeState) && (
<RegisterToVoteButton
state={homeState}
handler={handler}
chosen={selection !== "toss-up"}
/>
)}
{selection !== "home" &&
selection !== "same" &&
bestRegistrationUrl(schoolState) && (
<RegisterToVoteButton
state={schoolState}
handler={handler}
chosen={selection !== "toss-up"}
className={selection === "toss-up" ? "ml-4" : ""}
/>
)}
{buttons}
</div>
</div>
</div>
Expand Down
81 changes: 73 additions & 8 deletions src/components/VoteAmericaAnalytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
fireRegisterFinishEvent,
fireRegisterFollowUpEvent,
fireRegisterStartEvent,
fireVerifyFinishEvent,
fireVerifyStartEvent,
type RegisterFinishMethod,
type RegisterFollowUpMethod,
type RegisterUser,
type VoterUser,
} from "../utils/analytics";
import { assertNever } from "../utils/asserts";

Expand All @@ -16,11 +18,22 @@ import { assertNever } from "../utils/asserts";
*
* See https://docs.voteamerica.com/software/events/#register-tool
*/
interface RegisterStartEvent extends RegisterUser {
interface RegisterStartEvent extends VoterUser {
event: "action-start";
tool: "register";
}

/**
* VoteAmerica "start verify" event, when a user submits the Verify tool's
* intake form.
*
* See https://docs.voteamerica.com/software/events/#verify-tool
*/
interface VerifyStartEvent extends VoterUser {
event: "action-start";
tool: "verify";
}

/** Details about what took place during the VA+ form flow. */
type FinishMethod =
/** The user clicked a link to visit a state-hosted online voter site. */
Expand Down Expand Up @@ -50,7 +63,7 @@ type FinishMethod =
*
* See https://docs.voteamerica.com/software/events/#register-tool
*/
interface RegisterFinishEvent extends RegisterUser {
interface RegisterFinishEvent extends VoterUser {
event: "action-finish";
tool: "register";

Expand All @@ -61,6 +74,17 @@ interface RegisterFinishEvent extends RegisterUser {
url?: string;
}

/**
* VoteAmerica "finish verify" event, when the user has completed the
* full VoteAmerica form flow.
*
* See https://docs.voteamerica.com/software/events/#verify-tool
*/
interface VerifyFinishEvent extends VoterUser {
event: "action-finish";
tool: "verify";
}

/** Details about user follow-up actions, well after the VA+ form flow. */
type FollowUpMethod =
/**
Expand All @@ -81,7 +105,7 @@ type FollowUpMethod =
*
* See https://docs.voteamerica.com/software/events/#register-tool
*/
interface RegisterFollowUpEvent extends RegisterUser {
interface RegisterFollowUpEvent extends VoterUser {
event: "action-follow-up";
tool: "register";

Expand All @@ -98,9 +122,12 @@ type RegisterEvent =
| RegisterFinishEvent
| RegisterFollowUpEvent;

/** All verification event types. */
type VerifyEvent = VerifyStartEvent | VerifyFinishEvent;

/** The VoteAmerica event type. */
type VoteAmericaEvent = CustomEvent<{
data: RegisterEvent;
data: RegisterEvent | VerifyEvent;
embedEl: HTMLDivElement;
iFrame: HTMLIFrameElement;
}>;
Expand Down Expand Up @@ -138,9 +165,8 @@ const followUpMethodToCountMore = (
}
};

/** Listen to VoteAmerica events and manage them. */
const listener = (event: VoteAmericaEvent, intended: State) => {
const { data } = event.detail;
/** Listen to VoteAmerica reigster tool events. */
const registerListener = (data: RegisterEvent, intended: State) => {
switch (data.event) {
case "action-start":
console.log("RegisterStartEvent", data);
Expand Down Expand Up @@ -173,6 +199,45 @@ const listener = (event: VoteAmericaEvent, intended: State) => {
}
};

/** Listen to VoteAmerica verify tool events. */
const verifyListener = (data: VerifyEvent, intended: State) => {
switch (data.event) {
case "action-start":
console.log("VerifyStartEvent", data);
fireVerifyStartEvent({
state: data.state,
intended,
handler: "voteamerica",
});
break;
case "action-finish":
console.log("VerifyFinishEvent", data);
fireVerifyFinishEvent({
state: data.state,
intended,
handler: "voteamerica",
});
break;
default:
assertNever(data);
}
};

/** Listen to VoteAmerica events and manage them. */
const listener = (event: VoteAmericaEvent, intended: State) => {
const { data } = event.detail;
switch (data.tool) {
case "register":
registerListener(data, intended);
break;
case "verify":
verifyListener(data, intended);
break;
default:
assertNever(data);
}
};

const useVoteAmericaAnalytics = (intended: State) => {
const wrapListener = useCallback(
(event: VoteAmericaEvent) => listener(event, intended),
Expand Down
Loading

0 comments on commit 81f1988

Please sign in to comment.