2020-08-27 11:53:24 +02:00
< template >
< div v-if = "group" class="section" >
< nav class = "breadcrumb" aria -label = " breadcrumbs " >
< ul >
< li >
2020-11-30 10:24:11 +01:00
< router-link : to = "{ name: RouteName.ADMIN }" > { {
$t ( "Admin" )
} } < / router-link >
2020-08-27 11:53:24 +02:00
< / li >
< li >
< router-link
: to = " {
name : RouteName . ADMIN _GROUPS ,
} "
> { { $t ( "Groups" ) } } < / r o u t e r - l i n k
>
< / li >
< li class = "is-active" >
< router-link
: to = " {
name : RouteName . PROFILES ,
params : { id : group . id } ,
} "
2021-05-25 16:22:01 +02:00
> { { group . name || usernameWithDomain ( group ) } } < / r o u t e r - l i n k
2020-08-27 11:53:24 +02:00
>
< / li >
< / ul >
< / nav >
< div class = "actor-card" >
2021-09-30 09:25:44 +02:00
< p v-if = "group.suspended" >
< actor-card
: actor = "group"
: full = "true"
: popover = "false"
: limit = "false"
/ >
< / p >
2020-08-27 11:53:24 +02:00
< router-link
2021-09-30 09:25:44 +02:00
v - else
2020-11-30 10:24:11 +01:00
: to = " {
name : RouteName . GROUP ,
params : { preferredUsername : usernameWithDomain ( group ) } ,
} "
2020-08-27 11:53:24 +02:00
>
2020-11-30 10:24:11 +01:00
< actor-card
: actor = "group"
: full = "true"
: popover = "false"
: limit = "false"
/ >
2020-08-27 11:53:24 +02:00
< / router-link >
< / div >
< table v-if = "metadata.length > 0" class="table is-fullwidth" >
< tbody >
< tr v-for ="{ key, value, link } in metadata" :key ="key" >
< td > { { key } } < / td >
< td v-if = "link" >
< router-link :to = "link" >
{ { value } }
< / router-link >
< / td >
< td v-else > {{ value }} < / td >
< / tr >
< / tbody >
< / table >
< div class = "buttons" >
2020-11-30 10:24:11 +01:00
< b-button
@ click = "confirmSuspendProfile"
v - if = "!group.suspended"
type = "is-primary"
> { { $t ( "Suspend" ) } } < / b - b u t t o n
>
< b-button
@ click = "unsuspendProfile"
v - if = "group.suspended"
type = "is-primary"
> { { $t ( "Unsuspend" ) } } < / b - b u t t o n
>
< b-button
@ click = "refreshProfile"
v - if = "group.domain"
type = "is-primary"
outlined
> { { $t ( "Refresh profile" ) } } < / b - b u t t o n
>
2020-08-27 11:53:24 +02:00
< / div >
< section >
< h2 class = "subtitle" >
{ {
$tc ( "{number} members" , group . members . total , {
number : group . members . total ,
} )
} }
< / h2 >
< b-table
: data = "group.members.elements"
: loading = "$apollo.queries.group.loading"
paginated
backend - pagination
2021-05-12 18:10:07 +02:00
: current - page . sync = "membersPage"
: aria - next - label = "$t('Next page')"
: aria - previous - label = "$t('Previous page')"
: aria - page - label = "$t('Page')"
: aria - current - label = "$t('Current page')"
2020-08-27 11:53:24 +02:00
: total = "group.members.total"
: per - page = "EVENTS_PER_PAGE"
@ page - change = "onMembersPageChange"
>
2020-11-30 10:24:11 +01:00
< b-table-column
field = "actor.preferredUsername"
: label = "$t('Member')"
v - slot = "props"
>
2020-08-27 11:53:24 +02:00
< article class = "media" >
2020-11-30 10:24:11 +01:00
< figure
class = "media-left image is-48x48"
v - if = "props.row.actor.avatar"
>
< img
class = "is-rounded"
: src = "props.row.actor.avatar.url"
alt = ""
/ >
2020-08-27 11:53:24 +02:00
< / figure >
2020-11-30 10:24:11 +01:00
< b-icon
class = "media-left"
v - else
size = "is-large"
icon = "account-circle"
/ >
2020-08-27 11:53:24 +02:00
< div class = "media-content" >
< div class = "content" >
2020-11-30 10:24:11 +01:00
< span v-if = "props.row.actor.name" > {{
props . row . actor . name
} } < / s p a n
2021-05-12 18:10:07 +02:00
> < span v-else > @ {{ usernameWithDomain ( props.row.actor ) }} < / span
2020-08-27 11:53:24 +02:00
> < br / >
2021-05-12 18:10:07 +02:00
< span
v - if = "props.row.actor.name"
class = "is-size-7 has-text-grey"
2020-08-27 11:53:24 +02:00
> @ { { usernameWithDomain ( props . row . actor ) } } < / s p a n
>
< / div >
< / div >
< / article >
< / b-table-column >
< b-table-column field = "role" :label = "$t('Role')" v-slot = "props" >
2020-11-30 10:24:11 +01:00
< b-tag
type = "is-primary"
v - if = "props.row.role === MemberRole.ADMINISTRATOR"
>
2020-08-27 11:53:24 +02:00
{ { $t ( "Administrator" ) } }
< / b-tag >
2020-11-30 10:24:11 +01:00
< b-tag
type = "is-primary"
v - else - if = "props.row.role === MemberRole.MODERATOR"
>
2020-08-27 11:53:24 +02:00
{ { $t ( "Moderator" ) } }
< / b-tag >
< b-tag v -else -if = " props.row.role = = = MemberRole.MEMBER " >
{ { $t ( "Member" ) } }
< / b-tag >
2020-11-30 10:24:11 +01:00
< b-tag
type = "is-warning"
v - else - if = "props.row.role === MemberRole.NOT_APPROVED"
>
2020-08-27 11:53:24 +02:00
{ { $t ( "Not approved" ) } }
< / b-tag >
2020-11-30 10:24:11 +01:00
< b-tag
type = "is-danger"
v - else - if = "props.row.role === MemberRole.REJECTED"
>
2020-08-27 11:53:24 +02:00
{ { $t ( "Rejected" ) } }
< / b-tag >
2020-11-30 10:24:11 +01:00
< b-tag
type = "is-danger"
v - else - if = "props.row.role === MemberRole.INVITED"
>
2020-08-27 11:53:24 +02:00
{ { $t ( "Invited" ) } }
< / b-tag >
< / b-table-column >
< b-table-column field = "insertedAt" :label = "$t('Date')" v-slot = "props" >
< span class = "has-text-centered" >
{ { props . row . insertedAt | formatDateString } } < br / > { {
props . row . insertedAt | formatTimeString
} }
< / span >
< / b-table-column >
< template slot = "empty" >
2021-05-12 18:10:07 +02:00
< empty-content icon = "account-group" :inline = "true" >
{ { $t ( "No members found" ) } }
< / empty-content >
2020-08-27 11:53:24 +02:00
< / template >
< / b-table >
< / section >
< section >
< h2 class = "subtitle" >
{ {
$tc ( "{number} organized events" , group . organizedEvents . total , {
number : group . organizedEvents . total ,
} )
} }
< / h2 >
< b-table
: data = "group.organizedEvents.elements"
: loading = "$apollo.queries.group.loading"
paginated
backend - pagination
2021-05-12 18:10:07 +02:00
: current - page . sync = "organizedEventsPage"
: aria - next - label = "$t('Next page')"
: aria - previous - label = "$t('Previous page')"
: aria - page - label = "$t('Page')"
: aria - current - label = "$t('Current page')"
2020-08-27 11:53:24 +02:00
: total = "group.organizedEvents.total"
: per - page = "EVENTS_PER_PAGE"
@ page - change = "onOrganizedEventsPageChange"
>
< b-table-column field = "title" :label = "$t('Title')" v-slot = "props" >
2020-11-30 10:24:11 +01:00
< router-link
: to = "{ name: RouteName.EVENT, params: { uuid: props.row.uuid } }"
>
2020-08-27 11:53:24 +02:00
{ { props . row . title } }
2020-12-01 09:55:24 +01:00
< b-tag type = "is-info" v-if = "props.row.draft" > {{
$t ( "Draft" )
} } < / b-tag >
2020-08-27 11:53:24 +02:00
< / router-link >
< / b-table-column >
2020-11-30 10:24:11 +01:00
< b-table-column
field = "beginsOn"
: label = "$t('Begins on')"
v - slot = "props"
>
2020-08-27 11:53:24 +02:00
{ { props . row . beginsOn | formatDateTimeString } }
< / b-table-column >
< template slot = "empty" >
2021-05-12 18:10:07 +02:00
< empty-content icon = "account-group" :inline = "true" >
{ { $t ( "No organized events found" ) } }
< / empty-content >
2020-08-27 11:53:24 +02:00
< / template >
< / b-table >
< / section >
< section >
< h2 class = "subtitle" >
{ {
$tc ( "{number} posts" , group . posts . total , {
number : group . posts . total ,
} )
} }
< / h2 >
< b-table
: data = "group.posts.elements"
: loading = "$apollo.queries.group.loading"
paginated
backend - pagination
2021-05-12 18:10:07 +02:00
: current - page . sync = "postsPage"
: aria - next - label = "$t('Next page')"
: aria - previous - label = "$t('Previous page')"
: aria - page - label = "$t('Page')"
: aria - current - label = "$t('Current page')"
2020-08-27 11:53:24 +02:00
: total = "group.posts.total"
2021-05-12 18:10:07 +02:00
: per - page = "POSTS_PER_PAGE"
2020-08-27 11:53:24 +02:00
@ page - change = "onPostsPageChange"
>
< b-table-column field = "title" :label = "$t('Title')" v-slot = "props" >
2020-11-30 10:24:11 +01:00
< router-link
: to = "{ name: RouteName.POST, params: { slug: props.row.slug } }"
>
2020-08-27 11:53:24 +02:00
{ { props . row . title } }
2020-12-01 09:55:24 +01:00
< b-tag type = "is-info" v-if = "props.row.draft" > {{
$t ( "Draft" )
} } < / b-tag >
2020-08-27 11:53:24 +02:00
< / router-link >
< / b-table-column >
2020-11-30 10:24:11 +01:00
< b-table-column
field = "publishAt"
: label = "$t('Publication date')"
v - slot = "props"
>
2020-08-27 11:53:24 +02:00
{ { props . row . publishAt | formatDateTimeString } }
< / b-table-column >
< template slot = "empty" >
2021-05-12 18:10:07 +02:00
< empty-content icon = "bullhorn" :inline = "true" >
{ { $t ( "No posts found" ) } }
< / empty-content >
2020-08-27 11:53:24 +02:00
< / template >
< / b-table >
< / section >
< / div >
2021-12-13 17:33:10 +01:00
< empty-content v -else -if = " ! $ apollo.loading " icon = "account-multiple" >
{ { $t ( "This group was not found" ) } }
< template # desc >
< b-button
type = "is-text"
tag = "router-link"
: to = "{ name: RouteName.ADMIN_GROUPS }"
> { { $t ( "Back to group list" ) } } < / b - b u t t o n
>
< / template >
< / empty-content >
2020-08-27 11:53:24 +02:00
< / template >
< script lang = "ts" >
import { Component , Vue , Prop } from "vue-property-decorator" ;
2020-08-31 12:40:30 +02:00
import { GET _GROUP , REFRESH _PROFILE } from "@/graphql/group" ;
2020-11-23 16:58:50 +01:00
import { formatBytes } from "@/utils/datetime" ;
2020-11-27 19:27:44 +01:00
import { MemberRole } from "@/types/enums" ;
2020-08-27 11:53:24 +02:00
import { SUSPEND _PROFILE , UNSUSPEND _PROFILE } from "../../graphql/actor" ;
2020-11-27 19:27:44 +01:00
import { IGroup } from "../../types/actor" ;
2020-08-27 11:53:24 +02:00
import { usernameWithDomain , IActor } from "../../types/actor/actor.model" ;
import RouteName from "../../router/name" ;
import ActorCard from "../../components/Account/ActorCard.vue" ;
2021-05-12 18:10:07 +02:00
import EmptyContent from "../../components/Utils/EmptyContent.vue" ;
2021-06-11 14:21:27 +02:00
import { ApolloCache , FetchResult } from "@apollo/client/core" ;
2021-05-12 18:10:07 +02:00
import VueRouter from "vue-router" ;
const { isNavigationFailure , NavigationFailureType } = VueRouter ;
2020-08-27 11:53:24 +02:00
2021-05-25 16:22:01 +02:00
const EVENTS _PER _PAGE = 10 ;
const POSTS _PER _PAGE = 10 ;
const MEMBERS _PER _PAGE = 10 ;
2020-08-27 11:53:24 +02:00
@ Component ( {
apollo : {
group : {
query : GET _GROUP ,
fetchPolicy : "cache-and-network" ,
variables ( ) {
return {
id : this . id ,
2021-05-12 18:10:07 +02:00
organizedEventsPage : this . organizedEventsPage ,
2020-08-27 11:53:24 +02:00
organizedEventsLimit : EVENTS _PER _PAGE ,
2021-05-12 18:10:07 +02:00
postsPage : this . postsPage ,
postsLimit : POSTS _PER _PAGE ,
2020-08-27 11:53:24 +02:00
} ;
} ,
skip ( ) {
return ! this . id ;
} ,
update : ( data ) => data . getGroup ,
} ,
} ,
components : {
ActorCard ,
2021-05-12 18:10:07 +02:00
EmptyContent ,
2020-08-27 11:53:24 +02:00
} ,
2021-05-25 16:21:29 +02:00
metaInfo ( ) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const { group } = this ;
return {
title : group ? group . name || usernameWithDomain ( group ) : "" ,
} ;
} ,
2020-08-27 11:53:24 +02:00
} )
export default class AdminGroupProfile extends Vue {
@ Prop ( { required : true } ) id ! : string ;
group ! : IGroup ;
usernameWithDomain = usernameWithDomain ;
RouteName = RouteName ;
EVENTS _PER _PAGE = EVENTS _PER _PAGE ;
2021-05-12 18:10:07 +02:00
POSTS _PER _PAGE = POSTS _PER _PAGE ;
2020-08-27 11:53:24 +02:00
2021-05-12 18:10:07 +02:00
MEMBERS _PER _PAGE = MEMBERS _PER _PAGE ;
2020-08-27 11:53:24 +02:00
MemberRole = MemberRole ;
2021-05-12 18:10:07 +02:00
get organizedEventsPage ( ) : number {
return parseInt (
( this . $route . query . organizedEventsPage as string ) || "1" ,
10
) ;
}
set organizedEventsPage ( page : number ) {
this . pushRouter ( { organizedEventsPage : page . toString ( ) } ) ;
}
get membersPage ( ) : number {
return parseInt ( ( this . $route . query . membersPage as string ) || "1" , 10 ) ;
}
set membersPage ( page : number ) {
this . pushRouter ( { membersPage : page . toString ( ) } ) ;
}
get postsPage ( ) : number {
return parseInt ( ( this . $route . query . postsPage as string ) || "1" , 10 ) ;
}
set postsPage ( page : number ) {
this . pushRouter ( { postsPage : page . toString ( ) } ) ;
}
2020-09-29 09:53:48 +02:00
get metadata ( ) : Array < Record < string , string > > {
2020-08-27 11:53:24 +02:00
if ( ! this . group ) return [ ] ;
2020-09-29 09:53:48 +02:00
const res : Record < string , string > [ ] = [
2020-08-27 11:53:24 +02:00
{
key : this . $t ( "Status" ) as string ,
2020-11-30 10:24:11 +01:00
value : ( this . group . suspended
? this . $t ( "Suspended" )
: this . $t ( "Active" ) ) as string ,
2020-08-27 11:53:24 +02:00
} ,
{
key : this . $t ( "Domain" ) as string ,
2020-11-30 10:24:11 +01:00
value : ( this . group . domain
? this . group . domain
: this . $t ( "Local" ) ) as string ,
2020-08-27 11:53:24 +02:00
} ,
2020-11-23 16:58:50 +01:00
{
key : this . $i18n . t ( "Uploaded media size" ) as string ,
value : formatBytes ( this . group . mediaSize ) ,
} ,
2020-08-27 11:53:24 +02:00
] ;
return res ;
}
2020-09-29 09:53:48 +02:00
confirmSuspendProfile ( ) : void {
2021-05-17 19:01:08 +02:00
const message = (
this . group . domain
? this . $t (
"Are you sure you want to <b>suspend</b> this group? As this group originates from instance {instance}, this will only remove local members and delete the local data, as well as rejecting all the future data." ,
{ instance : this . group . domain }
)
: this . $t (
"Are you sure you want to <b>suspend</b> this group? All members - including remote ones - will be notified and removed from the group, and <b>all of the group data (events, posts, discussions, todos…) will be irretrievably destroyed</b>."
)
) as string ;
2020-08-27 11:53:24 +02:00
this . $buefy . dialog . confirm ( {
title : this . $t ( "Suspend group" ) as string ,
message ,
confirmText : this . $t ( "Suspend group" ) as string ,
cancelText : this . $t ( "Cancel" ) as string ,
type : "is-danger" ,
hasIcon : true ,
onConfirm : ( ) => this . suspendProfile ( ) ,
} ) ;
}
2020-09-29 09:53:48 +02:00
async suspendProfile ( ) : Promise < void > {
2021-05-12 18:10:07 +02:00
try {
await this . $apollo . mutate < { suspendProfile : { id : string } } > ( {
mutation : SUSPEND _PROFILE ,
variables : {
id : this . id ,
} ,
2021-06-11 14:21:27 +02:00
update : (
store : ApolloCache < { suspendProfile : { id : string } } > ,
{ data } : FetchResult
) => {
2021-05-12 18:10:07 +02:00
if ( data == null ) return ;
const profileId = this . id ;
2020-08-27 11:53:24 +02:00
2021-05-12 18:10:07 +02:00
const profileData = store . readQuery < { getGroup : IGroup } > ( {
query : GET _GROUP ,
variables : {
id : profileId ,
2021-06-30 18:04:01 +02:00
organizedEventsPage : this . organizedEventsPage ,
organizedEventsLimit : EVENTS _PER _PAGE ,
postsPage : this . postsPage ,
postsLimit : POSTS _PER _PAGE ,
2021-05-12 18:10:07 +02:00
} ,
} ) ;
2020-08-27 11:53:24 +02:00
2021-05-12 18:10:07 +02:00
if ( ! profileData ) return ;
store . writeQuery ( {
query : GET _GROUP ,
variables : {
id : profileId ,
} ,
data : {
getGroup : {
... profileData . getGroup ,
suspended : true ,
avatar : null ,
name : "" ,
summary : "" ,
} ,
} ,
} ) ;
} ,
} ) ;
} catch ( e ) {
console . error ( e ) ;
this . $notifier . error ( this . $t ( "Error while suspending group" ) as string ) ;
}
2020-08-27 11:53:24 +02:00
}
2020-09-29 09:53:48 +02:00
async unsuspendProfile ( ) : Promise < void > {
2021-05-12 18:10:07 +02:00
try {
const profileID = this . id ;
await this . $apollo . mutate < { unsuspendProfile : { id : string } } > ( {
mutation : UNSUSPEND _PROFILE ,
variables : {
id : this . id ,
2020-08-27 11:53:24 +02:00
} ,
2021-05-12 18:10:07 +02:00
refetchQueries : [
{
query : GET _GROUP ,
variables : {
id : profileID ,
} ,
} ,
] ,
} ) ;
} catch ( e ) {
console . error ( e ) ;
this . $notifier . error ( this . $t ( "Error while suspending group" ) as string ) ;
}
2020-08-27 11:53:24 +02:00
}
2020-09-29 09:53:48 +02:00
async refreshProfile ( ) : Promise < void > {
2021-05-12 18:10:07 +02:00
try {
this . $apollo . mutate < { refreshProfile : IActor } > ( {
mutation : REFRESH _PROFILE ,
variables : {
actorId : this . id ,
} ,
} ) ;
this . $notifier . success (
this . $t ( "Triggered profile refreshment" ) as string
) ;
} catch ( e ) {
console . error ( e ) ;
this . $notifier . error ( this . $t ( "Error while suspending group" ) as string ) ;
}
2020-08-27 11:53:24 +02:00
}
2020-09-29 09:53:48 +02:00
async onOrganizedEventsPageChange ( page : number ) : Promise < void > {
2020-08-27 11:53:24 +02:00
this . organizedEventsPage = page ;
await this . $apollo . queries . group . fetchMore ( {
variables : {
actorId : this . id ,
organizedEventsPage : this . organizedEventsPage ,
organizedEventsLimit : EVENTS _PER _PAGE ,
} ,
} ) ;
}
2020-09-29 09:53:48 +02:00
async onMembersPageChange ( page : number ) : Promise < void > {
2020-08-27 11:53:24 +02:00
this . membersPage = page ;
await this . $apollo . queries . group . fetchMore ( {
variables : {
actorId : this . id ,
memberPage : this . membersPage ,
memberLimit : EVENTS _PER _PAGE ,
} ,
} ) ;
}
2020-09-29 09:53:48 +02:00
async onPostsPageChange ( page : number ) : Promise < void > {
2020-08-27 11:53:24 +02:00
this . postsPage = page ;
await this . $apollo . queries . group . fetchMore ( {
variables : {
actorId : this . id ,
postsPage : this . postsPage ,
2021-05-12 18:10:07 +02:00
postLimit : POSTS _PER _PAGE ,
2020-08-27 11:53:24 +02:00
} ,
} ) ;
}
2021-05-12 18:10:07 +02:00
private async pushRouter ( args : Record < string , string > ) : Promise < void > {
try {
await this . $router . push ( {
name : RouteName . ADMIN _GROUP _PROFILE ,
query : { ... this . $route . query , ... args } ,
} ) ;
} catch ( e ) {
if ( isNavigationFailure ( e , NavigationFailureType . redirected ) ) {
throw Error ( e . toString ( ) ) ;
}
}
}
2020-08-27 11:53:24 +02:00
}
< / script >
< style lang = "scss" scoped >
table ,
section {
margin : 2 rem 0 ;
}
. actor - card {
background : # fff ;
padding : 1.5 rem ;
border - radius : 10 px ;
}
< / style >