Fix invite codes flash on desktop, use loading placeholder (#1591)

* Fix invite codes flashing untrue value before loaded

* Add loading placeholder for right nav invites
zio/stable
Patroll 2023-10-04 19:31:43 +02:00 committed by GitHub
parent 2ba0c6a711
commit 9278822088
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 142 additions and 89 deletions

View File

@ -25,13 +25,13 @@ export class MeModel {
savedFeeds: SavedFeedsModel savedFeeds: SavedFeedsModel
notifications: NotificationsFeedModel notifications: NotificationsFeedModel
follows: MyFollowsCache follows: MyFollowsCache
invites: ComAtprotoServerDefs.InviteCode[] = [] invites: ComAtprotoServerDefs.InviteCode[] | null = []
appPasswords: ComAtprotoServerListAppPasswords.AppPassword[] = [] appPasswords: ComAtprotoServerListAppPasswords.AppPassword[] = []
lastProfileStateUpdate = Date.now() lastProfileStateUpdate = Date.now()
lastNotifsUpdate = Date.now() lastNotifsUpdate = Date.now()
get invitesAvailable() { get invitesAvailable() {
return this.invites.filter(isInviteAvailable).length return this.invites?.filter(isInviteAvailable).length || null
} }
constructor(public rootStore: RootStoreModel) { constructor(public rootStore: RootStoreModel) {
@ -180,7 +180,9 @@ export class MeModel {
} catch (e) { } catch (e) {
this.rootStore.log.error('Failed to fetch user invite codes', e) this.rootStore.log.error('Failed to fetch user invite codes', e)
} }
await this.rootStore.invitedUsers.fetch(this.invites) if (this.invites) {
await this.rootStore.invitedUsers.fetch(this.invites)
}
} }
} }

View File

@ -26,6 +26,33 @@ export function Component({}: {}) {
store.shell.closeModal() store.shell.closeModal()
}, [store]) }, [store])
if (store.me.invites === null) {
return (
<View style={[styles.container, pal.view]} testID="inviteCodesModal">
<Text type="title-xl" style={[styles.title, pal.text]}>
Error
</Text>
<Text type="lg" style={[styles.description, pal.text]}>
An error occurred while loading invite codes.
</Text>
<View style={styles.flex1} />
<View
style={[
styles.btnContainer,
isTabletOrDesktop && styles.btnContainerDesktop,
]}>
<Button
type="primary"
label="Done"
style={styles.btn}
labelStyle={styles.btnLabel}
onPress={onClose}
/>
</View>
</View>
)
}
if (store.me.invites.length === 0) { if (store.me.invites.length === 0) {
return ( return (
<View style={[styles.container, pal.view]} testID="inviteCodesModal"> <View style={[styles.container, pal.view]} testID="inviteCodesModal">

View File

@ -322,37 +322,45 @@ export const SettingsScreen = withAuthRequired(
<View style={styles.spacer20} /> <View style={styles.spacer20} />
<Text type="xl-bold" style={[pal.text, styles.heading]}> {store.me.invitesAvailable !== null && (
Invite a Friend <>
</Text> <Text type="xl-bold" style={[pal.text, styles.heading]}>
<TouchableOpacity Invite a Friend
testID="inviteFriendBtn" </Text>
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]} <TouchableOpacity
onPress={isSwitching ? undefined : onPressInviteCodes} testID="inviteFriendBtn"
accessibilityRole="button" style={[
accessibilityLabel="Invite" styles.linkCard,
accessibilityHint="Opens invite code list"> pal.view,
<View isSwitching && styles.dimmed,
style={[ ]}
styles.iconContainer, onPress={isSwitching ? undefined : onPressInviteCodes}
store.me.invitesAvailable > 0 ? primaryBg : pal.btn, accessibilityRole="button"
]}> accessibilityLabel="Invite"
<FontAwesomeIcon accessibilityHint="Opens invite code list">
icon="ticket" <View
style={ style={[
(store.me.invitesAvailable > 0 styles.iconContainer,
? primaryText store.me.invitesAvailable > 0 ? primaryBg : pal.btn,
: pal.text) as FontAwesomeIconStyle ]}>
} <FontAwesomeIcon
/> icon="ticket"
</View> style={
<Text (store.me.invitesAvailable > 0
type="lg" ? primaryText
style={store.me.invitesAvailable > 0 ? pal.link : pal.text}> : pal.text) as FontAwesomeIconStyle
{formatCount(store.me.invitesAvailable)} invite{' '} }
{pluralize(store.me.invitesAvailable, 'code')} available />
</Text> </View>
</TouchableOpacity> <Text
type="lg"
style={store.me.invitesAvailable > 0 ? pal.link : pal.text}>
{formatCount(store.me.invitesAvailable)} invite{' '}
{pluralize(store.me.invitesAvailable, 'code')} available
</Text>
</TouchableOpacity>
</>
)}
<View style={styles.spacer20} /> <View style={styles.spacer20} />

View File

@ -426,32 +426,34 @@ const InviteCodes = observer(function InviteCodesImpl({
store.shell.openModal({name: 'invite-codes'}) store.shell.openModal({name: 'invite-codes'})
}, [store, track]) }, [store, track])
return ( return (
<TouchableOpacity store.me.invitesAvailable !== null && (
testID="menuItemInviteCodes" <TouchableOpacity
style={[styles.inviteCodes, style]} testID="menuItemInviteCodes"
onPress={onPress} style={[styles.inviteCodes, style]}
accessibilityRole="button" onPress={onPress}
accessibilityLabel={ accessibilityRole="button"
invitesAvailable === 1 accessibilityLabel={
? 'Invite codes: 1 available' invitesAvailable === 1
: `Invite codes: ${invitesAvailable} available` ? 'Invite codes: 1 available'
} : `Invite codes: ${invitesAvailable} available`
accessibilityHint="Opens list of invite codes"> }
<FontAwesomeIcon accessibilityHint="Opens list of invite codes">
icon="ticket" <FontAwesomeIcon
style={[ icon="ticket"
styles.inviteCodesIcon, style={[
store.me.invitesAvailable > 0 ? pal.link : pal.textLight, styles.inviteCodesIcon,
]} store.me.invitesAvailable > 0 ? pal.link : pal.textLight,
size={18} ]}
/> size={18}
<Text />
type="lg-medium" <Text
style={store.me.invitesAvailable > 0 ? pal.link : pal.textLight}> type="lg-medium"
{formatCount(store.me.invitesAvailable)} invite{' '} style={store.me.invitesAvailable > 0 ? pal.link : pal.textLight}>
{pluralize(store.me.invitesAvailable, 'code')} {formatCount(store.me.invitesAvailable)} invite{' '}
</Text> {pluralize(store.me.invitesAvailable, 'code')}
</TouchableOpacity> </Text>
</TouchableOpacity>
)
) )
}) })

