import React, {useState, useEffect, useMemo} from 'react' import {observer} from 'mobx-react-lite' import * as Toast from '../util/Toast' import { ActivityIndicator, FlatList, StyleSheet, useWindowDimensions, View, } from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import { TabView, SceneMap, Route, TabBar, TabBarProps, } from 'react-native-tab-view' import _omit from 'lodash.omit' import {AtUri} from '../../../third-party/uri' import {ProfileCard} from '../profile/ProfileCard' import {ErrorMessage} from '../util/error/ErrorMessage' import {Text} from '../util/text/Text' import {useStores} from '../../../state' import * as apilib from '../../../state/lib/api' import {ProfileViewModel} from '../../../state/models/profile-view' import {SuggestedInvitesView} from '../../../state/models/suggested-invites-view' import {Assertion} from '../../../state/models/get-assertions-view' import {FollowItem} from '../../../state/models/user-follows-view' import {s, colors} from '../../lib/styles' export const snapPoints = ['70%'] export const Component = observer(function Component({ profileView, }: { profileView: ProfileViewModel }) { const store = useStores() const layout = useWindowDimensions() const [index, setIndex] = useState(0) const tabRoutes = [ {key: 'suggestions', title: 'Suggestions'}, {key: 'pending', title: 'Pending Invites'}, ] const [hasSetup, setHasSetup] = useState(false) const [error, setError] = useState('') const suggestions = useMemo( () => new SuggestedInvitesView(store, {sceneDid: profileView.did}), [profileView.did], ) const [createdInvites, setCreatedInvites] = useState>( {}, ) // TODO: it would be much better if we just used the suggestions view for the deleted pending invites // but mobx isnt picking up on the state change in suggestions.unconfirmed and I dont have // time to debug that right now -prf const [deletedPendingInvites, setDeletedPendingInvites] = useState< Record >({}) useEffect(() => { let aborted = false if (hasSetup) { return } suggestions.setup().then(() => { if (aborted) return setHasSetup(true) }) return () => { aborted = true } }, [profileView.did]) const onPressInvite = async (follow: FollowItem) => { setError('') try { const assertionUri = await apilib.inviteToScene( store, profileView.did, follow.did, follow.declaration.cid, ) setCreatedInvites({[follow.did]: assertionUri, ...createdInvites}) Toast.show('Invite sent') } catch (e) { setError('There was an issue with the invite. Please try again.') console.error(e) } } const onPressUndo = async (subjectDid: string, assertionUri: string) => { setError('') const urip = new AtUri(assertionUri) try { await store.api.app.bsky.graph.assertion.delete({ did: profileView.did, rkey: urip.rkey, }) setCreatedInvites(_omit(createdInvites, [subjectDid])) } catch (e) { setError('There was an issue with the invite. Please try again.') console.error(e) } } const onPressDeleteInvite = async (assertion: Assertion) => { setError('') const urip = new AtUri(assertion.uri) try { await store.api.app.bsky.graph.assertion.delete({ did: profileView.did, rkey: urip.rkey, }) setDeletedPendingInvites({ [assertion.uri]: true, ...deletedPendingInvites, }) Toast.show('Invite removed') } catch (e) { setError('There was an issue with the invite. Please try again.') console.error(e) } } const renderSuggestionItem = ({item}: {item: FollowItem}) => { const createdInvite = createdInvites[item.did] return ( !createdInvite ? ( <> Invite ) : ( <> Undo invite ) } onPressButton={() => !createdInvite ? onPressInvite(item) : onPressUndo(item.did, createdInvite) } /> ) } const renderPendingInviteItem = ({item}: {item: Assertion}) => { const wasDeleted = deletedPendingInvites[item.uri] if (wasDeleted) { return } return ( ( <> Undo invite )} onPressButton={() => onPressDeleteInvite(item)} /> ) } const Suggestions = () => ( {hasSetup ? ( User search is still being implemented. For now, you can pick from your follows below. {!suggestions.hasContent ? ( {suggestions.myFollowsView.follows.length ? 'Sorry! You dont follow anybody for us to suggest.' : 'Sorry! All of the users you follow are members already.'} ) : ( item._reactKey} renderItem={renderSuggestionItem} style={s.flex1} /> )} ) : !error ? ( ) : undefined} ) const PendingInvites = () => ( {suggestions.sceneAssertionsView.isLoading ? ( ) : undefined} {!suggestions.unconfirmed.length ? ( No pending invites. ) : ( item._reactKey} renderItem={renderPendingInviteItem} style={s.flex1} /> )} ) const renderScene = SceneMap({ suggestions: Suggestions, pending: PendingInvites, }) const renderTabBar = (props: TabBarProps) => ( ) return ( Invite to {profileView.displayName || profileView.handle} {error !== '' ? ( ) : undefined} ) }) const styles = StyleSheet.create({ title: { textAlign: 'center', fontWeight: 'bold', fontSize: 18, marginBottom: 4, }, todoContainer: { backgroundColor: colors.pink1, margin: 10, padding: 10, borderRadius: 6, }, todoLabel: { color: colors.pink5, textAlign: 'center', }, tabBar: { flexDirection: 'row', }, tabItem: { alignItems: 'center', padding: 16, flex: 1, }, })