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:
201
frontend/src/views/social/EroticAccessView.vue
Normal file
201
frontend/src/views/social/EroticAccessView.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<div class="erotic-access-page">
|
||||
<section class="erotic-access-hero surface-card">
|
||||
<span class="erotic-access-eyebrow">{{ $t('socialnetwork.erotic.eyebrow') }}</span>
|
||||
<h2>{{ $t('socialnetwork.erotic.accessTitle') }}</h2>
|
||||
<p>{{ $t('socialnetwork.erotic.accessIntro') }}</p>
|
||||
</section>
|
||||
|
||||
<section class="erotic-access-panel surface-card">
|
||||
<div class="erotic-access-status">
|
||||
<strong>{{ statusTitle }}</strong>
|
||||
<span>{{ statusText }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="account?.adultVerificationRequest" class="erotic-access-request">
|
||||
<strong>{{ $t('socialnetwork.erotic.requestInfoTitle') }}</strong>
|
||||
<span>{{ account.adultVerificationRequest.originalName }}</span>
|
||||
<span v-if="account.adultVerificationRequest.submittedAt">{{ formattedSubmittedAt }}</span>
|
||||
<span v-if="account.adultVerificationRequest.note">{{ account.adultVerificationRequest.note }}</span>
|
||||
</div>
|
||||
|
||||
<div class="erotic-access-actions">
|
||||
<router-link to="/settings/account" class="erotic-access-link">
|
||||
{{ $t('socialnetwork.erotic.settingsLink') }}
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<form v-if="canRequestVerification" class="erotic-access-form" @submit.prevent="requestVerification">
|
||||
<label>
|
||||
<span>{{ $t('socialnetwork.erotic.documentLabel') }}</span>
|
||||
<input type="file" accept=".jpg,.jpeg,.png,.webp,.pdf" @change="handleFileChange" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ $t('socialnetwork.erotic.noteLabel') }}</span>
|
||||
<textarea v-model="note" rows="4"></textarea>
|
||||
</label>
|
||||
<button type="submit">{{ $t('socialnetwork.erotic.requestVerification') }}</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { showApiError, showSuccess } from '@/utils/feedback.js';
|
||||
|
||||
export default {
|
||||
name: 'EroticAccessView',
|
||||
data() {
|
||||
return {
|
||||
account: null,
|
||||
note: '',
|
||||
documentFile: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user']),
|
||||
status() {
|
||||
return this.account?.adultVerificationStatus || 'none';
|
||||
},
|
||||
canRequestVerification() {
|
||||
return this.account?.isAdult && ['none', 'rejected'].includes(this.status);
|
||||
},
|
||||
statusTitle() {
|
||||
if (!this.account?.isAdult) {
|
||||
return this.$t('settings.account.adultStatus.ineligible.title');
|
||||
}
|
||||
return this.$t(`socialnetwork.erotic.status.${this.status}.title`);
|
||||
},
|
||||
statusText() {
|
||||
if (!this.account?.isAdult) {
|
||||
return this.$t('settings.account.adultStatus.ineligible.body');
|
||||
}
|
||||
return this.$t(`socialnetwork.erotic.status.${this.status}.body`);
|
||||
},
|
||||
formattedSubmittedAt() {
|
||||
const value = this.account?.adultVerificationRequest?.submittedAt;
|
||||
if (!value) return '';
|
||||
return new Date(value).toLocaleString();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadAccount() {
|
||||
const response = await apiClient.post('/api/settings/account', { userId: this.user.id });
|
||||
this.account = response.data;
|
||||
},
|
||||
handleFileChange(event) {
|
||||
this.documentFile = event.target.files?.[0] || null;
|
||||
},
|
||||
async requestVerification() {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
if (this.documentFile) {
|
||||
formData.append('document', this.documentFile);
|
||||
}
|
||||
formData.append('note', this.note || '');
|
||||
await apiClient.post('/api/settings/adult-verification/request', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
await this.loadAccount();
|
||||
this.note = '';
|
||||
this.documentFile = null;
|
||||
showSuccess(this, this.$t('socialnetwork.erotic.requestSent'));
|
||||
} catch (error) {
|
||||
showApiError(this, error, this.$t('socialnetwork.erotic.requestError'));
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadAccount();
|
||||
if (this.account?.adultAccessEnabled) {
|
||||
this.$router.replace('/socialnetwork/erotic/pictures');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.erotic-access-page {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
max-width: 920px;
|
||||
}
|
||||
|
||||
.erotic-access-hero,
|
||||
.erotic-access-panel {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: linear-gradient(180deg, rgba(255, 249, 244, 0.98), rgba(245, 237, 229, 0.96));
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.erotic-access-hero {
|
||||
padding: 26px 28px;
|
||||
}
|
||||
|
||||
.erotic-access-eyebrow {
|
||||
display: inline-flex;
|
||||
margin-bottom: 8px;
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: rgba(164, 98, 72, 0.14);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.erotic-access-hero p {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.erotic-access-panel {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.erotic-access-status {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
padding: 14px 16px;
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(196, 162, 108, 0.14);
|
||||
}
|
||||
|
||||
.erotic-access-request,
|
||||
.erotic-access-form {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.erotic-access-request {
|
||||
padding: 14px 16px;
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.erotic-access-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.erotic-access-link {
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.erotic-access-form label {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user