From c52231a449e33b184c19219e9f694da402cec9e8 Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Tue, 9 Jun 2026 07:23:33 -0700 Subject: [PATCH] Update handling of join link previews (#10798) Co-authored-by: Samuel Newman --- bskyogcard/package.json | 2 +- bskyogcard/pnpm-lock.yaml | 10 +-- bskyogcard/src/routes/chat-invite.tsx | 4 +- package.json | 2 +- pnpm-lock.yaml | 10 +-- .../Post/Embed/JoinRequestEmbed.tsx | 13 ++-- src/components/dms/ChatInvite/Card.tsx | 3 +- src/components/dms/ChatInvite/Context.tsx | 4 +- src/components/dms/ChatInvite/Root.tsx | 19 +++--- src/components/dms/MessageItemInviteEmbed.tsx | 8 ++- src/components/dms/MessagesListHeader.tsx | 6 +- src/components/dms/SystemMessageItem.tsx | 3 +- .../intents/GroupChatJoinDialog.tsx | 17 ++--- src/lib/api/index.ts | 6 +- src/lib/api/resolve.ts | 12 ++-- src/screens/Messages/JoinRequest.tsx | 50 +++++++------- .../Messages/components/MessageInputEmbed.tsx | 28 +++++--- .../components/MessagesListGroupInfoPanel.tsx | 6 +- .../components/MessagesListInfoPanel.tsx | 3 +- .../components/OutgoingRequestListItem.tsx | 15 +++-- src/state/queries/join-links.ts | 65 +++++++++++++++++-- .../queries/messages/disable-join-link.ts | 2 + .../queries/messages/enable-join-link.ts | 2 + src/view/com/composer/Composer.tsx | 5 +- 24 files changed, 192 insertions(+), 103 deletions(-) diff --git a/bskyogcard/package.json b/bskyogcard/package.json index 0fb28334cb..50248e4cf9 100644 --- a/bskyogcard/package.json +++ b/bskyogcard/package.json @@ -18,7 +18,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@atproto/api": "0.20.6", + "@atproto/api": "0.20.11", "@atproto/common": "^0.6.1", "@resvg/resvg-js": "^2.6.2", "express": "^4.19.2", diff --git a/bskyogcard/pnpm-lock.yaml b/bskyogcard/pnpm-lock.yaml index 39685ea8ce..2bebbe145c 100644 --- a/bskyogcard/pnpm-lock.yaml +++ b/bskyogcard/pnpm-lock.yaml @@ -208,8 +208,8 @@ importers: .: dependencies: '@atproto/api': - specifier: 0.20.6 - version: 0.20.6 + specifier: 0.20.11 + version: 0.20.11 '@atproto/common': specifier: ^0.6.1 version: 0.6.1 @@ -259,8 +259,8 @@ importers: packages: - '@atproto/api@0.20.6': - resolution: {integrity: sha512-WnFPcUl+qZdXmt27+Tg93BDIvBt/WpXfLIiBzBTp3ms9aszM5hAsfc7G8KEsnsmnRvcm0xRfiKEjIt5FxTKdYg==} + '@atproto/api@0.20.11': + resolution: {integrity: sha512-1NoVJpBDAdotxo1iMZdMd75JstpdKWgBYOnxfVD4m+52bRjgU4cFg3EOGNUognZntFgL/bIHyEgyN7SJWVf6Ig==} engines: {node: '>=22'} '@atproto/common-web@0.5.0': @@ -1154,7 +1154,7 @@ packages: snapshots: - '@atproto/api@0.20.6': + '@atproto/api@0.20.11': dependencies: '@atproto/common-web': 0.5.0 '@atproto/lexicon': 0.7.1 diff --git a/bskyogcard/src/routes/chat-invite.tsx b/bskyogcard/src/routes/chat-invite.tsx index e3267cc0d0..36482743b8 100644 --- a/bskyogcard/src/routes/chat-invite.tsx +++ b/bskyogcard/src/routes/chat-invite.tsx @@ -1,6 +1,6 @@ import assert from 'node:assert' -import {type ChatBskyGroupDefs} from '@atproto/api' +import {ChatBskyGroupDefs} from '@atproto/api' import resvg from '@resvg/resvg-js' import {type Express} from 'express' import satori from 'satori' @@ -32,7 +32,7 @@ export default function (ctx: AppContext, app: Express) { codes: [code], }) const found = result.data.joinLinkPreviews[0] - if (!found) { + if (!ChatBskyGroupDefs.isJoinLinkPreviewView(found)) { return res.status(404).end('not found') } preview = found diff --git a/package.json b/package.json index 8853eb2210..3040ce2e90 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "prettier": "prettier --check ." }, "dependencies": { - "@atproto/api": "0.20.9", + "@atproto/api": "0.20.11", "@atproto/syntax": "0.6.1", "@bitdrift/react-native": "^0.6.8", "@braintree/sanitize-url": "^6.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2f65bf409..9d21e055cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -242,8 +242,8 @@ importers: .: dependencies: '@atproto/api': - specifier: 0.20.9 - version: 0.20.9 + specifier: 0.20.11 + version: 0.20.11 '@atproto/syntax': specifier: 0.6.1 version: 0.6.1 @@ -877,8 +877,8 @@ packages: graphql: optional: true - '@atproto/api@0.20.9': - resolution: {integrity: sha512-Yuw7Ewn+yMJZ8GskbuvI3lKPW65rsXic1xjFA2Dpq6H8WjVYs6xNZ31bkwtTYDDwjKIZcJmAVbAVgdfjo4T9iw==} + '@atproto/api@0.20.11': + resolution: {integrity: sha512-1NoVJpBDAdotxo1iMZdMd75JstpdKWgBYOnxfVD4m+52bRjgU4cFg3EOGNUognZntFgL/bIHyEgyN7SJWVf6Ig==} engines: {node: '>=22'} '@atproto/common-web@0.5.0': @@ -9493,7 +9493,7 @@ snapshots: '@0no-co/graphql.web@1.2.0': {} - '@atproto/api@0.20.9': + '@atproto/api@0.20.11': dependencies: '@atproto/common-web': 0.5.0 '@atproto/lexicon': 0.7.1 diff --git a/src/components/Post/Embed/JoinRequestEmbed.tsx b/src/components/Post/Embed/JoinRequestEmbed.tsx index e020bb9333..a3cc08cac6 100644 --- a/src/components/Post/Embed/JoinRequestEmbed.tsx +++ b/src/components/Post/Embed/JoinRequestEmbed.tsx @@ -1,7 +1,11 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type ChatBskyGroupDefs} from '@atproto/api' +import {ChatBskyGroupDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' +import { + type ChatInvitePreview, + isKnownJoinLinkPreview, +} from '#/state/queries/join-links' import {atoms as a, useTheme} from '#/alf' import * as ChatInvite from '#/components/dms/ChatInvite' import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' @@ -23,11 +27,12 @@ export function JoinRequestEmbed({ onOpen, }: { code?: string - preview?: ChatBskyGroupDefs.JoinLinkPreviewView + preview?: ChatInvitePreview style?: StyleProp onOpen?: () => void }) { - const resolvedCode = code ?? preview?.code + const resolvedCode = + code ?? (isKnownJoinLinkPreview(preview) ? preview.code : undefined) if (!resolvedCode) return null return ( @@ -73,7 +78,7 @@ export function JoinRequestEmbedBody({ ) } - if (!preview) { + if (!ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) { return ( = JoinIcon let label = preview.requireApproval ? l`Request to join` : l`Join` let color: ButtonColor = 'primary' - if (preview.enabledStatus !== 'enabled') { - canJoin = false - icon = WarningIcon - label = l`Chat invite link no longer available` - color = 'secondary' - } else if (preview.memberCount >= preview.memberLimit) { + if (preview.memberCount >= preview.memberLimit) { canJoin = false icon = HandIcon label = l`This chat is full` diff --git a/src/components/dms/MessageItemInviteEmbed.tsx b/src/components/dms/MessageItemInviteEmbed.tsx index d85e7fd08a..79416057c9 100644 --- a/src/components/dms/MessageItemInviteEmbed.tsx +++ b/src/components/dms/MessageItemInviteEmbed.tsx @@ -3,6 +3,7 @@ import {useWindowDimensions, View} from 'react-native' import {type $Typed, type ChatBskyEmbedJoinLink} from '@atproto/api' import {useConvoActive} from '#/state/messages/convo' +import {isKnownJoinLinkPreview} from '#/state/queries/join-links' import {atoms as a, native, useTheme, web} from '#/alf' import * as ChatInvite from '#/components/dms/ChatInvite' import {MessageContextProvider} from './MessageContext' @@ -27,6 +28,11 @@ let MessageItemInviteEmbed = ({ const screen = useWindowDimensions() const convo = useConvoActive() + const code = isKnownJoinLinkPreview(embed.joinLinkPreview) + ? embed.joinLinkPreview.code + : undefined + if (!code) return null + return ( diff --git a/src/components/dms/MessagesListHeader.tsx b/src/components/dms/MessagesListHeader.tsx index 745adc62b7..82b00645d2 100644 --- a/src/components/dms/MessagesListHeader.tsx +++ b/src/components/dms/MessagesListHeader.tsx @@ -121,7 +121,8 @@ function ProfileHeaderReady({ + numberOfLines={1} + emoji> {displayName} @@ -185,7 +186,8 @@ function GroupHeaderReady({ + numberOfLines={1} + emoji> {convo.details.name} diff --git a/src/components/dms/SystemMessageItem.tsx b/src/components/dms/SystemMessageItem.tsx index 9854255c84..7de6a0cd91 100644 --- a/src/components/dms/SystemMessageItem.tsx +++ b/src/components/dms/SystemMessageItem.tsx @@ -45,7 +45,8 @@ export function SystemMessageItem({ a.text_center, t.atoms.text_contrast_medium, {includeFontPadding: false, textAlignVertical: 'center'}, - ]}> + ]} + emoji> {text} diff --git a/src/components/intents/GroupChatJoinDialog.tsx b/src/components/intents/GroupChatJoinDialog.tsx index 8d235b9a07..dd3d8cb6e8 100644 --- a/src/components/intents/GroupChatJoinDialog.tsx +++ b/src/components/intents/GroupChatJoinDialog.tsx @@ -1,5 +1,6 @@ import {View} from 'react-native' import { + ChatBskyGroupDefs, ChatBskyGroupRequestJoin, ChatBskyGroupWithdrawJoinRequest, moderateProfile, @@ -211,7 +212,7 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { const joinLinkPreview = data.joinLinkPreviews[0] - if (!joinLinkPreview) { + if (!ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview)) { return ( <> @@ -253,12 +254,7 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { ? l`Request to join` : l`Join` let buttonColor: ButtonColor = 'primary' - if (joinLinkPreview.enabledStatus !== 'enabled') { - canJoin = false - ButtonIconImage = WarningIcon - buttonText = l`Chat invite link no longer available` - buttonColor = 'secondary' - } else if (joinLinkPreview.memberCount >= joinLinkPreview.memberLimit) { + if (joinLinkPreview.memberCount >= joinLinkPreview.memberLimit) { canJoin = false ButtonIconImage = HandIcon buttonText = l`This chat is full` @@ -311,10 +307,7 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { - + @@ -371,7 +364,7 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 6f92e2a1dd..40e6743b9a 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -9,6 +9,7 @@ import { AppBskyFeedPost, BlobRef, type BskyAgent, + ChatBskyGroupDefs, type ComAtprotoLabelDefs, type ComAtprotoRepoApplyWrites, type ComAtprotoRepoStrongRef, @@ -463,7 +464,10 @@ async function resolveMedia( }, } } - if (resolvedLink.type === 'chat-invite' && resolvedLink.view) { + if ( + resolvedLink.type === 'chat-invite' && + ChatBskyGroupDefs.isJoinLinkPreviewView(resolvedLink.view) + ) { return { $type: 'app.bsky.embed.external', external: { diff --git a/src/lib/api/resolve.ts b/src/lib/api/resolve.ts index ca0b342045..476c0f1a20 100644 --- a/src/lib/api/resolve.ts +++ b/src/lib/api/resolve.ts @@ -1,8 +1,7 @@ import { type AppBskyFeedDefs, type AppBskyGraphDefs, - type BskyAgent, - type ChatBskyGroupDefs, + type AtpAgent, type ComAtprotoRepoStrongRef, } from '@atproto/api' import {AtUri} from '@atproto/api' @@ -28,6 +27,7 @@ import { } from '#/lib/strings/url-helpers' import {type ComposerImage} from '#/state/gallery' import {createComposerImage} from '#/state/gallery' +import {type ChatInvitePreview} from '#/state/queries/join-links' import {type Gif} from '#/features/gifPicker/types' import {createGIFDescription} from '../gif-alt-text' @@ -77,7 +77,7 @@ type ResolvedChatInvite = { type: 'chat-invite' uri: string code: string - view?: ChatBskyGroupDefs.JoinLinkPreviewView + view?: ChatInvitePreview } export type ResolvedLink = @@ -95,7 +95,7 @@ export class EmbeddingDisabledError extends Error { } export async function resolveLink( - agent: BskyAgent, + agent: AtpAgent, uri: string, ): Promise { if (isShortLink(uri)) { @@ -217,7 +217,7 @@ export async function resolveLink( } export async function resolveGif( - agent: BskyAgent, + agent: AtpAgent, gif: Gif, ): Promise { const gifUrl = gif.media_formats.gif.url @@ -259,7 +259,7 @@ function getFileSlug(url: string | undefined): string | undefined { } async function resolveExternal( - agent: BskyAgent, + agent: AtpAgent, uri: string, ): Promise { const result = await getLinkMeta(agent, uri) diff --git a/src/screens/Messages/JoinRequest.tsx b/src/screens/Messages/JoinRequest.tsx index 1e23c3405b..64c898dc26 100644 --- a/src/screens/Messages/JoinRequest.tsx +++ b/src/screens/Messages/JoinRequest.tsx @@ -1,6 +1,6 @@ import {View} from 'react-native' import {ImageBackground} from 'expo-image' -import {moderateProfile} from '@atproto/api' +import {ChatBskyGroupDefs, moderateProfile} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' @@ -52,9 +52,7 @@ export function JoinRequest({setScreenState}: Props) { ? mobileDarkBg : mobileLightBg - const requiresApproval = data?.joinLinkPreviews[0]?.requireApproval - const requiresFollow = - data?.joinLinkPreviews[0]?.joinRule === 'followedByOwner' + const joinLinkPreview = data?.joinLinkPreviews[0] return ( @@ -69,7 +67,9 @@ export function JoinRequest({setScreenState}: Props) { a.justify_center, a.align_center, ]}> - {error ? ( + {error || + (data && + !ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview)) ? ( - {l`This invite link has expired`} + Chat invite link no longer available - ) : data && moderationOpts ? ( + ) : data && + moderationOpts && + ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview) ? ( - {data.joinLinkPreviews[0].memberCount}/ - {data.joinLinkPreviews[0].memberLimit} + {joinLinkPreview.memberCount}/ + {joinLinkPreview.memberLimit} @@ -144,7 +143,7 @@ export function JoinRequest({setScreenState}: Props) { a.font_bold, t.atoms.text, ]}> - {data.joinLinkPreviews[0].name} + {joinLinkPreview.name} @@ -168,17 +167,17 @@ export function JoinRequest({setScreenState}: Props) { By{' '} {createSanitizedDisplayName( - data.joinLinkPreviews[0].owner, + joinLinkPreview.owner, true, moderateProfile( - data.joinLinkPreviews[0].owner, + joinLinkPreview.owner, moderationOpts, ).ui('displayName'), )} @@ -192,7 +191,7 @@ export function JoinRequest({setScreenState}: Props) { t.atoms.text_contrast_medium, a.max_w_full, ]}> - {sanitizeHandle(data.joinLinkPreviews[0].owner.handle, '@')} + {sanitizeHandle(joinLinkPreview.owner.handle, '@')} - {requiresApproval + {joinLinkPreview.requireApproval ? l`Sign in to request access to this group chat.` : l`Sign in to accept invite.`}{' '} - {requiresFollow && + {joinLinkPreview.joinRule === 'followedByOwner' && l`Only people ${createSanitizedDisplayName( - data.joinLinkPreviews[0].owner, + joinLinkPreview.owner, true, - moderateProfile( - data.joinLinkPreviews[0].owner, - moderationOpts, - ).ui('displayName'), + moderateProfile(joinLinkPreview.owner, moderationOpts).ui( + 'displayName', + ), )} follows can join.`} diff --git a/src/screens/Messages/components/MessageInputEmbed.tsx b/src/screens/Messages/components/MessageInputEmbed.tsx index 21503ce8fa..c38141ce9d 100644 --- a/src/screens/Messages/components/MessageInputEmbed.tsx +++ b/src/screens/Messages/components/MessageInputEmbed.tsx @@ -4,6 +4,7 @@ import { AppBskyFeedPost, AppBskyRichtextFacet, AtUri, + ChatBskyGroupDefs, moderatePost, RichText as RichTextAPI, } from '@atproto/api' @@ -30,6 +31,7 @@ import {atoms as a, useTheme} from '#/alf' import {Button} from '#/components/Button' import * as ChatInvite from '#/components/dms/ChatInvite' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' +import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' import {Loader} from '#/components/Loader' import * as MediaPreview from '#/components/MediaPreview' import {ContentHider} from '#/components/moderation/ContentHider' @@ -52,12 +54,12 @@ export function useMessageEmbed() { const navigation = useNavigation() const embedFromParams = route.params.embed - const [embed, setEmbedState] = useState( + const [embed, setEmbed] = useState( embedFromParams ? {type: 'post', uri: embedFromParams} : undefined, ) if (embedFromParams && embed?.type !== 'post') { - setEmbedState({type: 'post', uri: embedFromParams}) + setEmbed({type: 'post', uri: embedFromParams}) } return { @@ -68,7 +70,7 @@ export function useMessageEmbed() { // Only the post embed is reflected in the route param (used by the // share-to-DM intent flow); invites are local-only. navigation.setParams({embed: ''}) - setEmbedState(undefined) + setEmbed(undefined) return } @@ -77,7 +79,7 @@ export function useMessageEmbed() { if (isBskyChatInviteUrl(embedUrl)) { const code = getChatInviteCodeFromUrl(embedUrl) if (code) { - setEmbedState({type: 'invite', code}) + setEmbed({type: 'invite', code}) } return } @@ -86,7 +88,7 @@ export function useMessageEmbed() { const url = convertBskyAppUrlIfNeeded(embedUrl) const [_0, user, _1, rkey] = url.split('/').filter(Boolean) const uri = makeRecordUri(user, 'app.bsky.feed.post', rkey) - setEmbedState({type: 'post', uri}) + setEmbed({type: 'post', uri}) } }, [embedFromParams, navigation], @@ -314,11 +316,19 @@ function MessageInputInviteEmbedBody() { ) } - if (!preview) { + if (!ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) { return ( - - - Could not load invite + + + + Chat invite link no longer available ) diff --git a/src/screens/Messages/components/MessagesListGroupInfoPanel.tsx b/src/screens/Messages/components/MessagesListGroupInfoPanel.tsx index 2129182632..8428b1a4b2 100644 --- a/src/screens/Messages/components/MessagesListGroupInfoPanel.tsx +++ b/src/screens/Messages/components/MessagesListGroupInfoPanel.tsx @@ -94,7 +94,8 @@ export function MessagesListGroupInfoPanel({ /> {convo.details.name ? ( + style={[a.text_2xl, a.font_bold, a.mt_lg, a.px_xl, a.text_center]} + emoji> {convo.details.name} ) : null} @@ -107,7 +108,8 @@ export function MessagesListGroupInfoPanel({ a.text_sm, t.atoms.text_contrast_high, showButtons ? null : a.mb_4xl, - ]}> + ]} + emoji> {names} ) : null} diff --git a/src/screens/Messages/components/MessagesListInfoPanel.tsx b/src/screens/Messages/components/MessagesListInfoPanel.tsx index b30bd6fe9d..e1e620adee 100644 --- a/src/screens/Messages/components/MessagesListInfoPanel.tsx +++ b/src/screens/Messages/components/MessagesListInfoPanel.tsx @@ -60,7 +60,8 @@ export function MessagesListInfoPanel({ ]}> + numberOfLines={1} + emoji> {displayName} diff --git a/src/screens/Messages/components/OutgoingRequestListItem.tsx b/src/screens/Messages/components/OutgoingRequestListItem.tsx index 8bd7ba78d0..2f8804092d 100644 --- a/src/screens/Messages/components/OutgoingRequestListItem.tsx +++ b/src/screens/Messages/components/OutgoingRequestListItem.tsx @@ -76,7 +76,14 @@ export function OutgoingRequestListItem({ moderationOpts={moderationOpts} /> - + - - + {convoView.viewer?.requestedAt ? ( + {({timeElapsed}) => ( )} - + ) : null} + | $Typed + | $Typed + +/** + * The full open-union shape, including the `{$type: string}` fallback for + * future variants. + */ +export type ChatInvitePreview = KnownChatInvitePreview | {$type: string} + +/** + * Narrows a preview to one of the three known variants, filtering out the + * `{$type: string}` open-union fallback for unrecognized future shapes. + */ +export function isKnownJoinLinkPreview( + preview: unknown, +): preview is KnownChatInvitePreview { + return ( + ChatBskyGroupDefs.isJoinLinkPreviewView(preview) || + ChatBskyGroupDefs.isDisabledJoinLinkPreviewView(preview) || + ChatBskyGroupDefs.isInvalidJoinLinkPreviewView(preview) + ) +} + const joinLinkPreviewQueryKeyRoot = 'join-link-preview' export const createJoinLinkPreviewQueryKey = (args: { @@ -22,6 +53,29 @@ export const createJoinLinkPreviewQueryKey = (args: { persistedVersion: 1, }) +/** + * Invalidate any join link preview queries whose `codes` include the given + * code. Use this when a link's state changes (e.g. it's disabled) so cached + * previews refetch and reflect the new state. + */ +export function invalidateJoinLinkPreviewsForCode( + queryClient: QueryClient, + code: string, +) { + return queryClient.invalidateQueries({ + predicate: query => { + const [root, args] = query.queryKey as Partial< + StructuredQueryKey<{codes?: string[]}> + > + return ( + root === joinLinkPreviewQueryKeyRoot && + Array.isArray(args?.codes) && + args.codes.includes(code) + ) + }, + }) +} + async function fetchJoinLinkPreviews({ agent, codes, @@ -104,7 +158,7 @@ export function useGetJoinLinkPreview() { }: { code: string hasSession: boolean - }): Promise => { + }): Promise => { try { const data = await queryClient.fetchQuery({ queryKey: createJoinLinkPreviewQueryKey({codes: [code], hasSession}), @@ -112,7 +166,8 @@ export function useGetJoinLinkPreview() { fetchJoinLinkPreviews({agent, codes: [code], hasSession}), staleTime: STALE.SECONDS.FIFTEEN, }) - return data.joinLinkPreviews[0] + const found = data.joinLinkPreviews[0] + return isKnownJoinLinkPreview(found) ? found : undefined } catch (error) { logger.error('Failed to fetch join link preview', {safeMessage: error}) return undefined diff --git a/src/state/queries/messages/disable-join-link.ts b/src/state/queries/messages/disable-join-link.ts index 522b5939eb..c143feb6cc 100644 --- a/src/state/queries/messages/disable-join-link.ts +++ b/src/state/queries/messages/disable-join-link.ts @@ -6,6 +6,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query' import {DM_SERVICE_HEADERS} from '#/lib/constants' import {logger} from '#/logger' +import {invalidateJoinLinkPreviewsForCode} from '#/state/queries/join-links' import {useAgent} from '#/state/session' import { rollbackConvoOptimistic, @@ -59,6 +60,7 @@ export function useDisableJoinLink( } }) } + void invalidateJoinLinkPreviewsForCode(queryClient, data.joinLink.code) onSuccess?.(data) }, onError: (e, _variables, context) => { diff --git a/src/state/queries/messages/enable-join-link.ts b/src/state/queries/messages/enable-join-link.ts index a2ba505f1d..4febc85441 100644 --- a/src/state/queries/messages/enable-join-link.ts +++ b/src/state/queries/messages/enable-join-link.ts @@ -3,6 +3,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query' import {DM_SERVICE_HEADERS} from '#/lib/constants' import {logger} from '#/logger' +import {invalidateJoinLinkPreviewsForCode} from '#/state/queries/join-links' import {useAgent} from '#/state/session' import { rollbackConvoOptimistic, @@ -56,6 +57,7 @@ export function useEnableJoinLink( } }) } + void invalidateJoinLinkPreviewsForCode(queryClient, data.joinLink.code) onSuccess?.(data) }, onError: (e, _variables, context) => { diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 25ed831c3c..e1d68acea6 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -52,6 +52,7 @@ import { type AppBskyUnspeccedGetPostThreadV2, AtUri, type BskyAgent, + ChatBskyGroupDefs, type RichText, } from '@atproto/api' import {plural} from '@lingui/core/macro' @@ -886,7 +887,9 @@ export const ComposePost = ({ })), }) const hasUnavailableChatInvite = linkQueries.some( - q => q.data?.type === 'chat-invite' && !q.data.view, + q => + q.data?.type === 'chat-invite' && + !ChatBskyGroupDefs.isJoinLinkPreviewView(q.data.view), ) const canPost =