Add adult verification and erotic moderation features: Implement new routes and controller methods for managing adult verification requests, status updates, and document retrieval. Introduce erotic moderation actions and reports, enhancing administrative capabilities. Update chat and navigation controllers to support adult content filtering and access control. Enhance user parameter handling for adult verification status and requests, improving overall user experience and compliance.

This commit is contained in:
Torsten Schulz (local)
2026-03-27 09:14:54 +01:00
parent f93687c753
commit 3e6c09ab29
73 changed files with 4459 additions and 197 deletions

View File

@@ -11,9 +11,10 @@
v-for="(item, key) in menu"
:key="key"
class="mainmenuitem"
:class="{ 'mainmenuitem--active': isItemActive(item), 'mainmenuitem--expanded': isMainExpanded(key) }"
:class="{ 'mainmenuitem--active': isItemActive(item), 'mainmenuitem--expanded': isMainExpanded(key), 'mainmenuitem--disabled': item.disabled }"
tabindex="0"
role="button"
:title="item.disabled ? $t(item.disabledReasonKey || 'socialnetwork.erotic.lockedShort') : undefined"
:aria-haspopup="hasTopLevelSubmenu(item) ? 'menu' : undefined"
:aria-expanded="hasTopLevelSubmenu(item) ? String(isMainExpanded(key)) : undefined"
@click="handleItem(item, $event, key)"
@@ -35,7 +36,8 @@
:key="subkey"
tabindex="0"
role="menuitem"
:class="{ 'submenu1__item--expanded': isSubExpanded(`${key}:${subkey}`) }"
:class="{ 'submenu1__item--expanded': isSubExpanded(`${key}:${subkey}`), 'submenu-item--disabled': subitem.disabled }"
:title="subitem.disabled ? $t(subitem.disabledReasonKey || 'socialnetwork.erotic.lockedShort') : undefined"
@click="handleSubItem(subitem, subkey, key, $event)"
@keydown.enter.prevent="handleSubItem(subitem, subkey, key, $event)"
@keydown.space.prevent="handleSubItem(subitem, subkey, key, $event)"
@@ -109,6 +111,8 @@
:key="subsubkey"
tabindex="0"
role="menuitem"
:class="{ 'submenu-item--disabled': subsubitem.disabled }"
:title="subsubitem.disabled ? $t(subsubitem.disabledReasonKey || 'socialnetwork.erotic.lockedShort') : undefined"
@click="handleItem(subsubitem, $event)"
@keydown.enter.prevent="handleItem(subsubitem, $event)"
@keydown.space.prevent="handleItem(subsubitem, $event)"
@@ -357,14 +361,20 @@ export default {
},
openMultiChat() {
// Räume können später dynamisch geladen werden, hier als Platzhalter ein Beispiel:
const exampleRooms = [
{ id: 1, title: 'Allgemein' },
{ id: 2, title: 'Rollenspiel' }
];
const ref = this.$root.$refs.multiChatDialog;
if (ref && typeof ref.open === 'function') {
ref.open(exampleRooms);
ref.open();
} else if (ref?.$refs?.dialog && typeof ref.$refs.dialog.open === 'function') {
ref.$refs.dialog.open();
} else {
console.error('MultiChatDialog nicht bereit oder ohne open()');
}
},
openEroticChat() {
const ref = this.$root.$refs.multiChatDialog;
if (ref && typeof ref.open === 'function') {
ref.open(null, { adultOnly: true });
} else if (ref?.$refs?.dialog && typeof ref.$refs.dialog.open === 'function') {
ref.$refs.dialog.open();
} else {
@@ -452,6 +462,10 @@ export default {
handleItem(item, event, key = null) {
event.stopPropagation();
if (item?.disabled) {
return;
}
if (key && this.hasTopLevelSubmenu(item)) {
if (this.isMobileNav) {
this.toggleMain(key);
@@ -603,6 +617,18 @@ ul {
border-color: rgba(248, 162, 43, 0.2);
}
.mainmenuitem--disabled,
.submenu-item--disabled {
opacity: 0.55;
cursor: not-allowed;
}
.mainmenuitem--disabled:hover {
transform: none;
background-color: transparent;
border-color: transparent;
}
.mainmenuitem--active {
background: rgba(255, 255, 255, 0.72);
border-color: rgba(248, 162, 43, 0.22);

View File

@@ -16,7 +16,7 @@
<th>{{ $t('falukant.branch.revenue.perMinute') }}</th>
<th>{{ $t('falukant.branch.revenue.profitAbsolute') }}</th>
<th>{{ $t('falukant.branch.revenue.profitPerMinute') }}</th>
<th>Bessere Preise</th>
<th>{{ $t('falukant.branch.revenue.betterPrices') }}</th>
</tr>
</thead>
<tbody>
@@ -29,12 +29,20 @@
<td>{{ calculateProductProfit(product).perMinute }}</td>
<td>
<div v-if="getBetterPrices(product.id) && getBetterPrices(product.id).length > 0" class="price-cities">
<span v-for="city in getBetterPrices(product.id)" :key="city.regionId"
:class="['city-price', getCityPriceClass(city.branchType)]"
:title="`${city.regionName}: ${formatPrice(city.price)}`">
<span class="city-name">{{ city.regionName }}</span>
<span class="city-price-value">({{ formatPrice(city.price) }})</span>
</span>
<template v-for="(city, idx) in getBetterPrices(product.id)" :key="city.regionId">
<span
v-if="idx > 0"
class="city-price-sep"
aria-hidden="true"
>, </span>
<span
:class="['city-price', getCityPriceClass(city.branchType)]"
:title="`${city.regionName}: ${formatPrice(city.price)}`"
>
<span class="city-name">{{ city.regionName }}</span>
<span class="city-price-value">({{ formatPrice(city.price) }})</span>
</span>
</template>
</div>
<span v-else class="no-better-prices"></span>
</td>
@@ -188,7 +196,14 @@
.price-cities {
display: flex;
flex-wrap: wrap;
gap: 0.3em;
align-items: baseline;
gap: 0.15em 0.35em;
line-height: 1.35;
min-width: 0;
}
.city-price-sep {
color: #666;
user-select: none;
}
.city-price {
padding: 0.2em 0.4em;