mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 15:17:25 +01:00
Add UI for managing blockers
This commit is contained in:
Submodule habitica-images updated: 992d838120...aa72332019
51
website/client/src/components/blocker/blocker_form.vue
Normal file
51
website/client/src/components/blocker/blocker_form.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div style="display: contents">
|
||||
<td></td>
|
||||
<td><select class="form-control" v-model="blocker.type">
|
||||
<option value="ipaddress">IP-Address</option>
|
||||
<option value="email">E-Mail</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><select class="form-control" v-model="blocker.area">
|
||||
<option value="full">Full</option>
|
||||
<option value="payments">Payments</option>
|
||||
</select></td>
|
||||
<td><input v-model="blocker.value"></td>
|
||||
<td><input v-model="blocker.reason"></td>
|
||||
<td>
|
||||
<button class="btn btn-primary mr-2" @click="$emit('save', blocker)">
|
||||
<span>Save</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" @click="$emit('cancel')">
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
</td>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BlockerForm',
|
||||
props: {
|
||||
isNew: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
blocker: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
type: '',
|
||||
area: '',
|
||||
value: '',
|
||||
reason: '',
|
||||
}),
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
// Add methods if needed
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div class="row standard-page col-12 d-flex justify-content-center">
|
||||
<div class="blocker-content">
|
||||
<h1>Blockers</h1>
|
||||
<h1>Blockers
|
||||
<button
|
||||
class="btn btn-primary float-right"
|
||||
@click="showCreateForm = true">Create</button></h1>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -10,16 +13,31 @@
|
||||
<th>Area</th>
|
||||
<th>Value</th>
|
||||
<th>Reason</th>
|
||||
<th class="action-column"></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="showCreateForm">
|
||||
<BlockerForm
|
||||
:is-new="true"
|
||||
:blocker="newBlocker"
|
||||
@save="createBlocker"
|
||||
@cancel="showCreateForm = false"
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="blocker in blockers"
|
||||
:key="blocker._id">
|
||||
<td>{{ blocker.created }}</td>
|
||||
<td>{{ blocker.type }}</td>
|
||||
<td>{{ blocker.area }}</td>
|
||||
<BlockerForm
|
||||
v-if="blocker._id === editedBlockerId"
|
||||
:blocker="blocker"
|
||||
@save="saveBlocker(blocker)"
|
||||
@cancel="editedBlockerId = null"
|
||||
/>
|
||||
<template v-else>
|
||||
<td>{{ blocker.createdAt }}</td>
|
||||
<td>{{ getTypeName(blocker.type) }}</td>
|
||||
<td>{{ getAreaName(blocker.area) }}</td>
|
||||
<td>{{ blocker.value }}</td>
|
||||
<td>{{ blocker.reason }}</td>
|
||||
<td>
|
||||
@@ -35,7 +53,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="removeBlocker(blocker._id)"
|
||||
@click="deleteBlocker(blocker._id)"
|
||||
>
|
||||
<span
|
||||
v-once
|
||||
@@ -44,11 +62,11 @@
|
||||
></span>
|
||||
</button>
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -72,11 +90,23 @@ import { mapState } from '@/libs/store';
|
||||
|
||||
import editIcon from '@/assets/svg/edit.svg';
|
||||
import deleteIcon from '@/assets/svg/delete.svg';
|
||||
import BlockerForm from './blocker_form.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BlockerForm,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showCreateForm: false,
|
||||
newBlocker: {
|
||||
type: '',
|
||||
area: '',
|
||||
value: '',
|
||||
reason: '',
|
||||
},
|
||||
blockers: [],
|
||||
editedBlockerId: null,
|
||||
icons: Object.freeze({
|
||||
editIcon,
|
||||
deleteIcon,
|
||||
@@ -95,7 +125,47 @@ export default {
|
||||
methods: {
|
||||
async loadBlockers () {
|
||||
this.blockers = await this.$store.dispatch('blockers:getBlockers');
|
||||
console.log(this.blockers);
|
||||
},
|
||||
editBlocker (id) {
|
||||
this.editedBlockerId = id;
|
||||
},
|
||||
async saveBlocker (blocker) {
|
||||
await this.$store.dispatch('blockers:updateBlocker', { blocker });
|
||||
this.editedBlockerId = null;
|
||||
this.loadBlockers();
|
||||
},
|
||||
async deleteBlocker (blockerId) {
|
||||
if (!window.confirm('Are you sure you want to delete this blocker?')) {
|
||||
return;
|
||||
}
|
||||
await this.$store.dispatch('blockers:deleteBlocker', { blockerId });
|
||||
this.loadBlockers();
|
||||
},
|
||||
async createBlocker (blocker) {
|
||||
await this.$store.dispatch('blockers:createBlocker', { blocker });
|
||||
this.showCreateForm = false;
|
||||
this.loadBlockers();
|
||||
},
|
||||
|
||||
getTypeName (type) {
|
||||
switch (type) {
|
||||
case 'ipaddress':
|
||||
return 'IP Address';
|
||||
case 'email':
|
||||
return 'E-Mail';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
},
|
||||
getAreaName (area) {
|
||||
switch (area) {
|
||||
case 'full':
|
||||
return 'Full';
|
||||
case 'payments':
|
||||
return 'Payments';
|
||||
default:
|
||||
return area;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,6 +2,18 @@ import axios from 'axios';
|
||||
|
||||
export async function getBlockers () {
|
||||
const response = await axios.get('/api/v4/admin/blockers');
|
||||
console.log(response);
|
||||
return response.data.data;
|
||||
}
|
||||
export async function createBlocker (store, payload) {
|
||||
const response = await axios.post('/api/v4/admin/blockers', payload.blocker);
|
||||
return response.data.data;
|
||||
}
|
||||
export async function updateBlocker (store, payload) {
|
||||
const response = await axios.put(`/api/v4/admin/blockers/${payload.blocker._id}`, payload.blocker);
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
export async function deleteBlocker (store, payload) {
|
||||
const response = await axios.delete(`/api/v4/admin/blockers/${payload.blockerId}`);
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import validator from 'validator';
|
||||
import _ from 'lodash';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import { ensurePermission } from '../../middlewares/ensureAccessRight';
|
||||
import { model as User } from '../../models/user';
|
||||
@@ -123,7 +125,7 @@ api.getBlockers = {
|
||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||
async handler (req, res) {
|
||||
const blockers = await Blocker
|
||||
.find()
|
||||
.find({ disabled: false })
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
@@ -131,4 +133,59 @@ api.getBlockers = {
|
||||
},
|
||||
};
|
||||
|
||||
api.createBlocker = {
|
||||
method: 'POST',
|
||||
url: '/admin/blockers',
|
||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||
async handler (req, res) {
|
||||
const id = uuid();
|
||||
const blocker = await Blocker({
|
||||
_id: id,
|
||||
...Blocker.sanitize(req.body),
|
||||
}).save();
|
||||
|
||||
res.respond(200, blocker);
|
||||
},
|
||||
};
|
||||
|
||||
api.updateBlocker = {
|
||||
method: 'PUT',
|
||||
url: '/admin/blockers/:blockerId',
|
||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||
async handler (req, res) {
|
||||
req.checkParams('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID();
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
const blocker = await Blocker.findById(req.params.blockerId).exec();
|
||||
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
|
||||
|
||||
_.merge(blocker, Blocker.sanitize(req.body));
|
||||
const savedBlocker = await blocker.save();
|
||||
|
||||
res.respond(200, savedBlocker);
|
||||
},
|
||||
};
|
||||
|
||||
api.deleteBlocker = {
|
||||
method: 'DELETE',
|
||||
url: '/admin/blockers/:blockerId',
|
||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||
async handler (req, res) {
|
||||
req.checkParams('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID();
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
const blocker = await Blocker.findById(req.params.blockerId).exec();
|
||||
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
|
||||
|
||||
blocker.disabled = true;
|
||||
const savedBlocker = await blocker.save();
|
||||
|
||||
res.respond(200, savedBlocker);
|
||||
},
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
import mongoose from 'mongoose';
|
||||
import EventEmitter from 'events';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import validator from 'validator';
|
||||
import baseModel from '../libs/baseModel';
|
||||
|
||||
export const blockTypes = [
|
||||
@@ -17,12 +15,6 @@ export const blockArea = [
|
||||
];
|
||||
|
||||
export const schema = new mongoose.Schema({
|
||||
id: {
|
||||
$type: String,
|
||||
default: uuid,
|
||||
validate: [v => validator.isUUID(v), 'Invalid uuid for tag.'],
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
$type: Boolean, default: false, // If true, the block is disabled
|
||||
},
|
||||
@@ -44,14 +36,11 @@ export const schema = new mongoose.Schema({
|
||||
}, {
|
||||
strict: true,
|
||||
minimize: false, // So empty objects are returned
|
||||
_id: false, // use id instead of _id
|
||||
typeKey: '$type', // So that we can use fields named `type`
|
||||
});
|
||||
|
||||
schema.plugin(baseModel, {
|
||||
timestamps: true,
|
||||
noSet: ['_id'],
|
||||
_id: false, // use id instead of _id
|
||||
});
|
||||
|
||||
schema.statics.watchBlockers = function watchBlockers (query, options) {
|
||||
|
||||
Reference in New Issue
Block a user