259 lines
7.3 KiB
Vue
259 lines
7.3 KiB
Vue
<template>
|
|
<div class="adult-verification">
|
|
<section class="adult-verification__hero surface-card">
|
|
<span class="adult-verification__eyebrow">Administration</span>
|
|
<h1>{{ $t('admin.adultVerification.title') }}</h1>
|
|
<p>{{ $t('admin.adultVerification.intro') }}</p>
|
|
</section>
|
|
|
|
<section class="adult-verification__filters surface-card">
|
|
<button
|
|
v-for="option in filterOptions"
|
|
:key="option.value"
|
|
type="button"
|
|
:class="{ active: statusFilter === option.value }"
|
|
@click="changeFilter(option.value)"
|
|
>
|
|
{{ option.label }}
|
|
</button>
|
|
</section>
|
|
|
|
<section class="adult-verification__list surface-card">
|
|
<div v-if="loading" class="adult-verification__state">{{ $t('general.loading') }}</div>
|
|
<div v-else-if="rows.length === 0" class="adult-verification__state">{{ $t('admin.adultVerification.empty') }}</div>
|
|
<table v-else>
|
|
<thead>
|
|
<tr>
|
|
<th>{{ $t('admin.adultVerification.username') }}</th>
|
|
<th>{{ $t('admin.adultVerification.age') }}</th>
|
|
<th>{{ $t('admin.adultVerification.statusLabel') }}</th>
|
|
<th>{{ $t('admin.adultVerification.requestLabel') }}</th>
|
|
<th>{{ $t('admin.adultVerification.actions') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="row in rows" :key="row.id">
|
|
<td>{{ row.username }}</td>
|
|
<td>{{ row.age }}</td>
|
|
<td>
|
|
<span class="adult-verification__badge" :class="`adult-verification__badge--${row.adultVerificationStatus}`">
|
|
{{ $t(`admin.adultVerification.status.${row.adultVerificationStatus}`) }}
|
|
</span>
|
|
</td>
|
|
<td class="adult-verification__request">
|
|
<template v-if="row.adultVerificationRequest">
|
|
<strong>{{ row.adultVerificationRequest.originalName }}</strong>
|
|
<span v-if="row.adultVerificationRequest.note">{{ row.adultVerificationRequest.note }}</span>
|
|
<button type="button" class="secondary" @click="openDocument(row)">
|
|
{{ $t('admin.adultVerification.openDocument') }}
|
|
</button>
|
|
</template>
|
|
<span v-else>—</span>
|
|
</td>
|
|
<td class="adult-verification__actions">
|
|
<button type="button" @click="setStatus(row, 'approved')">{{ $t('admin.adultVerification.approve') }}</button>
|
|
<button type="button" class="secondary" @click="setStatus(row, 'rejected')">{{ $t('admin.adultVerification.reject') }}</button>
|
|
<button
|
|
v-if="row.adultVerificationStatus !== 'pending'"
|
|
type="button"
|
|
class="secondary"
|
|
@click="setStatus(row, 'pending')"
|
|
>
|
|
{{ $t('admin.adultVerification.resetPending') }}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import apiClient from '@/utils/axios.js';
|
|
import { showApiError, showSuccess } from '@/utils/feedback.js';
|
|
|
|
export default {
|
|
name: 'AdminAdultVerificationView',
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
statusFilter: 'pending',
|
|
rows: []
|
|
};
|
|
},
|
|
computed: {
|
|
filterOptions() {
|
|
return [
|
|
{ value: 'pending', label: this.$t('admin.adultVerification.filters.pending') },
|
|
{ value: 'approved', label: this.$t('admin.adultVerification.filters.approved') },
|
|
{ value: 'rejected', label: this.$t('admin.adultVerification.filters.rejected') },
|
|
{ value: 'all', label: this.$t('admin.adultVerification.filters.all') }
|
|
];
|
|
}
|
|
},
|
|
methods: {
|
|
async load() {
|
|
this.loading = true;
|
|
try {
|
|
const response = await apiClient.get('/api/admin/users/adult-verification', {
|
|
params: { status: this.statusFilter }
|
|
});
|
|
this.rows = response.data || [];
|
|
} catch (error) {
|
|
this.rows = [];
|
|
showApiError(this, error, this.$t('admin.adultVerification.loadError'));
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
async changeFilter(filter) {
|
|
this.statusFilter = filter;
|
|
await this.load();
|
|
},
|
|
async setStatus(row, status) {
|
|
try {
|
|
await apiClient.put(`/api/admin/users/${row.id}/adult-verification`, { status });
|
|
showSuccess(this, this.$t(`admin.adultVerification.messages.${status}`));
|
|
await this.load();
|
|
} catch (error) {
|
|
showApiError(this, error, this.$t('admin.adultVerification.updateError'));
|
|
}
|
|
},
|
|
async openDocument(row) {
|
|
try {
|
|
const response = await apiClient.get(`/api/admin/users/${row.id}/adult-verification/document`, {
|
|
responseType: 'blob'
|
|
});
|
|
const url = window.URL.createObjectURL(response.data);
|
|
window.open(url, '_blank', 'noopener');
|
|
window.setTimeout(() => window.URL.revokeObjectURL(url), 10000);
|
|
} catch (error) {
|
|
showApiError(this, error, this.$t('admin.adultVerification.documentError'));
|
|
}
|
|
}
|
|
},
|
|
async mounted() {
|
|
await this.load();
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.adult-verification {
|
|
display: grid;
|
|
gap: 18px;
|
|
}
|
|
|
|
.adult-verification__hero,
|
|
.adult-verification__filters,
|
|
.adult-verification__list {
|
|
background: linear-gradient(180deg, rgba(255, 252, 247, 0.97), rgba(250, 244, 235, 0.95));
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-soft);
|
|
padding: 22px 24px;
|
|
}
|
|
|
|
.adult-verification__eyebrow {
|
|
display: inline-flex;
|
|
margin-bottom: 8px;
|
|
padding: 4px 10px;
|
|
border-radius: var(--radius-pill);
|
|
background: var(--color-secondary-soft);
|
|
color: var(--color-text-secondary);
|
|
font-size: 0.78rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.adult-verification__hero p,
|
|
.adult-verification__state {
|
|
margin: 0;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.adult-verification__filters {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.adult-verification__filters button.active {
|
|
background: var(--color-primary);
|
|
color: #fff;
|
|
}
|
|
|
|
.adult-verification table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.adult-verification th,
|
|
.adult-verification td {
|
|
padding: 12px 10px;
|
|
border-bottom: 1px solid rgba(93, 64, 55, 0.08);
|
|
text-align: left;
|
|
}
|
|
|
|
.adult-verification__badge {
|
|
display: inline-flex;
|
|
padding: 4px 10px;
|
|
border-radius: var(--radius-pill);
|
|
font-weight: 700;
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
.adult-verification__badge--pending {
|
|
background: rgba(216, 167, 65, 0.16);
|
|
}
|
|
|
|
.adult-verification__badge--approved {
|
|
background: rgba(92, 156, 106, 0.18);
|
|
}
|
|
|
|
.adult-verification__badge--rejected {
|
|
background: rgba(176, 88, 88, 0.16);
|
|
}
|
|
|
|
.adult-verification__actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.adult-verification__request {
|
|
display: grid;
|
|
gap: 4px;
|
|
}
|
|
|
|
.adult-verification__actions .secondary {
|
|
background: transparent;
|
|
}
|
|
|
|
@media (max-width: 760px) {
|
|
.adult-verification__hero,
|
|
.adult-verification__filters,
|
|
.adult-verification__list {
|
|
padding: 18px;
|
|
}
|
|
|
|
.adult-verification table,
|
|
.adult-verification thead,
|
|
.adult-verification tbody,
|
|
.adult-verification tr,
|
|
.adult-verification td {
|
|
display: block;
|
|
}
|
|
|
|
.adult-verification thead {
|
|
display: none;
|
|
}
|
|
|
|
.adult-verification td {
|
|
padding: 8px 0;
|
|
}
|
|
}
|
|
</style>
|