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:
269
frontend/src/views/social/EroticVideosView.vue
Normal file
269
frontend/src/views/social/EroticVideosView.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<div class="erotic-videos-page">
|
||||
<section class="erotic-videos-hero surface-card">
|
||||
<div>
|
||||
<span class="erotic-videos-eyebrow">{{ $t('socialnetwork.erotic.eyebrow') }}</span>
|
||||
<h2>{{ $t('socialnetwork.erotic.videosTitle') }}</h2>
|
||||
<p>{{ $t('socialnetwork.erotic.videosIntro') }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="erotic-videos-upload surface-card">
|
||||
<div class="erotic-videos-upload__header">
|
||||
<h3>{{ $t('socialnetwork.erotic.videoUploadTitle') }}</h3>
|
||||
<p>{{ $t('socialnetwork.erotic.videoUploadHint') }}</p>
|
||||
</div>
|
||||
|
||||
<form class="erotic-videos-form" @submit.prevent="handleUpload">
|
||||
<label>
|
||||
<span>{{ $t('socialnetwork.gallery.upload.image_title') }}</span>
|
||||
<input v-model="title" type="text" :placeholder="$t('socialnetwork.gallery.upload.image_title')" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ $t('socialnetwork.erotic.videoDescription') }}</span>
|
||||
<textarea v-model="description" rows="3" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ $t('socialnetwork.erotic.videoFile') }}</span>
|
||||
<input type="file" accept="video/mp4,video/webm,video/ogg,video/quicktime" required @change="onFileChange" />
|
||||
</label>
|
||||
<button type="submit">{{ $t('socialnetwork.gallery.upload.upload_button') }}</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="erotic-videos-list surface-card">
|
||||
<h3>{{ $t('socialnetwork.erotic.myVideos') }}</h3>
|
||||
<div v-if="videos.length === 0" class="erotic-videos-empty">
|
||||
{{ $t('socialnetwork.erotic.noVideos') }}
|
||||
</div>
|
||||
<ul v-else class="erotic-videos-grid">
|
||||
<li v-for="video in videos" :key="video.id" class="erotic-videos-card">
|
||||
<video v-if="!video.isModeratedHidden" :src="video.url" controls preload="metadata" />
|
||||
<div v-else class="erotic-videos-card__hidden">
|
||||
{{ $t('socialnetwork.erotic.hiddenByModeration') }}
|
||||
</div>
|
||||
<strong>{{ video.title }}</strong>
|
||||
<span v-if="video.isModeratedHidden" class="erotic-videos-card__badge">
|
||||
{{ $t('socialnetwork.erotic.moderationHidden') }}
|
||||
</span>
|
||||
<p v-if="video.description">{{ video.description }}</p>
|
||||
<div class="erotic-videos-card__actions">
|
||||
<button type="button" class="secondary" @click="startReport('video', video.id)">
|
||||
{{ $t('socialnetwork.erotic.reportAction') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="reportTarget.type === 'video' && reportTarget.id === video.id" class="erotic-report-form">
|
||||
<select v-model="reportReason">
|
||||
<option v-for="option in reportReasonOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
<textarea v-model="reportNote" rows="3" :placeholder="$t('socialnetwork.erotic.reportNote')" />
|
||||
<div class="erotic-report-form__actions">
|
||||
<button type="button" @click="submitReport">{{ $t('socialnetwork.erotic.submitReport') }}</button>
|
||||
<button type="button" class="secondary" @click="resetReport">{{ $t('general.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { showApiError, showSuccess } from '@/utils/feedback.js';
|
||||
|
||||
export default {
|
||||
name: 'EroticVideosView',
|
||||
data() {
|
||||
return {
|
||||
videos: [],
|
||||
fileToUpload: null,
|
||||
title: '',
|
||||
description: '',
|
||||
reportTarget: { type: null, id: null },
|
||||
reportReason: 'other',
|
||||
reportNote: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
reportReasonOptions() {
|
||||
return ['suspected_minor', 'non_consensual', 'violence', 'harassment', 'spam', 'other'].map(value => ({
|
||||
value,
|
||||
label: this.$t(`socialnetwork.erotic.reportReasons.${value}`)
|
||||
}));
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadVideos();
|
||||
},
|
||||
methods: {
|
||||
async loadVideos() {
|
||||
const response = await apiClient.get('/api/socialnetwork/erotic/videos');
|
||||
this.videos = await Promise.all(response.data.map(async (video) => ({
|
||||
...video,
|
||||
url: video.isModeratedHidden ? null : await this.fetchVideoUrl(video.hash),
|
||||
})));
|
||||
},
|
||||
async fetchVideoUrl(hash) {
|
||||
const response = await apiClient.get(`/api/socialnetwork/erotic/video/${hash}`, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
return URL.createObjectURL(response.data);
|
||||
},
|
||||
onFileChange(event) {
|
||||
this.fileToUpload = event.target.files[0];
|
||||
},
|
||||
async handleUpload() {
|
||||
if (!this.fileToUpload) return;
|
||||
const formData = new FormData();
|
||||
formData.append('video', this.fileToUpload);
|
||||
formData.append('title', this.title);
|
||||
formData.append('description', this.description);
|
||||
await apiClient.post('/api/socialnetwork/erotic/videos', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
this.fileToUpload = null;
|
||||
this.title = '';
|
||||
this.description = '';
|
||||
await this.loadVideos();
|
||||
},
|
||||
startReport(type, id) {
|
||||
this.reportTarget = { type, id };
|
||||
this.reportReason = 'other';
|
||||
this.reportNote = '';
|
||||
},
|
||||
resetReport() {
|
||||
this.reportTarget = { type: null, id: null };
|
||||
this.reportReason = 'other';
|
||||
this.reportNote = '';
|
||||
},
|
||||
async submitReport() {
|
||||
try {
|
||||
await apiClient.post('/api/socialnetwork/erotic/report', {
|
||||
targetType: this.reportTarget.type,
|
||||
targetId: this.reportTarget.id,
|
||||
reason: this.reportReason,
|
||||
note: this.reportNote
|
||||
});
|
||||
showSuccess(this, this.$t('socialnetwork.erotic.reportSubmitted'));
|
||||
this.resetReport();
|
||||
} catch (error) {
|
||||
showApiError(this, error, this.$t('socialnetwork.erotic.reportError'));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.erotic-videos-page {
|
||||
display: grid;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.erotic-videos-hero,
|
||||
.erotic-videos-upload,
|
||||
.erotic-videos-list {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.erotic-videos-hero {
|
||||
padding: 1.5rem;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(204, 44, 94, 0.18), transparent 38%),
|
||||
linear-gradient(140deg, rgba(37, 25, 33, 0.98), rgba(83, 34, 51, 0.96));
|
||||
color: #fff1f5;
|
||||
}
|
||||
|
||||
.erotic-videos-eyebrow {
|
||||
display: inline-flex;
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0.25rem 0.7rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 241, 245, 0.12);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.erotic-videos-hero p {
|
||||
color: rgba(255, 241, 245, 0.84);
|
||||
}
|
||||
|
||||
.erotic-videos-upload,
|
||||
.erotic-videos-list {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.erotic-videos-upload__header,
|
||||
.erotic-videos-form {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.erotic-videos-form label {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.erotic-videos-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 1rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.erotic-videos-card {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
padding: 0.85rem;
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(112, 60, 80, 0.08);
|
||||
}
|
||||
|
||||
.erotic-videos-card__hidden {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 160px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: rgba(96, 32, 48, 0.18);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.erotic-videos-card__badge {
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
padding: 0.2rem 0.65rem;
|
||||
border-radius: var(--radius-pill);
|
||||
background: rgba(176, 88, 88, 0.14);
|
||||
color: #8b3340;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.erotic-videos-card video {
|
||||
width: 100%;
|
||||
border-radius: var(--radius-sm);
|
||||
background: #120b0f;
|
||||
}
|
||||
|
||||
.erotic-videos-card__actions,
|
||||
.erotic-report-form,
|
||||
.erotic-report-form__actions {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.erotic-videos-empty {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user