about summary refs log tree commit diff
path: root/src/view/com/notifications
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/notifications')
-rw-r--r--src/view/com/notifications/Feed.tsx173
-rw-r--r--src/view/com/notifications/FeedItem.tsx185
-rw-r--r--src/view/com/notifications/InvitedUsers.tsx114
3 files changed, 177 insertions, 295 deletions
diff --git a/src/view/com/notifications/Feed.tsx b/src/view/com/notifications/Feed.tsx
index 74769bc76..260c9bbd5 100644
--- a/src/view/com/notifications/Feed.tsx
+++ b/src/view/com/notifications/Feed.tsx
@@ -1,66 +1,76 @@
 import React, {MutableRefObject} from 'react'
-import {observer} from 'mobx-react-lite'
 import {CenteredView, FlatList} from '../util/Views'
 import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
-import {NotificationsFeedModel} from 'state/models/feeds/notifications'
 import {FeedItem} from './FeedItem'
 import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
 import {ErrorMessage} from '../util/error/ErrorMessage'
 import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
 import {EmptyState} from '../util/EmptyState'
-import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
+import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
+import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
 import {s} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
+import {useUnreadNotificationsApi} from '#/state/queries/notifications/unread'
 import {logger} from '#/logger'
+import {cleanError} from '#/lib/strings/errors'
+import {useModerationOpts} from '#/state/queries/preferences'
 
 const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
 const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
-const LOADING_SPINNER = {_reactKey: '__loading_spinner__'}
+const LOADING_ITEM = {_reactKey: '__loading__'}
 
-export const Feed = observer(function Feed({
-  view,
+export function Feed({
   scrollElRef,
   onPressTryAgain,
   onScroll,
   ListHeaderComponent,
 }: {
-  view: NotificationsFeedModel
   scrollElRef?: MutableRefObject<FlatList<any> | null>
   onPressTryAgain?: () => void
-  onScroll?: OnScrollCb
+  onScroll?: OnScrollHandler
   ListHeaderComponent?: () => JSX.Element
 }) {
   const pal = usePalette('default')
   const [isPTRing, setIsPTRing] = React.useState(false)
-  const data = React.useMemo(() => {
-    let feedItems: any[] = []
-    if (view.isRefreshing && !isPTRing) {
-      feedItems = [LOADING_SPINNER]
-    }
-    if (view.hasLoaded) {
-      if (view.isEmpty) {
-        feedItems = feedItems.concat([EMPTY_FEED_ITEM])
-      } else {
-        feedItems = feedItems.concat(view.notifications)
+
+  const moderationOpts = useModerationOpts()
+  const {checkUnread} = useUnreadNotificationsApi()
+  const {
+    data,
+    isFetching,
+    isFetched,
+    isError,
+    error,
+    hasNextPage,
+    isFetchingNextPage,
+    fetchNextPage,
+  } = useNotificationFeedQuery({enabled: !!moderationOpts})
+  const isEmpty = !isFetching && !data?.pages[0]?.items.length
+
+  const items = React.useMemo(() => {
+    let arr: any[] = []
+    if (isFetched) {
+      if (isEmpty) {
+        arr = arr.concat([EMPTY_FEED_ITEM])
+      } else if (data) {
+        for (const page of data?.pages) {
+          arr = arr.concat(page.items)
+        }
       }
+      if (isError && !isEmpty) {
+        arr = arr.concat([LOAD_MORE_ERROR_ITEM])
+      }
+    } else {
+      arr.push(LOADING_ITEM)
     }
-    if (view.loadMoreError) {
-      feedItems = (feedItems || []).concat([LOAD_MORE_ERROR_ITEM])
-    }
-    return feedItems
-  }, [
-    view.hasLoaded,
-    view.isEmpty,
-    view.notifications,
-    view.loadMoreError,
-    view.isRefreshing,
-    isPTRing,
-  ])
+    return arr
+  }, [isFetched, isError, isEmpty, data])
 
   const onRefresh = React.useCallback(async () => {
     try {
       setIsPTRing(true)
-      await view.refresh()
+      await checkUnread({invalidate: true})
     } catch (err) {
       logger.error('Failed to refresh notifications feed', {
         error: err,
@@ -68,21 +78,21 @@ export const Feed = observer(function Feed({
     } finally {
       setIsPTRing(false)
     }
-  }, [view, setIsPTRing])
+  }, [checkUnread, setIsPTRing])
 
   const onEndReached = React.useCallback(async () => {
+    if (isFetching || !hasNextPage || isError) return
+
     try {
-      await view.loadMore()
+      await fetchNextPage()
     } catch (err) {
-      logger.error('Failed to load more notifications', {
-        error: err,
-      })
+      logger.error('Failed to load more notifications', {error: err})
     }
-  }, [view])
+  }, [isFetching, hasNextPage, isError, fetchNextPage])
 
   const onPressRetryLoadMore = React.useCallback(() => {
-    view.retryLoadMore()
-  }, [view])
+    fetchNextPage()
+  }, [fetchNextPage])
 
   // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf
   //   VirtualizedList: You have a large list that is slow to update - make sure your
@@ -105,77 +115,66 @@ export const Feed = observer(function Feed({
             onPress={onPressRetryLoadMore}
           />
         )
-      } else if (item === LOADING_SPINNER) {
-        return (
-          <View style={styles.loading}>
-            <ActivityIndicator size="small" />
-          </View>
-        )
+      } else if (item === LOADING_ITEM) {
+        return <NotificationFeedLoadingPlaceholder />
       }
-      return <FeedItem item={item} />
+      return <FeedItem item={item} moderationOpts={moderationOpts!} />
     },
-    [onPressRetryLoadMore],
+    [onPressRetryLoadMore, moderationOpts],
   )
 
   const FeedFooter = React.useCallback(
     () =>
-      view.isLoading ? (
+      isFetchingNextPage ? (
         <View style={styles.feedFooter}>
           <ActivityIndicator />
         </View>
       ) : (
         <View />
       ),
-    [view],
+    [isFetchingNextPage],
   )
 
+  const scrollHandler = useAnimatedScrollHandler(onScroll || {})
   return (
     <View style={s.hContentRegion}>
-      <CenteredView>
-        {view.isLoading && !data.length && (
-          <NotificationFeedLoadingPlaceholder />
-        )}
-        {view.hasError && (
+      {error && (
+        <CenteredView>
           <ErrorMessage
-            message={view.error}
+            message={cleanError(error)}
             onPressTryAgain={onPressTryAgain}
           />
-        )}
-      </CenteredView>
-      {data.length ? (
-        <FlatList
-          testID="notifsFeed"
-          ref={scrollElRef}
-          data={data}
-          keyExtractor={item => item._reactKey}
-          renderItem={renderItem}
-          ListHeaderComponent={ListHeaderComponent}
-          ListFooterComponent={FeedFooter}
-          refreshControl={
-            <RefreshControl
-              refreshing={isPTRing}
-              onRefresh={onRefresh}
-              tintColor={pal.colors.text}
-              titleColor={pal.colors.text}
-            />
-          }
-          onEndReached={onEndReached}
-          onEndReachedThreshold={0.6}
-          onScroll={onScroll}
-          scrollEventThrottle={100}
-          contentContainerStyle={s.contentContainer}
-          // @ts-ignore our .web version only -prf
-          desktopFixedHeight
-        />
-      ) : null}
+        </CenteredView>
+      )}
+      <FlatList
+        testID="notifsFeed"
+        ref={scrollElRef}
+        data={items}
+        keyExtractor={item => item._reactKey}
+        renderItem={renderItem}
+        ListHeaderComponent={ListHeaderComponent}
+        ListFooterComponent={FeedFooter}
+        refreshControl={
+          <RefreshControl
+            refreshing={isPTRing}
+            onRefresh={onRefresh}
+            tintColor={pal.colors.text}
+            titleColor={pal.colors.text}
+          />
+        }
+        onEndReached={onEndReached}
+        onEndReachedThreshold={0.6}
+        onScroll={scrollHandler}
+        scrollEventThrottle={1}
+        contentContainerStyle={s.contentContainer}
+        // @ts-ignore our .web version only -prf
+        desktopFixedHeight
+      />
     </View>
   )
-})
+}
 
 const styles = StyleSheet.create({
-  loading: {
-    paddingVertical: 20,
-  },
   feedFooter: {paddingTop: 20},
   emptyState: {paddingVertical: 40},
 })
diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx
index c38ab3fd5..aaa2ea2c6 100644
--- a/src/view/com/notifications/FeedItem.tsx
+++ b/src/view/com/notifications/FeedItem.tsx
@@ -1,5 +1,4 @@
-import React, {useMemo, useState, useEffect} from 'react'
-import {observer} from 'mobx-react-lite'
+import React, {memo, useMemo, useState, useEffect} from 'react'
 import {
   Animated,
   TouchableOpacity,
@@ -9,6 +8,9 @@ import {
 } from 'react-native'
 import {
   AppBskyEmbedImages,
+  AppBskyFeedDefs,
+  AppBskyFeedPost,
+  ModerationOpts,
   ProfileModeration,
   moderateProfile,
   AppBskyEmbedRecordWithMedia,
@@ -19,8 +21,7 @@ import {
   FontAwesomeIconStyle,
   Props,
 } from '@fortawesome/react-native-fontawesome'
-import {NotificationsFeedItemModel} from 'state/models/feeds/notifications'
-import {PostThreadModel} from 'state/models/content/post-thread'
+import {FeedNotification} from '#/state/queries/notifications/feed'
 import {s, colors} from 'lib/styles'
 import {niceDate} from 'lib/strings/time'
 import {sanitizeDisplayName} from 'lib/strings/display-names'
@@ -33,13 +34,14 @@ import {UserPreviewLink} from '../util/UserPreviewLink'
 import {ImageHorzList} from '../util/images/ImageHorzList'
 import {Post} from '../post/Post'
 import {Link, TextLink} from '../util/Link'
-import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
 import {formatCount} from '../util/numeric/format'
 import {makeProfileLink} from 'lib/routes/links'
 import {TimeElapsed} from '../util/TimeElapsed'
 import {isWeb} from 'platform/detection'
+import {Trans, msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
 
 const MAX_AUTHORS = 5
 
@@ -54,40 +56,34 @@ interface Author {
   moderation: ProfileModeration
 }
 
-export const FeedItem = observer(function FeedItemImpl({
+let FeedItem = ({
   item,
+  moderationOpts,
 }: {
-  item: NotificationsFeedItemModel
-}) {
-  const store = useStores()
+  item: FeedNotification
+  moderationOpts: ModerationOpts
+}): React.ReactNode => {
   const pal = usePalette('default')
   const [isAuthorsExpanded, setAuthorsExpanded] = useState<boolean>(false)
   const itemHref = useMemo(() => {
-    if (item.isLike || item.isRepost) {
-      const urip = new AtUri(item.subjectUri)
-      return `/profile/${urip.host}/post/${urip.rkey}`
-    } else if (item.isFollow) {
-      return makeProfileLink(item.author)
-    } else if (item.isReply) {
-      const urip = new AtUri(item.uri)
+    if (item.type === 'post-like' || item.type === 'repost') {
+      if (item.subjectUri) {
+        const urip = new AtUri(item.subjectUri)
+        return `/profile/${urip.host}/post/${urip.rkey}`
+      }
+    } else if (item.type === 'follow') {
+      return makeProfileLink(item.notification.author)
+    } else if (item.type === 'reply') {
+      const urip = new AtUri(item.notification.uri)
       return `/profile/${urip.host}/post/${urip.rkey}`
-    } else if (item.isCustomFeedLike) {
-      const urip = new AtUri(item.subjectUri)
-      return `/profile/${urip.host}/feed/${urip.rkey}`
+    } else if (item.type === 'feedgen-like') {
+      if (item.subjectUri) {
+        const urip = new AtUri(item.subjectUri)
+        return `/profile/${urip.host}/feed/${urip.rkey}`
+      }
     }
     return ''
   }, [item])
-  const itemTitle = useMemo(() => {
-    if (item.isLike || item.isRepost) {
-      return 'Post'
-    } else if (item.isFollow) {
-      return item.author.handle
-    } else if (item.isReply) {
-      return 'Post'
-    } else if (item.isCustomFeedLike) {
-      return 'Custom Feed'
-    }
-  }, [item])
 
   const onToggleAuthorsExpanded = () => {
     setAuthorsExpanded(currentlyExpanded => !currentlyExpanded)
@@ -96,15 +92,12 @@ export const FeedItem = observer(function FeedItemImpl({
   const authors: Author[] = useMemo(() => {
     return [
       {
-        href: makeProfileLink(item.author),
-        did: item.author.did,
-        handle: item.author.handle,
-        displayName: item.author.displayName,
-        avatar: item.author.avatar,
-        moderation: moderateProfile(
-          item.author,
-          store.preferences.moderationOpts,
-        ),
+        href: makeProfileLink(item.notification.author),
+        did: item.notification.author.did,
+        handle: item.notification.author.handle,
+        displayName: item.notification.author.displayName,
+        avatar: item.notification.author.avatar,
+        moderation: moderateProfile(item.notification.author, moderationOpts),
       },
       ...(item.additional?.map(({author}) => {
         return {
@@ -113,33 +106,35 @@ export const FeedItem = observer(function FeedItemImpl({
           handle: author.handle,
           displayName: author.displayName,
           avatar: author.avatar,
-          moderation: moderateProfile(author, store.preferences.moderationOpts),
+          moderation: moderateProfile(author, moderationOpts),
         }
       }) || []),
     ]
-  }, [store, item.additional, item.author])
+  }, [item, moderationOpts])
 
-  if (item.additionalPost?.notFound) {
+  if (item.subjectUri && !item.subject) {
     // don't render anything if the target post was deleted or unfindable
     return <View />
   }
 
-  if (item.isReply || item.isMention || item.isQuote) {
-    if (!item.additionalPost || item.additionalPost?.error) {
-      // hide errors - it doesnt help the user to show them
-      return <View />
+  if (
+    item.type === 'reply' ||
+    item.type === 'mention' ||
+    item.type === 'quote'
+  ) {
+    if (!item.subject) {
+      return null
     }
     return (
       <Link
-        testID={`feedItem-by-${item.author.handle}`}
+        testID={`feedItem-by-${item.notification.author.handle}`}
         href={itemHref}
-        title={itemTitle}
         noFeedback
         accessible={false}>
         <Post
-          view={item.additionalPost}
+          post={item.subject}
           style={
-            item.isRead
+            item.notification.isRead
               ? undefined
               : {
                   backgroundColor: pal.colors.unreadNotifBg,
@@ -154,23 +149,25 @@ export const FeedItem = observer(function FeedItemImpl({
   let action = ''
   let icon: Props['icon'] | 'HeartIconSolid'
   let iconStyle: Props['style'] = []
-  if (item.isLike) {
+  if (item.type === 'post-like') {
     action = 'liked your post'
     icon = 'HeartIconSolid'
     iconStyle = [
       s.likeColor as FontAwesomeIconStyle,
       {position: 'relative', top: -4},
     ]
-  } else if (item.isRepost) {
+  } else if (item.type === 'repost') {
     action = 'reposted your post'
     icon = 'retweet'
     iconStyle = [s.green3 as FontAwesomeIconStyle]
-  } else if (item.isFollow) {
+  } else if (item.type === 'follow') {
     action = 'followed you'
     icon = 'user-plus'
     iconStyle = [s.blue3 as FontAwesomeIconStyle]
-  } else if (item.isCustomFeedLike) {
-    action = `liked your custom feed '${new AtUri(item.subjectUri).rkey}'`
+  } else if (item.type === 'feedgen-like') {
+    action = `liked your custom feed${
+      item.subjectUri ? ` '${new AtUri(item.subjectUri).rkey}}'` : ''
+    }`
     icon = 'HeartIconSolid'
     iconStyle = [
       s.likeColor as FontAwesomeIconStyle,
@@ -182,12 +179,12 @@ export const FeedItem = observer(function FeedItemImpl({
 
   return (
     <Link
-      testID={`feedItem-by-${item.author.handle}`}
+      testID={`feedItem-by-${item.notification.author.handle}`}
       style={[
         styles.outer,
         pal.view,
         pal.border,
-        item.isRead
+        item.notification.isRead
           ? undefined
           : {
               backgroundColor: pal.colors.unreadNotifBg,
@@ -195,9 +192,11 @@ export const FeedItem = observer(function FeedItemImpl({
             },
       ]}
       href={itemHref}
-      title={itemTitle}
       noFeedback
-      accessible={(item.isLike && authors.length === 1) || item.isRepost}>
+      accessible={
+        (item.type === 'post-like' && authors.length === 1) ||
+        item.type === 'repost'
+      }>
       <View style={styles.layoutIcon}>
         {/* TODO: Prevent conditional rendering and move toward composable
         notifications for clearer accessibility labeling */}
@@ -232,7 +231,10 @@ export const FeedItem = observer(function FeedItemImpl({
             />
             {authors.length > 1 ? (
               <>
-                <Text style={[pal.text]}> and </Text>
+                <Text style={[pal.text, s.mr5, s.ml5]}>
+                  {' '}
+                  <Trans>and</Trans>{' '}
+                </Text>
                 <Text style={[pal.text, s.bold]}>
                   {formatCount(authors.length - 1)}{' '}
                   {pluralize(authors.length - 1, 'other')}
@@ -240,24 +242,26 @@ export const FeedItem = observer(function FeedItemImpl({
               </>
             ) : undefined}
             <Text style={[pal.text]}> {action}</Text>
-            <TimeElapsed timestamp={item.indexedAt}>
+            <TimeElapsed timestamp={item.notification.indexedAt}>
               {({timeElapsed}) => (
                 <Text
                   style={[pal.textLight, styles.pointer]}
-                  title={niceDate(item.indexedAt)}>
+                  title={niceDate(item.notification.indexedAt)}>
                   {' ' + timeElapsed}
                 </Text>
               )}
             </TimeElapsed>
           </Text>
         </ExpandListPressable>
-        {item.isLike || item.isRepost || item.isQuote ? (
-          <AdditionalPostText additionalPost={item.additionalPost} />
+        {item.type === 'post-like' || item.type === 'repost' ? (
+          <AdditionalPostText post={item.subject} />
         ) : null}
       </View>
     </Link>
   )
-})
+}
+FeedItem = memo(FeedItem)
+export {FeedItem}
 
 function ExpandListPressable({
   hasMultipleAuthors,
@@ -292,6 +296,8 @@ function CondensedAuthorsList({
   onToggleAuthorsExpanded: () => void
 }) {
   const pal = usePalette('default')
+  const {_} = useLingui()
+
   if (!visible) {
     return (
       <View style={styles.avis}>
@@ -299,7 +305,7 @@ function CondensedAuthorsList({
           style={styles.expandedAuthorsCloseBtn}
           onPress={onToggleAuthorsExpanded}
           accessibilityRole="button"
-          accessibilityLabel="Hide user list"
+          accessibilityLabel={_(msg`Hide user list`)}
           accessibilityHint="Collapses list of users for a given notification">
           <FontAwesomeIcon
             icon="angle-up"
@@ -307,7 +313,7 @@ function CondensedAuthorsList({
             style={[styles.expandedAuthorsCloseBtnIcon, pal.text]}
           />
           <Text type="sm-medium" style={pal.text}>
-            Hide
+            <Trans>Hide</Trans>
           </Text>
         </TouchableOpacity>
       </View>
@@ -328,7 +334,7 @@ function CondensedAuthorsList({
   }
   return (
     <TouchableOpacity
-      accessibilityLabel="Show users"
+      accessibilityLabel={_(msg`Show users`)}
       accessibilityHint="Opens an expanded list of users in this notification"
       onPress={onToggleAuthorsExpanded}>
       <View style={styles.avis}>
@@ -417,34 +423,25 @@ function ExpandedAuthorsList({
   )
 }
 
-function AdditionalPostText({
-  additionalPost,
-}: {
-  additionalPost?: PostThreadModel
-}) {
+function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) {
   const pal = usePalette('default')
-  if (
-    !additionalPost ||
-    !additionalPost.thread?.postRecord ||
-    additionalPost.error
-  ) {
-    return <View />
+  if (post && AppBskyFeedPost.isRecord(post?.record)) {
+    const text = post.record.text
+    const images = AppBskyEmbedImages.isView(post.embed)
+      ? post.embed.images
+      : AppBskyEmbedRecordWithMedia.isView(post.embed) &&
+        AppBskyEmbedImages.isView(post.embed.media)
+      ? post.embed.media.images
+      : undefined
+    return (
+      <>
+        {text?.length > 0 && <Text style={pal.textLight}>{text}</Text>}
+        {images && images?.length > 0 && (
+          <ImageHorzList images={images} style={styles.additionalPostImages} />
+        )}
+      </>
+    )
   }
-  const text = additionalPost.thread?.postRecord.text
-  const images = AppBskyEmbedImages.isView(additionalPost.thread.post.embed)
-    ? additionalPost.thread.post.embed.images
-    : AppBskyEmbedRecordWithMedia.isView(additionalPost.thread.post.embed) &&
-      AppBskyEmbedImages.isView(additionalPost.thread.post.embed.media)
-    ? additionalPost.thread.post.embed.media.images
-    : undefined
-  return (
-    <>
-      {text?.length > 0 && <Text style={pal.textLight}>{text}</Text>}
-      {images && images?.length > 0 && (
-        <ImageHorzList images={images} style={styles.additionalPostImages} />
-      )}
-    </>
-  )
 }
 
 const styles = StyleSheet.create({
diff --git a/src/view/com/notifications/InvitedUsers.tsx b/src/view/com/notifications/InvitedUsers.tsx
deleted file mode 100644
index aaf358b87..000000000
--- a/src/view/com/notifications/InvitedUsers.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import React from 'react'
-import {
-  FontAwesomeIcon,
-  FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
-import {StyleSheet, View} from 'react-native'
-import {observer} from 'mobx-react-lite'
-import {AppBskyActorDefs} from '@atproto/api'
-import {UserAvatar} from '../util/UserAvatar'
-import {Text} from '../util/text/Text'
-import {Link, TextLink} from '../util/Link'
-import {Button} from '../util/forms/Button'
-import {FollowButton} from '../profile/FollowButton'
-import {CenteredView} from '../util/Views.web'
-import {useStores} from 'state/index'
-import {usePalette} from 'lib/hooks/usePalette'
-import {s} from 'lib/styles'
-import {sanitizeDisplayName} from 'lib/strings/display-names'
-import {makeProfileLink} from 'lib/routes/links'
-
-export const InvitedUsers = observer(function InvitedUsersImpl() {
-  const store = useStores()
-  return (
-    <CenteredView>
-      {store.invitedUsers.profiles.map(profile => (
-        <InvitedUser key={profile.did} profile={profile} />
-      ))}
-    </CenteredView>
-  )
-})
-
-function InvitedUser({
-  profile,
-}: {
-  profile: AppBskyActorDefs.ProfileViewDetailed
-}) {
-  const pal = usePalette('default')
-  const store = useStores()
-
-  const onPressDismiss = React.useCallback(() => {
-    store.invitedUsers.markSeen(profile.did)
-  }, [store, profile])
-
-  return (
-    <View
-      testID="invitedUser"
-      style={[
-        styles.layout,
-        {
-          backgroundColor: pal.colors.unreadNotifBg,
-          borderColor: pal.colors.unreadNotifBorder,
-        },
-      ]}>
-      <View style={styles.layoutIcon}>
-        <FontAwesomeIcon
-          icon="user-plus"
-          size={24}
-          style={[styles.icon, s.blue3 as FontAwesomeIconStyle]}
-        />
-      </View>
-      <View style={s.flex1}>
-        <Link href={makeProfileLink(profile)}>
-          <UserAvatar avatar={profile.avatar} size={35} />
-        </Link>
-        <Text style={[styles.desc, pal.text]}>
-          <TextLink
-            type="md-bold"
-            style={pal.text}
-            href={makeProfileLink(profile)}
-            text={sanitizeDisplayName(profile.displayName || profile.handle)}
-          />{' '}
-          joined using your invite code!
-        </Text>
-        <View style={styles.btns}>
-          <FollowButton
-            unfollowedType="primary"
-            followedType="primary-light"
-            profile={profile}
-          />
-          <Button
-            testID="dismissBtn"
-            type="primary-light"
-            label="Dismiss"
-            onPress={onPressDismiss}
-          />
-        </View>
-      </View>
-    </View>
-  )
-}
-
-const styles = StyleSheet.create({
-  layout: {
-    flexDirection: 'row',
-    borderTopWidth: 1,
-    padding: 10,
-  },
-  layoutIcon: {
-    width: 70,
-    alignItems: 'flex-end',
-    paddingTop: 2,
-  },
-  icon: {
-    marginRight: 10,
-    marginTop: 4,
-  },
-  desc: {
-    paddingVertical: 6,
-  },
-  btns: {
-    flexDirection: 'row',
-    gap: 10,
-  },
-})