about summary refs log tree commit diff
path: root/src/state
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-08-14 01:13:51 +0300
committerGitHub <noreply@github.com>2025-08-14 01:13:51 +0300
commit7a334362aefadca29bef9de0f4459d0577d328ca (patch)
tree218ffe4b79e096c5926c38f3269a13d42f798961 /src/state
parentb2c56cbd6dfa9af576f947dd41a0d33376b184d1 (diff)
downloadvoidsky-7a334362aefadca29bef9de0f4459d0577d328ca.tar.zst
[Perf - part 1] Hoist service config query (#8812)
Diffstat (limited to 'src/state')
-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
3 files changed, 84 insertions, 27 deletions
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
+}