From cecb6e4e69eaccedf56f503589a59044fd7c6d19 Mon Sep 17 00:00:00 2001
From: Eric Bailey <git@esb.lol>
Date: Thu, 29 Feb 2024 19:30:30 -0600
Subject: Bump API SDK, add validation to MutedWords (#3055)

* Bump API SDK, add validation to MutedWords

* Tweaks to error state

* Comment

* Early return
---
 src/components/dialogs/MutedWords.tsx | 60 +++++++++++++++++++++++++++++------
 1 file changed, 51 insertions(+), 9 deletions(-)

(limited to 'src/components/dialogs/MutedWords.tsx')

diff --git a/src/components/dialogs/MutedWords.tsx b/src/components/dialogs/MutedWords.tsx
index 7c0d4fbca..453b13513 100644
--- a/src/components/dialogs/MutedWords.tsx
+++ b/src/components/dialogs/MutedWords.tsx
@@ -2,7 +2,7 @@ import React from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
-import {AppBskyActorDefs} from '@atproto/api'
+import {AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api'
 
 import {
   usePreferencesQuery,
@@ -10,7 +10,14 @@ import {
   useRemoveMutedWordMutation,
 } from '#/state/queries/preferences'
 import {isNative} from '#/platform/detection'
-import {atoms as a, useTheme, useBreakpoints, ViewStyleProp, web} from '#/alf'
+import {
+  atoms as a,
+  useTheme,
+  useBreakpoints,
+  ViewStyleProp,
+  web,
+  native,
+} from '#/alf'
 import {Text} from '#/components/Typography'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
@@ -48,24 +55,29 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) {
   const {isPending, mutateAsync: addMutedWord} = useUpsertMutedWordsMutation()
   const [field, setField] = React.useState('')
   const [options, setOptions] = React.useState(['content'])
-  const [_error, setError] = React.useState('')
+  const [error, setError] = React.useState('')
 
   const submit = React.useCallback(async () => {
-    const value = field.trim()
+    const sanitizedValue = sanitizeMutedWordValue(field)
     const targets = ['tag', options.includes('content') && 'content'].filter(
       Boolean,
     ) as AppBskyActorDefs.MutedWord['targets']
 
-    if (!value || !targets.length) return
+    if (!sanitizedValue || !targets.length) {
+      setField('')
+      setError(_(msg`Please enter a valid word, tag, or phrase to mute`))
+      return
+    }
 
     try {
-      await addMutedWord([{value, targets}])
+      // send raw value and rely on SDK as sanitization source of truth
+      await addMutedWord([{value: field, targets}])
       setField('')
     } catch (e: any) {
       logger.error(`Failed to save muted word`, {message: e.message})
       setError(e.message)
     }
-  }, [field, options, addMutedWord, setField])
+  }, [_, field, options, addMutedWord, setField])
 
   return (
     <Dialog.ScrollableInner label={_(msg`Manage your muted words and tags`)}>
@@ -87,7 +99,12 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) {
           label={_(msg`Enter a word or tag`)}
           placeholder={_(msg`Enter a word or tag`)}
           value={field}
-          onChangeText={setField}
+          onChangeText={value => {
+            if (error) {
+              setError('')
+            }
+            setField(value)
+          }}
           onSubmitEditing={submit}
         />
 
@@ -99,7 +116,7 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) {
           <View
             style={[
               a.pt_sm,
-              a.pb_md,
+              a.py_sm,
               a.flex_row,
               a.align_center,
               a.gap_sm,
@@ -151,8 +168,33 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) {
           </View>
         </Toggle.Group>
 
+        {error && (
+          <View
+            style={[
+              a.mb_lg,
+              a.flex_row,
+              a.rounded_sm,
+              a.p_md,
+              a.mb_xs,
+              t.atoms.bg_contrast_25,
+              {
+                backgroundColor: t.palette.negative_400,
+              },
+            ]}>
+            <Text
+              style={[
+                a.italic,
+                {color: t.palette.white},
+                native({marginTop: 2}),
+              ]}>
+              {error}
+            </Text>
+          </View>
+        )}
+
         <Text
           style={[
+            a.pt_xs,
             a.text_sm,
             a.italic,
             a.leading_snug,
-- 
cgit 1.4.1

