show informative message when token scope is wrong

zio/stable
Samuel Newman 2023-11-30 17:09:10 +00:00
parent 60886b76c8
commit c06611fb71
4 changed files with 98 additions and 27 deletions

View File

@ -3,6 +3,7 @@ import {useQuery} from '@tanstack/react-query'
import {getAgent} from '#/state/session' import {getAgent} from '#/state/session'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
import {cleanError} from '#/lib/strings/errors'
function isInviteAvailable(invite: ComAtprotoServerDefs.InviteCode): boolean { function isInviteAvailable(invite: ComAtprotoServerDefs.InviteCode): boolean {
return invite.available - invite.uses.length > 0 && !invite.disabled return invite.available - invite.uses.length > 0 && !invite.disabled
@ -17,7 +18,24 @@ export function useInviteCodesQuery() {
staleTime: STALE.HOURS.ONE, staleTime: STALE.HOURS.ONE,
queryKey: ['inviteCodes'], queryKey: ['inviteCodes'],
queryFn: async () => { 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) { if (!res.data?.codes) {
throw new Error(`useInviteCodesQuery: no codes returned`) throw new Error(`useInviteCodesQuery: no codes returned`)
@ -27,6 +45,7 @@ export function useInviteCodesQuery() {
const used = res.data.codes.filter(code => !isInviteAvailable(code)) const used = res.data.codes.filter(code => !isInviteAvailable(code))
return { return {
disabled: false,
all: [...available, ...used], all: [...available, ...used],
available, available,
used, used,

View File

@ -36,7 +36,6 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useAccountSwitcher} from 'lib/hooks/useAccountSwitcher' import {useAccountSwitcher} from 'lib/hooks/useAccountSwitcher'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {NavigationProp} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types'
import {pluralize} from 'lib/strings/helpers'
import {HandIcon, HashtagIcon} from 'lib/icons' import {HandIcon, HashtagIcon} from 'lib/icons'
import {formatCount} from 'view/com/util/numeric/format' import {formatCount} from 'view/com/util/numeric/format'
import Clipboard from '@react-native-clipboard/clipboard' import Clipboard from '@react-native-clipboard/clipboard'
@ -71,7 +70,7 @@ import {clearLegacyStorage} from '#/state/persisted/legacy'
// -prf // -prf
import {useDebugHeaderSetting} from 'lib/api/debug-appview-proxy-header' import {useDebugHeaderSetting} from 'lib/api/debug-appview-proxy-header'
import {STATUS_PAGE_URL} from 'lib/constants' 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 {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
@ -385,7 +384,8 @@ export function SettingsScreen({}: Props) {
onPress={isSwitchingAccounts ? undefined : onPressInviteCodes} onPress={isSwitchingAccounts ? undefined : onPressInviteCodes}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Invite`)} accessibilityLabel={_(msg`Invite`)}
accessibilityHint="Opens invite code list"> accessibilityHint="Opens invite code list"
disabled={invites?.disabled}>
<View <View
style={[ style={[
styles.iconContainer, styles.iconContainer,
@ -401,8 +401,18 @@ export function SettingsScreen({}: Props) {
/> />
</View> </View>
<Text type="lg" style={invitesAvailable > 0 ? pal.link : pal.text}> <Text type="lg" style={invitesAvailable > 0 ? pal.link : pal.text}>
{formatCount(invitesAvailable)} invite{' '} {invites?.disabled ? (
{pluralize(invitesAvailable, 'code')} available <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> </Text>
</TouchableOpacity> </TouchableOpacity>

View File

@ -42,7 +42,7 @@ import {NavigationProp} from 'lib/routes/types'
import {useNavigationTabState} from 'lib/hooks/useNavigationTabState' import {useNavigationTabState} from 'lib/hooks/useNavigationTabState'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {formatCount, formatCountShortOnly} from 'view/com/util/numeric/format' 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 {useLingui} from '@lingui/react'
import {useSetDrawerOpen} from '#/state/shell' import {useSetDrawerOpen} from '#/state/shell'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
@ -486,23 +486,28 @@ function InviteCodes({style}: {style?: StyleProp<ViewStyle>}) {
const {data: invites} = useInviteCodesQuery() const {data: invites} = useInviteCodesQuery()
const invitesAvailable = invites?.available?.length ?? 0 const invitesAvailable = invites?.available?.length ?? 0
const {openModal} = useModalControls() const {openModal} = useModalControls()
const {_} = useLingui()
const onPress = React.useCallback(() => { const onPress = React.useCallback(() => {
track('Menu:ItemClicked', {url: '#invite-codes'}) track('Menu:ItemClicked', {url: '#invite-codes'})
setDrawerOpen(false) setDrawerOpen(false)
openModal({name: 'invite-codes'}) openModal({name: 'invite-codes'})
}, [openModal, track, setDrawerOpen]) }, [openModal, track, setDrawerOpen])
return ( return (
<TouchableOpacity <TouchableOpacity
testID="menuItemInviteCodes" testID="menuItemInviteCodes"
style={[styles.inviteCodes, style]} style={[styles.inviteCodes, style]}
onPress={onPress} onPress={onPress}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={ accessibilityLabel={_(
invitesAvailable === 1 plural(invitesAvailable, {
? 'Invite codes: 1 available' one: 'Invite codes: # available',
: `Invite codes: ${invitesAvailable} available` other: 'Invite codes: # available',
} }),
accessibilityHint="Opens list of invite codes"> )}
accessibilityHint={_(msg`Opens list of invite codes`)}
disabled={invites?.disabled}>
<FontAwesomeIcon <FontAwesomeIcon
icon="ticket" icon="ticket"
style={[ style={[
@ -514,8 +519,17 @@ function InviteCodes({style}: {style?: StyleProp<ViewStyle>}) {
<Text <Text
type="lg-medium" type="lg-medium"
style={invitesAvailable > 0 ? pal.link : pal.textLight}> style={invitesAvailable > 0 ? pal.link : pal.textLight}>
{formatCount(invitesAvailable)} invite{' '} {invites?.disabled ? (
{pluralize(invitesAvailable, 'code')} <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> </Text>
</TouchableOpacity> </TouchableOpacity>
) )
@ -592,10 +606,11 @@ const styles = StyleSheet.create({
paddingLeft: 22, paddingLeft: 22,
paddingVertical: 8, paddingVertical: 8,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center',
}, },
inviteCodesIcon: { inviteCodesIcon: {
marginRight: 6, marginRight: 6,
flexShrink: 0,
marginTop: 2,
}, },
footer: { footer: {

View File

@ -9,11 +9,10 @@ import {TextLink} from 'view/com/util/Link'
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants' import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {pluralize} from 'lib/strings/helpers'
import {formatCount} from 'view/com/util/numeric/format' import {formatCount} from 'view/com/util/numeric/format'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {Plural, Trans, msg, plural} from '@lingui/macro'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useInviteCodesQuery} from '#/state/queries/invites' import {useInviteCodesQuery} from '#/state/queries/invites'
@ -106,21 +105,45 @@ function InviteCodes() {
const {openModal} = useModalControls() const {openModal} = useModalControls()
const {data: invites} = useInviteCodesQuery() const {data: invites} = useInviteCodesQuery()
const invitesAvailable = invites?.available?.length ?? 0 const invitesAvailable = invites?.available?.length ?? 0
const {_} = useLingui()
const onPress = React.useCallback(() => { const onPress = React.useCallback(() => {
openModal({name: 'invite-codes'}) openModal({name: 'invite-codes'})
}, [openModal]) }, [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 ( return (
<TouchableOpacity <TouchableOpacity
style={[styles.inviteCodes, pal.border]} style={[styles.inviteCodes, pal.border]}
onPress={onPress} onPress={onPress}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={ accessibilityLabel={_(
invitesAvailable === 1 plural(invitesAvailable, {
? 'Invite codes: 1 available' one: 'Invite codes: # available',
: `Invite codes: ${invitesAvailable} available` other: 'Invite codes: # available',
} }),
accessibilityHint="Opens list of invite codes"> )}
accessibilityHint={_(msg`Opens list of invite codes`)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="ticket" icon="ticket"
style={[ style={[
@ -132,8 +155,11 @@ function InviteCodes() {
<Text <Text
type="md-medium" type="md-medium"
style={invitesAvailable > 0 ? pal.link : pal.textLight}> style={invitesAvailable > 0 ? pal.link : pal.textLight}>
{formatCount(invitesAvailable)} invite{' '} <Plural
{pluralize(invitesAvailable, 'code')} available value={formatCount(invitesAvailable)}
one="# invite code available"
other="# invite codes available"
/>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
) )
@ -163,9 +189,10 @@ const styles = StyleSheet.create({
paddingHorizontal: 16, paddingHorizontal: 16,
paddingVertical: 12, paddingVertical: 12,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center',
}, },
inviteCodesIcon: { inviteCodesIcon: {
marginTop: 2,
marginRight: 6, marginRight: 6,
flexShrink: 0,
}, },
}) })