Initialer Laravel Commit für BetiX
This commit is contained in:
420
resources/js/pages/Admin/UserShow.vue
Normal file
420
resources/js/pages/Admin/UserShow.vue
Normal file
@@ -0,0 +1,420 @@
|
||||
<script setup lang="ts">
|
||||
import { Head, Link, useForm } from '@inertiajs/vue3';
|
||||
import { ref } from 'vue';
|
||||
import CasinoAdminLayout from '@/layouts/admin/CasinoAdminLayout.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
user: any;
|
||||
restrictions: any[];
|
||||
wallets: any[];
|
||||
vaultTransfers: any[];
|
||||
kycDocuments: any[];
|
||||
}>();
|
||||
|
||||
const activeTab = ref('overview');
|
||||
|
||||
const form = useForm({
|
||||
username: props.user.username,
|
||||
email: props.user.email,
|
||||
first_name: props.user.first_name || '',
|
||||
last_name: props.user.last_name || '',
|
||||
birthdate: props.user.birthdate || '',
|
||||
gender: props.user.gender || '',
|
||||
phone: props.user.phone || '',
|
||||
country: props.user.country || '',
|
||||
address_line1: props.user.address_line1 || '',
|
||||
address_line2: props.user.address_line2 || '',
|
||||
city: props.user.city || '',
|
||||
postal_code: props.user.postal_code || '',
|
||||
currency: props.user.currency || '',
|
||||
role: props.user.role || 'User',
|
||||
vip_level: props.user.vip_level,
|
||||
balance: props.user.balance,
|
||||
vault_balance: props.user.vault_balance,
|
||||
is_banned: props.user.is_banned,
|
||||
is_chat_banned: props.user.is_chat_banned,
|
||||
ban_reason: '',
|
||||
ban_ends_at: '',
|
||||
chat_ban_ends_at: '',
|
||||
});
|
||||
|
||||
const saveUser = () => {
|
||||
form.post(`/admin/users/${props.user.id}`, {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
// Optional: Show toast
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
if (!dateString) return 'N/A';
|
||||
return new Date(dateString).toLocaleString();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CasinoAdminLayout>
|
||||
<Head :title="`User: ${user.username}`" />
|
||||
|
||||
<template #title>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="avatar large">{{ user.username.charAt(0).toUpperCase() }}</div>
|
||||
<div>
|
||||
<div>{{ user.username }} <span class="badge text-xs ml-2" :class="user.is_banned ? 'bg-red-500 text-white' : 'bg-gray-800 text-gray-400'">{{ user.is_banned ? 'BANNED' : user.role }}</span></div>
|
||||
<div class="text-sm text-gray-500 font-normal mt-1">{{ user.email }} • ID: {{ user.id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<Link href="/admin/users" class="btn-ghost">
|
||||
<i data-lucide="arrow-left"></i> Back to Users
|
||||
</Link>
|
||||
</template>
|
||||
|
||||
<div class="user-layout">
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="user-sidebar">
|
||||
<div class="panel">
|
||||
<nav class="side-nav">
|
||||
<button :class="{active: activeTab === 'overview'}" @click="activeTab = 'overview'">
|
||||
<i data-lucide="user"></i> Overview
|
||||
</button>
|
||||
<button :class="{active: activeTab === 'financials'}" @click="activeTab = 'financials'">
|
||||
<i data-lucide="wallet"></i> Financials & Wallets
|
||||
</button>
|
||||
<button :class="{active: activeTab === 'security'}" @click="activeTab = 'security'">
|
||||
<i data-lucide="shield-alert"></i> Security & Bans
|
||||
</button>
|
||||
<button :class="{active: activeTab === 'deposits'}" @click="activeTab = 'deposits'">
|
||||
<i data-lucide="coins"></i> Deposits
|
||||
</button>
|
||||
<button :class="{active: activeTab === 'kyc'}" @click="activeTab = 'kyc'">
|
||||
<i data-lucide="file-check"></i> KYC Documents
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="panel mt-4 p-4">
|
||||
<h4 class="text-xs font-bold text-gray-500 uppercase mb-3">Quick Stats</h4>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-400">Joined</span>
|
||||
<span class="text-white">{{ new Date(user.created_at).toLocaleDateString() }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-400">Last Login</span>
|
||||
<span class="text-white">{{ formatDate(user.last_login_at) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-400">VIP Level</span>
|
||||
<span class="text-white">{{ user.vip_level }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="user-content">
|
||||
<!-- Overview Tab -->
|
||||
<div v-show="activeTab === 'overview'" class="panel p-6">
|
||||
<h3 class="section-title">General Information</h3>
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>Username</label>
|
||||
<input type="text" v-model="form.username" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Email Address</label>
|
||||
<input type="email" v-model="form.email" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>First Name</label>
|
||||
<input type="text" v-model="form.first_name" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Last Name</label>
|
||||
<input type="text" v-model="form.last_name" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Birthdate</label>
|
||||
<input type="date" v-model="form.birthdate" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Gender</label>
|
||||
<select v-model="form.gender" class="input-field">
|
||||
<option value="">—</option>
|
||||
<option value="male">Male</option>
|
||||
<option value="female">Female</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Phone</label>
|
||||
<input type="text" v-model="form.phone" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Country</label>
|
||||
<input type="text" v-model="form.country" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Address line 1</label>
|
||||
<input type="text" v-model="form.address_line1" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Address line 2</label>
|
||||
<input type="text" v-model="form.address_line2" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>City</label>
|
||||
<input type="text" v-model="form.city" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Postal Code</label>
|
||||
<input type="text" v-model="form.postal_code" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Currency</label>
|
||||
<input type="text" v-model="form.currency" class="input-field" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Role</label>
|
||||
<select v-model="form.role" class="input-field">
|
||||
<option value="Admin">Admin</option>
|
||||
<option value="Moderator">Moderator</option>
|
||||
<option value="User">User</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button class="btn-primary" @click="saveUser" :disabled="form.processing">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Financials Tab -->
|
||||
<div v-show="activeTab === 'financials'" class="space-y-6">
|
||||
<div class="panel p-6">
|
||||
<h3 class="section-title">Balances</h3>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="form-group">
|
||||
<label>Main Balance ($)</label>
|
||||
<input type="text" v-model="form.balance" class="input-field font-mono text-lg" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Vault Balance ($)</label>
|
||||
<input type="text" v-model="form.vault_balance" class="input-field font-mono text-lg" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button class="btn-primary" @click="saveUser" :disabled="form.processing">Update Balances</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="p-6 border-b border-[#1f1f22]">
|
||||
<h3 class="section-title m-0">Vault History</h3>
|
||||
</div>
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Type</th>
|
||||
<th class="text-right">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="t in vaultTransfers" :key="t.id">
|
||||
<td class="text-gray-400">{{ formatDate(t.created_at) }}</td>
|
||||
<td><span class="badge bg-gray-800">{{ t.type }}</span></td>
|
||||
<td class="text-right font-mono font-bold" :class="t.amount > 0 ? 'text-green-400' : 'text-red-400'">
|
||||
{{ t.amount > 0 ? '+' : '' }}{{ t.amount }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!vaultTransfers?.length">
|
||||
<td colspan="3" class="text-center py-6 text-gray-500">No vault transfers found.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security & Bans Tab -->
|
||||
<div v-show="activeTab === 'security'" class="space-y-6">
|
||||
<div class="panel p-6 border border-orange-900/30 bg-orange-900/5">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h3 class="text-orange-400 font-bold text-lg mb-1 flex items-center gap-2">
|
||||
<i data-lucide="message-circle-off"></i> Chat Ban
|
||||
</h3>
|
||||
<p class="text-sm text-gray-400">Mute this user from sending live chat messages.</p>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" v-model="form.is_chat_banned" />
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="form.is_chat_banned" class="space-y-4 pt-4 border-t border-orange-900/20">
|
||||
<div class="form-group">
|
||||
<label class="text-orange-400">Chat Ban Expiration</label>
|
||||
<input type="datetime-local" v-model="form.chat_ban_ends_at" class="input-field border-orange-900/50 focus:border-orange-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button class="bg-orange-600 hover:bg-orange-500 text-white px-4 py-2 rounded-lg font-bold transition" @click="saveUser">Apply Chat Ban</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel p-6 border border-red-900/30 bg-red-900/5">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h3 class="text-red-500 font-bold text-lg mb-1 flex items-center gap-2">
|
||||
<i data-lucide="shield-alert"></i> Account Ban
|
||||
</h3>
|
||||
<p class="text-sm text-gray-400">Completely lock this user out of the platform.</p>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" v-model="form.is_banned" />
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="form.is_banned" class="space-y-4 pt-4 border-t border-red-900/20">
|
||||
<div class="form-group">
|
||||
<label class="text-red-400">Ban Reason (Shown to user)</label>
|
||||
<input type="text" v-model="form.ban_reason" class="input-field border-red-900/50 focus:border-red-500" placeholder="e.g. Fraudulent Activity" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="text-red-400">Ban Expiration (Leave empty for permanent)</label>
|
||||
<input type="datetime-local" v-model="form.ban_ends_at" class="input-field border-red-900/50 focus:border-red-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button class="bg-red-600 hover:bg-red-500 text-white px-4 py-2 rounded-lg font-bold transition" @click="saveUser">Apply Security Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel p-6">
|
||||
<h3 class="section-title">Active Restrictions Log</h3>
|
||||
<div class="space-y-3">
|
||||
<div v-for="res in restrictions" :key="res.id" class="p-3 bg-[#161618] rounded-lg border border-[#27272a] flex justify-between items-center">
|
||||
<div>
|
||||
<div class="font-bold text-white">{{ res.type }}</div>
|
||||
<div class="text-xs text-gray-400">{{ res.reason || 'No reason provided' }}</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-xs" :class="res.active ? 'text-red-400' : 'text-gray-500'">{{ res.active ? 'ACTIVE' : 'EXPIRED' }}</div>
|
||||
<div class="text-xs text-gray-500">{{ formatDate(res.created_at) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!restrictions?.length" class="text-gray-500 text-sm">No restrictions on record.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Deposits Tab -->
|
||||
<div v-show="activeTab === 'deposits'" class="panel">
|
||||
<div class="p-6 border-b border-[#1f1f22]">
|
||||
<h3 class="section-title m-0">Crypto Deposits</h3>
|
||||
</div>
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Created</th>
|
||||
<th>Status</th>
|
||||
<th>Currency</th>
|
||||
<th class="text-right">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="d in (props as any).deposits" :key="d.id">
|
||||
<td class="text-gray-400">{{ formatDate(d.created_at) }}</td>
|
||||
<td><span class="badge" :class="{'bg-green-600 text-white': d.status==='finished', 'bg-yellow-600 text-white': d.status==='waiting'}">{{ d.status }}</span></td>
|
||||
<td class="text-gray-300">{{ d.pay_currency }}</td>
|
||||
<td class="text-right font-mono">{{ d.price_amount }}</td>
|
||||
</tr>
|
||||
<tr v-if="!(props as any).deposits?.length">
|
||||
<td colspan="4" class="text-center py-6 text-gray-500">No deposits yet.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- KYC Tab -->
|
||||
<div v-show="activeTab === 'kyc'" class="panel p-6">
|
||||
<h3 class="section-title">KYC Documents</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="doc in kycDocuments" :key="doc.id" class="border border-[#27272a] rounded-xl p-4 bg-[#161618]">
|
||||
<div class="flex justify-between items-start mb-3">
|
||||
<div>
|
||||
<div class="font-bold text-white">{{ doc.document_type }}</div>
|
||||
<div class="text-xs text-gray-400">Uploaded: {{ formatDate(doc.created_at) }}</div>
|
||||
</div>
|
||||
<span class="badge" :class="{'bg-green-500': doc.status === 'approved', 'bg-yellow-500': doc.status === 'pending', 'bg-red-500': doc.status === 'rejected'}">{{ doc.status }}</span>
|
||||
</div>
|
||||
<div class="aspect-video bg-black rounded-lg flex items-center justify-center overflow-hidden border border-[#27272a]">
|
||||
<img v-if="doc.file_url" :src="doc.file_url" class="w-full h-full object-cover" />
|
||||
<i v-else data-lucide="file-image" class="w-10 h-10 text-gray-600"></i>
|
||||
</div>
|
||||
<div class="mt-4 flex gap-2">
|
||||
<button class="flex-1 bg-green-600 hover:bg-green-500 text-white py-2 rounded-lg font-bold text-sm transition">Approve</button>
|
||||
<button class="flex-1 bg-red-600 hover:bg-red-500 text-white py-2 rounded-lg font-bold text-sm transition">Reject</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!kycDocuments?.length" class="col-span-full text-center py-10 text-gray-500">
|
||||
No KYC documents submitted yet.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</CasinoAdminLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.user-layout { display: grid; grid-template-columns: 240px 1fr; gap: 24px; align-items: start; }
|
||||
@media (max-width: 900px) { .user-layout { grid-template-columns: 1fr; } }
|
||||
|
||||
.panel { background: #111113; border: 1px solid #1f1f22; border-radius: 16px; overflow: hidden; }
|
||||
|
||||
.side-nav { display: flex; flex-direction: column; padding: 12px; gap: 4px; }
|
||||
.side-nav button { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-radius: 10px; background: transparent; border: none; color: #a1a1aa; font-weight: 600; font-size: 14px; cursor: pointer; transition: 0.2s; text-align: left; }
|
||||
.side-nav button i { width: 18px; height: 18px; }
|
||||
.side-nav button:hover { background: #1a1a1d; color: #fff; }
|
||||
.side-nav button.active { background: #27272a; color: #fff; }
|
||||
|
||||
.section-title { font-size: 16px; font-weight: 800; color: #fff; margin-bottom: 20px; border-bottom: 1px solid #1f1f22; padding-bottom: 12px; }
|
||||
|
||||
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
.form-group { display: flex; flex-direction: column; gap: 6px; }
|
||||
.form-group label { font-size: 12px; font-weight: 700; color: #a1a1aa; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.input-field { background: #09090b; border: 1px solid #27272a; border-radius: 10px; padding: 12px 16px; color: #fff; outline: none; font-size: 14px; transition: border-color 0.2s; }
|
||||
.input-field:focus { border-color: #3b82f6; }
|
||||
.input-field.disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.btn-primary { background: #3b82f6; color: white; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 700; cursor: pointer; transition: 0.2s; }
|
||||
.btn-primary:hover:not(:disabled) { background: #2563eb; }
|
||||
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.btn-ghost { background: transparent; border: none; color: #a1a1aa; font-weight: 600; font-size: 14px; cursor: pointer; display: flex; align-items: center; gap: 6px; text-decoration: none; }
|
||||
.btn-ghost:hover { color: #fff; }
|
||||
.btn-ghost i { width: 16px; height: 16px; }
|
||||
|
||||
.avatar { background: #27272a; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 800; color: #fff; }
|
||||
.avatar.large { width: 48px; height: 48px; font-size: 20px; background: rgba(59, 130, 246, 0.2); color: #3b82f6; border: 1px solid rgba(59, 130, 246, 0.3); }
|
||||
|
||||
.badge { padding: 4px 8px; border-radius: 6px; font-weight: 800; }
|
||||
|
||||
.admin-table { width: 100%; border-collapse: collapse; text-align: left; }
|
||||
.admin-table th { padding: 12px 24px; font-size: 12px; font-weight: 600; color: #a1a1aa; text-transform: uppercase; background: #0c0c0e; border-bottom: 1px solid #1f1f22; }
|
||||
.admin-table td { padding: 16px 24px; font-size: 14px; border-bottom: 1px solid #1f1f22; color: #e4e4e7; }
|
||||
.admin-table tr:last-child td { border-bottom: none; }
|
||||
|
||||
/* Toggle Switch */
|
||||
.toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; }
|
||||
.toggle-switch input { opacity: 0; width: 0; height: 0; }
|
||||
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #27272a; transition: .4s; border-radius: 34px; }
|
||||
.slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
|
||||
input:checked + .slider { background-color: #ef4444; }
|
||||
input:checked + .slider:before { transform: translateX(20px); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user