Refactor invites modal (#1930)
* Refactor invites modal * Replace in drawer * Delete stuff from me modelzio/stable
parent
8a1fd160e6
commit
e6efeea7c0
|
@ -1,8 +1,5 @@
|
||||||
import {makeAutoObservable, runInAction} from 'mobx'
|
import {makeAutoObservable, runInAction} from 'mobx'
|
||||||
import {
|
import {ComAtprotoServerListAppPasswords} from '@atproto/api'
|
||||||
ComAtprotoServerDefs,
|
|
||||||
ComAtprotoServerListAppPasswords,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
import {isObj, hasProp} from 'lib/type-guards'
|
import {isObj, hasProp} from 'lib/type-guards'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
@ -17,14 +14,9 @@ export class MeModel {
|
||||||
avatar: string = ''
|
avatar: string = ''
|
||||||
followsCount: number | undefined
|
followsCount: number | undefined
|
||||||
followersCount: number | undefined
|
followersCount: number | undefined
|
||||||
invites: ComAtprotoServerDefs.InviteCode[] = []
|
|
||||||
appPasswords: ComAtprotoServerListAppPasswords.AppPassword[] = []
|
appPasswords: ComAtprotoServerListAppPasswords.AppPassword[] = []
|
||||||
lastProfileStateUpdate = Date.now()
|
lastProfileStateUpdate = Date.now()
|
||||||
|
|
||||||
get invitesAvailable() {
|
|
||||||
return this.invites.filter(isInviteAvailable).length
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(public rootStore: RootStoreModel) {
|
constructor(public rootStore: RootStoreModel) {
|
||||||
makeAutoObservable(
|
makeAutoObservable(
|
||||||
this,
|
this,
|
||||||
|
@ -41,7 +33,6 @@ export class MeModel {
|
||||||
this.displayName = ''
|
this.displayName = ''
|
||||||
this.description = ''
|
this.description = ''
|
||||||
this.avatar = ''
|
this.avatar = ''
|
||||||
this.invites = []
|
|
||||||
this.appPasswords = []
|
this.appPasswords = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +81,6 @@ export class MeModel {
|
||||||
this.did = sess.currentSession?.did || ''
|
this.did = sess.currentSession?.did || ''
|
||||||
await this.fetchProfile()
|
await this.fetchProfile()
|
||||||
this.rootStore.emitSessionLoaded()
|
this.rootStore.emitSessionLoaded()
|
||||||
await this.fetchInviteCodes()
|
|
||||||
await this.fetchAppPasswords()
|
await this.fetchAppPasswords()
|
||||||
} else {
|
} else {
|
||||||
this.clear()
|
this.clear()
|
||||||
|
@ -102,7 +92,6 @@ export class MeModel {
|
||||||
logger.debug('Updating me profile information')
|
logger.debug('Updating me profile information')
|
||||||
this.lastProfileStateUpdate = Date.now()
|
this.lastProfileStateUpdate = Date.now()
|
||||||
await this.fetchProfile()
|
await this.fetchProfile()
|
||||||
await this.fetchInviteCodes()
|
|
||||||
await this.fetchAppPasswords()
|
await this.fetchAppPasswords()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,33 +118,6 @@ export class MeModel {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchInviteCodes() {
|
|
||||||
if (this.rootStore.session) {
|
|
||||||
try {
|
|
||||||
const res =
|
|
||||||
await this.rootStore.agent.com.atproto.server.getAccountInviteCodes(
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
runInAction(() => {
|
|
||||||
this.invites = res.data.codes
|
|
||||||
this.invites.sort((a, b) => {
|
|
||||||
if (!isInviteAvailable(a)) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if (!isInviteAvailable(b)) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
logger.error('Failed to fetch user invite codes', {
|
|
||||||
error: e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchAppPasswords() {
|
async fetchAppPasswords() {
|
||||||
if (this.rootStore.session) {
|
if (this.rootStore.session) {
|
||||||
try {
|
try {
|
||||||
|
@ -208,7 +170,3 @@ export class MeModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInviteAvailable(invite: ComAtprotoServerDefs.InviteCode): boolean {
|
|
||||||
return invite.available - invite.uses.length > 0 && !invite.disabled
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import {ComAtprotoServerDefs} from '@atproto/api'
|
||||||
|
import {useQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
|
||||||
|
function isInviteAvailable(invite: ComAtprotoServerDefs.InviteCode): boolean {
|
||||||
|
return invite.available - invite.uses.length > 0 && !invite.disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InviteCodesQueryResponse = Exclude<
|
||||||
|
ReturnType<typeof useInviteCodesQuery>['data'],
|
||||||
|
undefined
|
||||||
|
>
|
||||||
|
export function useInviteCodesQuery() {
|
||||||
|
const {agent} = useSession()
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['inviteCodes'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await agent.com.atproto.server.getAccountInviteCodes({})
|
||||||
|
|
||||||
|
if (!res.data?.codes) {
|
||||||
|
throw new Error(`useInviteCodesQuery: no codes returned`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const available = res.data.codes.filter(isInviteAvailable)
|
||||||
|
const used = res.data.codes.filter(code => !isInviteAvailable(code))
|
||||||
|
|
||||||
|
return {
|
||||||
|
all: [...available, ...used],
|
||||||
|
available,
|
||||||
|
used,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,5 +1,10 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
import {
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
ActivityIndicator,
|
||||||
|
} from 'react-native'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {ComAtprotoServerDefs} from '@atproto/api'
|
import {ComAtprotoServerDefs} from '@atproto/api'
|
||||||
import {
|
import {
|
||||||
|
@ -10,23 +15,41 @@ import Clipboard from '@react-native-clipboard/clipboard'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {Button} from '../util/forms/Button'
|
import {Button} from '../util/forms/Button'
|
||||||
import * as Toast from '../util/Toast'
|
import * as Toast from '../util/Toast'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {ScrollView} from './util'
|
import {ScrollView} from './util'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {Trans} from '@lingui/macro'
|
import {Trans} from '@lingui/macro'
|
||||||
|
import {cleanError} from 'lib/strings/errors'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useInvitesState, useInvitesAPI} from '#/state/invites'
|
import {useInvitesState, useInvitesAPI} from '#/state/invites'
|
||||||
import {UserInfoText} from '../util/UserInfoText'
|
import {UserInfoText} from '../util/UserInfoText'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {Link} from '../util/Link'
|
import {Link} from '../util/Link'
|
||||||
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
|
import {
|
||||||
|
useInviteCodesQuery,
|
||||||
|
InviteCodesQueryResponse,
|
||||||
|
} from '#/state/queries/invites'
|
||||||
|
|
||||||
export const snapPoints = ['70%']
|
export const snapPoints = ['70%']
|
||||||
|
|
||||||
export function Component({}: {}) {
|
export function Component() {
|
||||||
|
const {isLoading, data: invites, error} = useInviteCodesQuery()
|
||||||
|
|
||||||
|
return error ? (
|
||||||
|
<ErrorMessage message={cleanError(error)} />
|
||||||
|
) : isLoading || !invites ? (
|
||||||
|
<View style={{padding: 18}}>
|
||||||
|
<ActivityIndicator />
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<Inner invites={invites} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Inner({invites}: {invites: InviteCodesQueryResponse}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
|
||||||
const {closeModal} = useModalControls()
|
const {closeModal} = useModalControls()
|
||||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||||
|
|
||||||
|
@ -34,7 +57,7 @@ export function Component({}: {}) {
|
||||||
closeModal()
|
closeModal()
|
||||||
}, [closeModal])
|
}, [closeModal])
|
||||||
|
|
||||||
if (store.me.invites.length === 0) {
|
if (invites.all.length === 0) {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, pal.view]} testID="inviteCodesModal">
|
<View style={[styles.container, pal.view]} testID="inviteCodesModal">
|
||||||
<View style={[styles.empty, pal.viewLight]}>
|
<View style={[styles.empty, pal.viewLight]}>
|
||||||
|
@ -74,12 +97,21 @@ export function Component({}: {}) {
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<ScrollView style={[styles.scrollContainer, pal.border]}>
|
<ScrollView style={[styles.scrollContainer, pal.border]}>
|
||||||
{store.me.invites.map((invite, i) => (
|
{invites.available.map((invite, i) => (
|
||||||
<InviteCode
|
<InviteCode
|
||||||
testID={`inviteCode-${i}`}
|
testID={`inviteCode-${i}`}
|
||||||
key={invite.code}
|
key={invite.code}
|
||||||
invite={invite}
|
invite={invite}
|
||||||
used={invite.available - invite.uses.length <= 0 || invite.disabled}
|
invites={invites}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{invites.used.map((invite, i) => (
|
||||||
|
<InviteCode
|
||||||
|
used
|
||||||
|
testID={`inviteCode-${i}`}
|
||||||
|
key={invite.code}
|
||||||
|
invite={invite}
|
||||||
|
invites={invites}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -101,14 +133,14 @@ const InviteCode = observer(function InviteCodeImpl({
|
||||||
testID,
|
testID,
|
||||||
invite,
|
invite,
|
||||||
used,
|
used,
|
||||||
|
invites,
|
||||||
}: {
|
}: {
|
||||||
testID: string
|
testID: string
|
||||||
invite: ComAtprotoServerDefs.InviteCode
|
invite: ComAtprotoServerDefs.InviteCode
|
||||||
used?: boolean
|
used?: boolean
|
||||||
|
invites: InviteCodesQueryResponse
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
|
||||||
const {invitesAvailable} = store.me
|
|
||||||
const invitesState = useInvitesState()
|
const invitesState = useInvitesState()
|
||||||
const {setInviteCopied} = useInvitesAPI()
|
const {setInviteCopied} = useInvitesAPI()
|
||||||
|
|
||||||
|
@ -130,9 +162,9 @@ const InviteCode = observer(function InviteCodeImpl({
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={
|
accessibilityLabel={
|
||||||
invitesAvailable === 1
|
invites.available.length === 1
|
||||||
? 'Invite codes: 1 available'
|
? 'Invite codes: 1 available'
|
||||||
: `Invite codes: ${invitesAvailable} available`
|
: `Invite codes: ${invites.available.length} available`
|
||||||
}
|
}
|
||||||
accessibilityHint="Opens list of invite codes">
|
accessibilityHint="Opens list of invite codes">
|
||||||
<Text
|
<Text
|
||||||
|
|
|
@ -60,6 +60,7 @@ import {
|
||||||
import {useSession, useSessionApi, SessionAccount} from '#/state/session'
|
import {useSession, useSessionApi, SessionAccount} from '#/state/session'
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
import {useClearPreferencesMutation} from '#/state/queries/preferences'
|
import {useClearPreferencesMutation} from '#/state/queries/preferences'
|
||||||
|
import {useInviteCodesQuery} from '#/state/queries/invites'
|
||||||
|
|
||||||
// TEMPORARY (APP-700)
|
// TEMPORARY (APP-700)
|
||||||
// remove after backend testing finishes
|
// remove after backend testing finishes
|
||||||
|
@ -155,6 +156,8 @@ export const SettingsScreen = withAuthRequired(
|
||||||
const {isSwitchingAccounts, accounts, currentAccount} = useSession()
|
const {isSwitchingAccounts, accounts, currentAccount} = useSession()
|
||||||
const {clearCurrentAccount} = useSessionApi()
|
const {clearCurrentAccount} = useSessionApi()
|
||||||
const {mutate: clearPreferences} = useClearPreferencesMutation()
|
const {mutate: clearPreferences} = useClearPreferencesMutation()
|
||||||
|
const {data: invites} = useInviteCodesQuery()
|
||||||
|
const invitesAvailable = invites?.available?.length ?? 0
|
||||||
|
|
||||||
const primaryBg = useCustomPalette<ViewStyle>({
|
const primaryBg = useCustomPalette<ViewStyle>({
|
||||||
light: {backgroundColor: colors.blue0},
|
light: {backgroundColor: colors.blue0},
|
||||||
|
@ -362,6 +365,7 @@ export const SettingsScreen = withAuthRequired(
|
||||||
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
||||||
<Trans>Invite a Friend</Trans>
|
<Trans>Invite a Friend</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
testID="inviteFriendBtn"
|
testID="inviteFriendBtn"
|
||||||
style={[
|
style={[
|
||||||
|
@ -376,22 +380,20 @@ export const SettingsScreen = withAuthRequired(
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.iconContainer,
|
styles.iconContainer,
|
||||||
store.me.invitesAvailable > 0 ? primaryBg : pal.btn,
|
invitesAvailable > 0 ? primaryBg : pal.btn,
|
||||||
]}>
|
]}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon="ticket"
|
icon="ticket"
|
||||||
style={
|
style={
|
||||||
(store.me.invitesAvailable > 0
|
(invitesAvailable > 0
|
||||||
? primaryText
|
? primaryText
|
||||||
: pal.text) as FontAwesomeIconStyle
|
: pal.text) as FontAwesomeIconStyle
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text type="lg" style={invitesAvailable > 0 ? pal.link : pal.text}>
|
||||||
type="lg"
|
{formatCount(invitesAvailable)} invite{' '}
|
||||||
style={store.me.invitesAvailable > 0 ? pal.link : pal.text}>
|
{pluralize(invitesAvailable, 'code')} available
|
||||||
{formatCount(store.me.invitesAvailable)} invite{' '}
|
|
||||||
{pluralize(store.me.invitesAvailable, 'code')} available
|
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
} from '@fortawesome/react-native-fontawesome'
|
} from '@fortawesome/react-native-fontawesome'
|
||||||
import {s, colors} from 'lib/styles'
|
import {s, colors} from 'lib/styles'
|
||||||
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants'
|
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {
|
import {
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
HomeIconSolid,
|
HomeIconSolid,
|
||||||
|
@ -51,6 +50,7 @@ import {useSession, SessionAccount} from '#/state/session'
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
||||||
import {emitSoftReset} from '#/state/events'
|
import {emitSoftReset} from '#/state/events'
|
||||||
|
import {useInviteCodesQuery} from '#/state/queries/invites'
|
||||||
|
|
||||||
export function DrawerProfileCard({
|
export function DrawerProfileCard({
|
||||||
account,
|
account,
|
||||||
|
@ -464,10 +464,10 @@ const InviteCodes = observer(function InviteCodesImpl({
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
}) {
|
}) {
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
const store = useStores()
|
|
||||||
const setDrawerOpen = useSetDrawerOpen()
|
const setDrawerOpen = useSetDrawerOpen()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {invitesAvailable} = store.me
|
const {data: invites} = useInviteCodesQuery()
|
||||||
|
const invitesAvailable = invites?.available?.length ?? 0
|
||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
const onPress = React.useCallback(() => {
|
const onPress = React.useCallback(() => {
|
||||||
track('Menu:ItemClicked', {url: '#invite-codes'})
|
track('Menu:ItemClicked', {url: '#invite-codes'})
|
||||||
|
@ -490,15 +490,15 @@ const InviteCodes = observer(function InviteCodesImpl({
|
||||||
icon="ticket"
|
icon="ticket"
|
||||||
style={[
|
style={[
|
||||||
styles.inviteCodesIcon,
|
styles.inviteCodesIcon,
|
||||||
store.me.invitesAvailable > 0 ? pal.link : pal.textLight,
|
invitesAvailable > 0 ? pal.link : pal.textLight,
|
||||||
]}
|
]}
|
||||||
size={18}
|
size={18}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
type="lg-medium"
|
type="lg-medium"
|
||||||
style={store.me.invitesAvailable > 0 ? pal.link : pal.textLight}>
|
style={invitesAvailable > 0 ? pal.link : pal.textLight}>
|
||||||
{formatCount(store.me.invitesAvailable)} invite{' '}
|
{formatCount(invitesAvailable)} invite{' '}
|
||||||
{pluralize(store.me.invitesAvailable, 'code')}
|
{pluralize(invitesAvailable, 'code')}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,12 +9,12 @@ import {Text} from 'view/com/util/text/Text'
|
||||||
import {TextLink} from 'view/com/util/Link'
|
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 {useStores} from 'state/index'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {pluralize} from 'lib/strings/helpers'
|
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 {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
|
import {useInviteCodesQuery} from '#/state/queries/invites'
|
||||||
|
|
||||||
export const DesktopRightNav = observer(function DesktopRightNavImpl() {
|
export const DesktopRightNav = observer(function DesktopRightNavImpl() {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
@ -83,11 +83,10 @@ export const DesktopRightNav = observer(function DesktopRightNavImpl() {
|
||||||
})
|
})
|
||||||
|
|
||||||
const InviteCodes = observer(function InviteCodesImpl() {
|
const InviteCodes = observer(function InviteCodesImpl() {
|
||||||
const store = useStores()
|
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
|
const {data: invites} = useInviteCodesQuery()
|
||||||
const {invitesAvailable} = store.me
|
const invitesAvailable = invites?.available?.length ?? 0
|
||||||
|
|
||||||
const onPress = React.useCallback(() => {
|
const onPress = React.useCallback(() => {
|
||||||
openModal({name: 'invite-codes'})
|
openModal({name: 'invite-codes'})
|
||||||
|
@ -107,15 +106,15 @@ const InviteCodes = observer(function InviteCodesImpl() {
|
||||||
icon="ticket"
|
icon="ticket"
|
||||||
style={[
|
style={[
|
||||||
styles.inviteCodesIcon,
|
styles.inviteCodesIcon,
|
||||||
store.me.invitesAvailable > 0 ? pal.link : pal.textLight,
|
invitesAvailable > 0 ? pal.link : pal.textLight,
|
||||||
]}
|
]}
|
||||||
size={16}
|
size={16}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
type="md-medium"
|
type="md-medium"
|
||||||
style={store.me.invitesAvailable > 0 ? pal.link : pal.textLight}>
|
style={invitesAvailable > 0 ? pal.link : pal.textLight}>
|
||||||
{formatCount(store.me.invitesAvailable)} invite{' '}
|
{formatCount(invitesAvailable)} invite{' '}
|
||||||
{pluralize(store.me.invitesAvailable, 'code')} available
|
{pluralize(invitesAvailable, 'code')} available
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue