From 07604cc9fa06825e0ffe1a3822aa726324edcdc2 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 27 Mar 2026 13:23:44 +0100 Subject: [PATCH] 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. --- backend/controllers/navigationController.js | 4 +- backend/services/adminService.js | 9 +++ frontend/src/components/AppNavigation.vue | 74 ++++++++++++++----- .../src/i18n/locales/de/socialnetwork.json | 6 ++ .../src/i18n/locales/en/socialnetwork.json | 6 ++ .../src/i18n/locales/es/socialnetwork.json | 6 ++ frontend/src/views/settings/AccountView.vue | 53 ++++++++++--- .../src/views/social/EroticAccessView.vue | 52 ++++++++++++- 8 files changed, 175 insertions(+), 35 deletions(-) diff --git a/backend/controllers/navigationController.js b/backend/controllers/navigationController.js index 74068e0..8c7a287 100644 --- a/backend/controllers/navigationController.js +++ b/backend/controllers/navigationController.js @@ -96,9 +96,7 @@ const menuStructure = { }, eroticChat: { visible: ["over18"], - action: "openEroticChat", - view: "window", - class: "eroticChatWindow" + action: "openEroticChat" } } }, diff --git a/backend/services/adminService.js b/backend/services/adminService.js index 5e4915a..83ed357 100644 --- a/backend/services/adminService.js +++ b/backend/services/adminService.js @@ -35,6 +35,7 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { getAdultVerificationBaseDir, getLegacyAdultVerificationBaseDir } from '../utils/storagePaths.js'; +import { notifyUser } from '../utils/socket.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -385,6 +386,8 @@ class AdminService { where: { userId: user.id, paramTypeId: paramType.id } }); + const previousStatus = existing?.value || 'none'; + if (existing) { await existing.update({ value: status }); } else { @@ -395,6 +398,12 @@ class AdminService { }); } + await notifyUser(targetHashedId, 'reloadmenu', {}); + await notifyUser(targetHashedId, 'adultVerificationChanged', { + status, + previousStatus + }); + return { success: true }; } diff --git a/frontend/src/components/AppNavigation.vue b/frontend/src/components/AppNavigation.vue index 91ec884..d8281e6 100644 --- a/frontend/src/components/AppNavigation.vue +++ b/frontend/src/components/AppNavigation.vue @@ -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); diff --git a/frontend/src/i18n/locales/de/socialnetwork.json b/frontend/src/i18n/locales/de/socialnetwork.json index 0588c94..6063241 100644 --- a/frontend/src/i18n/locales/de/socialnetwork.json +++ b/frontend/src/i18n/locales/de/socialnetwork.json @@ -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", diff --git a/frontend/src/i18n/locales/en/socialnetwork.json b/frontend/src/i18n/locales/en/socialnetwork.json index 1a31b1d..66790ad 100644 --- a/frontend/src/i18n/locales/en/socialnetwork.json +++ b/frontend/src/i18n/locales/en/socialnetwork.json @@ -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", diff --git a/frontend/src/i18n/locales/es/socialnetwork.json b/frontend/src/i18n/locales/es/socialnetwork.json index 9089317..8d1b1e3 100644 --- a/frontend/src/i18n/locales/es/socialnetwork.json +++ b/frontend/src/i18n/locales/es/socialnetwork.json @@ -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", diff --git a/frontend/src/views/settings/AccountView.vue b/frontend/src/views/settings/AccountView.vue index 317e5d0..466f86b 100644 --- a/frontend/src/views/settings/AccountView.vue +++ b/frontend/src/views/settings/AccountView.vue @@ -69,7 +69,7 @@ diff --git a/frontend/src/views/social/EroticAccessView.vue b/frontend/src/views/social/EroticAccessView.vue index 949680a..862dcba 100644 --- a/frontend/src/views/social/EroticAccessView.vue +++ b/frontend/src/views/social/EroticAccessView.vue @@ -27,6 +27,11 @@ +
+ {{ $t('socialnetwork.erotic.verificationHintTitle') }} + {{ $t('socialnetwork.erotic.verificationHintBody') }} +
+