about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/state/models/members-view.ts32
-rw-r--r--src/view/screens/Profile.tsx37
-rw-r--r--todos.txt3
3 files changed, 69 insertions, 3 deletions
diff --git a/src/state/models/members-view.ts b/src/state/models/members-view.ts
index 370b6b431..19101afca 100644
--- a/src/state/models/members-view.ts
+++ b/src/state/models/members-view.ts
@@ -1,5 +1,7 @@
-import {makeAutoObservable} from 'mobx'
+import {makeAutoObservable, runInAction} from 'mobx'
 import * as GetMembers from '../../third-party/api/src/client/types/app/bsky/graph/getMembers'
+import {APP_BSKY_GRAPH} from '../../third-party/api'
+import {AtUri} from '../../third-party/uri'
 import {RootStoreModel} from './root-store'
 
 type Subject = GetMembers.OutputSchema['subject']
@@ -16,7 +18,12 @@ export class MembersViewModel {
   params: GetMembers.QueryParams
 
   // data
-  subject: Subject = {did: '', handle: '', displayName: ''}
+  subject: Subject = {
+    did: '',
+    handle: '',
+    displayName: '',
+    declaration: {cid: '', actorType: ''},
+  }
   members: MemberItem[] = []
 
   constructor(
@@ -65,6 +72,26 @@ export class MembersViewModel {
     // TODO
   }
 
+  async removeMember(did: string) {
+    const assertsRes = await this.rootStore.api.app.bsky.graph.getAssertions({
+      author: this.subject.did,
+      subject: did,
+      assertion: APP_BSKY_GRAPH.AssertMember,
+    })
+    if (assertsRes.data.assertions.length < 1) {
+      throw new Error('Could not find membership record')
+    }
+    for (const assert of assertsRes.data.assertions) {
+      await this.rootStore.api.app.bsky.graph.assertion.delete({
+        did: this.subject.did,
+        rkey: new AtUri(assert.uri).rkey,
+      })
+    }
+    runInAction(() => {
+      this.members = this.members.filter(m => m.did !== did)
+    })
+  }
+
   // state transitions
   // =
 
@@ -101,6 +128,7 @@ export class MembersViewModel {
     this.subject.did = res.data.subject.did
     this.subject.handle = res.data.subject.handle
     this.subject.displayName = res.data.subject.displayName
+    this.subject.declaration = res.data.subject.declaration
     this.members.length = 0
     let counter = 0
     for (const item of res.data.members) {
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 7fb3a5fb7..5dbe29060 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -1,15 +1,19 @@
 import React, {useEffect, useState, useMemo} from 'react'
 import {StyleSheet, Text, View} from 'react-native'
 import {observer} from 'mobx-react-lite'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {ViewSelector} from '../com/util/ViewSelector'
 import {ScreenParams} from '../routes'
 import {ProfileUiModel, Sections} from '../../state/models/profile-ui'
+import {MembershipItem} from '../../state/models/memberships-view'
 import {useStores} from '../../state'
+import {ConfirmModel} from '../../state/models/shell-ui'
 import {ProfileHeader} from '../com/profile/ProfileHeader'
 import {FeedItem} from '../com/posts/FeedItem'
 import {ProfileCard} from '../com/profile/ProfileCard'
 import {ErrorScreen} from '../com/util/ErrorScreen'
 import {ErrorMessage} from '../com/util/ErrorMessage'
+import Toast from '../com/util/Toast'
 import {s, colors} from '../lib/styles'
 import {UserGroupIcon} from '../lib/icons'
 
@@ -65,10 +69,28 @@ export const Profile = observer(({visible, params}: ScreenParams) => {
   const onPressTryAgain = () => {
     uiState.setup()
   }
+  const onPressRemoveMember = (membership: MembershipItem) => {
+    store.shell.openModal(
+      new ConfirmModel(
+        `Remove ${membership.displayName || membership.handle}?`,
+        `You'll be able to invite them again if you change your mind.`,
+        async () => {
+          await uiState.members.removeMember(membership.did)
+          Toast.show(`User removed`, {
+            duration: Toast.durations.LONG,
+            position: Toast.positions.TOP,
+          })
+        },
+      ),
+    )
+  }
 
   // rendering
   // =
 
+  const isSceneCreator =
+    uiState.isScene && store.me.did === uiState.profile.creator
+
   const renderHeader = () => {
     if (!uiState) {
       return <View />
@@ -155,11 +177,26 @@ export const Profile = observer(({visible, params}: ScreenParams) => {
         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}
+                renderButton={renderButton}
+                onPressButton={() => onPressRemoveMember(item)}
               />
             )
           }
diff --git a/todos.txt b/todos.txt
index 9f40fac75..11e5e7b92 100644
--- a/todos.txt
+++ b/todos.txt
@@ -18,9 +18,10 @@ Paul's todo list
   - User search
   - Use pagination to make sure there are suggestions
 - User profile
+  - User
+    - Invite to scene
   - Scene
     > Edit profile
-    > Remove member
 - Reply gating
   - Composer
   - View on post