summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@users.noreply.github.com>2021-07-01 12:25:32 +0200
committerGitHub <noreply@github.com>2021-07-01 12:25:32 +0200
commite07690525c94a82c203e328a66020c3ae8b68cfd (patch)
tree76aeb4a23f92d3e47070f141c9e1f764e85b3ee6
parent017026a8e42717cc8e1935af5ce61aa46b3e9f79 (diff)
parent904273d4a1ade3fa07e26cac1fab44f0c2cb0333 (diff)
Merge pull request #2316 from nextcloud/fix/pending-member
-rw-r--r--src/components/AppContent/CircleContent.vue40
-rw-r--r--src/components/AppNavigation/CircleNavigationItem.vue1
-rw-r--r--src/components/CircleDetails.vue22
-rw-r--r--src/components/MemberList.vue16
-rw-r--r--src/components/MembersList/MembersListItem.vue69
-rw-r--r--src/mixins/CircleActionsMixin.js19
-rw-r--r--src/models/circle.d.ts18
-rw-r--r--src/models/circle.ts33
-rw-r--r--src/models/constants.d.ts12
-rw-r--r--src/models/constants.ts20
-rw-r--r--src/models/member.d.ts5
-rw-r--r--src/models/member.ts8
-rw-r--r--src/services/circles.d.ts8
-rw-r--r--src/services/circles.ts12
-rw-r--r--src/store/circles.js21
15 files changed, 226 insertions, 78 deletions
diff --git a/src/components/AppContent/CircleContent.vue b/src/components/AppContent/CircleContent.vue
index a0f1de97..cbe59bcc 100644
--- a/src/components/AppContent/CircleContent.vue
+++ b/src/components/AppContent/CircleContent.vue
@@ -46,45 +46,27 @@
<CircleDetails :circle="circle">
<!-- not a member -->
<template v-if="!circle.isMember">
- <!-- Join request in progress -->
- <EmptyContent v-if="loadingJoin" icon="icon-loading">
- {{ t('contacts', 'Joining circle') }}
- </EmptyContent>
-
<!-- Pending request validation -->
- <EmptyContent v-else-if="circle.isPendingJoin" icon="icon-loading">
+ <EmptyContent v-if="circle.isPendingMember" icon="icon-loading">
{{ t('contacts', 'Your request to join this circle is pending approval') }}
</EmptyContent>
<EmptyContent v-else icon="icon-circles">
{{ t('contacts', 'You are not a member of {circle}', { circle: circle.displayName}) }}
-
- <!-- Only show the join button if the circle is accepting requests -->
- <template v-if="circle.canJoin" #desc>
- <button :disabled="loadingJoin" class="primary" @click="requestJoin">
- <Login slot="icon"
- :size="16"
- decorative />
- {{ t('contacts', 'Request to join') }}
- </button>
- </template>
</EmptyContent>
</template>
</CircleDetails>
</AppContent>
</template>
<script>
+import { showError } from '@nextcloud/dialogs'
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
import isMobile from '@nextcloud/vue/dist/Mixins/isMobile'
-import Login from 'vue-material-design-icons/Login'
-
import CircleDetails from '../CircleDetails'
import MemberList from '../MemberList'
import RouterMixin from '../../mixins/RouterMixin'
-import { joinCircle } from '../../services/circles.ts'
-import { showError } from '@nextcloud/dialogs'
export default {
name: 'CircleContent',
@@ -93,7 +75,6 @@ export default {
AppContent,
CircleDetails,
EmptyContent,
- Login,
MemberList,
},
@@ -108,7 +89,6 @@ export default {
data() {
return {
- loadingJoin: false,
loadingList: false,
showDetails: false,
}
@@ -164,22 +144,6 @@ export default {
}
},
- /**
- * Request to join this circle
- */
- async requestJoin() {
- this.loadingJoin = true
-
- try {
- await joinCircle(this.circle.id)
- } catch (error) {
- showError(t('contacts', 'Unable to join the circle'))
- } finally {
- this.loadingJoin = false
- }
-
- },
-
// Hide the circle details
hideDetails() {
this.showDetails = false
diff --git a/src/components/AppNavigation/CircleNavigationItem.vue b/src/components/AppNavigation/CircleNavigationItem.vue
index 9b4ec1d6..e2ee53eb 100644
--- a/src/components/AppNavigation/CircleNavigationItem.vue
+++ b/src/components/AppNavigation/CircleNavigationItem.vue
@@ -61,6 +61,7 @@
<!-- join circle -->
<ActionButton
v-else-if="!circle.isMember && circle.canJoin"
+ :disabled="loadingJoin"
@click="joinCircle">
{{ joinButtonTitle }}
<LocationEnter slot="icon"
diff --git a/src/components/CircleDetails.vue b/src/components/CircleDetails.vue
index 774d7dce..20552a86 100644
--- a/src/components/CircleDetails.vue
+++ b/src/components/CircleDetails.vue
@@ -54,7 +54,7 @@
</template>
</DetailsHeader>
- <section class="circle-details-section">
+ <section class="circle-details-section circle-details-section__actions">
<!-- copy circle link -->
<a class="circle-details__action-copy-link button"
:href="circleUrl"
@@ -62,6 +62,17 @@
@click.stop.prevent="copyToClipboard(circleUrl)">
{{ copyButtonText }}
</a>
+
+ <!-- Only show the join button if the circle is accepting requests -->
+ <button v-if="!circle.isPendingMember && !circle.isMember && circle.canJoin"
+ :disabled="loadingJoin"
+ class="primary"
+ @click="joinCircle">
+ <Login slot="icon"
+ :size="16"
+ decorative />
+ {{ t('contacts', 'Request to join') }}
+ </button>
</section>
<section v-if="showDescription" class="circle-details-section">
@@ -118,6 +129,7 @@ import AppContentDetails from '@nextcloud/vue/dist/Components/AppContentDetails'
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import RichContenteditable from '@nextcloud/vue/dist/Components/RichContenteditable'
+import Login from 'vue-material-design-icons/Login'
import Logout from 'vue-material-design-icons/Logout'
import { CircleEdit, editCircle } from '../services/circles.ts'
@@ -135,6 +147,7 @@ export default {
CircleConfigs,
ContentHeading,
DetailsHeader,
+ Login,
Logout,
RichContenteditable,
},
@@ -223,6 +236,13 @@ export default {
margin-top: 24px;
}
+ &__actions {
+ display: flex;
+ a, button {
+ margin-right: 8px;
+ }
+ }
+
&__description {
max-width: 800px;
}
diff --git a/src/components/MemberList.vue b/src/components/MemberList.vue
index e8a1a6d7..5c8e31ff 100644
--- a/src/components/MemberList.vue
+++ b/src/components/MemberList.vue
@@ -21,14 +21,16 @@
-->
<template>
- <AppContentList v-if="!hasMembers && loading">
- <EmptyContent icon="icon-loading">
+ <AppContentList v-if="!hasMembers" class="members-list">
+ <EmptyContent v-if="loading" icon="icon-loading">
{{ t('contacts', 'Loading members list …') }}
</EmptyContent>
- </AppContentList>
- <AppContentList v-else-if="!hasMembers">
- <EmptyContent icon="icon-contacts">
+ <EmptyContent v-else-if="!circle.isMember" icon="icon-contacts">
+ {{ t('contacts', 'The list of members is only visible to members of this circle') }}
+ </EmptyContent>
+
+ <EmptyContent v-else icon="icon-contacts">
{{ t('contacts', 'There is no member in this circle') }}
</EmptyContent>
</AppContentList>
@@ -302,5 +304,9 @@ export default {
width: 100%;
}
}
+
+ &::v-deep .empty-content {
+ margin: auto;
+ }
}
</style>
diff --git a/src/components/MembersList/MembersListItem.vue b/src/components/MembersList/MembersListItem.vue
index 47795d00..f4dd220e 100644
--- a/src/components/MembersList/MembersListItem.vue
+++ b/src/components/MembersList/MembersListItem.vue
@@ -34,12 +34,28 @@
:title="source.displayName"
:user="source.userId"
class="members-list__item">
- <Actions @close="onMenuClose">
- <template v-if="loading">
- <ActionText icon="icon-loading-small">
- {{ t('contacts', 'Loading …') }}
- </ActionText>
- </template>
+ <!-- Accept invite -->
+ <template v-if="!loading && isPendingApproval && circle.canManageMembers">
+ <Actions>
+ <ActionButton
+ icon="icon-checkmark"
+ @click="acceptMember">
+ {{ t('contacts', 'Accept membership request') }}
+ </ActionButton>
+ </Actions>
+ <Actions>
+ <ActionButton
+ icon="icon-close"
+ @click="deleteMember">
+ {{ t('contacts', 'Reject membership request') }}
+ </ActionButton>
+ </Actions>
+ </template>
+
+ <Actions v-else @close="onMenuClose">
+ <ActionText v-if="loading" icon="icon-loading-small">
+ {{ t('contacts', 'Loading …') }}
+ </ActionText>
<!-- Normal menu -->
<template v-else>
@@ -78,7 +94,7 @@
</template>
<script>
-import { CIRCLES_MEMBER_LEVELS, MemberLevels } from '../../models/constants.ts'
+import { CIRCLES_MEMBER_LEVELS, MemberLevels, MemberStatus } from '../../models/constants.ts'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ListItemIcon from '@nextcloud/vue/dist/Components/ListItemIcon'
@@ -134,6 +150,10 @@ export default {
* @returns {string}
*/
levelName() {
+ if (this.source.level === MemberLevels.NONE) {
+ return t('contacts', 'Pending')
+ }
+
return CIRCLES_MEMBER_LEVELS[this.source.level]
|| CIRCLES_MEMBER_LEVELS[MemberLevels.MEMBER]
},
@@ -188,6 +208,15 @@ export default {
},
/**
+ * Is the current member pending moderator approval?
+ * @returns {boolean}
+ */
+ isPendingApproval() {
+ return this.source?.level === MemberLevels.NONE
+ && this.source?.status === MemberStatus.REQUESTING
+ },
+
+ /**
* Can the current user change the level of others?
* @returns {boolean}
*/
@@ -195,7 +224,8 @@ export default {
// we can change if the member is at the same
// or lower level as the current user
// BUT not an owner as there can/must always be one
- return this.availableLevelsChange.length > 0
+ return this.source.level > MemberLevels.NONE
+ && this.availableLevelsChange.length > 0
&& this.currentUserLevel >= this.source.level
&& this.circle.canManageMembers
&& !(this.circle.isOwner && this.isCurrentUser)
@@ -206,7 +236,7 @@ export default {
* @returns {boolean}
*/
canDelete() {
- return this.currentUserLevel > MemberLevels.MEMBER
+ return this.circle.canManageMembers
&& this.source.level <= this.currentUserLevel
&& !this.isCurrentUser
},
@@ -278,6 +308,22 @@ export default {
}
},
+ async acceptMember() {
+ this.loading = true
+
+ try {
+ await await this.$store.dispatch('acceptCircleMember', {
+ circleId: this.circle.id,
+ memberId: this.source.id,
+ })
+ } catch (error) {
+ console.error('Could not accept member join request', this.source, error)
+ showError(t('contacts', 'Could not accept member join request'))
+ } finally {
+ this.loading = false
+ }
+ },
+
/**
* Reset menu on close
*/
@@ -295,12 +341,12 @@ export default {
order: 1;
padding-top: 22px;
padding-left: 8px;
+ user-select: none;
white-space: nowrap;
text-overflow: ellipsis;
+ pointer-events: none;
color: var(--color-primary-element);
line-height: 22px;
- user-select: none;
- pointer-events: none;
}
.members-list__item {
@@ -312,4 +358,5 @@ export default {
background-color: var(--color-background-hover);
}
}
+
</style>
diff --git a/src/mixins/CircleActionsMixin.js b/src/mixins/CircleActionsMixin.js
index 00f0c299..d79b6511 100644
--- a/src/mixins/CircleActionsMixin.js
+++ b/src/mixins/CircleActionsMixin.js
@@ -25,6 +25,7 @@ import { showError } from '@nextcloud/dialogs'
import { joinCircle } from '../services/circles.ts'
import Circle from '../models/circle.ts'
import CopyToClipboardMixin from './CopyToClipboardMixin'
+import Member from '../models/member.ts'
export default {
@@ -40,6 +41,7 @@ export default {
data() {
return {
loadingAction: false,
+ loadingJoin: false,
}
},
@@ -92,6 +94,9 @@ export default {
member,
leave: true,
})
+
+ // Reset initiator
+ this.circle.initiator = null
} catch (error) {
console.error('Could not leave the circle', member, error)
showError(t('contacts', 'Could not leave the circle {displayName}', this.circle))
@@ -102,13 +107,21 @@ export default {
},
async joinCircle() {
- this.loadingAction = true
+ this.loadingJoin = true
try {
- await joinCircle(this.circle.id)
+ const initiator = await joinCircle(this.circle.id)
+ const member = new Member(initiator, this.circle)
+
+ // Update initiator with newest membership values
+ this.circle.initiator = member
+
+ // Append new member
+ member.circle.addMember(member)
} catch (error) {
showError(t('contacts', 'Unable to join the circle'))
+ console.error('Unable to join the circle', error)
} finally {
- this.loadingAction = false
+ this.loadingJoin = false
}
},
diff --git a/src/models/circle.d.ts b/src/models/circle.d.ts
index badc2ced..25dc0536 100644
--- a/src/models/circle.d.ts
+++ b/src/models/circle.d.ts
@@ -65,8 +65,14 @@ export default class Circle {
/**
* Circle ini_initiator the current
* user info for this circle
+ * null if not a member
*/
- get initiator(): Member;
+ get initiator(): Member | null;
+ /**
+ * Set new circle initiator
+ * null if not a member
+ */
+ set initiator(initiator: Member | null);
/**
* Circle ownership
*/
@@ -123,7 +129,11 @@ export default class Circle {
/**
* Is the initiator a member of this circle?
*/
- get isMember(): boolean;
+ get isMember(): boolean | 0 | undefined;
+ /**
+ * Is the initiator a pending member of this circle?
+ */
+ get isPendingMember(): boolean;
/**
* Can the initiator delete this circle?
*/
@@ -131,11 +141,11 @@ export default class Circle {
/**
* Can the initiator leave this circle?
*/
- get canLeave(): boolean;
+ get canLeave(): boolean | 0 | undefined;
/**
* Can the initiator add/remove members to this circle?
*/
- get canManageMembers(): boolean;
+ get canManageMembers(): boolean | 0 | undefined;
/**
* Vue router param
*/
diff --git a/src/models/circle.ts b/src/models/circle.ts
index 38e0ab0d..de08d191 100644
--- a/src/models/circle.ts
+++ b/src/models/circle.ts
@@ -23,7 +23,7 @@
import Vue from 'vue'
import Member from './member'
-import { CircleConfig, CircleConfigs, MemberLevels } from './constants'
+import { CircleConfigs, MemberLevels } from './constants'
type MemberList = Record<string, Member>
@@ -116,12 +116,24 @@ export default class Circle {
/**
* Circle ini_initiator the current
* user info for this circle
+ * null if not a member
*/
- get initiator(): Member {
+ get initiator(): Member|null {
return this._initiator
}
/**
+ * Set new circle initiator
+ * null if not a member
+ */
+ set initiator(initiator: Member|null) {
+ if (initiator && initiator.constructor.name !== Member.name) {
+ throw new Error('Initiator must be a Member type')
+ }
+ Vue.set(this, '_initiator', initiator)
+ }
+
+ /**
* Circle ownership
*/
get owner(): Member {
@@ -135,7 +147,7 @@ export default class Circle {
if (owner.constructor.name !== Member.name) {
throw new Error('Owner must be a Member type')
}
- this._owner = owner
+ Vue.set(this, '_owner', owner)
}
/**
@@ -162,7 +174,7 @@ export default class Circle {
const singleId = member.singleId
if (this._members[singleId]) {
- console.warn('Ignoring duplicate member', member)
+ console.warn('Replacing existing member data', member)
}
Vue.set(this._members, singleId, member)
}
@@ -243,7 +255,15 @@ export default class Circle {
* Is the initiator a member of this circle?
*/
get isMember() {
- return this.initiator?.level > MemberLevels.NONE
+ return this.initiator?.level
+ && this.initiator?.level > MemberLevels.NONE
+ }
+
+ /**
+ * Is the initiator a pending member of this circle?
+ */
+ get isPendingMember() {
+ return this.initiator?.level === MemberLevels.NONE
}
/**
@@ -264,7 +284,8 @@ export default class Circle {
* Can the initiator add/remove members to this circle?
*/
get canManageMembers() {
- return this.initiator?.level >= MemberLevels.MODERATOR
+ return this.initiator?.level
+ && this.initiator?.level >= MemberLevels.MODERATOR
}
// PARAMS ---------------------------------------------
diff --git a/src/models/constants.d.ts b/src/models/constants.d.ts
index b2aaa26c..172623bb 100644
--- a/src/models/constants.d.ts
+++ b/src/models/constants.d.ts
@@ -19,13 +19,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
+export declare type DefaultGroup = string;
export declare type CircleConfig = number;
export declare type MemberLevel = number;
export declare type MemberType = number;
export declare const LIST_SIZE = 60;
-export declare const GROUP_ALL_CONTACTS: string;
-export declare const GROUP_NO_GROUP_CONTACTS: string;
-export declare const GROUP_RECENTLY_CONTACTED: string;
+export declare const GROUP_ALL_CONTACTS: DefaultGroup;
+export declare const GROUP_NO_GROUP_CONTACTS: DefaultGroup;
+export declare const GROUP_RECENTLY_CONTACTED: DefaultGroup;
export declare const ROUTE_CIRCLE = "circle";
export declare const ELLIPSIS_COUNT = 5;
export declare const CIRCLE_DESC: string;
@@ -76,3 +77,8 @@ export declare enum CircleConfigs {
CIRCLE_INVITE,
FEDERATED
}
+export declare enum MemberStatus {
+ INVITED = "Invited",
+ MEMBER = "Member",
+ REQUESTING = "Requesting"
+}
diff --git a/src/models/constants.ts b/src/models/constants.ts
index d8356e17..67b0e7ee 100644
--- a/src/models/constants.ts
+++ b/src/models/constants.ts
@@ -28,6 +28,7 @@ interface OC extends Nextcloud.Common.OC {
}
declare const OC: OC
+export type DefaultGroup = string
export type CircleConfig = number
export type MemberLevel = number
export type MemberType = number
@@ -35,10 +36,10 @@ export type MemberType = number
// Global sizes
export const LIST_SIZE = 60
-// Dynamic groups
-export const GROUP_ALL_CONTACTS = t('contacts', 'All contacts')
-export const GROUP_NO_GROUP_CONTACTS = t('contacts', 'Not grouped')
-export const GROUP_RECENTLY_CONTACTED = t('contactsinteraction', 'Recently contacted')
+// Dynamic default groups
+export const GROUP_ALL_CONTACTS: DefaultGroup = t('contacts', 'All contacts')
+export const GROUP_NO_GROUP_CONTACTS: DefaultGroup = t('contacts', 'Not grouped')
+export const GROUP_RECENTLY_CONTACTED: DefaultGroup = t('contactsinteraction', 'Recently contacted')
// Circle route, see vue-router conf
export const ROUTE_CIRCLE = 'circle'
@@ -78,6 +79,7 @@ const CIRCLE_CONFIG_ROOT: CircleConfig = 4096 // Circle cannot be inside anot
const CIRCLE_CONFIG_CIRCLE_INVITE: CircleConfig = 8192 // Circle must confirm when invited in another circle
const CIRCLE_CONFIG_FEDERATED: CircleConfig = 16384 // Federated
+// Existing members types
export const CIRCLES_MEMBER_TYPES = {
[MEMBER_TYPE_CIRCLE]: t('circles', 'Circle'),
[MEMBER_TYPE_USER]: t('circles', 'User'),
@@ -86,14 +88,16 @@ export const CIRCLES_MEMBER_TYPES = {
[MEMBER_TYPE_CONTACT]: t('circles', 'Contact'),
}
+// Available circles promote/demote levels
export const CIRCLES_MEMBER_LEVELS = {
- // [MEMBER_LEVEL_NONE]: t('circles', 'None'),
+ // [MEMBER_LEVEL_NONE]: t('circles', 'Pending'),
[MEMBER_LEVEL_MEMBER]: t('circles', 'Member'),
[MEMBER_LEVEL_MODERATOR]: t('circles', 'Moderator'),
[MEMBER_LEVEL_ADMIN]: t('circles', 'Admin'),
[MEMBER_LEVEL_OWNER]: t('circles', 'Owner'),
}
+// Available circle configs in the circle details view
export const PUBLIC_CIRCLE_CONFIG = {
[t('contacts', 'Invites')]: {
[CIRCLE_CONFIG_OPEN]: t('contacts', 'Anyone can request membership'),
@@ -205,3 +209,9 @@ export enum CircleConfigs {
CIRCLE_INVITE = CIRCLE_CONFIG_CIRCLE_INVITE,
FEDERATED = CIRCLE_CONFIG_FEDERATED,
}
+
+export enum MemberStatus {
+ INVITED = 'Invited',
+ MEMBER = 'Member',
+ REQUESTING = 'Requesting',
+}
diff --git a/src/models/member.d.ts b/src/models/member.d.ts
index cc929ca0..988bb0d9 100644
--- a/src/models/member.d.ts
+++ b/src/models/member.d.ts
@@ -66,6 +66,11 @@ export default class Member {
*/
get level(): MemberLevel;
/**
+ * Member request status
+ *
+ */
+ get status(): string;
+ /**
* Set member level
*/
set level(level: MemberLevel);
diff --git a/src/models/member.ts b/src/models/member.ts
index ac28975f..0e2adc5c 100644
--- a/src/models/member.ts
+++ b/src/models/member.ts
@@ -117,6 +117,14 @@ export default class Member {
}
/**
+ * Member request status
+ *
+ */
+ get status(): string {
+ return this._data.status
+ }
+
+ /**
* Set member level
*/
set level(level: MemberLevel) {
diff --git a/src/services/circles.d.ts b/src/services/circles.d.ts
index a19351db..e130d160 100644
--- a/src/services/circles.d.ts
+++ b/src/services/circles.d.ts
@@ -120,4 +120,12 @@ export declare const deleteMember: (circleId: string, memberId: string) => Promi
* @returns {Array}
*/
export declare const changeMemberLevel: (circleId: string, memberId: string, level: MemberLevel) => Promise<unknown[]>;
+/**
+ * Accept a circle member request
+ *
+ * @param {string} circleId the circle id
+ * @param {string} memberId the member id
+ * @returns {Array}
+ */
+export declare const acceptMember: (circleId: string, memberId: string) => Promise<any>;
export {};
diff --git a/src/services/circles.ts b/src/services/circles.ts
index d43c88ce..5d93bc7a 100644
--- a/src/services/circles.ts
+++ b/src/services/circles.ts
@@ -182,3 +182,15 @@ export const changeMemberLevel = async function(circleId: string, memberId: stri
})
return Object.values(response.data.ocs.data)
}
+
+/**
+ * Accept a circle member request
+ *
+ * @param {string} circleId the circle id
+ * @param {string} memberId the member id
+ * @returns {Array}
+ */
+export const acceptMember = async function(circleId: string, memberId: string) {
+ const response = await axios.put(generateOcsUrl('apps/circles/circles/{circleId}/members/{memberId}', { circleId, memberId }))
+ return response.data.ocs.data
+}
diff --git a/src/store/circles.js b/src/store/circles.js
index d424c15d..e542816e 100644
--- a/src/store/circles.js
+++ b/src/store/circles.js
@@ -23,7 +23,7 @@
import { showError } from '@nextcloud/dialogs'
import Vue from 'vue'
-import { createCircle, deleteCircle, deleteMember, getCircleMembers, getCircle, getCircles, leaveCircle, addMembers } from '../services/circles.ts'
+import { acceptMember, createCircle, deleteCircle, deleteMember, getCircleMembers, getCircle, getCircles, leaveCircle, addMembers } from '../services/circles.ts'
import Member from '../models/member.ts'
import Circle from '../models/circle.ts'
import logger from '../services/logger'
@@ -82,7 +82,7 @@ const mutations = {
*/
addMemberToCircle(state, { circleId, member }) {
const circle = state.circles[circleId]
- circle.addmember(member)
+ circle.addMember(member)
},
/**
@@ -256,6 +256,23 @@ const actions = {
logger.debug('Deleted member', { circleId, memberId })
},
+ /**
+ * Accept a circle member request
+ *
+ * @param {Object} context the store mutations Current context
+ * @param {Object} data destructuring object
+ * @param {string} data.circleId the circle id
+ * @param {string} data.memberId the member id
+ */
+ async acceptCircleMember(context, { circleId, memberId }) {
+ const circle = context.getters.getCircle(circleId)
+
+ const result = await acceptMember(circleId, memberId)
+ const member = new Member(result, circle)
+
+ await context.commit('addMemberToCircle', { circleId, member })
+ },
+
}
export default { state, mutations, getters, actions }