Fix the post-RSVP layout, calm the copy, make the latest answer win, add unsubscribe
This commit is contained in:
parent
1113032c6c
commit
d54d872090
4 changed files with 65 additions and 32 deletions
|
|
@ -65,6 +65,7 @@ function esc(s) {
|
|||
}
|
||||
|
||||
var HERO = "https://nyc3.digitaloceanspaces.com/corben-sites/images/c23ea202-5442-41b4-9a1b-1586e81c50eb/e7119448-83ba-4da4-95d8-b64045c21160/email-header-trim.png";
|
||||
var UNSUB = "https://api.corben.world/campaigns/unsubscribe?user_id=c23ea202-5442-41b4-9a1b-1586e81c50eb&email=";
|
||||
var SANS = "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif";
|
||||
|
||||
// One shell for every outbound email so they all look like the site. `image`
|
||||
|
|
@ -94,7 +95,9 @@ function shell(opts) {
|
|||
'<p style="margin:0;font:400 12px/1.6 ' + SANS + ';color:#6f6d88;border-top:1px solid rgba(255,255,255,.08);padding-top:18px;">' +
|
||||
'Hosted by <a href="https://onetouch.studio" style="color:#34d399;text-decoration:none;">OneTouch Studio</a>. ' +
|
||||
'You are getting this because you RSVPed at <a href="' + SITE + '" style="color:#34d399;text-decoration:none;">foundersbreak.onetouch.studio</a>.' +
|
||||
'Reply and we will take you off the list.' +
|
||||
'</p><p style="margin:12px 0 0;font:400 12px/1.6 ' + SANS + ';color:#6f6d88;">' +
|
||||
'<a href="' + UNSUB + (opts.unsubscribeFor || '') + '" style="color:#8b89a6;text-decoration:underline;">Unsubscribe from these emails</a>' +
|
||||
' and we will not email you about the Break again.' +
|
||||
'</p></td></tr>' +
|
||||
'</table></td></tr></table></body></html>';
|
||||
}
|
||||
|
|
@ -108,7 +111,7 @@ function marquee(when) {
|
|||
'<p style="margin:14px 0 0;font:400 15px/1.55 ' + SANS + ';color:#c2c0d4;">' +
|
||||
'<a href="' + MAP + '" style="color:#c2c0d4;text-decoration:none;border-bottom:1px solid rgba(16,185,129,.45);">' +
|
||||
VENUE + '<br>' + STREET + ', ' + CITY + '</a></p>' +
|
||||
'<p style="margin:12px 0 0;font:400 14px/1.5 ' + SANS + ';color:#8b89a6;">Free to get in. 18 and over, so bring an ID.</p>' +
|
||||
'<p style="margin:12px 0 0;font:400 14px/1.5 ' + SANS + ';color:#8b89a6;">Free entry. 18 and over, so bring an ID.</p>' +
|
||||
'</td></tr></table>';
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +138,7 @@ function answerButtons() {
|
|||
'<a href="' + no + '" style="display:inline-block;padding:12px 26px;font:600 15px/1 ' + SANS + ';color:#c2c0d4;text-decoration:none;border-radius:10px;">Not this week</a>' +
|
||||
'</td>' +
|
||||
'</tr></table>' +
|
||||
'<p style="margin:12px 0 0;font:400 13px/1.5 ' + SANS + ';color:#6f6d88;">One tap, nothing to fill in. You are already on the list either way.</p>';
|
||||
'<p style="margin:12px 0 0;font:400 13px/1.5 ' + SANS + ';color:#6f6d88;">One tap, nothing to fill in. Either answer keeps you on the list for future weeks.</p>';
|
||||
}
|
||||
|
||||
// ── main ────────────────────────────────────────────────────────────────
|
||||
|
|
@ -157,9 +160,9 @@ if (!force && (await kv.get(marker))) return { skipped: "already sent for " + ta
|
|||
var when = pretty(target);
|
||||
|
||||
var body =
|
||||
'<p style="margin:0;">Tomorrow night. Same tables, same time. Come build something with people who are also building something.</p>' +
|
||||
'<p style="margin:0;">The Break is tomorrow night. Same place, same time.</p>' +
|
||||
marquee(when) +
|
||||
'<p style="margin:20px 0 0;">Still zero pool skills required. Bring a valid ID and whatever you are working on.</p>' +
|
||||
'<p style="margin:20px 0 0;">Bring a valid ID and whatever you are working on.</p>' +
|
||||
answerButtons();
|
||||
|
||||
var campaign = await tool("create_campaign", {
|
||||
|
|
@ -174,9 +177,10 @@ var campaign = await tool("create_campaign", {
|
|||
image: HERO,
|
||||
imageAlt: "A blueprint drawing of a pool table and the break shot.",
|
||||
body: body,
|
||||
unsubscribeFor: '{{email}}',
|
||||
}),
|
||||
text_body: "The Break is tomorrow, " + when + ".\n\n" + VENUE + ", " + STREET + ", " + CITY +
|
||||
"\n7:00 to 9:00 PM. Free, 18+.\n\nZero pool skills required. Bring a valid ID.\n\n" + SITE,
|
||||
"\n7:00 to 9:00 PM. Free entry, 18 and over.\n\nBring a valid ID.\n\n" + SITE,
|
||||
tags: ["founders-break", "reminder"],
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ function prettyDate(iso) {
|
|||
}
|
||||
|
||||
var HERO = "https://nyc3.digitaloceanspaces.com/corben-sites/images/c23ea202-5442-41b4-9a1b-1586e81c50eb/e7119448-83ba-4da4-95d8-b64045c21160/email-header-trim.png";
|
||||
var UNSUB = "https://api.corben.world/campaigns/unsubscribe?user_id=c23ea202-5442-41b4-9a1b-1586e81c50eb&email=";
|
||||
var SANS = "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif";
|
||||
|
||||
// One shell for every outbound email so they all look like the site. `image`
|
||||
|
|
@ -73,7 +74,9 @@ function shell(opts) {
|
|||
'<p style="margin:0;font:400 12px/1.6 ' + SANS + ';color:#6f6d88;border-top:1px solid rgba(255,255,255,.08);padding-top:18px;">' +
|
||||
'Hosted by <a href="https://onetouch.studio" style="color:#34d399;text-decoration:none;">OneTouch Studio</a>. ' +
|
||||
'You are getting this because you RSVPed at <a href="' + SITE + '" style="color:#34d399;text-decoration:none;">foundersbreak.onetouch.studio</a>.' +
|
||||
'Reply and we will take you off the list.' +
|
||||
'</p><p style="margin:12px 0 0;font:400 12px/1.6 ' + SANS + ';color:#6f6d88;">' +
|
||||
'<a href="' + UNSUB + (opts.unsubscribeFor || '') + '" style="color:#8b89a6;text-decoration:underline;">Unsubscribe from these emails</a>' +
|
||||
' and we will not email you about the Break again.' +
|
||||
'</p></td></tr>' +
|
||||
'</table></td></tr></table></body></html>';
|
||||
}
|
||||
|
|
@ -87,7 +90,7 @@ function marquee(when) {
|
|||
'<p style="margin:14px 0 0;font:400 15px/1.55 ' + SANS + ';color:#c2c0d4;">' +
|
||||
'<a href="' + MAP + '" style="color:#c2c0d4;text-decoration:none;border-bottom:1px solid rgba(16,185,129,.45);">' +
|
||||
VENUE + '<br>' + STREET + ', ' + CITY + '</a></p>' +
|
||||
'<p style="margin:12px 0 0;font:400 14px/1.5 ' + SANS + ';color:#8b89a6;">Free to get in. 18 and over, so bring an ID.</p>' +
|
||||
'<p style="margin:12px 0 0;font:400 14px/1.5 ' + SANS + ';color:#8b89a6;">Free entry. 18 and over, so bring an ID.</p>' +
|
||||
'</td></tr></table>';
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +119,13 @@ if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email)) return { skipped: "no usable e
|
|||
var seenKey = "founders-break:seen:" + email.toLowerCase();
|
||||
var returning = !!(await kv.get(seenKey));
|
||||
|
||||
// One answer per person per event date. Clicking Yes then No must land on No,
|
||||
// and clicking the same answer twice must not send a second email.
|
||||
var answerKey = "founders-break:answer:" + (eventDate || "next") + ":" + email.toLowerCase();
|
||||
var previous = await kv.get(answerKey);
|
||||
var changed = previous !== attending;
|
||||
await kv.set(answerKey, attending);
|
||||
|
||||
var first = name.split(/\s+/)[0] || "there";
|
||||
var last = name.split(/\s+/).slice(1).join(" ");
|
||||
var when = prettyDate(eventDate);
|
||||
|
|
@ -194,11 +204,14 @@ if (attending === "no") {
|
|||
// Saying no gets no email. Confirming a non-attendance by mail is noise, and
|
||||
// the page they clicked from already told them it landed.
|
||||
mailed = "skipped (declined)";
|
||||
} else if (!changed) {
|
||||
// Same answer as last time, most likely a second click on the same link.
|
||||
mailed = "skipped (already confirmed)";
|
||||
} else if (returning) {
|
||||
var backBody =
|
||||
'<p style="margin:0;">You are down for this one, ' + esc(first) + '. Same place, same time.</p>' +
|
||||
'<p style="margin:0;">You are confirmed for this week, ' + esc(first) + '. Same place, same time.</p>' +
|
||||
marquee(when) +
|
||||
'<p style="margin:20px 0 0;">Bring whatever you are working on. If you know someone who should be in that room, forward this.</p>' +
|
||||
'<p style="margin:20px 0 0;">Bring whatever you are working on. Forward this to anyone who should be there.</p>' +
|
||||
button("See what to expect", SITE);
|
||||
|
||||
await tool("send_email", {
|
||||
|
|
@ -206,40 +219,42 @@ if (attending === "no") {
|
|||
to: email,
|
||||
subject: "See you " + when,
|
||||
html: shell({
|
||||
headline: "You are down for " + when.split(",")[0] + ".",
|
||||
headline: "Confirmed for " + when.split(",")[0] + ".",
|
||||
preview: when + ", 7:00 PM at Silver Q. Free, 18+.",
|
||||
image: HERO,
|
||||
imageAlt: "A blueprint drawing of a pool table and the break shot.",
|
||||
body: backBody,
|
||||
unsubscribeFor: encodeURIComponent(email),
|
||||
}),
|
||||
text: "You are down for this one, " + first + ".\n\n" + when + ", 7:00 to 9:00 PM\n" +
|
||||
VENUE + "\n" + STREET + ", " + CITY + "\nFree to get in. 18 and over, so bring an ID.\n\n" + SITE,
|
||||
text: "You are confirmed for this week, " + first + ".\n\n" + when + ", 7:00 to 9:00 PM\n" +
|
||||
VENUE + "\n" + STREET + ", " + CITY + "\nFree entry. 18 and over, so bring an ID.\n\n" + SITE,
|
||||
});
|
||||
mailed = "returning";
|
||||
} else {
|
||||
var confirmBody =
|
||||
'<p style="margin:0;">Nice one, ' + esc(first) + '. Nothing else to do now. Just turn up and find us at the tables.</p>' +
|
||||
'<p style="margin:0;">You are on the list, ' + esc(first) + '. There is nothing else to do before then.</p>' +
|
||||
marquee(when) +
|
||||
'<p style="margin:20px 0 0;">You do not need to be good at pool. Most of us are not. Bring whatever you are working on and we will point you at the people worth meeting.</p>' +
|
||||
'<p style="margin:14px 0 0;">We will nudge you the day before so it does not sneak up on you.</p>' +
|
||||
'<p style="margin:20px 0 0;">Pool experience is not expected. Bring whatever you are working on and we will introduce you to the people worth meeting.</p>' +
|
||||
'<p style="margin:14px 0 0;">A reminder will follow the day before.</p>' +
|
||||
button("See what to expect", SITE);
|
||||
|
||||
await tool("send_email", {
|
||||
from: env.FROM,
|
||||
to: email,
|
||||
subject: "You are in: The Founder's Break, " + when,
|
||||
subject: "You are on the list: The Founder's Break, " + when,
|
||||
html: shell({
|
||||
headline: "You are in.",
|
||||
headline: "You are on the list.",
|
||||
preview: when + ", 7:00 PM at Silver Q. Free, 18+.",
|
||||
image: HERO,
|
||||
imageAlt: "A blueprint drawing of a pool table and the break shot.",
|
||||
body: confirmBody,
|
||||
unsubscribeFor: encodeURIComponent(email),
|
||||
}),
|
||||
text: "Nice one, " + first + ". Nothing else to do now, just turn up and find us at the tables.\n\n" +
|
||||
text: "You are on the list, " + first + ". There is nothing else to do before then.\n\n" +
|
||||
when + ", 7:00 to 9:00 PM\n" + VENUE + "\n" + STREET + ", " + CITY +
|
||||
"\nFree to get in. 18 and over, so bring an ID.\n\n" +
|
||||
"You do not need to be good at pool. Bring whatever you are working on.\n" +
|
||||
"We will nudge you the day before.\n\n" + SITE,
|
||||
"\nFree entry. 18 and over, so bring an ID.\n\n" +
|
||||
"Pool experience is not expected. Bring whatever you are working on.\n" +
|
||||
"A reminder will follow the day before.\n\n" + SITE,
|
||||
});
|
||||
mailed = "first time";
|
||||
}
|
||||
|
|
@ -257,7 +272,8 @@ var notifyBody =
|
|||
line("Email", email) +
|
||||
line("Building", building || "did not say") +
|
||||
line("Coming to", when) +
|
||||
line("Answer", attending === "no" ? "NOT coming this week" : "coming") +
|
||||
line("Answer", (attending === "no" ? "NOT coming this week" : "coming") +
|
||||
(previous && changed ? " (changed from " + esc(previous) + ")" : "")) +
|
||||
line("Came from", viaEmail ? "one-click in the reminder email" : "the site form") +
|
||||
line("Mailing list", listed ? "added" : "not added, check the logs") +
|
||||
'<p style="margin:18px 0 0;font:400 14px/1.5 ' + SANS + ';color:#8b89a6;">Confirmation sent: ' + mailed + '.</p>';
|
||||
|
|
@ -270,6 +286,7 @@ await tool("send_email", {
|
|||
headline: esc(name || "Someone") + (attending === "no" ? " cannot make it." : " RSVPed."),
|
||||
preview: (name || email) + " for " + when,
|
||||
body: notifyBody,
|
||||
unsubscribeFor: encodeURIComponent(env.NOTIFY),
|
||||
}),
|
||||
text: (name || "(no name)") + (attending === "no" ? " is NOT coming " : " RSVPed for ") + when +
|
||||
"\nEmail: " + email + "\nBuilding: " + (building || "did not say") +
|
||||
|
|
@ -278,5 +295,6 @@ await tool("send_email", {
|
|||
|
||||
return {
|
||||
ok: true, contact_id: contactId, listed: listed, event_date: eventDate,
|
||||
attending: attending, returning: returning, confirmation: mailed, via_email: viaEmail,
|
||||
attending: attending, previous: previous || null, changed: changed,
|
||||
returning: returning, confirmation: mailed, via_email: viaEmail,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@
|
|||
var submit = document.getElementById("rsvp-submit");
|
||||
var status = document.getElementById("form-status");
|
||||
var done = document.getElementById("rsvp-done");
|
||||
var live = document.getElementById("rsvp-live");
|
||||
var nameField = document.getElementById("f-name");
|
||||
var emailField = document.getElementById("f-email");
|
||||
|
||||
|
|
@ -225,13 +226,18 @@
|
|||
});
|
||||
|
||||
function showDone(kind) {
|
||||
// Replace the whole interactive block, not just the form, so the heading
|
||||
// and the QR do not sit stranded above the confirmation.
|
||||
if (live) live.hidden = true;
|
||||
form.hidden = true;
|
||||
done.hidden = false;
|
||||
if (kind === "no") {
|
||||
var h = done.querySelector("h3"), p = done.querySelector("p"), ics = done.querySelector(".ics");
|
||||
h.textContent = "No worries.";
|
||||
p.textContent = "We have taken you off this week. You will still get next week's reminder.";
|
||||
if (kind === "no") {
|
||||
h.textContent = "Marked as not attending.";
|
||||
p.textContent = "You are still on the list. We will send the reminder again next week.";
|
||||
if (ics) ics.hidden = true;
|
||||
} else {
|
||||
if (ics) ics.hidden = false;
|
||||
}
|
||||
done.setAttribute("tabindex", "-1");
|
||||
done.focus({ preventScroll: true });
|
||||
|
|
|
|||
|
|
@ -276,6 +276,10 @@
|
|||
<!-- RSVP -->
|
||||
<section class="rsvp" id="rsvp">
|
||||
<div class="rsvp-panel reveal">
|
||||
<!-- Everything the visitor interacts with lives in one wrapper so the
|
||||
confirmation can replace the whole block. Hiding only the form left
|
||||
the heading and the QR stranded above the confirmation. -->
|
||||
<div id="rsvp-live">
|
||||
<h2>RSVP for <%= isTonight ? "tonight" : nextLong %></h2>
|
||||
<p class="rsvp-sub">Not required, but it helps us know how many tables to hold. We will email you a reminder the day before.</p>
|
||||
|
||||
|
|
@ -316,14 +320,15 @@
|
|||
alt="QR code linking to the RSVP form on this page.">
|
||||
</a>
|
||||
<div class="qr-copy">
|
||||
<p class="qr-title">Point a phone at this.</p>
|
||||
<p>It opens straight to the RSVP. Screenshot it and send it to whoever should be there.</p>
|
||||
<p class="qr-title">Scan to RSVP</p>
|
||||
<p>Opens this page at the form. Screenshot it to share.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rsvp-done" id="rsvp-done" hidden>
|
||||
<h3>You are on the list.</h3>
|
||||
<p>Check your inbox for a confirmation. See you <%= isTonight ? "tonight" : nextLong %> at <%= V.name %>, <%= V.street %>, <%= site.startTime %>. Bring your ideas and a valid ID.</p>
|
||||
<p><%= isTonight ? "Tonight" : nextLong %> at <%= V.name %>, <%= V.street %>, <%= site.startTime %>. A confirmation is on its way to your inbox.</p>
|
||||
<a class="ics" href="/assets/founders-break.ics">Add the whole series to your calendar</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue