feat(backend, frontend): Erweiterung der Falukant-Benutzerverwaltung um Branch- und Lageraktualisierungsfunktionen
- Hinzufügen von Routen und Methoden zur Verwaltung von Niederlassungen und Lagerbeständen im AdminController und AdminService. - Implementierung der Logik zum Abrufen von Niederlassungen und Aktualisieren von Lagerbeständen. - Anpassung der Benutzeroberfläche zur Unterstützung der neuen Funktionen, einschließlich eines Tab-Systems zur Anzeige von Benutzerdaten und Niederlassungen. - Aktualisierung der Übersetzungen für die neuen Funktionen in den Sprachdateien.
This commit is contained in:
@@ -13,6 +13,8 @@ class AdminController {
|
||||
this.searchUser = this.searchUser.bind(this);
|
||||
this.getFalukantUserById = this.getFalukantUserById.bind(this);
|
||||
this.changeFalukantUser = this.changeFalukantUser.bind(this);
|
||||
this.getFalukantUserBranches = this.getFalukantUserBranches.bind(this);
|
||||
this.updateFalukantStock = this.updateFalukantStock.bind(this);
|
||||
this.getRoomTypes = this.getRoomTypes.bind(this);
|
||||
this.getGenderRestrictions = this.getGenderRestrictions.bind(this);
|
||||
this.getUserRights = this.getUserRights.bind(this);
|
||||
@@ -137,6 +139,31 @@ class AdminController {
|
||||
}
|
||||
}
|
||||
|
||||
async getFalukantUserBranches(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { falukantUserId } = req.params;
|
||||
const response = await AdminService.getFalukantUserBranches(userId, falukantUserId);
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(403).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async updateFalukantStock(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { stockId } = req.params;
|
||||
const { quantity } = req.body;
|
||||
const response = await AdminService.updateFalukantStock(userId, stockId, quantity);
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(403).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getRoomTypes(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
|
||||
@@ -24,6 +24,8 @@ router.post('/contacts/answer', authenticate, adminController.answerContact);
|
||||
router.post('/falukant/searchuser', authenticate, adminController.searchUser);
|
||||
router.get('/falukant/getuser/:id', authenticate, adminController.getFalukantUserById);
|
||||
router.post('/falukant/edituser', authenticate, adminController.changeFalukantUser);
|
||||
router.get('/falukant/branches/:falukantUserId', authenticate, adminController.getFalukantUserBranches);
|
||||
router.put('/falukant/stock/:stockId', authenticate, adminController.updateFalukantStock);
|
||||
|
||||
// --- Minigames Admin ---
|
||||
router.get('/minigames/match3/campaigns', authenticate, adminController.getMatch3Campaigns);
|
||||
|
||||
@@ -15,6 +15,11 @@ import FalukantUser from "../models/falukant/data/user.js";
|
||||
import FalukantCharacter from "../models/falukant/data/character.js";
|
||||
import FalukantPredefineFirstname from "../models/falukant/predefine/firstname.js";
|
||||
import FalukantPredefineLastname from "../models/falukant/predefine/lastname.js";
|
||||
import Branch from "../models/falukant/data/branch.js";
|
||||
import FalukantStock from "../models/falukant/data/stock.js";
|
||||
import FalukantStockType from "../models/falukant/type/stock.js";
|
||||
import RegionData from "../models/falukant/data/region.js";
|
||||
import BranchType from "../models/falukant/type/branch.js";
|
||||
import Room from '../models/chat/room.js';
|
||||
|
||||
class AdminService {
|
||||
@@ -251,6 +256,63 @@ class AdminService {
|
||||
return user;
|
||||
}
|
||||
|
||||
async getFalukantUserBranches(userId, falukantUserId) {
|
||||
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
|
||||
try {
|
||||
// Zuerst die Branches laden
|
||||
const branches = await Branch.findAll({
|
||||
where: {
|
||||
falukantUserId: falukantUserId
|
||||
}
|
||||
});
|
||||
|
||||
// Dann für jede Branch die zusätzlichen Daten laden
|
||||
const branchesWithData = await Promise.all(branches.map(async (branch) => {
|
||||
const region = await RegionData.findByPk(branch.regionId);
|
||||
const branchType = await BranchType.findByPk(branch.branchTypeId);
|
||||
const stocks = await FalukantStock.findAll({
|
||||
where: { branchId: branch.id },
|
||||
include: [{
|
||||
model: FalukantStockType,
|
||||
as: 'stockType',
|
||||
attributes: ['labelTr']
|
||||
}]
|
||||
});
|
||||
|
||||
return {
|
||||
...branch.toJSON(),
|
||||
region: region ? { name: region.name } : null,
|
||||
branchType: branchType ? { labelTr: branchType.labelTr } : null,
|
||||
stocks: stocks
|
||||
};
|
||||
}));
|
||||
|
||||
return branchesWithData;
|
||||
} catch (error) {
|
||||
console.error('Error in getFalukantUserBranches:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async updateFalukantStock(userId, stockId, quantity) {
|
||||
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
|
||||
const stock = await FalukantStock.findByPk(stockId);
|
||||
if (!stock) {
|
||||
throw new Error('Stock not found');
|
||||
}
|
||||
|
||||
stock.quantity = quantity;
|
||||
await stock.save();
|
||||
|
||||
return stock;
|
||||
}
|
||||
|
||||
async changeFalukantUser(userId, falukantUserId, falukantData) {
|
||||
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||
throw new Error('noaccess');
|
||||
|
||||
@@ -45,8 +45,25 @@
|
||||
},
|
||||
"falukant": {
|
||||
"edituser": {
|
||||
"title": "Falukant Benutzer bearbeiten",
|
||||
"username": "Benutzername",
|
||||
"characterName": "Charaktername",
|
||||
"user": "Benutzer",
|
||||
"success": "Die Änderungen wurden gespeichert.",
|
||||
"error": "Die Änderungen konnten nicht gespeichert werden."
|
||||
"error": "Die Änderungen konnten nicht gespeichert werden.",
|
||||
"errorLoadingBranches": "Fehler beim Laden der Niederlassungen.",
|
||||
"errorUpdatingStock": "Fehler beim Aktualisieren des Lagers.",
|
||||
"stockUpdated": "Lager erfolgreich aktualisiert.",
|
||||
"search": "Suchen",
|
||||
"tabs": {
|
||||
"userdata": "Benutzerdaten",
|
||||
"branches": "Niederlassungen"
|
||||
},
|
||||
"branches": {
|
||||
"title": "Niederlassungen & Lager",
|
||||
"noStocks": "Kein Lager vorhanden",
|
||||
"noBranches": "Keine Niederlassungen gefunden"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chatrooms": {
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
"create": "Erstellen",
|
||||
"update": "Aktualisieren",
|
||||
"save": "Speichern",
|
||||
"yes": "Ja",
|
||||
"no": "Nein"
|
||||
}
|
||||
|
||||
@@ -54,6 +54,29 @@
|
||||
"objectiveDescriptionPlaceholder": "e.g. Collect 100 points",
|
||||
"objectiveRequired": "Required for level completion",
|
||||
"noObjectives": "No victory conditions defined. Click 'Add Objective' to create some."
|
||||
},
|
||||
"falukant": {
|
||||
"edituser": {
|
||||
"title": "Edit Falukant User",
|
||||
"username": "Username",
|
||||
"characterName": "Character Name",
|
||||
"user": "User",
|
||||
"success": "Changes have been saved.",
|
||||
"error": "Changes could not be saved.",
|
||||
"errorLoadingBranches": "Error loading branches.",
|
||||
"errorUpdatingStock": "Error updating warehouse.",
|
||||
"stockUpdated": "Warehouse successfully updated.",
|
||||
"search": "Search",
|
||||
"tabs": {
|
||||
"userdata": "User Data",
|
||||
"branches": "Branches"
|
||||
},
|
||||
"branches": {
|
||||
"title": "Branches & Warehouse",
|
||||
"noStocks": "No warehouse available",
|
||||
"noBranches": "No branches found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,5 +8,14 @@
|
||||
},
|
||||
"message": {
|
||||
"close": "Close"
|
||||
},
|
||||
"common": {
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"create": "Create",
|
||||
"update": "Update",
|
||||
"save": "Save",
|
||||
"yes": "Yes",
|
||||
"no": "No"
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,80 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Edit Falukant User</h1>
|
||||
<div>
|
||||
<label>Username: <input type="text" v-model="user.username" /></label>
|
||||
<label>Character name: <input type="text" v-model="user.characterName" /></label>
|
||||
<button @click="searchUser">Search</button>
|
||||
<div class="edit-user-view">
|
||||
<h1>{{ $t('admin.falukant.edituser.title') }}</h1>
|
||||
|
||||
<!-- Benutzer-Suche -->
|
||||
<div class="search-section">
|
||||
<label>{{ $t('admin.falukant.edituser.username') }}: <input type="text" v-model="user.username" @keyup.enter="searchUser" /></label>
|
||||
<label>{{ $t('admin.falukant.edituser.characterName') }}: <input type="text" v-model="user.characterName" @keyup.enter="searchUser" /></label>
|
||||
<button @click="searchUser">{{ $t('admin.falukant.edituser.search') }}</button>
|
||||
</div>
|
||||
|
||||
<!-- Benutzer-Liste -->
|
||||
<ul v-for="user in users" class="user-list">
|
||||
<li @click="selectUser(user)">{{ user.username }} ({{ user.falukantUser[0].character.definedFirstName.name }} {{
|
||||
user.falukantUser[0].character.definedLastName.name }})</li>
|
||||
</ul>
|
||||
<div v-if="editableUser" class="edit-form">
|
||||
<h2>User: {{ editableUser.username }}</h2>
|
||||
<h3>Character-Name: {{ editableUser.falukantData[0].character.definedFirstName.name }} {{
|
||||
|
||||
<!-- Tab-System für ausgewählten Benutzer -->
|
||||
<div v-if="editableUser" class="user-edit-section">
|
||||
<h2>{{ $t('admin.falukant.edituser.user') }}: {{ editableUser.username }}</h2>
|
||||
<h3>{{ $t('admin.falukant.edituser.characterName') }}: {{ editableUser.falukantData[0].character.definedFirstName.name }} {{
|
||||
editableUser.falukantData[0].character.definedLastName.name }}</h3>
|
||||
<label>Money: <input type="number" v-model="editableUser.falukantData[0].money" /></label>
|
||||
<label>Age: <input type="number" v-model="age" /></label>
|
||||
<label>Noble title:
|
||||
<select v-model="editableUser.falukantData[0].character.title_of_nobility">
|
||||
<option v-for="title in titles" :value="title.id">{{ $t(`falukant.titles.male.${title.labelTr}`) }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>House: <select v-model="editableUser.falukantData[0].house">
|
||||
<option v-for="house in houses" :value="house.id">{{ $t(`${house.labelTr}`) }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<button @click="saveUser">Save</button>
|
||||
<button @click="deleteUser">Delete</button>
|
||||
|
||||
<SimpleTabs v-model="activeTab" :tabs="tabs" @change="onTabChange" />
|
||||
|
||||
<!-- Tab-Inhalt -->
|
||||
<div class="tab-content">
|
||||
<!-- Userdaten Tab -->
|
||||
<div v-if="activeTab === 'userdata'" class="tab-pane">
|
||||
<div class="edit-form">
|
||||
<label>{{ $t('falukant.overview.metadata.money') }}: <input type="number" v-model="editableUser.falukantData[0].money" /></label>
|
||||
<label>{{ $t('falukant.overview.metadata.age') }}: <input type="number" v-model="age" /></label>
|
||||
<label>{{ $t('falukant.overview.metadata.nobleTitle') }}:
|
||||
<select v-model="editableUser.falukantData[0].character.title_of_nobility">
|
||||
<option v-for="title in titles" :value="title.id">{{ $t(`falukant.titles.male.${title.labelTr}`) }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>{{ $t('falukant.house.title') }}: <select v-model="editableUser.falukantData[0].house">
|
||||
<option v-for="house in houses" :value="house.id">{{ $t(`falukant.house.type.${house.labelTr}`) }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="action-buttons">
|
||||
<button @click="saveUser">{{ $t('common.save') }}</button>
|
||||
<button @click="deleteUser">{{ $t('common.delete') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Niederlassungen Tab -->
|
||||
<div v-else-if="activeTab === 'branches'" class="tab-pane">
|
||||
<div v-if="loading.branches" class="loading">{{ $t('loading') }}</div>
|
||||
<div v-else class="branches-section">
|
||||
<h3>{{ $t('admin.falukant.edituser.branches.title') }}</h3>
|
||||
<div v-for="branch in userBranches" :key="branch.id" class="branch-card">
|
||||
<h4>{{ branch.region.name }} - {{ $t(`falukant.branch.types.${branch.branchType.labelTr}`) }}</h4>
|
||||
|
||||
<!-- Lager-Übersicht -->
|
||||
<div class="warehouse-section">
|
||||
<h5>{{ $t('falukant.branch.storage.title') }}</h5>
|
||||
<div v-if="branch.stocks && branch.stocks.length" class="stock-list">
|
||||
<div v-for="stock in branch.stocks" :key="stock.id" class="stock-item">
|
||||
<span class="stock-name">{{ $t(`falukant.branch.stocktype.${stock.stockType.labelTr}`) }}</span>
|
||||
<input type="number" v-model="stock.quantity" class="stock-quantity" />
|
||||
<button @click="updateStock(stock)">{{ $t('common.update') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-stocks">
|
||||
{{ $t('admin.falukant.edituser.branches.noStocks') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!userBranches.length" class="no-branches">
|
||||
{{ $t('admin.falukant.edituser.branches.noBranches') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -34,10 +82,14 @@
|
||||
<script lang="js">
|
||||
import { mapState } from 'vuex';
|
||||
import { mapActions } from 'vuex';
|
||||
import SimpleTabs from '@/components/SimpleTabs.vue';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
|
||||
export default {
|
||||
name: 'AdminFalukantEditUserView',
|
||||
components: {
|
||||
SimpleTabs
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
@@ -50,6 +102,16 @@ export default {
|
||||
originalAge: null,
|
||||
originalUser: null,
|
||||
titles: [],
|
||||
houses: [],
|
||||
activeTab: 'userdata',
|
||||
tabs: [
|
||||
{ value: 'userdata', label: 'admin.falukant.edituser.tabs.userdata' },
|
||||
{ value: 'branches', label: 'admin.falukant.edituser.tabs.branches' }
|
||||
],
|
||||
userBranches: [],
|
||||
loading: {
|
||||
branches: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -74,6 +136,7 @@ export default {
|
||||
this.age = Math.floor((Date.now() - new Date(this.editableUser.falukantData[0].character.birthdate)) / (24 * 60 * 60 * 1000));
|
||||
this.originalAge = this.age;
|
||||
this.users = [];
|
||||
this.activeTab = 'userdata';
|
||||
},
|
||||
async saveUser() {
|
||||
const dataToChange = {
|
||||
@@ -99,18 +162,246 @@ export default {
|
||||
const dataToChange = {
|
||||
id: this.editableUser.falukantData[0].id,
|
||||
};
|
||||
},
|
||||
onTabChange(tab) {
|
||||
if (tab === 'branches' && this.editableUser && !this.userBranches.length) {
|
||||
this.loadUserBranches();
|
||||
}
|
||||
},
|
||||
async loadUserBranches() {
|
||||
if (!this.editableUser) return;
|
||||
|
||||
this.loading.branches = true;
|
||||
try {
|
||||
const branchesResult = await apiClient.get(`/api/admin/falukant/branches/${this.editableUser.falukantData[0].id}`);
|
||||
this.userBranches = branchesResult.data;
|
||||
} catch (error) {
|
||||
console.error('Error loading user branches:', error);
|
||||
this.$root.$refs.errorDialog.open('tr:admin.falukant.edituser.errorLoadingBranches');
|
||||
} finally {
|
||||
this.loading.branches = false;
|
||||
}
|
||||
},
|
||||
async updateStock(stock) {
|
||||
try {
|
||||
await apiClient.put(`/api/admin/falukant/stock/${stock.id}`, {
|
||||
quantity: stock.quantity
|
||||
});
|
||||
this.$root.$refs.messageDialog.open('tr:admin.falukant.edituser.stockUpdated');
|
||||
} catch (error) {
|
||||
console.error('Error updating stock:', error);
|
||||
this.$root.$refs.errorDialog.open('tr:admin.falukant.edituser.errorUpdatingStock');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-list>li {
|
||||
.edit-user-view {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.search-section label {
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.search-section input {
|
||||
margin-left: 5px;
|
||||
padding: 5px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.search-section button {
|
||||
padding: 8px 15px;
|
||||
background: #1976d2;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.user-list > li {
|
||||
cursor: pointer;
|
||||
color: #0066ff;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
margin-bottom: 5px;
|
||||
border-radius: 3px;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.user-list > li:hover {
|
||||
background: #e9e9e9;
|
||||
}
|
||||
|
||||
.user-edit-section {
|
||||
margin-top: 30px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.user-edit-section h2, .user-edit-section h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
margin-top: 20px;
|
||||
max-height: calc(100vh - 300px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
padding: 20px 0;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.edit-form label {
|
||||
display: block;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.edit-form input, .edit-form select {
|
||||
margin-left: 10px;
|
||||
padding: 5px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.action-buttons button {
|
||||
margin-right: 10px;
|
||||
padding: 8px 15px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-buttons button:first-child {
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-buttons button:last-child {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.branches-section {
|
||||
max-height: calc(100vh - 400px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.branches-section h3 {
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
padding: 10px 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.branch-card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.branch-card h4 {
|
||||
margin-top: 0;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.warehouse-section {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.warehouse-section h5 {
|
||||
margin-bottom: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stock-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.stock-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.stock-name {
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stock-quantity {
|
||||
width: 80px;
|
||||
padding: 5px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.stock-item button {
|
||||
padding: 5px 10px;
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.no-stocks, .no-branches {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user