Bluesky app fork with some witchin' additions 💫

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

authored by

Eric Bailey and committed by
GitHub
32b40631 853c32b4

+53 -27
+2 -2
src/Navigation.tsx
··· 54 import {useUnreadNotifications} from './state/queries/notifications/unread' 55 import {useSession} from './state/session' 56 import { 57 - setEmailConfirmationRequested, 58 shouldRequestEmailConfirmation, 59 } from './state/shell/reminders' 60 import {AccessibilitySettingsScreen} from './view/screens/AccessibilitySettings' 61 import {CommunityGuidelinesScreen} from './view/screens/CommunityGuidelines' ··· 585 586 if (currentAccount && shouldRequestEmailConfirmation(currentAccount)) { 587 openModal({name: 'verify-email', showReminder: true}) 588 - setEmailConfirmationRequested() 589 } 590 } 591
··· 54 import {useUnreadNotifications} from './state/queries/notifications/unread' 55 import {useSession} from './state/session' 56 import { 57 shouldRequestEmailConfirmation, 58 + snoozeEmailConfirmationPrompt, 59 } from './state/shell/reminders' 60 import {AccessibilitySettingsScreen} from './view/screens/AccessibilitySettings' 61 import {CommunityGuidelinesScreen} from './view/screens/CommunityGuidelines' ··· 585 586 if (currentAccount && shouldRequestEmailConfirmation(currentAccount)) { 587 openModal({name: 'verify-email', showReminder: true}) 588 + snoozeEmailConfirmationPrompt() 589 } 590 } 591
+11
src/lib/strings/time.ts
··· 19 } 20 return age 21 }
··· 19 } 20 return age 21 } 22 + 23 + /** 24 + * Compares two dates by year, month, and day only 25 + */ 26 + export function simpleAreDatesEqual(a: Date, b: Date): boolean { 27 + return ( 28 + a.getFullYear() === b.getFullYear() && 29 + a.getMonth() === b.getMonth() && 30 + a.getDate() === b.getDate() 31 + ) 32 + }
+8
src/state/session/agent.ts
··· 11 import {tryFetchGates} from '#/lib/statsig/statsig' 12 import {getAge} from '#/lib/strings/time' 13 import {logger} from '#/logger' 14 import { 15 configureModerationForAccount, 16 configureModerationForGuest, ··· 189 } 190 } else { 191 agent.setPersonalDetails({birthDate: birthDate.toISOString()}) 192 } 193 194 return prepareAgent(agent, gates, moderation, onSessionChange)
··· 11 import {tryFetchGates} from '#/lib/statsig/statsig' 12 import {getAge} from '#/lib/strings/time' 13 import {logger} from '#/logger' 14 + import {snoozeEmailConfirmationPrompt} from '#/state/shell/reminders' 15 import { 16 configureModerationForAccount, 17 configureModerationForGuest, ··· 190 } 191 } else { 192 agent.setPersonalDetails({birthDate: birthDate.toISOString()}) 193 + } 194 + 195 + try { 196 + // snooze first prompt after signup, defer to next prompt 197 + snoozeEmailConfirmationPrompt() 198 + } catch (e: any) { 199 + logger.error(e, {context: `session: failed snoozeEmailConfirmationPrompt`}) 200 } 201 202 return prepareAgent(agent, gates, moderation, onSessionChange)
+1 -3
src/state/shell/reminders.e2e.ts
··· 1 - export function init() {} 2 - 3 export function shouldRequestEmailConfirmation() { 4 return false 5 } 6 7 - export function setEmailConfirmationRequested() {}
··· 1 export function shouldRequestEmailConfirmation() { 2 return false 3 } 4 5 + export function snoozeEmailConfirmationPrompt() {}
+31 -22
src/state/shell/reminders.ts
··· 1 import * as persisted from '#/state/persisted' 2 - import {toHashCode} from 'lib/strings/helpers' 3 import {isOnboardingActive} from './onboarding' 4 - import {SessionAccount} from '../session' 5 6 export function shouldRequestEmailConfirmation(account: SessionAccount) { 7 - if (!account) { 8 - return false 9 } 10 - if (account.emailConfirmed) { 11 - return false 12 - } 13 - if (isOnboardingActive()) { 14 - return false 15 - } 16 - // only prompt once 17 - if (persisted.get('reminders').lastEmailConfirm) { 18 - return false 19 - } 20 - const today = new Date() 21 - // shard the users into 2 day of the week buckets 22 - // (this is to avoid a sudden influx of email updates when 23 - // this feature rolls out) 24 - const code = toHashCode(account.did) % 7 25 - if (code !== today.getDay() && code !== (today.getDay() + 1) % 7) { 26 return false 27 } 28 return true 29 } 30 31 - export function setEmailConfirmationRequested() { 32 persisted.write('reminders', { 33 ...persisted.get('reminders'), 34 - lastEmailConfirm: new Date().toISOString(), 35 }) 36 }
··· 1 + import {simpleAreDatesEqual} from '#/lib/strings/time' 2 + import {logger} from '#/logger' 3 import * as persisted from '#/state/persisted' 4 + import {SessionAccount} from '../session' 5 import {isOnboardingActive} from './onboarding' 6 7 export function shouldRequestEmailConfirmation(account: SessionAccount) { 8 + // ignore logged out 9 + if (!account) return false 10 + // ignore confirmed accounts, this is the success state of this reminder 11 + if (account.emailConfirmed) return false 12 + // wait for onboarding to complete 13 + if (isOnboardingActive()) return false 14 + 15 + const snoozedAt = persisted.get('reminders').lastEmailConfirm 16 + const today = new Date() 17 + 18 + logger.debug('Checking email confirmation reminder', { 19 + today, 20 + snoozedAt, 21 + }) 22 + 23 + // never been snoozed, new account 24 + if (!snoozedAt) { 25 + return true 26 } 27 + 28 + // already snoozed today 29 + if (simpleAreDatesEqual(new Date(Date.parse(snoozedAt)), new Date())) { 30 return false 31 } 32 + 33 return true 34 } 35 36 + export function snoozeEmailConfirmationPrompt() { 37 + const lastEmailConfirm = new Date().toISOString() 38 + logger.debug('Snoozing email confirmation reminder', { 39 + snoozedAt: lastEmailConfirm, 40 + }) 41 persisted.write('reminders', { 42 ...persisted.get('reminders'), 43 + lastEmailConfirm, 44 }) 45 }