show informative message when token scope is wrong
parent
60886b76c8
commit
c06611fb71
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue