Add liveEvents context
This commit is contained in:
+8
-1
@@ -69,6 +69,10 @@ import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbe
|
||||
import {ToastOutlet} from '#/components/Toast'
|
||||
import {Provider as AgeAssuranceV2Provider} from '#/ageAssurance'
|
||||
import {prefetchAgeAssuranceConfig} from '#/ageAssurance'
|
||||
import {
|
||||
prefetchLiveEvents,
|
||||
Provider as LiveEventsProvider,
|
||||
} from '#/features/liveEvents/context'
|
||||
import * as Geo from '#/geolocation'
|
||||
import {Splash} from '#/Splash'
|
||||
import {BottomSheetProvider} from '../modules/bottom-sheet'
|
||||
@@ -92,6 +96,7 @@ if (isAndroid) {
|
||||
*/
|
||||
Geo.resolve()
|
||||
prefetchAgeAssuranceConfig()
|
||||
prefetchLiveEvents()
|
||||
|
||||
function InnerApp() {
|
||||
const [isReady, setIsReady] = React.useState(false)
|
||||
@@ -230,7 +235,9 @@ function App() {
|
||||
<StarterPackProvider>
|
||||
<SafeAreaProvider
|
||||
initialMetrics={initialWindowMetrics}>
|
||||
<InnerApp />
|
||||
<LiveEventsProvider>
|
||||
<InnerApp />
|
||||
</LiveEventsProvider>
|
||||
</SafeAreaProvider>
|
||||
</StarterPackProvider>
|
||||
</BottomSheetProvider>
|
||||
|
||||
+8
-1
@@ -57,6 +57,10 @@ import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbe
|
||||
import {ToastOutlet} from '#/components/Toast'
|
||||
import {Provider as AgeAssuranceV2Provider} from '#/ageAssurance'
|
||||
import {prefetchAgeAssuranceConfig} from '#/ageAssurance'
|
||||
import {
|
||||
prefetchLiveEvents,
|
||||
Provider as LiveEventsProvider,
|
||||
} from '#/features/liveEvents/context'
|
||||
import * as Geo from '#/geolocation'
|
||||
import {Splash} from '#/Splash'
|
||||
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||
@@ -67,6 +71,7 @@ import {Provider as HideBottomBarBorderProvider} from './lib/hooks/useHideBottom
|
||||
*/
|
||||
Geo.resolve()
|
||||
prefetchAgeAssuranceConfig()
|
||||
prefetchLiveEvents()
|
||||
|
||||
function InnerApp() {
|
||||
const [isReady, setIsReady] = React.useState(false)
|
||||
@@ -198,7 +203,9 @@ function App() {
|
||||
<LightboxStateProvider>
|
||||
<PortalProvider>
|
||||
<StarterPackProvider>
|
||||
<InnerApp />
|
||||
<LiveEventsProvider>
|
||||
<InnerApp />
|
||||
</LiveEventsProvider>
|
||||
</StarterPackProvider>
|
||||
</PortalProvider>
|
||||
</LightboxStateProvider>
|
||||
|
||||
+6
-1
@@ -11,7 +11,12 @@ import {
|
||||
import {themes} from '#/alf/themes'
|
||||
import {type Device} from '#/storage'
|
||||
|
||||
export {type TextStyleProp, type Theme, type ViewStyleProp} from '@bsky.app/alf'
|
||||
export {
|
||||
type TextStyleProp,
|
||||
type Theme,
|
||||
utils,
|
||||
type ViewStyleProp,
|
||||
} from '@bsky.app/alf'
|
||||
export {atoms} from '#/alf/atoms'
|
||||
export * from '#/alf/breakpoints'
|
||||
export * from '#/alf/fonts'
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import {createContext, useContext} from 'react'
|
||||
import {QueryClient, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {IS_DEV, LIVE_EVENTS_URL} from '#/env'
|
||||
import {type LiveEventsWorkerResponse} from '#/features/liveEvents/types'
|
||||
|
||||
const qc = new QueryClient()
|
||||
const liveEventsQueryKey = ['live-events']
|
||||
|
||||
async function fetchLiveEvents(): Promise<LiveEventsWorkerResponse | null> {
|
||||
try {
|
||||
const res = await fetch(`${LIVE_EVENTS_URL}/config`)
|
||||
if (!res.ok) return null
|
||||
const data = await res.json()
|
||||
return data
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const Context = createContext<LiveEventsWorkerResponse>({
|
||||
feeds: [],
|
||||
})
|
||||
|
||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
const {data} = useQuery(
|
||||
{
|
||||
staleTime: IS_DEV ? 5e3 : 1000 * 60,
|
||||
queryKey: liveEventsQueryKey,
|
||||
async queryFn() {
|
||||
return fetchLiveEvents()
|
||||
},
|
||||
},
|
||||
qc,
|
||||
)
|
||||
|
||||
return (
|
||||
<Context.Provider value={data || {feeds: []}}>{children}</Context.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export async function prefetchLiveEvents() {
|
||||
const data = await fetchLiveEvents()
|
||||
if (data) {
|
||||
qc.setQueryData(liveEventsQueryKey, data)
|
||||
}
|
||||
}
|
||||
|
||||
export function useLiveEvents() {
|
||||
const ctx = useContext(Context)
|
||||
if (!ctx) {
|
||||
throw new Error('useLiveEventsContext must be used within a Provider')
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import {createContext, useContext} from 'react'
|
||||
import {QueryClient, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {IS_DEV, LIVE_EVENTS_URL} from '#/env'
|
||||
|
||||
type LiveEventFeedImageLayout = 'wide' // maybe more in the future
|
||||
type LiveEventFeedImage = {
|
||||
alt: string
|
||||
overlayColor: string
|
||||
url: string
|
||||
blurhash: string
|
||||
}
|
||||
type LiveEventFeed = {
|
||||
title: string
|
||||
url: string
|
||||
images: Partial<Record<LiveEventFeedImageLayout, LiveEventFeedImage>>
|
||||
}
|
||||
type LiveEventsConfig = {
|
||||
feeds: LiveEventFeed[]
|
||||
}
|
||||
|
||||
const qc = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
/**
|
||||
* We clear this manually, so disable automatic garbage collection.
|
||||
* @see https://tanstack.com/query/latest/docs/framework/react/plugins/persistQueryClient#how-it-works
|
||||
*/
|
||||
gcTime: Infinity,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const liveEventsQueryKey = ['live-events']
|
||||
|
||||
async function getLiveEvents(): Promise<LiveEventsConfig | null> {
|
||||
const res = await fetch(`${LIVE_EVENTS_URL}/config`)
|
||||
if (!res.ok) return null
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export function getLiveEventsCache() {
|
||||
return qc.getQueryData<LiveEventsConfig>(liveEventsQueryKey)
|
||||
}
|
||||
|
||||
export async function prefetchLiveEvents() {
|
||||
const data = await getLiveEvents()
|
||||
if (data) {
|
||||
qc.setQueryData(liveEventsQueryKey, data)
|
||||
}
|
||||
}
|
||||
|
||||
export const Context = createContext<LiveEventsConfig>({
|
||||
feeds: [],
|
||||
})
|
||||
|
||||
export function useLiveEventsContext() {
|
||||
const ctx = useContext(Context)
|
||||
if (!ctx) {
|
||||
throw new Error('useLiveEventsContext must be used within a Provider')
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
const {data} = useQuery(
|
||||
{
|
||||
/**
|
||||
* Will re-fetch when stale, at most every minute (or 5s in dev for easier
|
||||
* testing).
|
||||
*
|
||||
* @see https://tanstack.com/query/latest/docs/framework/react/guides/initial-query-data#initial-data-from-the-cache-with-initialdataupdatedat
|
||||
*/
|
||||
staleTime: IS_DEV ? 5e3 : 1000 * 60,
|
||||
/**
|
||||
* N.B. if prefetch failed above, we'll have no `initialData`, and this
|
||||
* query will run on startup.
|
||||
*/
|
||||
initialData: getLiveEventsCache(),
|
||||
initialDataUpdatedAt: () =>
|
||||
qc.getQueryState(liveEventsQueryKey)?.dataUpdatedAt,
|
||||
queryKey: liveEventsQueryKey,
|
||||
async queryFn() {
|
||||
console.debug(`live-events: fetching config`)
|
||||
return getLiveEvents()
|
||||
},
|
||||
},
|
||||
qc,
|
||||
)
|
||||
|
||||
return (
|
||||
<Context.Provider value={data || {feeds: []}}>{children}</Context.Provider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export type LiveEventFeedImageLayout = 'wide' // maybe more in the future
|
||||
|
||||
export type LiveEventFeedImage = {
|
||||
alt: string
|
||||
overlayColor: string
|
||||
url: string
|
||||
blurhash: string
|
||||
}
|
||||
|
||||
export type LiveEventFeed = {
|
||||
title: string
|
||||
url: string
|
||||
images: Record<LiveEventFeedImageLayout, LiveEventFeedImage>
|
||||
}
|
||||
|
||||
export type LiveEventsWorkerResponse = {
|
||||
feeds: LiveEventFeed[]
|
||||
}
|
||||
Reference in New Issue
Block a user