about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2024-06-18 17:21:34 -0500
committerGitHub <noreply@github.com>2024-06-18 17:21:34 -0500
commit32b40631851c3c3f5ae2400c4bb89e009e71a9da (patch)
tree810af1880e2fddf1753c2d397b49aafe41148078
parent853c32b4d8cfe86ed02c688c32fc99e788c33838 (diff)
downloadvoidsky-32b40631851c3c3f5ae2400c4bb89e009e71a9da.tar.zst
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
-rw-r--r--src/Navigation.tsx4
-rw-r--r--src/lib/strings/time.ts11
-rw-r--r--src/state/session/agent.ts8
-rw-r--r--src/state/shell/reminders.e2e.ts4
-rw-r--r--src/state/shell/reminders.ts53
5 files changed, 53 insertions, 27 deletions
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index 67b89e262..5d4ba0e3f 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 1194e0240..bfefea9bc 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 48f5614bd..6afb3af88 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 e8c12792a..94809a680 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 ee924eb00..db6ee9391 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,
   })
 }