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
notifications: NotificationsFeedModel
follows: MyFollowsCache
invites: ComAtprotoServerDefs.InviteCode[] = []
invites: ComAtprotoServerDefs.InviteCode[] | null = []
appPasswords: ComAtprotoServerListAppPasswords.AppPassword[] = []
lastProfileStateUpdate = Date.now()
lastNotifsUpdate = Date.now()
get invitesAvailable() {
return this.invites.filter(isInviteAvailable).length
return this.invites?.filter(isInviteAvailable).length || null
}
constructor(public rootStore: RootStoreModel) {
@ -180,7 +180,9 @@ export class MeModel {
} catch (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])
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) {
return (
<View style={[styles.container, pal.view]} testID="inviteCodesModal">

View File

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

@ -426,32 +426,34 @@ const InviteCodes = observer(function InviteCodesImpl({
store.shell.openModal({name: 'invite-codes'})
}, [store, track])
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">
<FontAwesomeIcon
icon="ticket"
style={[
styles.inviteCodesIcon,
store.me.invitesAvailable > 0 ? pal.link : pal.textLight,
]}
size={18}
/>
<Text
type="lg-medium"
style={store.me.invitesAvailable > 0 ? pal.link : pal.textLight}>
{formatCount(store.me.invitesAvailable)} invite{' '}
{pluralize(store.me.invitesAvailable, 'code')}
</Text>
</TouchableOpacity>
store.me.invitesAvailable !== null && (
<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">
<FontAwesomeIcon
icon="ticket"
style={[
styles.inviteCodesIcon,
store.me.invitesAvailable > 0 ? pal.link : pal.textLight,
]}
size={18}
/>
<Text
type="lg-medium"
style={store.me.invitesAvailable > 0 ? pal.link : pal.textLight}>
{formatCount(store.me.invitesAvailable)} invite{' '}
{pluralize(store.me.invitesAvailable, 'code')}
</Text>
</TouchableOpacity>
)
)
})

View File

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