From 32b40631851c3c3f5ae2400c4bb89e009e71a9da Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 18 Jun 2024 17:21:34 -0500 Subject: [PATCH] Verify email reminders (#4510) * Clarify intent * Increase email reminder period to once per day * Fallback * Snooze immediately after account creation, prevent showing right after signup * Fix e2e test exports * Remove redundant check * Better simple date generation * Replace in DateField * Use non-string comparison * Revert change to unrelated code * Also parse * Remove side effect --- src/Navigation.tsx | 4 +-- src/lib/strings/time.ts | 11 +++++++ src/state/session/agent.ts | 8 +++++ src/state/shell/reminders.e2e.ts | 4 +-- src/state/shell/reminders.ts | 53 +++++++++++++++++++------------- 5 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 67b89e26..5d4ba0e3 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -54,8 +54,8 @@ import {useModalControls} from './state/modals' import {useUnreadNotifications} from './state/queries/notifications/unread' import {useSession} from './state/session' import { - setEmailConfirmationRequested, shouldRequestEmailConfirmation, + snoozeEmailConfirmationPrompt, } from './state/shell/reminders' import {AccessibilitySettingsScreen} from './view/screens/AccessibilitySettings' import {CommunityGuidelinesScreen} from './view/screens/CommunityGuidelines' @@ -585,7 +585,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { if (currentAccount && shouldRequestEmailConfirmation(currentAccount)) { openModal({name: 'verify-email', showReminder: true}) - setEmailConfirmationRequested() + snoozeEmailConfirmationPrompt() } } diff --git a/src/lib/strings/time.ts b/src/lib/strings/time.ts index 1194e024..bfefea9b 100644 --- a/src/lib/strings/time.ts +++ b/src/lib/strings/time.ts @@ -19,3 +19,14 @@ export function getAge(birthDate: Date): number { } return age } + +/** + * Compares two dates by year, month, and day only + */ +export function simpleAreDatesEqual(a: Date, b: Date): boolean { + return ( + a.getFullYear() === b.getFullYear() && + a.getMonth() === b.getMonth() && + a.getDate() === b.getDate() + ) +} diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts index 48f5614b..6afb3af8 100644 --- a/src/state/session/agent.ts +++ b/src/state/session/agent.ts @@ -11,6 +11,7 @@ import { import {tryFetchGates} from '#/lib/statsig/statsig' import {getAge} from '#/lib/strings/time' import {logger} from '#/logger' +import {snoozeEmailConfirmationPrompt} from '#/state/shell/reminders' import { configureModerationForAccount, configureModerationForGuest, @@ -191,6 +192,13 @@ export async function createAgentAndCreateAccount( agent.setPersonalDetails({birthDate: birthDate.toISOString()}) } + try { + // snooze first prompt after signup, defer to next prompt + snoozeEmailConfirmationPrompt() + } catch (e: any) { + logger.error(e, {context: `session: failed snoozeEmailConfirmationPrompt`}) + } + return prepareAgent(agent, gates, moderation, onSessionChange) } diff --git a/src/state/shell/reminders.e2e.ts b/src/state/shell/reminders.e2e.ts index e8c12792..94809a68 100644 --- a/src/state/shell/reminders.e2e.ts +++ b/src/state/shell/reminders.e2e.ts @@ -1,7 +1,5 @@ -export function init() {} - export function shouldRequestEmailConfirmation() { return false } -export function setEmailConfirmationRequested() {} +export function snoozeEmailConfirmationPrompt() {} diff --git a/src/state/shell/reminders.ts b/src/state/shell/reminders.ts index ee924eb0..db6ee939 100644 --- a/src/state/shell/reminders.ts +++ b/src/state/shell/reminders.ts @@ -1,36 +1,45 @@ +import {simpleAreDatesEqual} from '#/lib/strings/time' +import {logger} from '#/logger' import * as persisted from '#/state/persisted' -import {toHashCode} from 'lib/strings/helpers' -import {isOnboardingActive} from './onboarding' import {SessionAccount} from '../session' +import {isOnboardingActive} from './onboarding' export function shouldRequestEmailConfirmation(account: SessionAccount) { - if (!account) { - return false - } - if (account.emailConfirmed) { - return false - } - if (isOnboardingActive()) { - return false - } - // only prompt once - if (persisted.get('reminders').lastEmailConfirm) { - return false - } + // ignore logged out + if (!account) return false + // ignore confirmed accounts, this is the success state of this reminder + if (account.emailConfirmed) return false + // wait for onboarding to complete + if (isOnboardingActive()) return false + + const snoozedAt = persisted.get('reminders').lastEmailConfirm const today = new Date() - // shard the users into 2 day of the week buckets - // (this is to avoid a sudden influx of email updates when - // this feature rolls out) - const code = toHashCode(account.did) % 7 - if (code !== today.getDay() && code !== (today.getDay() + 1) % 7) { + + logger.debug('Checking email confirmation reminder', { + today, + snoozedAt, + }) + + // never been snoozed, new account + if (!snoozedAt) { + return true + } + + // already snoozed today + if (simpleAreDatesEqual(new Date(Date.parse(snoozedAt)), new Date())) { return false } + return true } -export function setEmailConfirmationRequested() { +export function snoozeEmailConfirmationPrompt() { + const lastEmailConfirm = new Date().toISOString() + logger.debug('Snoozing email confirmation reminder', { + snoozedAt: lastEmailConfirm, + }) persisted.write('reminders', { ...persisted.get('reminders'), - lastEmailConfirm: new Date().toISOString(), + lastEmailConfirm, }) }