Move invite-state to new persistence + context and replace the notifications with just showing uses in the modal (#1840)
parent
74f8390f1d
commit
e75b2d508b
|
@ -23,6 +23,7 @@ import {queryClient} from 'lib/react-query'
|
||||||
import {TestCtrls} from 'view/com/testing/TestCtrls'
|
import {TestCtrls} from 'view/com/testing/TestCtrls'
|
||||||
import {Provider as ShellStateProvider} from 'state/shell'
|
import {Provider as ShellStateProvider} from 'state/shell'
|
||||||
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
||||||
|
import {Provider as InvitesStateProvider} from 'state/invites'
|
||||||
|
|
||||||
SplashScreen.preventAutoHideAsync()
|
SplashScreen.preventAutoHideAsync()
|
||||||
|
|
||||||
|
@ -80,7 +81,9 @@ function App() {
|
||||||
return (
|
return (
|
||||||
<ShellStateProvider>
|
<ShellStateProvider>
|
||||||
<MutedThreadsProvider>
|
<MutedThreadsProvider>
|
||||||
<InnerApp />
|
<InvitesStateProvider>
|
||||||
|
<InnerApp />
|
||||||
|
</InvitesStateProvider>
|
||||||
</MutedThreadsProvider>
|
</MutedThreadsProvider>
|
||||||
</ShellStateProvider>
|
</ShellStateProvider>
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {ThemeProvider} from 'lib/ThemeContext'
|
||||||
import {queryClient} from 'lib/react-query'
|
import {queryClient} from 'lib/react-query'
|
||||||
import {Provider as ShellStateProvider} from 'state/shell'
|
import {Provider as ShellStateProvider} from 'state/shell'
|
||||||
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
||||||
|
import {Provider as InvitesStateProvider} from 'state/invites'
|
||||||
|
|
||||||
const InnerApp = observer(function AppImpl() {
|
const InnerApp = observer(function AppImpl() {
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
@ -70,7 +71,9 @@ function App() {
|
||||||
return (
|
return (
|
||||||
<ShellStateProvider>
|
<ShellStateProvider>
|
||||||
<MutedThreadsProvider>
|
<MutedThreadsProvider>
|
||||||
<InnerApp />
|
<InvitesStateProvider>
|
||||||
|
<InnerApp />
|
||||||
|
</InvitesStateProvider>
|
||||||
</MutedThreadsProvider>
|
</MutedThreadsProvider>
|
||||||
</ShellStateProvider>
|
</ShellStateProvider>
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import React from 'react'
|
||||||
|
import * as persisted from '#/state/persisted'
|
||||||
|
|
||||||
|
type StateContext = persisted.Schema['invites']
|
||||||
|
type ApiContext = {
|
||||||
|
setInviteCopied: (code: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateContext = React.createContext<StateContext>(
|
||||||
|
persisted.defaults.invites,
|
||||||
|
)
|
||||||
|
const apiContext = React.createContext<ApiContext>({
|
||||||
|
setInviteCopied(_: string) {},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const [state, setState] = React.useState(persisted.get('invites'))
|
||||||
|
|
||||||
|
const api = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
setInviteCopied(code: string) {
|
||||||
|
setState(state => {
|
||||||
|
state = {
|
||||||
|
...state,
|
||||||
|
copiedInvites: state.copiedInvites.includes(code)
|
||||||
|
? state.copiedInvites
|
||||||
|
: state.copiedInvites.concat([code]),
|
||||||
|
}
|
||||||
|
persisted.write('invites', state)
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[setState],
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
return persisted.onUpdate(() => {
|
||||||
|
setState(persisted.get('invites'))
|
||||||
|
})
|
||||||
|
}, [setState])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<stateContext.Provider value={state}>
|
||||||
|
<apiContext.Provider value={api}>{children}</apiContext.Provider>
|
||||||
|
</stateContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useInvitesState() {
|
||||||
|
return React.useContext(stateContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useInvitesAPI() {
|
||||||
|
return React.useContext(apiContext)
|
||||||
|
}
|
|
@ -304,7 +304,7 @@ export class NotificationsFeedModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
get unreadCountLabel(): string {
|
get unreadCountLabel(): string {
|
||||||
const count = this.unreadCount + this.rootStore.invitedUsers.numNotifs
|
const count = this.unreadCount
|
||||||
if (count >= MAX_VISIBLE_NOTIFS) {
|
if (count >= MAX_VISIBLE_NOTIFS) {
|
||||||
return `${MAX_VISIBLE_NOTIFS}+`
|
return `${MAX_VISIBLE_NOTIFS}+`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
import {makeAutoObservable, runInAction} from 'mobx'
|
|
||||||
import {ComAtprotoServerDefs, AppBskyActorDefs} from '@atproto/api'
|
|
||||||
import {RootStoreModel} from './root-store'
|
|
||||||
import {isObj, hasProp, isStrArray} from 'lib/type-guards'
|
|
||||||
import {logger} from '#/logger'
|
|
||||||
|
|
||||||
export class InvitedUsers {
|
|
||||||
copiedInvites: string[] = []
|
|
||||||
seenDids: string[] = []
|
|
||||||
profiles: AppBskyActorDefs.ProfileViewDetailed[] = []
|
|
||||||
|
|
||||||
get numNotifs() {
|
|
||||||
return this.profiles.length
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(public rootStore: RootStoreModel) {
|
|
||||||
makeAutoObservable(
|
|
||||||
this,
|
|
||||||
{rootStore: false, serialize: false, hydrate: false},
|
|
||||||
{autoBind: true},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize() {
|
|
||||||
return {seenDids: this.seenDids, copiedInvites: this.copiedInvites}
|
|
||||||
}
|
|
||||||
|
|
||||||
hydrate(v: unknown) {
|
|
||||||
if (isObj(v) && hasProp(v, 'seenDids') && isStrArray(v.seenDids)) {
|
|
||||||
this.seenDids = v.seenDids
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
isObj(v) &&
|
|
||||||
hasProp(v, 'copiedInvites') &&
|
|
||||||
isStrArray(v.copiedInvites)
|
|
||||||
) {
|
|
||||||
this.copiedInvites = v.copiedInvites
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetch(invites: ComAtprotoServerDefs.InviteCode[]) {
|
|
||||||
// pull the dids of invited users not marked seen
|
|
||||||
const dids = []
|
|
||||||
for (const invite of invites) {
|
|
||||||
for (const use of invite.uses) {
|
|
||||||
if (!this.seenDids.includes(use.usedBy)) {
|
|
||||||
dids.push(use.usedBy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch their profiles
|
|
||||||
this.profiles = []
|
|
||||||
if (dids.length) {
|
|
||||||
try {
|
|
||||||
const res = await this.rootStore.agent.app.bsky.actor.getProfiles({
|
|
||||||
actors: dids,
|
|
||||||
})
|
|
||||||
runInAction(() => {
|
|
||||||
// save the ones following -- these are the ones we want to notify the user about
|
|
||||||
this.profiles = res.data.profiles.filter(
|
|
||||||
profile => !profile.viewer?.following,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
this.rootStore.me.follows.hydrateMany(this.profiles)
|
|
||||||
} catch (e) {
|
|
||||||
logger.error('Failed to fetch profiles for invited users', {
|
|
||||||
error: e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isInviteCopied(invite: string) {
|
|
||||||
return this.copiedInvites.includes(invite)
|
|
||||||
}
|
|
||||||
|
|
||||||
setInviteCopied(invite: string) {
|
|
||||||
if (!this.isInviteCopied(invite)) {
|
|
||||||
this.copiedInvites.push(invite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
markSeen(did: string) {
|
|
||||||
this.seenDids.push(did)
|
|
||||||
this.profiles = this.profiles.filter(profile => profile.did !== did)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -193,7 +193,6 @@ export class MeModel {
|
||||||
error: e,
|
error: e,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await this.rootStore.invitedUsers.fetch(this.invites)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {ProfilesCache} from './cache/profiles-view'
|
||||||
import {PostsCache} from './cache/posts'
|
import {PostsCache} from './cache/posts'
|
||||||
import {LinkMetasCache} from './cache/link-metas'
|
import {LinkMetasCache} from './cache/link-metas'
|
||||||
import {MeModel} from './me'
|
import {MeModel} from './me'
|
||||||
import {InvitedUsers} from './invited-users'
|
|
||||||
import {PreferencesModel} from './ui/preferences'
|
import {PreferencesModel} from './ui/preferences'
|
||||||
import {resetToTab} from '../../Navigation'
|
import {resetToTab} from '../../Navigation'
|
||||||
import {ImageSizesCache} from './cache/image-sizes'
|
import {ImageSizesCache} from './cache/image-sizes'
|
||||||
|
@ -42,7 +41,6 @@ export class RootStoreModel {
|
||||||
shell = new ShellUiModel(this)
|
shell = new ShellUiModel(this)
|
||||||
preferences = new PreferencesModel(this)
|
preferences = new PreferencesModel(this)
|
||||||
me = new MeModel(this)
|
me = new MeModel(this)
|
||||||
invitedUsers = new InvitedUsers(this)
|
|
||||||
handleResolutions = new HandleResolutionsCache()
|
handleResolutions = new HandleResolutionsCache()
|
||||||
profiles = new ProfilesCache(this)
|
profiles = new ProfilesCache(this)
|
||||||
posts = new PostsCache(this)
|
posts = new PostsCache(this)
|
||||||
|
@ -68,7 +66,6 @@ export class RootStoreModel {
|
||||||
session: this.session.serialize(),
|
session: this.session.serialize(),
|
||||||
me: this.me.serialize(),
|
me: this.me.serialize(),
|
||||||
preferences: this.preferences.serialize(),
|
preferences: this.preferences.serialize(),
|
||||||
invitedUsers: this.invitedUsers.serialize(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,9 +86,6 @@ export class RootStoreModel {
|
||||||
if (hasProp(v, 'preferences')) {
|
if (hasProp(v, 'preferences')) {
|
||||||
this.preferences.hydrate(v.preferences)
|
this.preferences.hydrate(v.preferences)
|
||||||
}
|
}
|
||||||
if (hasProp(v, 'invitedUsers')) {
|
|
||||||
this.invitedUsers.hydrate(v.invitedUsers)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,11 +97,9 @@ export function transform(legacy: LegacySchema): Schema {
|
||||||
legacy.preferences.requireAltTextEnabled ||
|
legacy.preferences.requireAltTextEnabled ||
|
||||||
defaults.requireAltTextEnabled,
|
defaults.requireAltTextEnabled,
|
||||||
mutedThreads: legacy.mutedThreads.uris || defaults.mutedThreads,
|
mutedThreads: legacy.mutedThreads.uris || defaults.mutedThreads,
|
||||||
invitedUsers: {
|
invites: {
|
||||||
seenDids: legacy.invitedUsers.seenDids || defaults.invitedUsers.seenDids,
|
|
||||||
copiedInvites:
|
copiedInvites:
|
||||||
legacy.invitedUsers.copiedInvites ||
|
legacy.invitedUsers.copiedInvites || defaults.invites.copiedInvites,
|
||||||
defaults.invitedUsers.copiedInvites,
|
|
||||||
},
|
},
|
||||||
onboarding: {
|
onboarding: {
|
||||||
step: legacy.onboarding.step || defaults.onboarding.step,
|
step: legacy.onboarding.step || defaults.onboarding.step,
|
||||||
|
|
|
@ -29,8 +29,7 @@ export const schema = z.object({
|
||||||
}),
|
}),
|
||||||
requireAltTextEnabled: z.boolean(), // should move to server
|
requireAltTextEnabled: z.boolean(), // should move to server
|
||||||
mutedThreads: z.array(z.string()), // should move to server
|
mutedThreads: z.array(z.string()), // should move to server
|
||||||
invitedUsers: z.object({
|
invites: z.object({
|
||||||
seenDids: z.array(z.string()),
|
|
||||||
copiedInvites: z.array(z.string()),
|
copiedInvites: z.array(z.string()),
|
||||||
}),
|
}),
|
||||||
onboarding: z.object({
|
onboarding: z.object({
|
||||||
|
@ -58,8 +57,7 @@ export const defaults: Schema = {
|
||||||
},
|
},
|
||||||
requireAltTextEnabled: false,
|
requireAltTextEnabled: false,
|
||||||
mutedThreads: [],
|
mutedThreads: [],
|
||||||
invitedUsers: {
|
invites: {
|
||||||
seenDids: [],
|
|
||||||
copiedInvites: [],
|
copiedInvites: [],
|
||||||
},
|
},
|
||||||
onboarding: {
|
onboarding: {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
|
import {ComAtprotoServerDefs} from '@atproto/api'
|
||||||
import {
|
import {
|
||||||
FontAwesomeIcon,
|
FontAwesomeIcon,
|
||||||
FontAwesomeIconStyle,
|
FontAwesomeIconStyle,
|
||||||
|
@ -14,6 +15,10 @@ 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 {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%']
|
export const snapPoints = ['70%']
|
||||||
|
|
||||||
|
@ -66,7 +71,7 @@ export function Component({}: {}) {
|
||||||
<InviteCode
|
<InviteCode
|
||||||
testID={`inviteCode-${i}`}
|
testID={`inviteCode-${i}`}
|
||||||
key={invite.code}
|
key={invite.code}
|
||||||
code={invite.code}
|
invite={invite}
|
||||||
used={invite.available - invite.uses.length <= 0 || invite.disabled}
|
used={invite.available - invite.uses.length <= 0 || invite.disabled}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -87,52 +92,81 @@ export function Component({}: {}) {
|
||||||
|
|
||||||
const InviteCode = observer(function InviteCodeImpl({
|
const InviteCode = observer(function InviteCodeImpl({
|
||||||
testID,
|
testID,
|
||||||
code,
|
invite,
|
||||||
used,
|
used,
|
||||||
}: {
|
}: {
|
||||||
testID: string
|
testID: string
|
||||||
code: string
|
invite: ComAtprotoServerDefs.InviteCode
|
||||||
used?: boolean
|
used?: boolean
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const {invitesAvailable} = store.me
|
const {invitesAvailable} = store.me
|
||||||
|
const invitesState = useInvitesState()
|
||||||
|
const {setInviteCopied} = useInvitesAPI()
|
||||||
|
|
||||||
const onPress = React.useCallback(() => {
|
const onPress = React.useCallback(() => {
|
||||||
Clipboard.setString(code)
|
Clipboard.setString(invite.code)
|
||||||
Toast.show('Copied to clipboard')
|
Toast.show('Copied to clipboard')
|
||||||
store.invitedUsers.setInviteCopied(code)
|
setInviteCopied(invite.code)
|
||||||
}, [store, code])
|
}, [setInviteCopied, invite])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<View
|
||||||
testID={testID}
|
style={[
|
||||||
style={[styles.inviteCode, pal.border]}
|
pal.border,
|
||||||
onPress={onPress}
|
{borderBottomWidth: 1, paddingHorizontal: 20, paddingVertical: 14},
|
||||||
accessibilityRole="button"
|
]}>
|
||||||
accessibilityLabel={
|
<TouchableOpacity
|
||||||
invitesAvailable === 1
|
testID={testID}
|
||||||
? 'Invite codes: 1 available'
|
style={[styles.inviteCode]}
|
||||||
: `Invite codes: ${invitesAvailable} available`
|
onPress={onPress}
|
||||||
}
|
accessibilityRole="button"
|
||||||
accessibilityHint="Opens list of invite codes">
|
accessibilityLabel={
|
||||||
<Text
|
invitesAvailable === 1
|
||||||
testID={`${testID}-code`}
|
? 'Invite codes: 1 available'
|
||||||
type={used ? 'md' : 'md-bold'}
|
: `Invite codes: ${invitesAvailable} available`
|
||||||
style={used ? [pal.textLight, styles.strikeThrough] : pal.text}>
|
}
|
||||||
{code}
|
accessibilityHint="Opens list of invite codes">
|
||||||
</Text>
|
<Text
|
||||||
<View style={styles.flex1} />
|
testID={`${testID}-code`}
|
||||||
{!used && store.invitedUsers.isInviteCopied(code) && (
|
type={used ? 'md' : 'md-bold'}
|
||||||
<Text style={[pal.textLight, styles.codeCopied]}>Copied</Text>
|
style={used ? [pal.textLight, styles.strikeThrough] : pal.text}>
|
||||||
)}
|
{invite.code}
|
||||||
{!used && (
|
</Text>
|
||||||
<FontAwesomeIcon
|
<View style={styles.flex1} />
|
||||||
icon={['far', 'clone']}
|
{!used && invitesState.copiedInvites.includes(invite.code) && (
|
||||||
style={pal.text as FontAwesomeIconStyle}
|
<Text style={[pal.textLight, styles.codeCopied]}>Copied</Text>
|
||||||
/>
|
)}
|
||||||
)}
|
{!used && (
|
||||||
</TouchableOpacity>
|
<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: {
|
inviteCode: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderBottomWidth: 1,
|
|
||||||
paddingHorizontal: 20,
|
|
||||||
paddingVertical: 14,
|
|
||||||
},
|
},
|
||||||
codeCopied: {
|
codeCopied: {
|
||||||
marginRight: 8,
|
marginRight: 8,
|
||||||
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -10,7 +10,6 @@ import {withAuthRequired} from 'view/com/auth/withAuthRequired'
|
||||||
import {ViewHeader} from '../com/util/ViewHeader'
|
import {ViewHeader} from '../com/util/ViewHeader'
|
||||||
import {Feed} from '../com/notifications/Feed'
|
import {Feed} from '../com/notifications/Feed'
|
||||||
import {TextLink} from 'view/com/util/Link'
|
import {TextLink} from 'view/com/util/Link'
|
||||||
import {InvitedUsers} from '../com/notifications/InvitedUsers'
|
|
||||||
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
|
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
|
||||||
|
@ -145,7 +144,6 @@ export const NotificationsScreen = withAuthRequired(
|
||||||
return (
|
return (
|
||||||
<View testID="notificationsScreen" style={s.hContentRegion}>
|
<View testID="notificationsScreen" style={s.hContentRegion}>
|
||||||
<ViewHeader title="Notifications" canGoBack={false} />
|
<ViewHeader title="Notifications" canGoBack={false} />
|
||||||
<InvitedUsers />
|
|
||||||
<Feed
|
<Feed
|
||||||
view={store.me.notifications}
|
view={store.me.notifications}
|
||||||
onPressTryAgain={onPressTryAgain}
|
onPressTryAgain={onPressTryAgain}
|
||||||
|
|
Loading…
Reference in New Issue