View File

@ -7,6 +7,7 @@ import {DesktopSearch} from './Search'
import {DesktopFeeds} from './Feeds' import {DesktopFeeds} from './Feeds'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
import {TextLink} from 'view/com/util/Link' import {TextLink} from 'view/com/util/Link'
import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
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 {useStores} from 'state/index' import {useStores} from 'state/index'
@ -89,32 +90,41 @@ const InviteCodes = observer(function InviteCodesImpl() {
const onPress = React.useCallback(() => { const onPress = React.useCallback(() => {
store.shell.openModal({name: 'invite-codes'}) store.shell.openModal({name: 'invite-codes'})
}, [store]) }, [store])
return ( return (
<TouchableOpacity <View style={[styles.separator, pal.border]}>
style={[styles.inviteCodes, pal.border]} {store.me.invitesAvailable === null ? (
onPress={onPress} <View style={[s.p10]}>
accessibilityRole="button" <LoadingPlaceholder width={186} height={32} style={[styles.br40]} />
accessibilityLabel={ </View>
invitesAvailable === 1 ) : (
? 'Invite codes: 1 available' <TouchableOpacity
: `Invite codes: ${invitesAvailable} available` style={[styles.inviteCodes]}
} onPress={onPress}
accessibilityHint="Opens list of invite codes"> accessibilityRole="button"
<FontAwesomeIcon accessibilityLabel={
icon="ticket" invitesAvailable === 1
style={[ ? 'Invite codes: 1 available'
styles.inviteCodesIcon, : `Invite codes: ${invitesAvailable} available`
store.me.invitesAvailable > 0 ? pal.link : pal.textLight, }
]} accessibilityHint="Opens list of invite codes">
size={16} <FontAwesomeIcon
/> icon="ticket"
<Text style={[
type="md-medium" styles.inviteCodesIcon,
style={store.me.invitesAvailable > 0 ? pal.link : pal.textLight}> store.me.invitesAvailable > 0 ? pal.link : pal.textLight,
{formatCount(store.me.invitesAvailable)} invite{' '} ]}
{pluralize(store.me.invitesAvailable, 'code')} available size={16}
</Text> />
</TouchableOpacity> <Text
type="md-medium"
style={store.me.invitesAvailable > 0 ? pal.link : pal.textLight}>
{formatCount(store.me.invitesAvailable)} invite{' '}
{pluralize(store.me.invitesAvailable, 'code')} available
</Text>
</TouchableOpacity>
)}
</View>
) )
}) })
@ -131,16 +141,20 @@ const styles = StyleSheet.create({
message: { message: {
paddingVertical: 18, paddingVertical: 18,
paddingHorizontal: 10, paddingHorizontal: 12,
}, },
messageLine: { messageLine: {
marginBottom: 10, marginBottom: 10,
}, },
inviteCodes: { separator: {
borderTopWidth: 1, borderTopWidth: 1,
paddingHorizontal: 16, },
paddingVertical: 12, br40: {borderRadius: 40},
inviteCodes: {
paddingHorizontal: 12,
paddingVertical: 16,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
}, },