about summary refs log tree commit diff
path: root/src/view/com/threadgate/WhoCanReply.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/threadgate/WhoCanReply.tsx')
-rw-r--r--src/view/com/threadgate/WhoCanReply.tsx234
1 files changed, 139 insertions, 95 deletions
diff --git a/src/view/com/threadgate/WhoCanReply.tsx b/src/view/com/threadgate/WhoCanReply.tsx
index c1e36d481..3ffbaa7ae 100644
--- a/src/view/com/threadgate/WhoCanReply.tsx
+++ b/src/view/com/threadgate/WhoCanReply.tsx
@@ -1,128 +1,172 @@
 import React from 'react'
-import {StyleProp, View, ViewStyle} from 'react-native'
-import {
-  AppBskyFeedDefs,
-  AppBskyFeedThreadgate,
-  AppBskyGraphDefs,
-  AtUri,
-} from '@atproto/api'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {Trans} from '@lingui/macro'
+import {Keyboard, StyleProp, View, ViewStyle} from 'react-native'
+import {AppBskyFeedDefs, AppBskyGraphDefs, AtUri} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useQueryClient} from '@tanstack/react-query'
 
+import {useAnalytics} from '#/lib/analytics/analytics'
+import {createThreadgate} from '#/lib/api'
 import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
 import {usePalette} from '#/lib/hooks/usePalette'
-import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {makeListLink, makeProfileLink} from '#/lib/routes/links'
 import {colors} from '#/lib/styles'
+import {logger} from '#/logger'
+import {isNative} from '#/platform/detection'
+import {useModalControls} from '#/state/modals'
+import {RQKEY_ROOT as POST_THREAD_RQKEY_ROOT} from '#/state/queries/post-thread'
+import {
+  ThreadgateSetting,
+  threadgateViewToSettings,
+} from '#/state/queries/threadgate'
+import {useAgent} from '#/state/session'
+import * as Toast from 'view/com/util/Toast'
+import {Button} from '#/components/Button'
 import {TextLink} from '../util/Link'
 import {Text} from '../util/text/Text'
 
 export function WhoCanReply({
   post,
+  isThreadAuthor,
   style,
 }: {
   post: AppBskyFeedDefs.PostView
+  isThreadAuthor: boolean
   style?: StyleProp<ViewStyle>
 }) {
+  const {track} = useAnalytics()
+  const {_} = useLingui()
   const pal = usePalette('default')
-  const {isMobile} = useWebMediaQueries()
+  const agent = useAgent()
+  const queryClient = useQueryClient()
+  const {openModal} = useModalControls()
   const containerStyles = useColorSchemeStyle(
     {
-      borderColor: pal.colors.unreadNotifBorder,
       backgroundColor: pal.colors.unreadNotifBg,
     },
     {
-      borderColor: pal.colors.unreadNotifBorder,
       backgroundColor: pal.colors.unreadNotifBg,
     },
   )
-  const iconStyles = useColorSchemeStyle(
+  const textStyles = useColorSchemeStyle(
+    {color: colors.blue5},
+    {color: colors.blue1},
+  )
+  const hoverStyles = useColorSchemeStyle(
     {
-      backgroundColor: colors.blue3,
+      backgroundColor: colors.white,
     },
     {
-      backgroundColor: colors.blue3,
+      backgroundColor: pal.colors.background,
     },
   )
-  const textStyles = useColorSchemeStyle(
-    {color: colors.gray7},
-    {color: colors.blue1},
-  )
-  const record = React.useMemo(
-    () =>
-      post.threadgate &&
-      AppBskyFeedThreadgate.isRecord(post.threadgate.record) &&
-      AppBskyFeedThreadgate.validateRecord(post.threadgate.record).success
-        ? post.threadgate.record
-        : null,
+  const settings = React.useMemo(
+    () => threadgateViewToSettings(post.threadgate),
     [post],
   )
-  if (record) {
-    return (
-      <View
-        style={[
-          {
-            flexDirection: 'row',
-            alignItems: 'center',
-            gap: isMobile ? 8 : 10,
-            paddingHorizontal: isMobile ? 16 : 18,
-            paddingVertical: 12,
-            borderWidth: 1,
-            borderLeftWidth: isMobile ? 0 : 1,
-            borderRightWidth: isMobile ? 0 : 1,
-          },
-          containerStyles,
-          style,
-        ]}>
-        <View
-          style={[
-            {
-              flexDirection: 'row',
-              alignItems: 'center',
-              justifyContent: 'center',
-              width: 32,
-              height: 32,
-              borderRadius: 19,
-            },
-            iconStyles,
-          ]}>
-          <FontAwesomeIcon
-            icon={['far', 'comments']}
-            size={16}
-            color={'#fff'}
-          />
-        </View>
-        <View style={{flex: 1}}>
-          <Text type="sm" style={[{flexWrap: 'wrap'}, textStyles]}>
-            {!record.allow?.length ? (
-              <Trans>Replies to this thread are disabled</Trans>
-            ) : (
-              <Trans>
-                Only{' '}
-                {record.allow.map((rule, i) => (
-                  <>
-                    <Rule
-                      key={`rule-${i}`}
-                      rule={rule}
-                      post={post}
-                      lists={post.threadgate!.lists}
-                    />
-                    <Separator
-                      key={`sep-${i}`}
-                      i={i}
-                      length={record.allow!.length}
-                    />
-                  </>
-                ))}{' '}
-                can reply.
-              </Trans>
+  const isRootPost = !('reply' in post.record)
+
+  const onPressEdit = () => {
+    track('Post:EditThreadgateOpened')
+    if (isNative && Keyboard.isVisible()) {
+      Keyboard.dismiss()
+    }
+    openModal({
+      name: 'threadgate',
+      settings,
+      async onConfirm(newSettings: ThreadgateSetting[]) {
+        try {
+          if (newSettings.length) {
+            await createThreadgate(agent, post.uri, newSettings)
+          } else {
+            await agent.api.com.atproto.repo.deleteRecord({
+              repo: agent.session!.did,
+              collection: 'app.bsky.feed.threadgate',
+              rkey: new AtUri(post.uri).rkey,
+            })
+          }
+          Toast.show('Thread settings updated')
+          queryClient.invalidateQueries({
+            queryKey: [POST_THREAD_RQKEY_ROOT],
+          })
+          track('Post:ThreadgateEdited')
+        } catch (err) {
+          Toast.show(
+            'There was an issue. Please check your internet connection and try again.',
+          )
+          logger.error('Failed to edit threadgate', {message: err})
+        }
+      },
+    })
+  }
+
+  if (!isRootPost) {
+    return null
+  }
+  if (!settings.length && !isThreadAuthor) {
+    return null
+  }
+
+  return (
+    <View
+      style={[
+        {
+          flexDirection: 'row',
+          alignItems: 'center',
+          gap: 10,
+          paddingLeft: 18,
+          paddingRight: 14,
+          paddingVertical: 10,
+          borderTopWidth: 1,
+        },
+        pal.border,
+        containerStyles,
+        style,
+      ]}>
+      <View style={{flex: 1, paddingVertical: 6}}>
+        <Text type="sm" style={[{flexWrap: 'wrap'}, textStyles]}>
+          {!settings.length ? (
+            <Trans>Everybody can reply.</Trans>
+          ) : settings[0].type === 'nobody' ? (
+            <Trans>Replies to this thread are disabled.</Trans>
+          ) : (
+            <Trans>
+              Only{' '}
+              {settings.map((rule, i) => (
+                <>
+                  <Rule
+                    key={`rule-${i}`}
+                    rule={rule}
+                    post={post}
+                    lists={post.threadgate!.lists}
+                  />
+                  <Separator key={`sep-${i}`} i={i} length={settings.length} />
+                </>
+              ))}{' '}
+              can reply.
+            </Trans>
+          )}
+        </Text>
+      </View>
+      {isThreadAuthor && (
+        <View>
+          <Button label={_(msg`Edit`)} onPress={onPressEdit}>
+            {({hovered}) => (
+              <View
+                style={[
+                  hovered && hoverStyles,
+                  {paddingVertical: 6, paddingHorizontal: 8, borderRadius: 8},
+                ]}>
+                <Text type="sm" style={pal.link}>
+                  <Trans>Edit</Trans>
+                </Text>
+              </View>
             )}
-          </Text>
+          </Button>
         </View>
-      </View>
-    )
-  }
-  return null
+      )}
+    </View>
+  )
 }
 
 function Rule({
@@ -130,15 +174,15 @@ function Rule({
   post,
   lists,
 }: {
-  rule: any
+  rule: ThreadgateSetting
   post: AppBskyFeedDefs.PostView
   lists: AppBskyGraphDefs.ListViewBasic[] | undefined
 }) {
   const pal = usePalette('default')
-  if (AppBskyFeedThreadgate.isMentionRule(rule)) {
+  if (rule.type === 'mention') {
     return <Trans>mentioned users</Trans>
   }
-  if (AppBskyFeedThreadgate.isFollowingRule(rule)) {
+  if (rule.type === 'following') {
     return (
       <Trans>
         users followed by{' '}
@@ -151,7 +195,7 @@ function Rule({
       </Trans>
     )
   }
-  if (AppBskyFeedThreadgate.isListRule(rule)) {
+  if (rule.type === 'list') {
     const list = lists?.find(l => l.uri === rule.list)
     if (list) {
       const listUrip = new AtUri(list.uri)