about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/App.native.tsx27
-rw-r--r--src/App.web.tsx15
-rw-r--r--src/components/dialogs/nuxs/index.tsx6
-rw-r--r--src/lib/hooks/useEmail.ts36
-rw-r--r--src/lib/hooks/useOpenComposer.tsx4
-rw-r--r--src/lib/hooks/useRequireEmailVerification.tsx2
-rw-r--r--src/screens/Messages/Conversation.tsx2
-rw-r--r--src/screens/Messages/components/MessageInput.tsx2
-rw-r--r--src/screens/Messages/components/RequestButtons.tsx2
-rw-r--r--src/state/email-verification.tsx64
-rw-r--r--src/state/queries/email-verification-required.ts25
-rw-r--r--src/state/service-config.tsx22
12 files changed, 119 insertions, 88 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 4037eedd2..0b46da9dd 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -29,6 +29,7 @@ import {Provider as A11yProvider} from '#/state/a11y'
 import {Provider as AgeAssuranceProvider} from '#/state/ageAssurance'
 import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
 import {Provider as DialogStateProvider} from '#/state/dialogs'
+import {Provider as EmailVerificationProvider} from '#/state/email-verification'
 import {listenSessionDropped} from '#/state/events'
 import {
   beginResolveGeolocation,
@@ -155,18 +156,20 @@ function InnerApp() {
                                               <MutedThreadsProvider>
                                                 <ProgressGuideProvider>
                                                   <ServiceAccountManager>
-                                                    <HideBottomBarBorderProvider>
-                                                      <GestureHandlerRootView
-                                                        style={s.h100pct}>
-                                                        <GlobalGestureEventsProvider>
-                                                          <IntentDialogProvider>
-                                                            <TestCtrls />
-                                                            <Shell />
-                                                            <NuxDialogs />
-                                                          </IntentDialogProvider>
-                                                        </GlobalGestureEventsProvider>
-                                                      </GestureHandlerRootView>
-                                                    </HideBottomBarBorderProvider>
+                                                    <EmailVerificationProvider>
+                                                      <HideBottomBarBorderProvider>
+                                                        <GestureHandlerRootView
+                                                          style={s.h100pct}>
+                                                          <GlobalGestureEventsProvider>
+                                                            <IntentDialogProvider>
+                                                              <TestCtrls />
+                                                              <Shell />
+                                                              <NuxDialogs />
+                                                            </IntentDialogProvider>
+                                                          </GlobalGestureEventsProvider>
+                                                        </GestureHandlerRootView>
+                                                      </HideBottomBarBorderProvider>
+                                                    </EmailVerificationProvider>
                                                   </ServiceAccountManager>
                                                 </ProgressGuideProvider>
                                               </MutedThreadsProvider>
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 2897aa3f2..dfa1e7480 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -18,6 +18,7 @@ import {Provider as A11yProvider} from '#/state/a11y'
 import {Provider as AgeAssuranceProvider} from '#/state/ageAssurance'
 import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
 import {Provider as DialogStateProvider} from '#/state/dialogs'
+import {Provider as EmailVerificationProvider} from '#/state/email-verification'
 import {listenSessionDropped} from '#/state/events'
 import {
   beginResolveGeolocation,
@@ -136,12 +137,14 @@ function InnerApp() {
                                                 <SafeAreaProvider>
                                                   <ProgressGuideProvider>
                                                     <ServiceConfigProvider>
-                                                      <HideBottomBarBorderProvider>
-                                                        <IntentDialogProvider>
-                                                          <Shell />
-                                                          <NuxDialogs />
-                                                        </IntentDialogProvider>
-                                                      </HideBottomBarBorderProvider>
+                                                      <EmailVerificationProvider>
+                                                        <HideBottomBarBorderProvider>
+                                                          <IntentDialogProvider>
+                                                            <Shell />
+                                                            <NuxDialogs />
+                                                          </IntentDialogProvider>
+                                                        </HideBottomBarBorderProvider>
+                                                      </EmailVerificationProvider>
                                                     </ServiceConfigProvider>
                                                   </ProgressGuideProvider>
                                                 </SafeAreaProvider>
diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx
index 2daf4a268..985d58eec 100644
--- a/src/components/dialogs/nuxs/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -3,6 +3,7 @@ import {type AppBskyActorDefs} from '@atproto/api'
 
 import {useGate} from '#/lib/statsig/statsig'
 import {logger} from '#/logger'
+import {STALE} from '#/state/queries'
 import {Nux, useNuxs, useResetNuxs, useSaveNux} from '#/state/queries/nuxs'
 import {
   usePreferencesQuery,
@@ -56,7 +57,10 @@ export function useNuxDialogContext() {
 export function NuxDialogs() {
   const {currentAccount} = useSession()
   const {data: preferences} = usePreferencesQuery()
-  const {data: profile} = useProfileQuery({did: currentAccount?.did})
+  const {data: profile} = useProfileQuery({
+    did: currentAccount?.did,
+    staleTime: STALE.INFINITY, // createdAt isn't gonna change
+  })
   const onboardingActive = useOnboardingState().isActive
 
   const isLoading =
diff --git a/src/lib/hooks/useEmail.ts b/src/lib/hooks/useEmail.ts
deleted file mode 100644
index 1e940ec96..000000000
--- a/src/lib/hooks/useEmail.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import {STALE} from '#/state/queries'
-import {useServiceConfigQuery} from '#/state/queries/email-verification-required'
-import {useProfileQuery} from '#/state/queries/profile'
-import {useSession} from '#/state/session'
-import {BSKY_SERVICE} from '../constants'
-import {getHostnameFromUrl} from '../strings/url-helpers'
-
-export function useEmail() {
-  const {currentAccount} = useSession()
-
-  const {data: serviceConfig} = useServiceConfigQuery()
-  const {data: profile} = useProfileQuery({
-    did: currentAccount?.did,
-    staleTime: STALE.INFINITY,
-  })
-
-  const checkEmailConfirmed = !!serviceConfig?.checkEmailConfirmed
-
-  // Date set for 11 AM PST on the 18th of November
-  const isNewEnough =
-    !!profile?.createdAt &&
-    Date.parse(profile.createdAt) >= Date.parse('2024-11-18T19:00:00.000Z')
-
-  const isSelfHost =
-    currentAccount &&
-    getHostnameFromUrl(currentAccount.service) !==
-      getHostnameFromUrl(BSKY_SERVICE)
-
-  const needsEmailVerification =
-    !isSelfHost &&
-    checkEmailConfirmed &&
-    !currentAccount?.emailConfirmed &&
-    isNewEnough
-
-  return {needsEmailVerification}
-}
diff --git a/src/lib/hooks/useOpenComposer.tsx b/src/lib/hooks/useOpenComposer.tsx
index 50c04d1e1..789fa1f87 100644
--- a/src/lib/hooks/useOpenComposer.tsx
+++ b/src/lib/hooks/useOpenComposer.tsx
@@ -2,10 +2,10 @@ import {useMemo} from 'react'
 import {Trans} from '@lingui/macro'
 
 import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
-import {useOpenComposer as rootUseOpenComposer} from '#/state/shell/composer'
+import {useOpenComposer as useRootOpenComposer} from '#/state/shell/composer'
 
 export function useOpenComposer() {
-  const {openComposer} = rootUseOpenComposer()
+  const {openComposer} = useRootOpenComposer()
   const requireEmailVerification = useRequireEmailVerification()
   return useMemo(() => {
     return {
diff --git a/src/lib/hooks/useRequireEmailVerification.tsx b/src/lib/hooks/useRequireEmailVerification.tsx
index 26045847e..2e5b33978 100644
--- a/src/lib/hooks/useRequireEmailVerification.tsx
+++ b/src/lib/hooks/useRequireEmailVerification.tsx
@@ -1,7 +1,7 @@
 import {useCallback} from 'react'
 import {Keyboard} from 'react-native'
 
-import {useEmail} from '#/lib/hooks/useEmail'
+import {useEmail} from '#/state/email-verification'
 import {useRequireAuth, useSession} from '#/state/session'
 import {useCloseAllActiveElements} from '#/state/util'
 import {
diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx
index 7f3b53b94..2e820cc12 100644
--- a/src/screens/Messages/Conversation.tsx
+++ b/src/screens/Messages/Conversation.tsx
@@ -15,7 +15,6 @@ import {
 } from '@react-navigation/native'
 import {type NativeStackScreenProps} from '@react-navigation/native-stack'
 
-import {useEmail} from '#/lib/hooks/useEmail'
 import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
 import {
@@ -24,6 +23,7 @@ import {
 } from '#/lib/routes/types'
 import {isWeb} from '#/platform/detection'
 import {type Shadow, useMaybeProfileShadow} from '#/state/cache/profile-shadow'
+import {useEmail} from '#/state/email-verification'
 import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo'
 import {ConvoStatus} from '#/state/messages/convo/types'
 import {useCurrentConvoId} from '#/state/messages/current-convo-id'
diff --git a/src/screens/Messages/components/MessageInput.tsx b/src/screens/Messages/components/MessageInput.tsx
index 6cde1d4fe..87f22b232 100644
--- a/src/screens/Messages/components/MessageInput.tsx
+++ b/src/screens/Messages/components/MessageInput.tsx
@@ -18,8 +18,8 @@ import Graphemer from 'graphemer'
 
 import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
 import {useHaptics} from '#/lib/haptics'
-import {useEmail} from '#/lib/hooks/useEmail'
 import {isIOS, isWeb} from '#/platform/detection'
+import {useEmail} from '#/state/email-verification'
 import {
   useMessageDraft,
   useSaveMessageDraft,
diff --git a/src/screens/Messages/components/RequestButtons.tsx b/src/screens/Messages/components/RequestButtons.tsx
index 3490bec0d..560888552 100644
--- a/src/screens/Messages/components/RequestButtons.tsx
+++ b/src/screens/Messages/components/RequestButtons.tsx
@@ -5,9 +5,9 @@ import {useLingui} from '@lingui/react'
 import {StackActions, useNavigation} from '@react-navigation/native'
 import {useQueryClient} from '@tanstack/react-query'
 
-import {useEmail} from '#/lib/hooks/useEmail'
 import {type NavigationProp} from '#/lib/routes/types'
 import {useProfileShadow} from '#/state/cache/profile-shadow'
+import {useEmail} from '#/state/email-verification'
 import {useAcceptConversation} from '#/state/queries/messages/accept-conversation'
 import {precacheConvoQuery} from '#/state/queries/messages/conversation'
 import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
diff --git a/src/state/email-verification.tsx b/src/state/email-verification.tsx
new file mode 100644
index 000000000..0528d59ea
--- /dev/null
+++ b/src/state/email-verification.tsx
@@ -0,0 +1,64 @@
+import {createContext, useContext, useMemo} from 'react'
+
+import {BSKY_SERVICE} from '#/lib/constants'
+import {getHostnameFromUrl} from '#/lib/strings/url-helpers'
+import {STALE} from '#/state/queries'
+import {useProfileQuery} from '#/state/queries/profile'
+import {useCheckEmailConfirmed} from '#/state/service-config'
+import {useSession} from '#/state/session'
+
+type EmailVerificationContext = {
+  needsEmailVerification: boolean
+}
+
+const EmailVerificationContext = createContext<EmailVerificationContext | null>(
+  null,
+)
+EmailVerificationContext.displayName = 'EmailVerificationContext'
+
+export function Provider({children}: {children: React.ReactNode}) {
+  const {currentAccount} = useSession()
+
+  const {data: profile} = useProfileQuery({
+    did: currentAccount?.did,
+    staleTime: STALE.INFINITY,
+  })
+
+  const checkEmailConfirmed = useCheckEmailConfirmed()
+
+  // Date set for 11 AM PST on the 18th of November
+  const isNewEnough =
+    !!profile?.createdAt &&
+    Date.parse(profile.createdAt) >= Date.parse('2024-11-18T19:00:00.000Z')
+
+  const isSelfHost =
+    currentAccount &&
+    getHostnameFromUrl(currentAccount.service) !==
+      getHostnameFromUrl(BSKY_SERVICE)
+
+  const needsEmailVerification =
+    !isSelfHost &&
+    checkEmailConfirmed &&
+    !currentAccount?.emailConfirmed &&
+    isNewEnough
+
+  const value = useMemo(
+    () => ({needsEmailVerification}),
+    [needsEmailVerification],
+  )
+
+  return (
+    <EmailVerificationContext.Provider value={value}>
+      {children}
+    </EmailVerificationContext.Provider>
+  )
+}
+Provider.displayName = 'EmailVerificationProvider'
+
+export function useEmail() {
+  const ctx = useContext(EmailVerificationContext)
+  if (!ctx) {
+    throw new Error('useEmail must be used within a EmailVerificationProvider')
+  }
+  return ctx
+}
diff --git a/src/state/queries/email-verification-required.ts b/src/state/queries/email-verification-required.ts
deleted file mode 100644
index 94ff5cbc6..000000000
--- a/src/state/queries/email-verification-required.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import {useQuery} from '@tanstack/react-query'
-
-interface ServiceConfig {
-  checkEmailConfirmed: boolean
-}
-
-export function useServiceConfigQuery() {
-  return useQuery({
-    queryKey: ['service-config'],
-    queryFn: async () => {
-      const res = await fetch(
-        'https://api.bsky.app/xrpc/app.bsky.unspecced.getConfig',
-      )
-      if (!res.ok) {
-        return {
-          checkEmailConfirmed: false,
-        }
-      }
-
-      const json = await res.json()
-      return json as ServiceConfig
-    },
-    staleTime: 5 * 60 * 1000,
-  })
-}
diff --git a/src/state/service-config.tsx b/src/state/service-config.tsx
index 94be5b445..242022b60 100644
--- a/src/state/service-config.tsx
+++ b/src/state/service-config.tsx
@@ -21,6 +21,8 @@ TrendingContext.displayName = 'TrendingContext'
 const LiveNowContext = createContext<LiveNowContext | null>(null)
 LiveNowContext.displayName = 'LiveNowContext'
 
+const CheckEmailConfirmedContext = createContext<boolean | null>(null)
+
 export function Provider({children}: {children: React.ReactNode}) {
   const langPrefs = useLanguagePrefs()
   const {data: config, isLoading: isInitialLoad} = useServiceConfigQuery()
@@ -61,10 +63,16 @@ export function Provider({children}: {children: React.ReactNode}) {
 
   const liveNow = useMemo<LiveNowContext>(() => config?.liveNow ?? [], [config])
 
+  // probably true, so default to true when loading
+  // if the call fails, the query will set it to false for us
+  const checkEmailConfirmed = config?.checkEmailConfirmed ?? true
+
   return (
     <TrendingContext.Provider value={trending}>
       <LiveNowContext.Provider value={liveNow}>
-        {children}
+        <CheckEmailConfirmedContext.Provider value={checkEmailConfirmed}>
+          {children}
+        </CheckEmailConfirmedContext.Provider>
       </LiveNowContext.Provider>
     </TrendingContext.Provider>
   )
@@ -78,7 +86,7 @@ export function useLiveNowConfig() {
   const ctx = useContext(LiveNowContext)
   if (!ctx) {
     throw new Error(
-      'useLiveNowConfig must be used within a LiveNowConfigProvider',
+      'useLiveNowConfig must be used within a ServiceConfigManager',
     )
   }
   return ctx
@@ -88,3 +96,13 @@ export function useCanGoLive(did?: string) {
   const config = useLiveNowConfig()
   return !!config.find(cfg => cfg.did === did)
 }
+
+export function useCheckEmailConfirmed() {
+  const ctx = useContext(CheckEmailConfirmedContext)
+  if (ctx === null) {
+    throw new Error(
+      'useCheckEmailConfirmed must be used within a ServiceConfigManager',
+    )
+  }
+  return ctx
+}