Add admin dashboard, event reporting, moderation report screens, moderation log

Close #156 and #158

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2019-09-09 09:31:08 +02:00
parent 164429964a
commit 27f2597b07
77 changed files with 1682 additions and 201 deletions

View File

@@ -5,7 +5,7 @@
<div class="tag-container" v-if="event.tags">
<b-tag v-for="tag in event.tags.slice(0, 3)" :key="tag.slug" type="is-secondary">{{ tag.title }}</b-tag>
</div>
<img src="https://picsum.photos/g/400/225/?random">
<img src="https://picsum.photos/g/400/225/?random" />
</figure>
</div>
<div class="content">

View File

@@ -1,8 +1,5 @@
<template>
<translate
v-if="!endsOn"
:translate-params="{date: formatDate(beginsOn), time: formatTime(beginsOn)}"
>The %{ date } at %{ time }</translate>
<span v-if="!endsOn">{{ beginsOn | formatDateTimeString }}</span>
<translate
v-else-if="isSameDay()"
:translate-params="{date: formatDate(beginsOn), startTime: formatTime(beginsOn), endTime: formatTime(endsOn)}"
@@ -21,11 +18,13 @@ export default class EventFullDate extends Vue {
@Prop({ required: false }) endsOn!: string;
formatDate(value) {
return value ? new Date(value).toLocaleString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }) : null;
if (!this.$options.filters) return;
return this.$options.filters.formatDateString(value);
}
formatTime(value) {
return value ? new Date(value).toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric' }) : null;
if (!this.$options.filters) return;
return this.$options.filters.formatTimeString(value);
}
isSameDay() {

View File

@@ -44,6 +44,10 @@
<router-link :to="{ name: ActorRouteName.CREATE_GROUP }" v-translate>Create group</router-link>
</span>
<span class="navbar-item" v-if="currentUser.role === ICurrentUserRole.ADMINISTRATOR">
<router-link :to="{ name: AdminRouteName.DASHBOARD }" v-translate>Administration</router-link>
</span>
<a v-translate class="navbar-item" v-on:click="logout()">Log out</a>
</div>
</div>
@@ -71,10 +75,12 @@ import { LOGGED_PERSON } from '@/graphql/actor';
import { IPerson } from '@/types/actor';
import { CONFIG } from '@/graphql/config';
import { IConfig } from '@/types/config.model';
import { ICurrentUser } from '@/types/current-user.model';
import { ICurrentUser, ICurrentUserRole } from '@/types/current-user.model';
import Logo from '@/components/Logo.vue';
import SearchField from '@/components/SearchField.vue';
import { ActorRouteName } from '@/router/actor';
import { AdminRouteName } from '@/router/admin';
import { RouteName } from '@/router';
@Component({
apollo: {
@@ -98,9 +104,11 @@ export default class NavBar extends Vue {
loggedPerson: IPerson | null = null;
config!: IConfig;
currentUser!: ICurrentUser;
ICurrentUserRole = ICurrentUserRole;
showNavbar: boolean = false;
ActorRouteName = ActorRouteName;
AdminRouteName = AdminRouteName;
@Watch('currentUser')
async onCurrentUserChanged() {
@@ -119,7 +127,8 @@ export default class NavBar extends Vue {
async logout() {
await logout(this.$apollo.provider.defaultClient);
return this.$router.push({ path: '/' });
if (this.$route.name === RouteName.HOME) return;
return this.$router.push({ name: RouteName.HOME });
}
}
</script>

View File

@@ -0,0 +1,45 @@
<template>
<div class="card" v-if="report">
<div class="card-content">
<div class="media">
<div class="media-left">
<figure class="image is-48x48" v-if="report.reported.avatar">
<img :src="report.reported.avatar.url" />
</figure>
</div>
<div class="media-content">
<p class="title is-4">{{ report.reported.name }}</p>
<p class="subtitle is-6">@{{ report.reported.preferredUsername }}</p>
</div>
</div>
<div class="content columns">
<div class="column is-one-quarter box">Reported by <img v-if="report.reporter.avatar" class="image" :src="report.reporter.avatar.url" /> @{{ report.reporter.preferredUsername }}</div>
<div class="column box" v-if="report.event">
<img class="image" v-if="report.event.picture" :src="report.event.picture.url" />
<span>{{ report.event.title }}</span>
</div>
<div class="column box" v-if="report.reportContent">{{ report.reportContent }}</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { IReport } from '@/types/report.model';
import { EventRouteName } from '@/router/event';
@Component
export default class ReportCard extends Vue {
@Prop({ required: true }) report!: IReport;
EventRouteName = EventRouteName;
}
</script>
<style lang="scss">
.content img.image {
display: inline;
height: 1.5em;
vertical-align: text-bottom;
}
</style>

View File

@@ -0,0 +1,101 @@
<template>
<div class="modal-card">
<header class="modal-card-head" v-if="title">
<p class="modal-card-title">{{ title }}</p>
</header>
<section
class="modal-card-body is-flex"
:class="{ 'is-titleless': !title }">
<div class="media">
<div
class="media-left">
<b-icon
icon="alert"
type="is-warning"
size="is-large"/>
</div>
<div class="media-content">
<p>The report will be sent to the moderators of your instance.
You can explain why you report this content below.</p>
<div class="control">
<b-input
v-model="content"
type="textarea"
@keyup.enter="confirm"
placeholder="Additional comments"
/>
</div>
<p v-if="outsideDomain">
The content came from another server. Transfer an anonymous copy of the report ?
</p>
<div class="control" v-if="outsideDomain">
<b-switch v-model="forward">Transfer to {{ outsideDomain }}</b-switch>
</div>
</div>
</div>
</section>
<footer class="modal-card-foot">
<button
class="button"
ref="cancelButton"
@click="cancel('button')">
{{ cancelText }}
</button>
<button
class="button is-primary"
ref="confirmButton"
@click="confirm">
{{ confirmText }}
</button>
</footer>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { removeElement } from 'buefy/src/utils/helpers';
@Component({
mounted() {
this.$data.isActive = true;
},
})
export default class ReportModal extends Vue {
@Prop({ type: Function, default: () => {} }) onConfirm;
@Prop({ type: String }) title;
@Prop({ type: String, default: '' }) outsideDomain;
@Prop({ type: String, default: 'Cancel' }) cancelText;
@Prop({ type: String, default: 'Send the report' }) confirmText;
isActive: boolean = false;
content: string = '';
forward: boolean = false;
confirm() {
this.onConfirm(this.content, this.forward);
this.close();
}
/**
* Close the Dialog.
*/
close() {
this.isActive = false;
// Timeout for the animation complete before destroying
setTimeout(() => {
this.$destroy();
removeElement(this.$el);
}, 150);
}
}
</script>
<style lang="scss">
.modal-card .modal-card-foot {
justify-content: flex-end;
}
</style>