about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2023-11-30 17:09:10 +0000
committerSamuel Newman <mozzius@protonmail.com>2023-11-30 17:09:10 +0000
commitc06611fb71c3a0fdce54e2889bf2caaf7460143c (patch)
treefee9b411ea3439a89b2c09a71afce1389eb211ec
parent60886b76c8baded58d84c111f2d92db49514f940 (diff)
downloadvoidsky-c06611fb71c3a0fdce54e2889bf2caaf7460143c.tar.zst
show informative message when token scope is wrong
-rw-r--r--src/state/queries/invites.ts21
-rw-r--r--src/view/screens/Settings.tsx20
-rw-r--r--src/view/shell/Drawer.tsx35
-rw-r--r--src/view/shell/desktop/RightNav.tsx49
4 files changed, 98 insertions, 27 deletions
diff --git a/src/state/queries/invites.ts b/src/state/queries/invites.ts
index c42ba29d5..367917af5 100644
--- a/src/state/queries/invites.ts
+++ b/src/state/queries/invites.ts
@@ -3,6 +3,7 @@ import {useQuery} from '@tanstack/react-query'
 
 import {getAgent} from '#/state/session'
 import {STALE} from '#/state/queries'
+import {cleanError} from '#/lib/strings/errors'
 
 function isInviteAvailable(invite: ComAtprotoServerDefs.InviteCode): boolean {
   return invite.available - invite.uses.length > 0 && !invite.disabled
@@ -17,7 +18,24 @@ export function useInviteCodesQuery() {
     staleTime: STALE.HOURS.ONE,
     queryKey: ['inviteCodes'],
     queryFn: async () => {
-      const res = await getAgent().com.atproto.server.getAccountInviteCodes({})
+      const res = await getAgent()
+        .com.atproto.server.getAccountInviteCodes({})
+        .catch(e => {
+          if (cleanError(e) === 'Bad token scope') {
+            return null
+          } else {
+            throw e
+          }
+        })
+
+      if (res === null) {
+        return {
+          disabled: true,
+          all: [],
+          available: [],
+          used: [],
+        }
+      }
 
       if (!res.data?.codes) {
         throw new Error(`useInviteCodesQuery: no codes returned`)
@@ -27,6 +45,7 @@ export function useInviteCodesQuery() {
       const used = res.data.codes.filter(code => !isInviteAvailable(code))
 
       return {
+        disabled: false,
         all: [...available, ...used],
         available,
         used,
diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx
index 88cc2d532..944f5e81a 100644
--- a/src/view/screens/Settings.tsx
+++ b/src/view/screens/Settings.tsx
@@ -36,7 +36,6 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {useAccountSwitcher} from 'lib/hooks/useAccountSwitcher'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {NavigationProp} from 'lib/routes/types'
-import {pluralize} from 'lib/strings/helpers'
 import {HandIcon, HashtagIcon} from 'lib/icons'
 import {formatCount} from 'view/com/util/numeric/format'
 import Clipboard from '@react-native-clipboard/clipboard'
@@ -71,7 +70,7 @@ import {clearLegacyStorage} from '#/state/persisted/legacy'
 // -prf
 import {useDebugHeaderSetting} from 'lib/api/debug-appview-proxy-header'
 import {STATUS_PAGE_URL} from 'lib/constants'
-import {Trans, msg} from '@lingui/macro'
+import {Plural, Trans, msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useQueryClient} from '@tanstack/react-query'
 
@@ -385,7 +384,8 @@ export function SettingsScreen({}: Props) {
           onPress={isSwitchingAccounts ? undefined : onPressInviteCodes}
           accessibilityRole="button"
           accessibilityLabel={_(msg`Invite`)}
-          accessibilityHint="Opens invite code list">
+          accessibilityHint="Opens invite code list"
+          disabled={invites?.disabled}>
           <View
             style={[
               styles.iconContainer,
@@ -401,8 +401,18 @@ export function SettingsScreen({}: Props) {
             />
           </View>
           <Text type="lg" style={invitesAvailable > 0 ? pal.link : pal.text}>
-            {formatCount(invitesAvailable)} invite{' '}
-            {pluralize(invitesAvailable, 'code')} available
+            {invites?.disabled ? (
+              <Trans>
+                Your invite codes are hidden when logged in using an App
+                Password
+              </Trans>
+            ) : (
+              <Plural
+                value={formatCount(invitesAvailable)}
+                one="# invite code available"
+                other="# invite codes available"
+              />
+            )}
           </Text>
         </TouchableOpacity>
 
diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
index 9df9b70b3..ee6c980b8 100644
--- a/src/view/shell/Drawer.tsx
+++ b/src/view/shell/Drawer.tsx
@@ -42,7 +42,7 @@ import {NavigationProp} from 'lib/routes/types'
 import {useNavigationTabState} from 'lib/hooks/useNavigationTabState'
 import {isWeb} from 'platform/detection'
 import {formatCount, formatCountShortOnly} from 'view/com/util/numeric/format'
-import {Trans, msg} from '@lingui/macro'
+import {Plural, Trans, msg, plural} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useSetDrawerOpen} from '#/state/shell'
 import {useModalControls} from '#/state/modals'
@@ -486,23 +486,28 @@ function InviteCodes({style}: {style?: StyleProp<ViewStyle>}) {
   const {data: invites} = useInviteCodesQuery()
   const invitesAvailable = invites?.available?.length ?? 0
   const {openModal} = useModalControls()
+  const {_} = useLingui()
+
   const onPress = React.useCallback(() => {
     track('Menu:ItemClicked', {url: '#invite-codes'})
     setDrawerOpen(false)
     openModal({name: 'invite-codes'})
   }, [openModal, track, setDrawerOpen])
+
   return (
     <TouchableOpacity
       testID="menuItemInviteCodes"
       style={[styles.inviteCodes, style]}
       onPress={onPress}
       accessibilityRole="button"
-      accessibilityLabel={
-        invitesAvailable === 1
-          ? 'Invite codes: 1 available'
-          : `Invite codes: ${invitesAvailable} available`
-      }
-      accessibilityHint="Opens list of invite codes">
+      accessibilityLabel={_(
+        plural(invitesAvailable, {
+          one: 'Invite codes: # available',
+          other: 'Invite codes: # available',
+        }),
+      )}
+      accessibilityHint={_(msg`Opens list of invite codes`)}
+      disabled={invites?.disabled}>
       <FontAwesomeIcon
         icon="ticket"
         style={[
@@ -514,8 +519,17 @@ function InviteCodes({style}: {style?: StyleProp<ViewStyle>}) {
       <Text
         type="lg-medium"
         style={invitesAvailable > 0 ? pal.link : pal.textLight}>
-        {formatCount(invitesAvailable)} invite{' '}
-        {pluralize(invitesAvailable, 'code')}
+        {invites?.disabled ? (
+          <Trans>
+            Your invite codes are hidden when logged in using an App Password
+          </Trans>
+        ) : (
+          <Plural
+            value={formatCount(invitesAvailable)}
+            one="# invite code available"
+            other="# invite codes available"
+          />
+        )}
       </Text>
     </TouchableOpacity>
   )
@@ -592,10 +606,11 @@ const styles = StyleSheet.create({
     paddingLeft: 22,
     paddingVertical: 8,
     flexDirection: 'row',
-    alignItems: 'center',
   },
   inviteCodesIcon: {
     marginRight: 6,
+    flexShrink: 0,
+    marginTop: 2,
   },
 
   footer: {
diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx
index 7087a4397..9a5186549 100644
--- a/src/view/shell/desktop/RightNav.tsx
+++ b/src/view/shell/desktop/RightNav.tsx
@@ -9,11 +9,10 @@ import {TextLink} from 'view/com/util/Link'
 import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants'
 import {s} from 'lib/styles'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
-import {pluralize} from 'lib/strings/helpers'
 import {formatCount} from 'view/com/util/numeric/format'
 import {useModalControls} from '#/state/modals'
 import {useLingui} from '@lingui/react'
-import {msg} from '@lingui/macro'
+import {Plural, Trans, msg, plural} from '@lingui/macro'
 import {useSession} from '#/state/session'
 import {useInviteCodesQuery} from '#/state/queries/invites'
 
@@ -106,21 +105,45 @@ function InviteCodes() {
   const {openModal} = useModalControls()
   const {data: invites} = useInviteCodesQuery()
   const invitesAvailable = invites?.available?.length ?? 0
+  const {_} = useLingui()
 
   const onPress = React.useCallback(() => {
     openModal({name: 'invite-codes'})
   }, [openModal])
+
+  if (!invites) {
+    return null
+  }
+
+  if (invites?.disabled) {
+    return (
+      <View style={[styles.inviteCodes, pal.border]}>
+        <FontAwesomeIcon
+          icon="ticket"
+          style={[styles.inviteCodesIcon, pal.textLight]}
+          size={16}
+        />
+        <Text type="md-medium" style={pal.textLight}>
+          <Trans>
+            Your invite codes are hidden when logged in using an App Password
+          </Trans>
+        </Text>
+      </View>
+    )
+  }
+
   return (
     <TouchableOpacity
       style={[styles.inviteCodes, pal.border]}
       onPress={onPress}
       accessibilityRole="button"
-      accessibilityLabel={
-        invitesAvailable === 1
-          ? 'Invite codes: 1 available'
-          : `Invite codes: ${invitesAvailable} available`
-      }
-      accessibilityHint="Opens list of invite codes">
+      accessibilityLabel={_(
+        plural(invitesAvailable, {
+          one: 'Invite codes: # available',
+          other: 'Invite codes: # available',
+        }),
+      )}
+      accessibilityHint={_(msg`Opens list of invite codes`)}>
       <FontAwesomeIcon
         icon="ticket"
         style={[
@@ -132,8 +155,11 @@ function InviteCodes() {
       <Text
         type="md-medium"
         style={invitesAvailable > 0 ? pal.link : pal.textLight}>
-        {formatCount(invitesAvailable)} invite{' '}
-        {pluralize(invitesAvailable, 'code')} available
+        <Plural
+          value={formatCount(invitesAvailable)}
+          one="# invite code available"
+          other="# invite codes available"
+        />
       </Text>
     </TouchableOpacity>
   )
@@ -163,9 +189,10 @@ const styles = StyleSheet.create({
     paddingHorizontal: 16,
     paddingVertical: 12,
     flexDirection: 'row',
-    alignItems: 'center',
   },
   inviteCodesIcon: {
+    marginTop: 2,
     marginRight: 6,
+    flexShrink: 0,
   },
 })