about summary refs log tree commit diff
path: root/src/view/screens/AppPasswords.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/screens/AppPasswords.tsx')
-rw-r--r--src/view/screens/AppPasswords.tsx211
1 files changed, 133 insertions, 78 deletions
diff --git a/src/view/screens/AppPasswords.tsx b/src/view/screens/AppPasswords.tsx
index 74d293ef4..154035f22 100644
--- a/src/view/screens/AppPasswords.tsx
+++ b/src/view/screens/AppPasswords.tsx
@@ -1,80 +1,114 @@
 import React from 'react'
-import {StyleSheet, TouchableOpacity, View} from 'react-native'
+import {
+  ActivityIndicator,
+  StyleSheet,
+  TouchableOpacity,
+  View,
+} from 'react-native'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {ScrollView} from 'react-native-gesture-handler'
 import {Text} from '../com/util/text/Text'
 import {Button} from '../com/util/forms/Button'
 import * as Toast from '../com/util/Toast'
-import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
-import {withAuthRequired} from 'view/com/auth/withAuthRequired'
-import {observer} from 'mobx-react-lite'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
 import {CommonNavigatorParams} from 'lib/routes/types'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {useFocusEffect} from '@react-navigation/native'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {CenteredView} from 'view/com/util/Views'
+import {Trans, msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
 import {useSetMinimalShellMode} from '#/state/shell'
+import {useModalControls} from '#/state/modals'
+import {useLanguagePrefs} from '#/state/preferences'
+import {
+  useAppPasswordsQuery,
+  useAppPasswordDeleteMutation,
+} from '#/state/queries/app-passwords'
+import {ErrorScreen} from '../com/util/error/ErrorScreen'
+import {cleanError} from '#/lib/strings/errors'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'>
-export const AppPasswords = withAuthRequired(
-  observer(function AppPasswordsImpl({}: Props) {
-    const pal = usePalette('default')
-    const store = useStores()
-    const setMinimalShellMode = useSetMinimalShellMode()
-    const {screen} = useAnalytics()
-    const {isTabletOrDesktop} = useWebMediaQueries()
+export function AppPasswords({}: Props) {
+  const pal = usePalette('default')
+  const setMinimalShellMode = useSetMinimalShellMode()
+  const {screen} = useAnalytics()
+  const {isTabletOrDesktop} = useWebMediaQueries()
+  const {openModal} = useModalControls()
+  const {data: appPasswords, error} = useAppPasswordsQuery()
 
-    useFocusEffect(
-      React.useCallback(() => {
-        screen('AppPasswords')
-        setMinimalShellMode(false)
-      }, [screen, setMinimalShellMode]),
-    )
+  useFocusEffect(
+    React.useCallback(() => {
+      screen('AppPasswords')
+      setMinimalShellMode(false)
+    }, [screen, setMinimalShellMode]),
+  )
 
-    const onAdd = React.useCallback(async () => {
-      store.shell.openModal({name: 'add-app-password'})
-    }, [store])
+  const onAdd = React.useCallback(async () => {
+    openModal({name: 'add-app-password'})
+  }, [openModal])
 
-    // no app passwords (empty) state
-    if (store.me.appPasswords.length === 0) {
-      return (
-        <CenteredView
-          style={[
-            styles.container,
-            isTabletOrDesktop && styles.containerDesktop,
-            pal.view,
-            pal.border,
-          ]}
-          testID="appPasswordsScreen">
-          <AppPasswordsHeader />
-          <View style={[styles.empty, pal.viewLight]}>
-            <Text type="lg" style={[pal.text, styles.emptyText]}>
+  if (error) {
+    return (
+      <CenteredView
+        style={[
+          styles.container,
+          isTabletOrDesktop && styles.containerDesktop,
+          pal.view,
+          pal.border,
+        ]}
+        testID="appPasswordsScreen">
+        <ErrorScreen
+          title="Oops!"
+          message="There was an issue with fetching your app passwords"
+          details={cleanError(error)}
+        />
+      </CenteredView>
+    )
+  }
+
+  // no app passwords (empty) state
+  if (appPasswords?.length === 0) {
+    return (
+      <CenteredView
+        style={[
+          styles.container,
+          isTabletOrDesktop && styles.containerDesktop,
+          pal.view,
+          pal.border,
+        ]}
+        testID="appPasswordsScreen">
+        <AppPasswordsHeader />
+        <View style={[styles.empty, pal.viewLight]}>
+          <Text type="lg" style={[pal.text, styles.emptyText]}>
+            <Trans>
               You have not created any app passwords yet. You can create one by
               pressing the button below.
-            </Text>
-          </View>
-          {!isTabletOrDesktop && <View style={styles.flex1} />}
-          <View
-            style={[
-              styles.btnContainer,
-              isTabletOrDesktop && styles.btnContainerDesktop,
-            ]}>
-            <Button
-              testID="appPasswordBtn"
-              type="primary"
-              label="Add App Password"
-              style={styles.btn}
-              labelStyle={styles.btnLabel}
-              onPress={onAdd}
-            />
-          </View>
-        </CenteredView>
-      )
-    }
+            </Trans>
+          </Text>
+        </View>
+        {!isTabletOrDesktop && <View style={styles.flex1} />}
+        <View
+          style={[
+            styles.btnContainer,
+            isTabletOrDesktop && styles.btnContainerDesktop,
+          ]}>
+          <Button
+            testID="appPasswordBtn"
+            type="primary"
+            label="Add App Password"
+            style={styles.btn}
+            labelStyle={styles.btnLabel}
+            onPress={onAdd}
+          />
+        </View>
+      </CenteredView>
+    )
+  }
 
+  if (appPasswords?.length) {
     // has app passwords
     return (
       <CenteredView
@@ -92,7 +126,7 @@ export const AppPasswords = withAuthRequired(
             pal.border,
             !isTabletOrDesktop && styles.flex1,
           ]}>
-          {store.me.appPasswords.map((password, i) => (
+          {appPasswords.map((password, i) => (
             <AppPassword
               key={password.name}
               testID={`appPassword-${i}`}
@@ -127,15 +161,29 @@ export const AppPasswords = withAuthRequired(
         )}
       </CenteredView>
     )
-  }),
-)
+  }
+
+  return (
+    <CenteredView
+      style={[
+        styles.container,
+        isTabletOrDesktop && styles.containerDesktop,
+        pal.view,
+        pal.border,
+      ]}
+      testID="appPasswordsScreen">
+      <ActivityIndicator />
+    </CenteredView>
+  )
+}
 
 function AppPasswordsHeader() {
   const {isTabletOrDesktop} = useWebMediaQueries()
   const pal = usePalette('default')
+  const {_} = useLingui()
   return (
     <>
-      <ViewHeader title="App Passwords" showOnDesktop />
+      <ViewHeader title={_(msg`App Passwords`)} showOnDesktop />
       <Text
         type="sm"
         style={[
@@ -143,8 +191,10 @@ function AppPasswordsHeader() {
           pal.text,
           isTabletOrDesktop && styles.descriptionDesktop,
         ]}>
-        Use app passwords to login to other Bluesky clients without giving full
-        access to your account or password.
+        <Trans>
+          Use app passwords to login to other Bluesky clients without giving
+          full access to your account or password.
+        </Trans>
       </Text>
     </>
   )
@@ -160,21 +210,24 @@ function AppPassword({
   createdAt: string
 }) {
   const pal = usePalette('default')
-  const store = useStores()
+  const {_} = useLingui()
+  const {openModal} = useModalControls()
+  const {contentLanguages} = useLanguagePrefs()
+  const deleteMutation = useAppPasswordDeleteMutation()
 
   const onDelete = React.useCallback(async () => {
-    store.shell.openModal({
+    openModal({
       name: 'confirm',
-      title: 'Delete App Password',
-      message: `Are you sure you want to delete the app password "${name}"?`,
+      title: _(msg`Delete app password`),
+      message: _(
+        msg`Are you sure you want to delete the app password "${name}"?`,
+      ),
       async onPressConfirm() {
-        await store.me.deleteAppPassword(name)
+        await deleteMutation.mutateAsync({name})
         Toast.show('App password deleted')
       },
     })
-  }, [store, name])
-
-  const {contentLanguages} = store.preferences
+  }, [deleteMutation, openModal, name, _])
 
   const primaryLocale =
     contentLanguages.length > 0 ? contentLanguages[0] : 'en-US'
@@ -185,22 +238,24 @@ function AppPassword({
       style={[styles.item, pal.border]}
       onPress={onDelete}
       accessibilityRole="button"
-      accessibilityLabel="Delete app password"
+      accessibilityLabel={_(msg`Delete app password`)}
       accessibilityHint="">
       <View>
         <Text type="md-bold" style={pal.text}>
           {name}
         </Text>
         <Text type="md" style={[pal.text, styles.pr10]} numberOfLines={1}>
-          Created{' '}
-          {Intl.DateTimeFormat(primaryLocale, {
-            year: 'numeric',
-            month: 'numeric',
-            day: 'numeric',
-            hour: '2-digit',
-            minute: '2-digit',
-            second: '2-digit',
-          }).format(new Date(createdAt))}
+          <Trans>
+            Created{' '}
+            {Intl.DateTimeFormat(primaryLocale, {
+              year: 'numeric',
+              month: 'numeric',
+              day: 'numeric',
+              hour: '2-digit',
+              minute: '2-digit',
+              second: '2-digit',
+            }).format(new Date(createdAt))}
+          </Trans>
         </Text>
       </View>
       <FontAwesomeIcon icon={['far', 'trash-can']} style={styles.trashIcon} />