[Chat] Reusable loading/unavailable states for chat invites (#10819)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,10 +22,20 @@ export type ChatInviteAction = {
|
||||
side: 'left' | 'right'
|
||||
}
|
||||
|
||||
/**
|
||||
* The resolved state of a chat invite:
|
||||
* - `loading`: the preview is still being fetched.
|
||||
* - `error`: the fetch failed (e.g. network). Surfaces may want to fall back to
|
||||
* a plain link rather than show a chat-specific error.
|
||||
* - `unavailable`: the preview resolved but the link is disabled, invalid, or an
|
||||
* unrecognized variant - there's nothing to join.
|
||||
* - `available`: a usable `JoinLinkPreviewView` is present.
|
||||
*/
|
||||
export type ChatInviteStatus = 'loading' | 'error' | 'unavailable' | 'available'
|
||||
|
||||
export type ChatInviteContextValue = {
|
||||
code: string
|
||||
loading: boolean
|
||||
error: boolean
|
||||
status: ChatInviteStatus
|
||||
preview: ChatInvitePreview | undefined
|
||||
/**
|
||||
* The derived action descriptor. Undefined while loading or when there's no
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Loader} from '#/components/Loader'
|
||||
|
||||
/**
|
||||
* Loading state for a chat invite: a centered spinner. The outer container
|
||||
* (height, border, etc.) varies per surface, so pass it via `style`.
|
||||
*/
|
||||
export function Loading({style}: {style?: StyleProp<ViewStyle>}) {
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<View style={[a.align_center, a.justify_center, style]}>
|
||||
<Loader size="md" fill={t.atoms.text.color} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -18,7 +18,11 @@ import {type Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand'
|
||||
import {useIntentDialogs} from '#/components/intents/IntentDialogs'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {type ChatInviteAction, ChatInviteProvider} from './Context'
|
||||
import {
|
||||
type ChatInviteAction,
|
||||
ChatInviteProvider,
|
||||
type ChatInviteStatus,
|
||||
} from './Context'
|
||||
|
||||
/**
|
||||
* Headless data + state owner for a chat invite. Fetches the join link preview
|
||||
@@ -61,7 +65,18 @@ export function Root({
|
||||
})
|
||||
|
||||
const preview = data?.joinLinkPreviews[0]
|
||||
const loading = isPending && !preview
|
||||
|
||||
let status: ChatInviteStatus
|
||||
if (isPending && !preview) {
|
||||
status = 'loading'
|
||||
} else if (error) {
|
||||
status = 'error'
|
||||
} else if (ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) {
|
||||
status = 'available'
|
||||
} else {
|
||||
// Resolved to a disabled/invalid/unrecognized preview - nothing to join.
|
||||
status = 'unavailable'
|
||||
}
|
||||
|
||||
let action: ChatInviteAction | undefined
|
||||
if (ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) {
|
||||
@@ -135,8 +150,7 @@ export function Root({
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatInviteProvider
|
||||
value={{code, loading, error: !!error, preview, action, hasFixedHeight}}>
|
||||
<ChatInviteProvider value={{code, status, preview, action, hasFixedHeight}}>
|
||||
{children}
|
||||
</ChatInviteProvider>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useChatInvite} from './Context'
|
||||
|
||||
/**
|
||||
* "No longer available" state for a chat invite, shown when the link is
|
||||
* disabled, invalid, or otherwise can't be resolved. The outer container
|
||||
* (height, border, etc.) varies per surface, so pass it via `style`.
|
||||
*/
|
||||
export function Unavailable({style}: {style?: StyleProp<ViewStyle>}) {
|
||||
const t = useTheme()
|
||||
const {hasFixedHeight} = useChatInvite()
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[a.flex_row, a.align_center, a.justify_center, a.gap_xs, style]}>
|
||||
<WarningIcon size="md" fill={t.atoms.text_contrast_medium.color} />
|
||||
<Text
|
||||
style={[a.text_sm, a.font_medium, t.atoms.text_contrast_medium]}
|
||||
allowFontScaling={!hasFixedHeight}>
|
||||
<Trans>Chat invite link no longer available</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,10 @@ export {Card} from './Card'
|
||||
export {
|
||||
type ChatInviteAction,
|
||||
type ChatInviteContextValue,
|
||||
type ChatInviteStatus,
|
||||
useChatInvite,
|
||||
} from './Context'
|
||||
export {JoinButton} from './JoinButton'
|
||||
export {Loading} from './Loading'
|
||||
export {Root} from './Root'
|
||||
export {Unavailable} from './Unavailable'
|
||||
|
||||
Reference in New Issue
Block a user