Initialer Laravel Commit für BetiX
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

This commit is contained in:
2026-04-04 18:01:50 +02:00
commit 0280278978
374 changed files with 65210 additions and 0 deletions

View 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>