feat(navigation): enhance adult verification handling and notifications
- Updated navigationController to simplify the eroticChat menu structure. - Enhanced adminService to notify users of adult verification status changes, including previous status. - Improved AppNavigation and related components to register and unregister socket listeners for adult verification updates. - Added localized messages for adult verification notifications in English, German, and Spanish. - Introduced a verification hint in the EroticAccessView to guide users on document submission.
This commit is contained in:
@@ -184,6 +184,7 @@
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { EventBus } from '@/utils/eventBus.js';
|
||||
import { showInfo } from '@/utils/feedback.js';
|
||||
|
||||
export default {
|
||||
name: 'AppNavigation',
|
||||
@@ -198,7 +199,11 @@ export default {
|
||||
pinnedSubKey: null,
|
||||
suppressHover: false,
|
||||
hoverReleaseTimer: null,
|
||||
isMobileNav: false
|
||||
isMobileNav: false,
|
||||
_forumsChangedHandler: null,
|
||||
_friendLoginChangedHandler: null,
|
||||
_reloadMenuHandler: null,
|
||||
_adultVerificationChangedHandler: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -212,10 +217,9 @@ export default {
|
||||
this.collapseMenus();
|
||||
},
|
||||
socket(newSocket) {
|
||||
this.unregisterSocketListeners();
|
||||
if (newSocket) {
|
||||
newSocket.on('forumschanged', this.fetchForums);
|
||||
newSocket.on('friendloginchanged', this.fetchFriends);
|
||||
newSocket.on('reloadmenu', this.loadMenu);
|
||||
this.registerSocketListeners(newSocket);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -230,14 +234,12 @@ export default {
|
||||
window.addEventListener('resize', this.updateViewportState);
|
||||
document.addEventListener('click', this.handleDocumentClick);
|
||||
document.addEventListener('keydown', this.handleDocumentKeydown);
|
||||
if (this.socket) {
|
||||
this.registerSocketListeners(this.socket);
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
const sock = this.socket;
|
||||
if (sock) {
|
||||
sock.off('forumschanged');
|
||||
sock.off('friendloginchanged');
|
||||
sock.off('reloadmenu');
|
||||
}
|
||||
this.unregisterSocketListeners();
|
||||
window.removeEventListener('resize', this.updateViewportState);
|
||||
document.removeEventListener('click', this.handleDocumentClick);
|
||||
document.removeEventListener('keydown', this.handleDocumentKeydown);
|
||||
@@ -248,6 +250,38 @@ export default {
|
||||
methods: {
|
||||
...mapActions(['loadMenu', 'logout']),
|
||||
|
||||
registerSocketListeners(sock) {
|
||||
if (!sock) return;
|
||||
this._forumsChangedHandler = () => this.fetchForums();
|
||||
this._friendLoginChangedHandler = () => this.fetchFriends();
|
||||
this._reloadMenuHandler = () => this.loadMenu();
|
||||
this._adultVerificationChangedHandler = async (payload = {}) => {
|
||||
await this.loadMenu();
|
||||
if (payload.status === 'approved') {
|
||||
showInfo(this, this.$t('socialnetwork.erotic.notifications.approved'));
|
||||
} else if (payload.status === 'rejected') {
|
||||
showInfo(this, this.$t('socialnetwork.erotic.notifications.rejected'));
|
||||
}
|
||||
};
|
||||
sock.on('forumschanged', this._forumsChangedHandler);
|
||||
sock.on('friendloginchanged', this._friendLoginChangedHandler);
|
||||
sock.on('reloadmenu', this._reloadMenuHandler);
|
||||
sock.on('adultVerificationChanged', this._adultVerificationChangedHandler);
|
||||
},
|
||||
|
||||
unregisterSocketListeners() {
|
||||
const sock = this.socket;
|
||||
if (!sock) return;
|
||||
if (this._forumsChangedHandler) sock.off('forumschanged', this._forumsChangedHandler);
|
||||
if (this._friendLoginChangedHandler) sock.off('friendloginchanged', this._friendLoginChangedHandler);
|
||||
if (this._reloadMenuHandler) sock.off('reloadmenu', this._reloadMenuHandler);
|
||||
if (this._adultVerificationChangedHandler) sock.off('adultVerificationChanged', this._adultVerificationChangedHandler);
|
||||
this._forumsChangedHandler = null;
|
||||
this._friendLoginChangedHandler = null;
|
||||
this._reloadMenuHandler = null;
|
||||
this._adultVerificationChangedHandler = null;
|
||||
},
|
||||
|
||||
updateViewportState() {
|
||||
this.isMobileNav = window.innerWidth <= 960;
|
||||
if (!this.isMobileNav) {
|
||||
@@ -458,8 +492,8 @@ export default {
|
||||
/**
|
||||
* Einheitliche Klick‑Logik:
|
||||
* 1) Nur aufklappen, wenn noch Untermenüs existieren
|
||||
* 2) Bei `view`: Dialog/Window öffnen
|
||||
* 3) Bei `action`: custom action aufrufen
|
||||
* 2) Bei `action`: custom action aufrufen
|
||||
* 3) Bei `view`: Dialog/Window öffnen
|
||||
* 4) Sonst: normale Router-Navigation
|
||||
*/
|
||||
handleItem(item, event, key = null) {
|
||||
@@ -481,7 +515,14 @@ export default {
|
||||
|
||||
if (this.hasChildren(item)) return;
|
||||
|
||||
// 2) view → Dialog/Window
|
||||
// 2) custom action (openForum, openChat, ...)
|
||||
if (item.action && typeof this[item.action] === 'function') {
|
||||
this[item.action](item.params, event);
|
||||
this.collapseMenus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 3) view → Dialog/Window
|
||||
if (item.view) {
|
||||
const dialogRef = this.$root.$refs[item.class];
|
||||
if (!dialogRef) {
|
||||
@@ -500,13 +541,6 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3) custom action (openForum, openChat, ...)
|
||||
if (item.action && typeof this[item.action] === 'function') {
|
||||
this[item.action](item.params, event);
|
||||
this.collapseMenus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 4) Standard‑Navigation
|
||||
if (item.path) {
|
||||
this.$router.push(item.path);
|
||||
|
||||
@@ -261,6 +261,12 @@
|
||||
"documentLabel": "Nachweisdatei",
|
||||
"noteLabel": "Kurze Notiz für die Moderation",
|
||||
"settingsLink": "Account-Einstellungen öffnen",
|
||||
"verificationHintTitle": "Hinweis zum Nachweis",
|
||||
"verificationHintBody": "Du kannst ein Foto senden. Wenn dein Alter darauf nicht eindeutig erkennbar ist, wird der Antrag abgelehnt und du musst stattdessen einen Ausweis einreichen.",
|
||||
"notifications": {
|
||||
"approved": "Dein Erotikbereich wurde von der Moderation freigeschaltet.",
|
||||
"rejected": "Dein Antrag auf den Erotikbereich wurde abgelehnt. Wenn dein Alter auf Fotos nicht eindeutig erkennbar ist, sende bitte einen Ausweis."
|
||||
},
|
||||
"picturesTitle": "Erotikbilder",
|
||||
"picturesIntro": "Eigene Inhalte bleiben strikt vom normalen Galeriebereich getrennt. Hier verwaltest du nur Bilder für den freigeschalteten Erotikbereich.",
|
||||
"uploadTitle": "Erotikbild hochladen",
|
||||
|
||||
@@ -261,6 +261,12 @@
|
||||
"documentLabel": "Verification document",
|
||||
"noteLabel": "Short note for moderation",
|
||||
"settingsLink": "Open account settings",
|
||||
"verificationHintTitle": "Verification note",
|
||||
"verificationHintBody": "You may submit a photo. If your age is not clearly recognizable there, the request will be rejected and you will need to submit an ID instead.",
|
||||
"notifications": {
|
||||
"approved": "Your erotic area access has been approved by moderation.",
|
||||
"rejected": "Your erotic area request was rejected. If your age is not clearly recognizable in photos, please submit an ID."
|
||||
},
|
||||
"picturesTitle": "Erotic pictures",
|
||||
"picturesIntro": "Your content stays strictly separate from the normal gallery. Manage only images for the unlocked erotic area here.",
|
||||
"uploadTitle": "Upload erotic picture",
|
||||
|
||||
@@ -261,6 +261,12 @@
|
||||
"documentLabel": "Documento de verificación",
|
||||
"noteLabel": "Breve nota para moderación",
|
||||
"settingsLink": "Abrir ajustes de la cuenta",
|
||||
"verificationHintTitle": "Nota sobre la verificación",
|
||||
"verificationHintBody": "Puedes enviar una foto. Si tu edad no se reconoce con claridad, la solicitud será rechazada y tendrás que enviar un documento de identidad.",
|
||||
"notifications": {
|
||||
"approved": "La moderación ha aprobado tu acceso al área erótica.",
|
||||
"rejected": "Tu solicitud para el área erótica fue rechazada. Si tu edad no se reconoce claramente en las fotos, envía un documento de identidad."
|
||||
},
|
||||
"picturesTitle": "Imágenes eróticas",
|
||||
"picturesIntro": "Tus contenidos permanecen estrictamente separados de la galería normal. Aquí gestionas solo imágenes del área erótica desbloqueada.",
|
||||
"uploadTitle": "Subir imagen erótica",
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { showApiError, showError, showSuccess } from '@/utils/feedback.js';
|
||||
import { showApiError, showError, showInfo, showSuccess } from '@/utils/feedback.js';
|
||||
|
||||
export default {
|
||||
name: "AccountSettingsView",
|
||||
@@ -88,7 +88,7 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user']),
|
||||
...mapGetters(['user', 'socket']),
|
||||
requiresOldPassword() {
|
||||
return this.newpassword.trim().length > 0;
|
||||
},
|
||||
@@ -115,6 +115,33 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadAccount() {
|
||||
const response = await apiClient.post('/api/settings/account', { userId: this.user.id });
|
||||
this.username = response.data.username;
|
||||
this.showInSearch = response.data.showinsearch;
|
||||
this.email = response.data.email;
|
||||
this.isAdult = !!response.data.isAdult;
|
||||
this.adultVerificationStatus = response.data.adultVerificationStatus || 'none';
|
||||
this.adultVerificationRequest = response.data.adultVerificationRequest || null;
|
||||
},
|
||||
registerSocketListeners(sock) {
|
||||
if (!sock) return;
|
||||
this._adultVerificationChangedHandler = async (payload = {}) => {
|
||||
await this.loadAccount();
|
||||
if (payload.status === 'approved') {
|
||||
showInfo(this, this.$t('socialnetwork.erotic.notifications.approved'));
|
||||
} else if (payload.status === 'rejected') {
|
||||
showInfo(this, this.$t('socialnetwork.erotic.notifications.rejected'));
|
||||
}
|
||||
};
|
||||
sock.on('adultVerificationChanged', this._adultVerificationChangedHandler);
|
||||
},
|
||||
unregisterSocketListeners() {
|
||||
if (this.socket && this._adultVerificationChangedHandler) {
|
||||
this.socket.off('adultVerificationChanged', this._adultVerificationChangedHandler);
|
||||
}
|
||||
this._adultVerificationChangedHandler = null;
|
||||
},
|
||||
async changeAccount() {
|
||||
try {
|
||||
// Prüfe ob ein neues Passwort eingegeben wurde
|
||||
@@ -172,19 +199,27 @@ export default {
|
||||
|
||||
},
|
||||
async mounted() {
|
||||
const response = await apiClient.post('/api/settings/account', { userId: this.user.id });
|
||||
this.username = response.data.username;
|
||||
this.showInSearch = response.data.showinsearch;
|
||||
this.email = response.data.email;
|
||||
this.isAdult = !!response.data.isAdult;
|
||||
this.adultVerificationStatus = response.data.adultVerificationStatus || 'none';
|
||||
this.adultVerificationRequest = response.data.adultVerificationRequest || null;
|
||||
if (this.socket) {
|
||||
this.registerSocketListeners(this.socket);
|
||||
}
|
||||
await this.loadAccount();
|
||||
|
||||
// Stelle sicher, dass Passwort-Felder leer sind
|
||||
this.newpassword = '';
|
||||
this.newpasswordretype = '';
|
||||
this.oldpassword = '';
|
||||
},
|
||||
watch: {
|
||||
socket(newSocket) {
|
||||
this.unregisterSocketListeners();
|
||||
if (newSocket) {
|
||||
this.registerSocketListeners(newSocket);
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.unregisterSocketListeners();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="erotic-access-hint">
|
||||
<strong>{{ $t('socialnetwork.erotic.verificationHintTitle') }}</strong>
|
||||
<span>{{ $t('socialnetwork.erotic.verificationHintBody') }}</span>
|
||||
</div>
|
||||
|
||||
<form v-if="canRequestVerification" class="erotic-access-form" @submit.prevent="requestVerification">
|
||||
<label>
|
||||
<span>{{ $t('socialnetwork.erotic.documentLabel') }}</span>
|
||||
@@ -47,7 +52,7 @@
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { showApiError, showSuccess } from '@/utils/feedback.js';
|
||||
import { showApiError, showInfo, showSuccess } from '@/utils/feedback.js';
|
||||
|
||||
export default {
|
||||
name: 'EroticAccessView',
|
||||
@@ -59,7 +64,7 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user']),
|
||||
...mapGetters(['user', 'socket']),
|
||||
status() {
|
||||
return this.account?.adultVerificationStatus || 'none';
|
||||
},
|
||||
@@ -89,6 +94,25 @@ export default {
|
||||
const response = await apiClient.post('/api/settings/account', { userId: this.user.id });
|
||||
this.account = response.data;
|
||||
},
|
||||
registerSocketListeners(sock) {
|
||||
if (!sock) return;
|
||||
this._adultVerificationChangedHandler = async (payload = {}) => {
|
||||
await this.loadAccount();
|
||||
if (payload.status === 'approved') {
|
||||
showInfo(this, this.$t('socialnetwork.erotic.notifications.approved'));
|
||||
this.$router.replace('/socialnetwork/erotic/pictures');
|
||||
} else if (payload.status === 'rejected') {
|
||||
showInfo(this, this.$t('socialnetwork.erotic.notifications.rejected'));
|
||||
}
|
||||
};
|
||||
sock.on('adultVerificationChanged', this._adultVerificationChangedHandler);
|
||||
},
|
||||
unregisterSocketListeners() {
|
||||
if (this.socket && this._adultVerificationChangedHandler) {
|
||||
this.socket.off('adultVerificationChanged', this._adultVerificationChangedHandler);
|
||||
}
|
||||
this._adultVerificationChangedHandler = null;
|
||||
},
|
||||
handleFileChange(event) {
|
||||
this.documentFile = event.target.files?.[0] || null;
|
||||
},
|
||||
@@ -114,10 +138,24 @@ export default {
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (this.socket) {
|
||||
this.registerSocketListeners(this.socket);
|
||||
}
|
||||
await this.loadAccount();
|
||||
if (this.account?.adultAccessEnabled) {
|
||||
this.$router.replace('/socialnetwork/erotic/pictures');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
socket(newSocket) {
|
||||
this.unregisterSocketListeners();
|
||||
if (newSocket) {
|
||||
this.registerSocketListeners(newSocket);
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.unregisterSocketListeners();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -175,7 +213,8 @@ export default {
|
||||
}
|
||||
|
||||
.erotic-access-request,
|
||||
.erotic-access-form {
|
||||
.erotic-access-form,
|
||||
.erotic-access-hint {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
@@ -187,6 +226,13 @@ export default {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.erotic-access-hint {
|
||||
padding: 14px 16px;
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(164, 98, 72, 0.08);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.erotic-access-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
Reference in New Issue
Block a user