Remove scene-related modals
This commit is contained in:
@@ -25,22 +25,6 @@ export class EditProfileModal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateSceneModal {
|
|
||||||
name = 'create-scene'
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
makeAutoObservable(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class InviteToSceneModal {
|
|
||||||
name = 'invite-to-scene'
|
|
||||||
|
|
||||||
constructor(public profileView: ProfileViewModel) {
|
|
||||||
makeAutoObservable(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ServerInputModal {
|
export class ServerInputModal {
|
||||||
name = 'server-input'
|
name = 'server-input'
|
||||||
|
|
||||||
@@ -143,7 +127,6 @@ export class ShellUiModel {
|
|||||||
activeModal:
|
activeModal:
|
||||||
| ConfirmModal
|
| ConfirmModal
|
||||||
| EditProfileModal
|
| EditProfileModal
|
||||||
| CreateSceneModal
|
|
||||||
| ServerInputModal
|
| ServerInputModal
|
||||||
| ReportPostModal
|
| ReportPostModal
|
||||||
| ReportAccountModal
|
| ReportAccountModal
|
||||||
@@ -191,7 +174,6 @@ export class ShellUiModel {
|
|||||||
modal:
|
modal:
|
||||||
| ConfirmModal
|
| ConfirmModal
|
||||||
| EditProfileModal
|
| EditProfileModal
|
||||||
| CreateSceneModal
|
|
||||||
| ServerInputModal
|
| ServerInputModal
|
||||||
| ReportPostModal
|
| ReportPostModal
|
||||||
| ReportAccountModal,
|
| ReportAccountModal,
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
import {makeAutoObservable, runInAction} from 'mobx'
|
|
||||||
import {RootStoreModel} from './root-store'
|
|
||||||
import {UserFollowsViewModel, FollowItem} from './user-follows-view'
|
|
||||||
import {GetAssertionsView} from './get-assertions-view'
|
|
||||||
import {APP_BSKY_SYSTEM, APP_BSKY_GRAPH} from '@atproto/api'
|
|
||||||
|
|
||||||
export interface SuggestedInvitesViewParams {
|
|
||||||
sceneDid: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SuggestedInvitesView {
|
|
||||||
// state
|
|
||||||
isLoading = false
|
|
||||||
isRefreshing = false
|
|
||||||
hasLoaded = false
|
|
||||||
error = ''
|
|
||||||
params: SuggestedInvitesViewParams
|
|
||||||
sceneAssertionsView: GetAssertionsView
|
|
||||||
myFollowsView: UserFollowsViewModel
|
|
||||||
|
|
||||||
// data
|
|
||||||
suggestions: FollowItem[] = []
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public rootStore: RootStoreModel,
|
|
||||||
params: SuggestedInvitesViewParams,
|
|
||||||
) {
|
|
||||||
makeAutoObservable(
|
|
||||||
this,
|
|
||||||
{
|
|
||||||
rootStore: false,
|
|
||||||
params: false,
|
|
||||||
},
|
|
||||||
{autoBind: true},
|
|
||||||
)
|
|
||||||
this.params = params
|
|
||||||
this.sceneAssertionsView = new GetAssertionsView(rootStore, {
|
|
||||||
author: params.sceneDid,
|
|
||||||
assertion: APP_BSKY_GRAPH.AssertMember,
|
|
||||||
})
|
|
||||||
this.myFollowsView = new UserFollowsViewModel(rootStore, {
|
|
||||||
user: rootStore.me.did || '',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasContent() {
|
|
||||||
return this.suggestions.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasError() {
|
|
||||||
return this.error !== ''
|
|
||||||
}
|
|
||||||
|
|
||||||
get isEmpty() {
|
|
||||||
return this.hasLoaded && !this.hasContent
|
|
||||||
}
|
|
||||||
|
|
||||||
get unconfirmed() {
|
|
||||||
return this.sceneAssertionsView.unconfirmed
|
|
||||||
}
|
|
||||||
|
|
||||||
// public api
|
|
||||||
// =
|
|
||||||
|
|
||||||
async setup() {
|
|
||||||
await this._fetch(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
async refresh() {
|
|
||||||
await this._fetch(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadMore() {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
// state transitions
|
|
||||||
// =
|
|
||||||
|
|
||||||
private _xLoading(isRefreshing = false) {
|
|
||||||
this.isLoading = true
|
|
||||||
this.isRefreshing = isRefreshing
|
|
||||||
this.error = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
private _xIdle(err?: any) {
|
|
||||||
this.isLoading = false
|
|
||||||
this.isRefreshing = false
|
|
||||||
this.hasLoaded = true
|
|
||||||
this.error = err ? err.toString() : ''
|
|
||||||
if (err) {
|
|
||||||
this.rootStore.log.error('Failed to fetch suggested invites', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loader functions
|
|
||||||
// =
|
|
||||||
|
|
||||||
private async _fetch(isRefreshing = false) {
|
|
||||||
this._xLoading(isRefreshing)
|
|
||||||
try {
|
|
||||||
// TODO need to fetch all!
|
|
||||||
await this.sceneAssertionsView.setup()
|
|
||||||
} catch (e: any) {
|
|
||||||
this.rootStore.log.error(
|
|
||||||
'Failed to fetch current scene members in suggested invites',
|
|
||||||
e,
|
|
||||||
)
|
|
||||||
this._xIdle(
|
|
||||||
'Failed to fetch the current scene members. Check your internet connection and try again.',
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await this.myFollowsView.setup()
|
|
||||||
} catch (e: any) {
|
|
||||||
this.rootStore.log.error(
|
|
||||||
'Failed to fetch current followers in suggested invites',
|
|
||||||
e,
|
|
||||||
)
|
|
||||||
this._xIdle(
|
|
||||||
'Failed to fetch the your current followers. Check your internet connection and try again.',
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect all followed users that arent already in the scene
|
|
||||||
const newSuggestions: FollowItem[] = []
|
|
||||||
for (const follow of this.myFollowsView.follows) {
|
|
||||||
if (follow.declaration.actorType !== APP_BSKY_SYSTEM.ActorUser) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (!this.sceneAssertionsView.getBySubject(follow.did)) {
|
|
||||||
newSuggestions.push(follow)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
runInAction(() => {
|
|
||||||
this.suggestions = newSuggestions
|
|
||||||
})
|
|
||||||
this._xIdle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
import React, {useState} from 'react'
|
|
||||||
import * as Toast from '../util/Toast'
|
|
||||||
import {
|
|
||||||
ActivityIndicator,
|
|
||||||
StyleSheet,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import LinearGradient from 'react-native-linear-gradient'
|
|
||||||
import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
|
|
||||||
import {AppBskyActorCreateScene} from '@atproto/api'
|
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
|
||||||
import {Text} from '../util/text/Text'
|
|
||||||
import {useStores} from '../../../state'
|
|
||||||
import {s, colors, gradients} from '../../lib/styles'
|
|
||||||
import {
|
|
||||||
makeValidHandle,
|
|
||||||
createFullHandle,
|
|
||||||
enforceLen,
|
|
||||||
MAX_DISPLAY_NAME,
|
|
||||||
MAX_DESCRIPTION,
|
|
||||||
} from '../../../lib/strings'
|
|
||||||
|
|
||||||
export const snapPoints = ['60%']
|
|
||||||
|
|
||||||
export function Component({}: {}) {
|
|
||||||
const store = useStores()
|
|
||||||
const [error, setError] = useState<string>('')
|
|
||||||
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
|
||||||
const [handle, setHandle] = useState<string>('')
|
|
||||||
const [displayName, setDisplayName] = useState<string>('')
|
|
||||||
const [description, setDescription] = useState<string>('')
|
|
||||||
const onPressSave = async () => {
|
|
||||||
setIsProcessing(true)
|
|
||||||
if (error) {
|
|
||||||
setError('')
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (!store.me.did) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const desc = await store.api.com.atproto.server.getAccountsConfig()
|
|
||||||
const fullHandle = createFullHandle(
|
|
||||||
handle,
|
|
||||||
desc.data.availableUserDomains[0],
|
|
||||||
)
|
|
||||||
// create scene actor
|
|
||||||
const createSceneRes = await store.api.app.bsky.actor.createScene({
|
|
||||||
handle: fullHandle,
|
|
||||||
})
|
|
||||||
// set the scene profile
|
|
||||||
await store.api.app.bsky.actor
|
|
||||||
.updateProfile({
|
|
||||||
did: createSceneRes.data.did,
|
|
||||||
displayName,
|
|
||||||
description,
|
|
||||||
})
|
|
||||||
.catch(e =>
|
|
||||||
// an error here is not critical
|
|
||||||
store.log.error('Failed to update scene profile during creation', e),
|
|
||||||
)
|
|
||||||
// follow the scene
|
|
||||||
await store.api.app.bsky.graph.follow
|
|
||||||
.create(
|
|
||||||
{
|
|
||||||
did: store.me.did,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
subject: {
|
|
||||||
did: createSceneRes.data.did,
|
|
||||||
declarationCid: createSceneRes.data.declaration.cid,
|
|
||||||
},
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.catch(e =>
|
|
||||||
// an error here is not critical
|
|
||||||
store.log.error('Failed to follow scene after creation', e),
|
|
||||||
)
|
|
||||||
Toast.show('Scene created')
|
|
||||||
store.shell.closeModal()
|
|
||||||
store.nav.navigate(`/profile/${fullHandle}`)
|
|
||||||
} catch (e: any) {
|
|
||||||
if (e instanceof AppBskyActorCreateScene.InvalidHandleError) {
|
|
||||||
setError(
|
|
||||||
'The handle can only contain letters, numbers, and dashes, and must start with a letter.',
|
|
||||||
)
|
|
||||||
} else if (e instanceof AppBskyActorCreateScene.HandleNotAvailableError) {
|
|
||||||
setError(`The handle "${handle}" is not available.`)
|
|
||||||
} else {
|
|
||||||
store.log.error('Failed to create scene', e)
|
|
||||||
setError(
|
|
||||||
'Failed to create the scene. Check your internet connection and try again.',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
setIsProcessing(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const onPressCancel = () => {
|
|
||||||
store.shell.closeModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.outer}>
|
|
||||||
<BottomSheetScrollView style={styles.inner}>
|
|
||||||
<Text style={[styles.title, s.black]}>Create a scene</Text>
|
|
||||||
<Text style={styles.description}>
|
|
||||||
Scenes are invite-only groups which aggregate what's popular with
|
|
||||||
members.
|
|
||||||
</Text>
|
|
||||||
<View style={{paddingBottom: 50}}>
|
|
||||||
<View style={styles.group}>
|
|
||||||
<Text style={[styles.label, s.black]}>Scene Handle</Text>
|
|
||||||
<BottomSheetTextInput
|
|
||||||
style={styles.textInput}
|
|
||||||
placeholder="e.g. alices-friends"
|
|
||||||
placeholderTextColor={colors.gray4}
|
|
||||||
autoCorrect={false}
|
|
||||||
value={handle}
|
|
||||||
onChangeText={str => setHandle(makeValidHandle(str))}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={styles.group}>
|
|
||||||
<Text style={[styles.label, s.black]}>Scene Display Name</Text>
|
|
||||||
<BottomSheetTextInput
|
|
||||||
style={styles.textInput}
|
|
||||||
placeholder="e.g. Alice's Friends"
|
|
||||||
placeholderTextColor={colors.gray4}
|
|
||||||
value={displayName}
|
|
||||||
onChangeText={v =>
|
|
||||||
setDisplayName(enforceLen(v, MAX_DISPLAY_NAME))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={styles.group}>
|
|
||||||
<Text style={[styles.label, s.black]}>Scene Description</Text>
|
|
||||||
<BottomSheetTextInput
|
|
||||||
style={[styles.textArea]}
|
|
||||||
placeholder="e.g. Artists, dog-lovers, and memelords."
|
|
||||||
placeholderTextColor={colors.gray4}
|
|
||||||
multiline
|
|
||||||
value={description}
|
|
||||||
onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
{error !== '' && (
|
|
||||||
<View style={s.mb10}>
|
|
||||||
<ErrorMessage message={error} numberOfLines={3} />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
{handle.length >= 2 && !isProcessing ? (
|
|
||||||
<TouchableOpacity style={s.mt10} onPress={onPressSave}>
|
|
||||||
<LinearGradient
|
|
||||||
colors={[gradients.primary.start, gradients.primary.end]}
|
|
||||||
start={{x: 0, y: 0}}
|
|
||||||
end={{x: 1, y: 1}}
|
|
||||||
style={[styles.btn]}>
|
|
||||||
<Text style={[s.white, s.bold, s.f18]}>Create Scene</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
</TouchableOpacity>
|
|
||||||
) : (
|
|
||||||
<View style={s.mt10}>
|
|
||||||
<View style={[styles.btn]}>
|
|
||||||
{isProcessing ? (
|
|
||||||
<ActivityIndicator />
|
|
||||||
) : (
|
|
||||||
<Text style={[s.gray4, s.bold, s.f18]}>Create Scene</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<TouchableOpacity style={s.mt10} onPress={onPressCancel}>
|
|
||||||
<View style={[styles.btn, {backgroundColor: colors.white}]}>
|
|
||||||
<Text style={[s.black, s.bold]}>Cancel</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</BottomSheetScrollView>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
outer: {
|
|
||||||
flex: 1,
|
|
||||||
// paddingTop: 20,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: 24,
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: 17,
|
|
||||||
paddingHorizontal: 22,
|
|
||||||
color: colors.gray5,
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
inner: {
|
|
||||||
padding: 14,
|
|
||||||
height: 350,
|
|
||||||
},
|
|
||||||
group: {
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
paddingHorizontal: 4,
|
|
||||||
paddingBottom: 4,
|
|
||||||
},
|
|
||||||
textInput: {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: colors.gray3,
|
|
||||||
borderRadius: 6,
|
|
||||||
paddingHorizontal: 14,
|
|
||||||
paddingVertical: 10,
|
|
||||||
fontSize: 16,
|
|
||||||
color: colors.black,
|
|
||||||
},
|
|
||||||
textArea: {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: colors.gray3,
|
|
||||||
borderRadius: 6,
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
paddingTop: 10,
|
|
||||||
fontSize: 16,
|
|
||||||
color: colors.black,
|
|
||||||
height: 70,
|
|
||||||
textAlignVertical: 'top',
|
|
||||||
},
|
|
||||||
btn: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: '100%',
|
|
||||||
borderRadius: 32,
|
|
||||||
padding: 14,
|
|
||||||
backgroundColor: colors.gray1,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
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<boolean>(false)
|
|
||||||
const [error, setError] = useState<string>('')
|
|
||||||
const suggestions = useMemo(
|
|
||||||
() => new SuggestedInvitesView(store, {sceneDid: profileView.did}),
|
|
||||||
[profileView.did],
|
|
||||||
)
|
|
||||||
const [createdInvites, setCreatedInvites] = useState<Record<string, string>>(
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
// 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<string, boolean>
|
|
||||||
>({})
|
|
||||||
|
|
||||||
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: any) {
|
|
||||||
setError('There was an issue with the invite. Please try again.')
|
|
||||||
store.log.error('Failed to invite user to scene', 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: any) {
|
|
||||||
setError('There was an issue with the invite. Please try again.')
|
|
||||||
store.log.error('Failed to delete a scene invite', 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: any) {
|
|
||||||
setError('There was an issue with the invite. Please try again.')
|
|
||||||
store.log.error('Failed to delete an invite', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderSuggestionItem = ({item}: {item: FollowItem}) => {
|
|
||||||
const createdInvite = createdInvites[item.did]
|
|
||||||
return (
|
|
||||||
<ProfileCard
|
|
||||||
did={item.did}
|
|
||||||
handle={item.handle}
|
|
||||||
displayName={item.displayName}
|
|
||||||
avatar={item.avatar}
|
|
||||||
renderButton={() =>
|
|
||||||
!createdInvite ? (
|
|
||||||
<>
|
|
||||||
<FontAwesomeIcon icon="user-plus" style={[s.mr5]} size={14} />
|
|
||||||
<Text style={[s.fw400, s.f14]}>Invite</Text>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<FontAwesomeIcon icon="x" style={[s.mr5]} size={14} />
|
|
||||||
<Text style={[s.fw400, s.f14]}>Undo invite</Text>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onPressButton={() =>
|
|
||||||
!createdInvite
|
|
||||||
? onPressInvite(item)
|
|
||||||
: onPressUndo(item.did, createdInvite)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderPendingInviteItem = ({item}: {item: Assertion}) => {
|
|
||||||
const wasDeleted = deletedPendingInvites[item.uri]
|
|
||||||
if (wasDeleted) {
|
|
||||||
return <View />
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ProfileCard
|
|
||||||
did={item.subject.did}
|
|
||||||
handle={item.subject.handle}
|
|
||||||
displayName={item.subject.displayName}
|
|
||||||
avatar={item.subject.avatar}
|
|
||||||
renderButton={() => (
|
|
||||||
<>
|
|
||||||
<FontAwesomeIcon icon="x" style={[s.mr5]} size={14} />
|
|
||||||
<Text style={[s.fw400, s.f14]}>Undo invite</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
onPressButton={() => onPressDeleteInvite(item)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Suggestions = () => (
|
|
||||||
<View style={s.flex1}>
|
|
||||||
{hasSetup ? (
|
|
||||||
<View style={s.flex1}>
|
|
||||||
<View style={styles.todoContainer}>
|
|
||||||
<Text style={styles.todoLabel}>
|
|
||||||
User search is still being implemented. For now, you can pick from
|
|
||||||
your follows below.
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
{!suggestions.hasContent ? (
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
textAlign: 'center',
|
|
||||||
paddingTop: 10,
|
|
||||||
paddingHorizontal: 40,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: colors.gray5,
|
|
||||||
}}>
|
|
||||||
{suggestions.myFollowsView.follows.length
|
|
||||||
? 'Sorry! You dont follow anybody for us to suggest.'
|
|
||||||
: 'Sorry! All of the users you follow are members already.'}
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<FlatList
|
|
||||||
data={suggestions.suggestions}
|
|
||||||
keyExtractor={item => item._reactKey}
|
|
||||||
renderItem={renderSuggestionItem}
|
|
||||||
style={s.flex1}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
) : !error ? (
|
|
||||||
<ActivityIndicator />
|
|
||||||
) : undefined}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
|
|
||||||
const PendingInvites = () => (
|
|
||||||
<View style={s.flex1}>
|
|
||||||
{suggestions.sceneAssertionsView.isLoading ? (
|
|
||||||
<ActivityIndicator />
|
|
||||||
) : undefined}
|
|
||||||
<View style={s.flex1}>
|
|
||||||
{!suggestions.unconfirmed.length ? (
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
textAlign: 'center',
|
|
||||||
paddingTop: 10,
|
|
||||||
paddingHorizontal: 40,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: colors.gray5,
|
|
||||||
}}>
|
|
||||||
No pending invites.
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<FlatList
|
|
||||||
data={suggestions.unconfirmed}
|
|
||||||
keyExtractor={item => item._reactKey}
|
|
||||||
renderItem={renderPendingInviteItem}
|
|
||||||
style={s.flex1}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
|
|
||||||
const renderScene = SceneMap({
|
|
||||||
suggestions: Suggestions,
|
|
||||||
pending: PendingInvites,
|
|
||||||
})
|
|
||||||
|
|
||||||
const renderTabBar = (props: TabBarProps<Route>) => (
|
|
||||||
<TabBar
|
|
||||||
{...props}
|
|
||||||
style={{backgroundColor: 'white'}}
|
|
||||||
activeColor="black"
|
|
||||||
inactiveColor={colors.gray5}
|
|
||||||
labelStyle={{textTransform: 'none'}}
|
|
||||||
indicatorStyle={{backgroundColor: colors.purple3}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={s.flex1}>
|
|
||||||
<Text style={styles.title}>
|
|
||||||
Invite to {profileView.displayName || profileView.handle}
|
|
||||||
</Text>
|
|
||||||
{error !== '' ? (
|
|
||||||
<View style={s.p10}>
|
|
||||||
<ErrorMessage message={error} />
|
|
||||||
</View>
|
|
||||||
) : undefined}
|
|
||||||
<TabView
|
|
||||||
navigationState={{index, routes: tabRoutes}}
|
|
||||||
renderScene={renderScene}
|
|
||||||
renderTabBar={renderTabBar}
|
|
||||||
onIndexChange={setIndex}
|
|
||||||
initialLayout={{width: layout.width}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -9,8 +9,6 @@ import * as models from '../../../state/models/shell-ui'
|
|||||||
|
|
||||||
import * as ConfirmModal from './Confirm'
|
import * as ConfirmModal from './Confirm'
|
||||||
import * as EditProfileModal from './EditProfile'
|
import * as EditProfileModal from './EditProfile'
|
||||||
import * as CreateSceneModal from './CreateScene'
|
|
||||||
import * as InviteToSceneModal from './InviteToScene'
|
|
||||||
import * as ServerInputModal from './ServerInput'
|
import * as ServerInputModal from './ServerInput'
|
||||||
import * as ReportPostModal from './ReportPost'
|
import * as ReportPostModal from './ReportPost'
|
||||||
import * as ReportAccountModal from './ReportAccount'
|
import * as ReportAccountModal from './ReportAccount'
|
||||||
@@ -55,16 +53,6 @@ export const Modal = observer(function Modal() {
|
|||||||
{...(store.shell.activeModal as models.EditProfileModal)}
|
{...(store.shell.activeModal as models.EditProfileModal)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if (store.shell.activeModal?.name === 'create-scene') {
|
|
||||||
snapPoints = CreateSceneModal.snapPoints
|
|
||||||
element = <CreateSceneModal.Component />
|
|
||||||
} else if (store.shell.activeModal?.name === 'invite-to-scene') {
|
|
||||||
snapPoints = InviteToSceneModal.snapPoints
|
|
||||||
element = (
|
|
||||||
<InviteToSceneModal.Component
|
|
||||||
{...(store.shell.activeModal as models.InviteToSceneModal)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (store.shell.activeModal?.name === 'server-input') {
|
} else if (store.shell.activeModal?.name === 'server-input') {
|
||||||
snapPoints = ServerInputModal.snapPoints
|
snapPoints = ServerInputModal.snapPoints
|
||||||
element = (
|
element = (
|
||||||
|
|||||||
@@ -92,9 +92,6 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
|
|||||||
// rendering
|
// rendering
|
||||||
// =
|
// =
|
||||||
|
|
||||||
const isSceneCreator =
|
|
||||||
uiState.isScene && store.me.did === uiState.profile.creator
|
|
||||||
|
|
||||||
const renderHeader = () => {
|
const renderHeader = () => {
|
||||||
if (!uiState) {
|
if (!uiState) {
|
||||||
return <View />
|
return <View />
|
||||||
@@ -126,8 +123,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
|
|||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
uiState.selectedView === Sections.Posts ||
|
uiState.selectedView === Sections.Posts ||
|
||||||
uiState.selectedView === Sections.PostsWithReplies ||
|
uiState.selectedView === Sections.PostsWithReplies
|
||||||
uiState.selectedView === Sections.Trending
|
|
||||||
) {
|
) {
|
||||||
if (uiState.feed.hasContent) {
|
if (uiState.feed.hasContent) {
|
||||||
if (uiState.selectedView === Sections.Posts) {
|
if (uiState.selectedView === Sections.Posts) {
|
||||||
@@ -147,80 +143,12 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
|
|||||||
return <FeedItem item={item} ignoreMuteFor={uiState.profile.did} />
|
return <FeedItem item={item} ignoreMuteFor={uiState.profile.did} />
|
||||||
}
|
}
|
||||||
} else if (uiState.feed.isEmpty) {
|
} else if (uiState.feed.isEmpty) {
|
||||||
items = items.concat([EMPTY_ITEM])
|
|
||||||
if (uiState.profile.isScene) {
|
|
||||||
renderItem = () => (
|
|
||||||
<EmptyState
|
|
||||||
icon="user-group"
|
|
||||||
message="As members upvote posts, they will trend here. Follow the scene to see its trending posts in your timeline."
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
renderItem = () => (
|
|
||||||
<EmptyState
|
|
||||||
icon={['far', 'message']}
|
|
||||||
message="No posts yet!"
|
|
||||||
style={{paddingVertical: 40}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (uiState.selectedView === Sections.Scenes) {
|
|
||||||
if (uiState.memberships.hasContent) {
|
|
||||||
items = uiState.memberships.memberships.slice()
|
|
||||||
renderItem = (item: any) => {
|
|
||||||
return (
|
|
||||||
<ProfileCard
|
|
||||||
did={item.did}
|
|
||||||
handle={item.handle}
|
|
||||||
displayName={item.displayName}
|
|
||||||
avatar={item.avatar}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (uiState.memberships.isEmpty) {
|
|
||||||
items = items.concat([EMPTY_ITEM])
|
items = items.concat([EMPTY_ITEM])
|
||||||
renderItem = () => (
|
renderItem = () => (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
icon="user-group"
|
icon={['far', 'message']}
|
||||||
message="This user hasn't joined any scenes."
|
message="No posts yet!"
|
||||||
/>
|
style={{paddingVertical: 40}}
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (uiState.selectedView === Sections.Members) {
|
|
||||||
if (uiState.members.hasContent) {
|
|
||||||
items = uiState.members.members.slice()
|
|
||||||
renderItem = (item: any) => {
|
|
||||||
const shouldAdmin = isSceneCreator && item.did !== store.me.did
|
|
||||||
const renderButton = shouldAdmin
|
|
||||||
? () => (
|
|
||||||
<>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="user-xmark"
|
|
||||||
style={[s.mr5]}
|
|
||||||
size={14}
|
|
||||||
/>
|
|
||||||
<Text style={[s.fw400, s.f14]}>Remove</Text>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
return (
|
|
||||||
<ProfileCard
|
|
||||||
did={item.did}
|
|
||||||
handle={item.handle}
|
|
||||||
displayName={item.displayName}
|
|
||||||
avatar={item.avatar}
|
|
||||||
renderButton={renderButton}
|
|
||||||
onPressButton={() => onPressRemoveMember(item)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (uiState.members.isEmpty) {
|
|
||||||
items = items.concat([EMPTY_ITEM])
|
|
||||||
renderItem = () => (
|
|
||||||
<EmptyState
|
|
||||||
icon="user-group"
|
|
||||||
message="This scene doesn't have any members."
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user