summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2021-06-28 13:04:34 +0200
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2021-07-01 10:56:33 +0200
commitee443f249edd2a7f8506b98ade7e58eacfed5def (patch)
tree3c1233c2ae440d785b59bad3863408e97d01efaf
parent8bbc0c2d3bc40122297b19665cf1df3ff3670e1f (diff)
Approve request from contacts UI
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
-rw-r--r--src/components/AppContent/CircleContent.vue2
-rw-r--r--src/components/CircleDetails.vue2
-rw-r--r--src/components/MemberList.vue16
-rw-r--r--src/components/MembersList/MembersListItem.vue69
-rw-r--r--src/mixins/CircleActionsMixin.js5
-rw-r--r--src/models/circle.d.ts8
-rw-r--r--src/models/circle.ts16
-rw-r--r--src/models/constants.d.ts11
-rw-r--r--src/models/constants.ts19
-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
14 files changed, 172 insertions, 30 deletions
diff --git a/src/components/AppContent/CircleContent.vue b/src/components/AppContent/CircleContent.vue
index 1fe478e4..cbe59bcc 100644
--- a/src/components/AppContent/CircleContent.vue
+++ b/src/components/AppContent/CircleContent.vue
@@ -47,7 +47,7 @@
<!-- not a member -->
<template v-if="!circle.isMember">
<!-- Pending request validation -->
- <EmptyContent v-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>
diff --git a/src/components/CircleDetails.vue b/src/components/CircleDetails.vue
index b9346491..20552a86 100644
--- a/src/components/CircleDetails.vue
+++ b/src/components/CircleDetails.vue
@@ -64,7 +64,7 @@
</a>
<!-- Only show the join button if the circle is accepting requests -->
- <button v-if="!circle.isMember && circle.canJoin"
+ <button v-if="!circle.isPendingMember && !circle.isMember && circle.canJoin"
:disabled="loadingJoin"
class="primary"
@click="joinCircle">
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 873db246..60e83765 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 {
@@ -105,9 +106,11 @@ export default {
async joinCircle() {
this.loadingJoin = true
try {
- await joinCircle(this.circle.id)
+ const initiator = await joinCircle(this.circle.id)
+ this.circle.initiator = new Member(initiator)
} catch (error) {
showError(t('contacts', 'Unable to join the circle'))
+ console.error('Unable to join the circle', error)
} finally {
this.loadingJoin = false
}
diff --git a/src/models/circle.d.ts b/src/models/circle.d.ts
index badc2ced..910c4b30 100644
--- a/src/models/circle.d.ts
+++ b/src/models/circle.d.ts
@@ -68,6 +68,10 @@ export default class Circle {
*/
get initiator(): Member;
/**
+ * Set new circle initiator
+ */
+ set initiator(initiator: Member);
+ /**
* Circle ownership
*/
get owner(): Member;
@@ -125,6 +129,10 @@ export default class Circle {
*/
get isMember(): boolean;
/**
+ * Is the initiator a pending member of this circle?
+ */
+ get isPendingMember(): boolean;
+ /**
* Can the initiator delete this circle?
*/
get canDelete(): boolean;
diff --git a/src/models/circle.ts b/src/models/circle.ts
index 38e0ab0d..75346a0b 100644
--- a/src/models/circle.ts
+++ b/src/models/circle.ts
@@ -122,6 +122,13 @@ export default class Circle {
}
/**
+ * Set new circle initiator
+ */
+ set initiator(initiator: Member) {
+ this._initiator = initiator
+ }
+
+ /**
* Circle ownership
*/
get owner(): Member {
@@ -162,7 +169,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)
}
@@ -247,6 +254,13 @@ export default class Circle {
}
/**
+ * Is the initiator a pending member of this circle?
+ */
+ get isPendingMember() {
+ return this.initiator?.level === MemberLevels.NONE
+ }
+
+ /**
* Can the initiator delete this circle?
*/
get canDelete() {
diff --git a/src/models/constants.d.ts b/src/models/constants.d.ts
index b2aaa26c..dcfe6110 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,7 @@ export declare enum CircleConfigs {
CIRCLE_INVITE,
FEDERATED
}
+export declare enum MemberStatus {
+ INVITED = "invited",
+ REQUESTING = "Requesting"
+}
diff --git a/src/models/constants.ts b/src/models/constants.ts
index 9b7e83aa..b7a9b7f5 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', 'Pending'),
+ // [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,8 @@ export enum CircleConfigs {
CIRCLE_INVITE = CIRCLE_CONFIG_CIRCLE_INVITE,
FEDERATED = CIRCLE_CONFIG_FEDERATED,
}
+
+export enum MemberStatus {
+ INVITED = 'invited',
+ 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 }