Move invite-state to new persistence + context and replace the notifications with just showing uses in the modal (#1840)

This commit is contained in:
Paul Frazee 2023-11-08 09:10:59 -08:00 committed by GitHub
parent 74f8390f1d
commit e75b2d508b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 137 additions and 259 deletions

View file

@ -1,6 +1,7 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {ComAtprotoServerDefs} from '@atproto/api'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
@ -14,6 +15,10 @@ import {ScrollView} from './util'
import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useInvitesState, useInvitesAPI} from '#/state/invites'
import {UserInfoText} from '../util/UserInfoText'
import {makeProfileLink} from '#/lib/routes/links'
import {Link} from '../util/Link'
export const snapPoints = ['70%']
@ -66,7 +71,7 @@ export function Component({}: {}) {
<InviteCode
testID={`inviteCode-${i}`}
key={invite.code}
code={invite.code}
invite={invite}
used={invite.available - invite.uses.length <= 0 || invite.disabled}
/>
))}
@ -87,52 +92,81 @@ export function Component({}: {}) {
const InviteCode = observer(function InviteCodeImpl({
testID,
code,
invite,
used,
}: {
testID: string
code: string
invite: ComAtprotoServerDefs.InviteCode
used?: boolean
}) {
const pal = usePalette('default')
const store = useStores()
const {invitesAvailable} = store.me
const invitesState = useInvitesState()
const {setInviteCopied} = useInvitesAPI()
const onPress = React.useCallback(() => {
Clipboard.setString(code)
Clipboard.setString(invite.code)
Toast.show('Copied to clipboard')
store.invitedUsers.setInviteCopied(code)
}, [store, code])
setInviteCopied(invite.code)
}, [setInviteCopied, invite])
return (
<TouchableOpacity
testID={testID}
style={[styles.inviteCode, pal.border]}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel={
invitesAvailable === 1
? 'Invite codes: 1 available'
: `Invite codes: ${invitesAvailable} available`
}
accessibilityHint="Opens list of invite codes">
<Text
testID={`${testID}-code`}
type={used ? 'md' : 'md-bold'}
style={used ? [pal.textLight, styles.strikeThrough] : pal.text}>
{code}
</Text>
<View style={styles.flex1} />
{!used && store.invitedUsers.isInviteCopied(code) && (
<Text style={[pal.textLight, styles.codeCopied]}>Copied</Text>
)}
{!used && (
<FontAwesomeIcon
icon={['far', 'clone']}
style={pal.text as FontAwesomeIconStyle}
/>
)}
</TouchableOpacity>
<View
style={[
pal.border,
{borderBottomWidth: 1, paddingHorizontal: 20, paddingVertical: 14},
]}>
<TouchableOpacity
testID={testID}
style={[styles.inviteCode]}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel={
invitesAvailable === 1
? 'Invite codes: 1 available'
: `Invite codes: ${invitesAvailable} available`
}
accessibilityHint="Opens list of invite codes">
<Text
testID={`${testID}-code`}
type={used ? 'md' : 'md-bold'}
style={used ? [pal.textLight, styles.strikeThrough] : pal.text}>
{invite.code}
</Text>
<View style={styles.flex1} />
{!used && invitesState.copiedInvites.includes(invite.code) && (
<Text style={[pal.textLight, styles.codeCopied]}>Copied</Text>
)}
{!used && (
<FontAwesomeIcon
icon={['far', 'clone']}
style={pal.text as FontAwesomeIconStyle}
/>
)}
</TouchableOpacity>
{invite.uses.length > 0 ? (
<View
style={{
flexDirection: 'column',
gap: 8,
paddingTop: 6,
}}>
<Text style={pal.text}>Used by:</Text>
{invite.uses.map(use => (
<Link
key={use.usedBy}
href={makeProfileLink({handle: use.usedBy, did: ''})}
style={{
flexDirection: 'row',
}}>
<Text style={pal.text}> </Text>
<UserInfoText did={use.usedBy} style={pal.link} />
</Link>
))}
</View>
) : null}
</View>
)
})
@ -176,9 +210,6 @@ const styles = StyleSheet.create({
inviteCode: {
flexDirection: 'row',
alignItems: 'center',
borderBottomWidth: 1,
paddingHorizontal: 20,
paddingVertical: 14,
},
codeCopied: {
marginRight: 8,

View file

@ -1,114 +0,0 @@
import React from 'react'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {AppBskyActorDefs} from '@atproto/api'
import {UserAvatar} from '../util/UserAvatar'
import {Text} from '../util/text/Text'
import {Link, TextLink} from '../util/Link'
import {Button} from '../util/forms/Button'
import {FollowButton} from '../profile/FollowButton'
import {CenteredView} from '../util/Views.web'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {makeProfileLink} from 'lib/routes/links'
export const InvitedUsers = observer(function InvitedUsersImpl() {
const store = useStores()
return (
<CenteredView>
{store.invitedUsers.profiles.map(profile => (
<InvitedUser key={profile.did} profile={profile} />
))}
</CenteredView>
)
})
function InvitedUser({
profile,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
}) {
const pal = usePalette('default')
const store = useStores()
const onPressDismiss = React.useCallback(() => {
store.invitedUsers.markSeen(profile.did)
}, [store, profile])
return (
<View
testID="invitedUser"
style={[
styles.layout,
{
backgroundColor: pal.colors.unreadNotifBg,
borderColor: pal.colors.unreadNotifBorder,
},
]}>
<View style={styles.layoutIcon}>
<FontAwesomeIcon
icon="user-plus"
size={24}
style={[styles.icon, s.blue3 as FontAwesomeIconStyle]}
/>
</View>
<View style={s.flex1}>
<Link href={makeProfileLink(profile)}>
<UserAvatar avatar={profile.avatar} size={35} />
</Link>
<Text style={[styles.desc, pal.text]}>
<TextLink
type="md-bold"
style={pal.text}
href={makeProfileLink(profile)}
text={sanitizeDisplayName(profile.displayName || profile.handle)}
/>{' '}
joined using your invite code!
</Text>
<View style={styles.btns}>
<FollowButton
unfollowedType="primary"
followedType="primary-light"
profile={profile}
/>
<Button
testID="dismissBtn"
type="primary-light"
label="Dismiss"
onPress={onPressDismiss}
/>
</View>
</View>
</View>
)
}
const styles = StyleSheet.create({
layout: {
flexDirection: 'row',
borderTopWidth: 1,
padding: 10,
},
layoutIcon: {
width: 70,
alignItems: 'flex-end',
paddingTop: 2,
},
icon: {
marginRight: 10,
marginTop: 4,
},
desc: {
paddingVertical: 6,
},
btns: {
flexDirection: 'row',
gap: 10,
},
})

View file

@ -10,7 +10,6 @@ import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {ViewHeader} from '../com/util/ViewHeader'
import {Feed} from '../com/notifications/Feed'
import {TextLink} from 'view/com/util/Link'
import {InvitedUsers} from '../com/notifications/InvitedUsers'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {useStores} from 'state/index'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
@ -145,7 +144,6 @@ export const NotificationsScreen = withAuthRequired(
return (
<View testID="notificationsScreen" style={s.hContentRegion}>
<ViewHeader title="Notifications" canGoBack={false} />
<InvitedUsers />
<Feed
view={store.me.notifications}
onPressTryAgain={onPressTryAgain}