|
|
<x-app-layout> |
|
|
<x-slot name="header"> |
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<h1 class="heading-2 mb-2">Product Management</h1> |
|
|
<p class="text-muted">Manage your store inventory with precision and ease</p> |
|
|
</div> |
|
|
<button id="show_category_modal" class="btn-primary"> |
|
|
<i class="fas fa-plus mr-2"></i>Add New Product |
|
|
</button> |
|
|
</div> |
|
|
</x-slot> |
|
|
|
|
|
<div class="py-12"> |
|
|
<div class="container-custom"> |
|
|
@if (session('success')) |
|
|
<div class="card mb-6 bg-green-900/30 border-green-500/30"> |
|
|
<div class="flex items-center"> |
|
|
<i class="fas fa-check-circle text-green-400 mr-3 text-xl"></i> |
|
|
<span class="text-green-300 font-medium">{{ session('success') }}</span> |
|
|
</div> |
|
|
</div> |
|
|
@endif |
|
|
|
|
|
<!-- Product Inventory Card --> |
|
|
<div class="card mb-8"> |
|
|
<!-- Header Section --> |
|
|
<div class="flex flex-col lg:flex-row lg:items-center justify-between mb-8 gap-6"> |
|
|
<div class="flex items-center"> |
|
|
<div class="w-12 h-12 bg-gradient-to-r from-blue-500 to-purple-500 rounded-xl flex items-center justify-center mr-4"> |
|
|
<i class="fas fa-boxes text-white"></i> |
|
|
</div> |
|
|
<div> |
|
|
<h2 class="heading-3 mb-1">Product Inventory</h2> |
|
|
<p class="text-muted"> |
|
|
@if(request()->hasAny(['game', 'stock', 'search'])) |
|
|
{{ $products->count() }} filtered results |
|
|
@if($products->count() > 0) |
|
|
<span class="text-white/40">•</span> |
|
|
<a href="{{ route('products.listOFproduct') }}" class="text-purple-400 hover:text-purple-300 text-sm"> |
|
|
View all products |
|
|
</a> |
|
|
@endif |
|
|
@else |
|
|
{{ $products->count() }} products in your catalog |
|
|
@endif |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Enhanced Search and Filter Section --> |
|
|
<div class="mb-8"> |
|
|
<form method="GET" action="{{ route('products.listOFproduct') }}" class="space-y-4"> |
|
|
<!-- Search Bar - Full Width --> |
|
|
<div class="relative"> |
|
|
<input type="text" name="search" value="{{ request('search') }}" |
|
|
placeholder="🔍 Search products by name or description..." |
|
|
class="input-field pl-12 pr-4 w-full text-lg py-4 bg-black/40 border-2 border-white/20 focus:border-purple-500/60 rounded-xl"> |
|
|
<i class="fas fa-search absolute left-4 top-1/2 transform -translate-y-1/2 text-white/60 text-lg"></i> |
|
|
@if(request('search')) |
|
|
<button type="button" onclick="clearSearch()" class="absolute right-4 top-1/2 transform -translate-y-1/2 text-white/60 hover:text-white"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
@endif |
|
|
</div> |
|
|
|
|
|
<!-- Filters Row --> |
|
|
<div class="flex flex-col sm:flex-row items-center gap-4"> |
|
|
<select name="stock" class="input-field flex-1 min-w-0"> |
|
|
<option value="">📦 All Stock</option> |
|
|
<option value="in_stock" {{ request('stock') == 'in_stock' ? 'selected' : '' }}> |
|
|
✅ In Stock |
|
|
</option> |
|
|
<option value="low_stock" {{ request('stock') == 'low_stock' ? 'selected' : '' }}> |
|
|
⚠️ Low Stock |
|
|
</option> |
|
|
<option value="out_of_stock" {{ request('stock') == 'out_of_stock' ? 'selected' : '' }}> |
|
|
❌ Out of Stock |
|
|
</option> |
|
|
</select> |
|
|
|
|
|
<select name="game" class="input-field flex-1 min-w-0"> |
|
|
<option value="">🎮 All Categories</option> |
|
|
<optgroup label="Main Categories"> |
|
|
<option value="Genshin" {{ request('game') == 'Genshin' ? 'selected' : '' }}>⭐ Genshin Impact</option> |
|
|
<option value="Starrail" {{ request('game') == 'Starrail' ? 'selected' : '' }}>🚀 Honkai: Star Rail</option> |
|
|
<option value="WutheringWave" {{ request('game') == 'WutheringWave' ? 'selected' : '' }}>🌊 Wuthering Waves</option> |
|
|
</optgroup> |
|
|
<optgroup label="Additional Games"> |
|
|
<option value="ZenlessZoneZero" {{ request('game') == 'ZenlessZoneZero' ? 'selected' : '' }}>🏙️ Zenless Zone Zero</option> |
|
|
<option value="Arknights" {{ request('game') == 'Arknights' ? 'selected' : '' }}>♞ Arknights</option> |
|
|
<option value="AzurLane" {{ request('game') == 'AzurLane' ? 'selected' : '' }}>🚢 Azur Lane</option> |
|
|
</optgroup> |
|
|
@if(isset($customGames) && $customGames->count() > 0) |
|
|
<optgroup label="Custom Games"> |
|
|
@foreach($customGames as $customGame) |
|
|
<option value="{{ $customGame->name }}" {{ request('game') == $customGame->name ? 'selected' : '' }}> |
|
|
🎮 {{ $customGame->name }} |
|
|
</option> |
|
|
@endforeach |
|
|
</optgroup> |
|
|
@endif |
|
|
</select> |
|
|
|
|
|
<div class="flex items-center gap-2 flex-shrink-0"> |
|
|
<button type="submit" class="btn-primary px-6 py-3"> |
|
|
<i class="fas fa-filter mr-2"></i>Apply Filters |
|
|
</button> |
|
|
<a href="{{ route('products.listOFproduct') }}" class="btn-secondary px-4 py-3"> |
|
|
<i class="fas fa-refresh mr-1"></i>Clear |
|
|
</a> |
|
|
</div> |
|
|
</div> |
|
|
</form> |
|
|
</div> |
|
|
|
|
|
@if($products->isEmpty()) |
|
|
<div class="text-center py-16"> |
|
|
<div class="w-20 h-20 bg-gradient-to-r from-gray-600 to-gray-800 rounded-full flex items-center justify-center mx-auto mb-6 opacity-50"> |
|
|
<i class="fas fa-box-open text-3xl text-white"></i> |
|
|
</div> |
|
|
<h3 class="heading-3 mb-4">No Products Yet</h3> |
|
|
<p class="text-muted mb-8">Start building your inventory by adding your first product.</p> |
|
|
<button class="btn-primary" onclick="document.getElementById('show_category_modal').click()"> |
|
|
<i class="fas fa-plus mr-2"></i>Add Your First Product |
|
|
</button> |
|
|
</div> |
|
|
@else |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8"> |
|
|
@foreach($products as $product) |
|
|
<div class="product-card glass-bg border border-white/10 rounded-2xl overflow-hidden transition-all duration-500 hover:border-purple-500/50 hover:shadow-2xl hover:shadow-purple-500/20 group transform hover:scale-105"> |
|
|
<!-- Product Image --> |
|
|
<div class="relative h-64 overflow-hidden"> |
|
|
<img src="{{ $product->image }}" alt="{{ $product->name }}" |
|
|
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"> |
|
|
|
|
|
<!-- Gradient Overlay --> |
|
|
<div class="absolute inset-0 bg-gradient-to-t from-black/90 via-black/30 to-transparent"></div> |
|
|
|
|
|
<!-- Stock Badge --> |
|
|
<div class="absolute top-4 right-4"> |
|
|
@if($product->Amount === 0) |
|
|
<span class="px-4 py-2 rounded-xl text-sm font-semibold bg-red-500/90 text-white border border-red-400/50 shadow-lg backdrop-blur-sm"> |
|
|
<i class="fas fa-times-circle mr-2"></i>Out of Stock |
|
|
</span> |
|
|
@elseif($product->Amount <= 5) |
|
|
<span class="px-4 py-2 rounded-xl text-sm font-semibold bg-orange-500/90 text-white border border-orange-400/50 shadow-lg backdrop-blur-sm animate-pulse"> |
|
|
<i class="fas fa-exclamation-triangle mr-2"></i>{{ $product->Amount }} Left |
|
|
</span> |
|
|
@else |
|
|
<span class="px-4 py-2 rounded-xl text-sm font-semibold bg-green-500/90 text-white border border-green-400/50 shadow-lg backdrop-blur-sm"> |
|
|
<i class="fas fa-check-circle mr-2"></i>In Stock |
|
|
</span> |
|
|
@endif |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Product Content --> |
|
|
<div class="p-6"> |
|
|
<!-- Price Display --> |
|
|
<div class="mb-4"> |
|
|
<div class="bg-black/90 backdrop-blur-sm rounded-xl px-5 py-3 border border-purple-500/30"> |
|
|
<div class="text-xs text-white/60 mb-1">Price</div> |
|
|
<div class="text-2xl font-bold text-white"> |
|
|
฿{{ number_format($product->price, 2) }} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Game Category Badge --> |
|
|
@if($product->game) |
|
|
<div class="mb-4"> |
|
|
<span class="px-3 py-2 rounded-full text-sm font-semibold |
|
|
@if($product->game == 'Genshin') |
|
|
bg-yellow-500/20 text-yellow-300 border border-yellow-500/30 |
|
|
@elseif($product->game == 'Starrail') |
|
|
bg-purple-500/20 text-purple-300 border border-purple-500/30 |
|
|
@elseif($product->game == 'WutheringWave') |
|
|
bg-cyan-500/20 text-cyan-300 border border-cyan-500/30 |
|
|
@else |
|
|
bg-blue-500/20 text-blue-300 border border-blue-500/30 |
|
|
@endif"> |
|
|
@if($product->game == 'Genshin') |
|
|
<i class="fas fa-star mr-2"></i>Genshin Impact |
|
|
@elseif($product->game == 'Starrail') |
|
|
<i class="fas fa-rocket mr-2"></i>Honkai: Star Rail |
|
|
@elseif($product->game == 'WutheringWave') |
|
|
<i class="fas fa-wave-square mr-2"></i>Wuthering Waves |
|
|
@else |
|
|
<i class="fas fa-gamepad mr-2"></i>{{ $product->game }} |
|
|
@endif |
|
|
</span> |
|
|
</div> |
|
|
@endif |
|
|
|
|
|
<!-- Product Title --> |
|
|
<h3 class="text-xl font-bold text-white mb-3 group-hover:text-purple-300 transition-colors duration-300"> |
|
|
{{ $product->name }} |
|
|
</h3> |
|
|
|
|
|
<!-- Product Description --> |
|
|
<p class="text-white/70 text-sm mb-6 line-clamp-3 leading-relaxed"> |
|
|
{{ $product->description }} |
|
|
</p> |
|
|
|
|
|
<!-- Action Buttons --> |
|
|
<div class="flex items-center space-x-3"> |
|
|
<button class="flex-1 py-3 px-4 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-500 hover:to-purple-500 text-white rounded-xl font-semibold transition-all duration-300 shadow-lg hover:shadow-purple-500/30 transform hover:scale-105" onclick="editProduct({{ $product->id }}, '{{ addslashes($product->name) }}', '{{ addslashes($product->description) }}', {{ $product->price }}, {{ $product->Amount }}, '{{ $product->game }}', '{{ $product->image }}')"> |
|
|
<i class="fas fa-edit mr-2"></i>Edit |
|
|
</button> |
|
|
<form action="{{ route('products.destroy', $product->id) }}" method="POST" class="inline"> |
|
|
@csrf |
|
|
@method('DELETE') |
|
|
<button type="submit" class="py-3 px-4 bg-gradient-to-r from-red-600 to-red-700 hover:from-red-500 hover:to-red-600 text-white rounded-xl font-semibold transition-all duration-300 shadow-lg hover:shadow-red-500/30 transform hover:scale-105" |
|
|
onclick="return confirm('Are you sure you want to delete this product?')"> |
|
|
<i class="fas fa-trash mr-2"></i>Delete |
|
|
</button> |
|
|
</form> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
@endforeach |
|
|
</div> |
|
|
@endif |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Add/Edit Product Modal --> |
|
|
<div id="productModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm hidden items-center justify-center z-50 p-4"> |
|
|
<div class="card max-w-2xl w-full max-h-[90vh] flex flex-col"> |
|
|
<div class="flex-shrink-0 flex items-center justify-between mb-6"> |
|
|
<h3 class="heading-3" id="modalTitle">Add New Product</h3> |
|
|
<button onclick="closeModal()" class="text-white/60 hover:text-white text-2xl transition-colors"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="flex-1 overflow-y-auto custom-scrollbar pr-2"> |
|
|
<form id="productForm" method="POST" enctype="multipart/form-data" class="space-y-6"> |
|
|
@csrf |
|
|
<div id="methodField"></div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Product Name</label> |
|
|
<input type="text" name="name" id="productName" required class="input-field"> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Description</label> |
|
|
<textarea name="description" id="productDescription" rows="3" class="input-field resize-none"></textarea> |
|
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Price (฿)</label> |
|
|
<input type="number" name="price" id="productPrice" step="0.01" required class="input-field"> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Stock Amount</label> |
|
|
<input type="number" name="Amount" id="productAmount" required class="input-field"> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Game Category</label> |
|
|
<select name="game" id="productGame" class="input-field"> |
|
|
<option value="">Select Game</option> |
|
|
<optgroup label="Main Categories"> |
|
|
<option value="Genshin">Genshin Impact</option> |
|
|
<option value="Starrail">Honkai: Star Rail</option> |
|
|
<option value="WutheringWave">Wuthering Waves</option> |
|
|
</optgroup> |
|
|
<optgroup label="Additional Games"> |
|
|
<option value="ZenlessZoneZero">Zenless Zone Zero</option> |
|
|
<option value="Arknights">Arknights</option> |
|
|
<option value="AzurLane">Azur Lane</option> |
|
|
</optgroup> |
|
|
@if(isset($customGames) && $customGames->count() > 0) |
|
|
<optgroup label="Custom Games" id="existingCustomGames"> |
|
|
@foreach($customGames as $customGame) |
|
|
<option value="{{ $customGame->name }}">{{ $customGame->name }}</option> |
|
|
@endforeach |
|
|
</optgroup> |
|
|
@endif |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<!-- Product Specifications Section --> |
|
|
<div class="border-t border-white/10 pt-6"> |
|
|
<h4 class="text-lg font-semibold text-white mb-4 flex items-center"> |
|
|
<i class="fas fa-cog mr-2 text-purple-400"></i> |
|
|
Product Specifications |
|
|
</h4> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Product Type</label> |
|
|
<select name="product_type" id="productType" class="input-field"> |
|
|
<option value="Digital Account">Digital Account</option> |
|
|
<option value="Game Currency">Game Currency</option> |
|
|
<option value="Items">Items</option> |
|
|
<option value="Boost Service">Boost Service</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Platform</label> |
|
|
<select name="platform" id="productPlatform" class="input-field"> |
|
|
<option value="Multi-Platform">Multi-Platform</option> |
|
|
<option value="PC">PC</option> |
|
|
<option value="Mobile">Mobile</option> |
|
|
<option value="PlayStation">PlayStation</option> |
|
|
<option value="Xbox">Xbox</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Region</label> |
|
|
<select name="region" id="productRegion" class="input-field"> |
|
|
<option value="Global">Global</option> |
|
|
<option value="Asia">Asia</option> |
|
|
<option value="Europe">Europe</option> |
|
|
<option value="America">America</option> |
|
|
<option value="China">China</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Delivery Method</label> |
|
|
<select name="delivery_method" id="deliveryMethod" class="input-field"> |
|
|
<option value="Instant Digital">Instant Digital</option> |
|
|
<option value="Manual Delivery">Manual Delivery</option> |
|
|
<option value="Email Delivery">Email Delivery</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Warranty Period</label> |
|
|
<select name="warranty" id="productWarranty" class="input-field"> |
|
|
<option value="Lifetime Support">Lifetime Support</option> |
|
|
<option value="1 Year">1 Year</option> |
|
|
<option value="6 Months">6 Months</option> |
|
|
<option value="3 Months">3 Months</option> |
|
|
<option value="1 Month">1 Month</option> |
|
|
<option value="No Warranty">No Warranty</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Support Level</label> |
|
|
<select name="support_level" id="supportLevel" class="input-field"> |
|
|
<option value="24/7 Available">24/7 Available</option> |
|
|
<option value="Business Hours">Business Hours</option> |
|
|
<option value="Email Only">Email Only</option> |
|
|
<option value="Limited Support">Limited Support</option> |
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-4"> |
|
|
<label class="block text-sm font-medium text-white mb-2">Additional Features</label> |
|
|
<textarea name="features" id="productFeatures" rows="3" placeholder="List key features, benefits, or special notes about this product..." class="input-field resize-none"></textarea> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-white mb-2">Product Image</label> |
|
|
<div id="currentImagePreview" class="hidden mb-4"> |
|
|
<div class="text-sm text-white/70 mb-2">Current Image:</div> |
|
|
<img id="currentImage" src="" alt="Current product image" |
|
|
class="w-32 h-32 object-cover rounded-lg border border-white/20"> |
|
|
</div> |
|
|
|
|
|
<!-- Custom File Upload --> |
|
|
<div class="relative"> |
|
|
<div id="fileUploadArea" class="border-2 border-dashed border-white/20 rounded-xl p-6 text-center hover:border-purple-400/50 transition-all duration-300 cursor-pointer bg-black/20 hover:bg-black/30"> |
|
|
<div id="uploadContent"> |
|
|
<i class="fas fa-cloud-upload-alt text-3xl text-white/40 mb-3"></i> |
|
|
<div class="text-white/80 mb-2"> |
|
|
<span class="text-purple-400 hover:text-purple-300 font-medium">Choose file</span> |
|
|
<span class="text-white/60"> or drag and drop</span> |
|
|
</div> |
|
|
<p class="text-xs text-white/50">PNG, JPG, GIF up to 10MB</p> |
|
|
</div> |
|
|
<div id="fileInfo" class="hidden"> |
|
|
<i class="fas fa-file-image text-2xl text-green-400 mb-2"></i> |
|
|
<div class="text-green-400 font-medium" id="fileName"></div> |
|
|
<div class="text-white/60 text-sm" id="fileSize"></div> |
|
|
<button type="button" id="removeFile" class="mt-2 text-red-400 hover:text-red-300 text-sm transition-colors"> |
|
|
<i class="fas fa-times mr-1"></i>Remove |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<input type="file" name="image" id="productImage" accept="image |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.custom-scrollbar::-webkit-scrollbar { |
|
|
width: 8px; |
|
|
} |
|
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-track { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
border-radius: 4px; |
|
|
} |
|
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-thumb { |
|
|
background: rgba(102, 126, 234, 0.6); |
|
|
border-radius: 4px; |
|
|
transition: background 0.3s ease; |
|
|
} |
|
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover { |
|
|
background: rgba(102, 126, 234, 0.8); |
|
|
} |
|
|
|
|
|
|
|
|
.custom-scrollbar { |
|
|
scrollbar-width: thin; |
|
|
scrollbar-color: rgba(102, 126, 234, 0.6) rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
z-index: 99999 !important; |
|
|
position: fixed !important; |
|
|
top: auto !important; |
|
|
right: auto !important; |
|
|
} |
|
|
|
|
|
|
|
|
.relative { |
|
|
position: relative; |
|
|
z-index: auto; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
position: fixed !important; |
|
|
z-index: 99999 !important; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
transform: translateY(-10px); |
|
|
opacity: 0; |
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|
|
pointer-events: none; |
|
|
} |
|
|
|
|
|
|
|
|
transform: translateY(0); |
|
|
opacity: 1; |
|
|
pointer-events: auto; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
|
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.2); |
|
|
} |
|
|
|
|
|
|
|
|
border-color: rgba(102, 126, 234, 0.8) !important; |
|
|
background: rgba(102, 126, 234, 0.1) !important; |
|
|
transform: scale(1.02); |
|
|
} |
|
|
|
|
|
|
|
|
.custom-game-card.delete-mode { |
|
|
animation: gentleShake 2s ease-in-out infinite; |
|
|
filter: brightness(0.8) saturate(0.7); |
|
|
} |
|
|
|
|
|
.custom-game-card.delete-mode .delete-btn { |
|
|
opacity: 1 !important; |
|
|
visibility: visible !important; |
|
|
transform: scale(1) !important; |
|
|
animation: pulseGlow 1.5s ease-in-out infinite; |
|
|
} |
|
|
|
|
|
@keyframes gentleShake { |
|
|
0%, 100% { transform: translateX(0); } |
|
|
25% { transform: translateX(-1px); } |
|
|
75% { transform: translateX(1px); } |
|
|
} |
|
|
|
|
|
@keyframes pulseGlow { |
|
|
0%, 100% { |
|
|
box-shadow: 0 0 5px rgba(239, 68, 68, 0.5); |
|
|
transform: scale(1); |
|
|
} |
|
|
50% { |
|
|
box-shadow: 0 0 15px rgba(239, 68, 68, 0.8); |
|
|
transform: scale(1.05); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.custom-game-card:not(.delete-mode) { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
|
|
|
.custom-game-card button { |
|
|
transition: all 0.2s ease; |
|
|
} |
|
|
|
|
|
.custom-game-card button:hover { |
|
|
transform: scale(1.02); |
|
|
} |
|
|
|
|
|
|
|
|
.custom-notification { |
|
|
backdrop-filter: blur(10px); |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
|
|
|
|
|
|
.delete-mode-overlay { |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
background: rgba(0, 0, 0, 0.3); |
|
|
z-index: 5; |
|
|
pointer-events: none; |
|
|
} |
|
|
</style> |
|
|
|
|
|
<script> |
|
|
|
|
|
function clearSearch() { |
|
|
const searchInput = document.querySelector('input[name="search"]'); |
|
|
searchInput.value = ''; |
|
|
searchInput.form.submit(); |
|
|
} |
|
|
|
|
|
|
|
|
document.querySelector('input[name="search"]').addEventListener('keypress', function(e) { |
|
|
if (e.key === 'Enter') { |
|
|
e.preventDefault(); |
|
|
this.form.submit(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
function openModal(title = 'Add New Product', action = '{{ route("products.store") }}', method = 'POST') { |
|
|
document.getElementById('modalTitle').textContent = title; |
|
|
document.getElementById('productForm').action = action; |
|
|
document.getElementById('methodField').innerHTML = method === 'PUT' ? '@method("PUT")' : ''; |
|
|
document.getElementById('productModal').classList.remove('hidden'); |
|
|
document.getElementById('productModal').classList.add('flex'); |
|
|
} |
|
|
|
|
|
function closeModal() { |
|
|
document.getElementById('productModal').classList.add('hidden'); |
|
|
document.getElementById('productModal').classList.remove('flex'); |
|
|
document.getElementById('productForm').reset(); |
|
|
|
|
|
|
|
|
document.getElementById('currentImagePreview').classList.add('hidden'); |
|
|
|
|
|
|
|
|
document.getElementById('productImage').setAttribute('required', 'required'); |
|
|
} |
|
|
|
|
|
function editProduct(id, name, description, price, amount, game, image, productType = 'Digital Account', platform = 'Multi-Platform', region = 'Global', deliveryMethod = 'Instant Digital', warranty = 'Lifetime Support', supportLevel = '24/7 Available', features = '') { |
|
|
|
|
|
document.getElementById('productName').value = name || ''; |
|
|
document.getElementById('productDescription').value = description || ''; |
|
|
document.getElementById('productPrice').value = price || ''; |
|
|
document.getElementById('productAmount').value = amount || ''; |
|
|
document.getElementById('productGame').value = game || ''; |
|
|
|
|
|
|
|
|
document.getElementById('productType').value = productType || 'Digital Account'; |
|
|
document.getElementById('productPlatform').value = platform || 'Multi-Platform'; |
|
|
document.getElementById('productRegion').value = region || 'Global'; |
|
|
document.getElementById('deliveryMethod').value = deliveryMethod || 'Instant Digital'; |
|
|
document.getElementById('productWarranty').value = warranty || 'Lifetime Support'; |
|
|
document.getElementById('supportLevel').value = supportLevel || '24/7 Available'; |
|
|
document.getElementById('productFeatures').value = features || ''; |
|
|
|
|
|
|
|
|
const currentImagePreview = document.getElementById('currentImagePreview'); |
|
|
const currentImage = document.getElementById('currentImage'); |
|
|
if (image) { |
|
|
currentImage.src = image; |
|
|
currentImagePreview.classList.remove('hidden'); |
|
|
} else { |
|
|
currentImagePreview.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('productImage').value = ''; |
|
|
|
|
|
|
|
|
document.getElementById('productImage').removeAttribute('required'); |
|
|
|
|
|
openModal('Edit Product', `/products/${id}`, 'PUT'); |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('show_category_modal').addEventListener('click', () => { |
|
|
console.log('Category modal button clicked'); |
|
|
const categoryModal = document.getElementById('categoryModal'); |
|
|
if (categoryModal) { |
|
|
categoryModal.classList.remove('hidden'); |
|
|
categoryModal.classList.add('flex'); |
|
|
console.log('Category modal shown'); |
|
|
} else { |
|
|
console.error('Category modal not found'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
function closeCategoryModal() { |
|
|
document.getElementById('categoryModal').classList.add('hidden'); |
|
|
document.getElementById('categoryModal').classList.remove('flex'); |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('categoryModal').addEventListener('click', (e) => { |
|
|
if (e.target === e.currentTarget) { |
|
|
closeCategoryModal(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
function showOtherGamesModal() { |
|
|
console.log('showOtherGamesModal called'); |
|
|
closeCategoryModal(); |
|
|
const otherGamesModal = document.getElementById('otherGamesModal'); |
|
|
if (otherGamesModal) { |
|
|
otherGamesModal.classList.remove('hidden'); |
|
|
otherGamesModal.classList.add('flex'); |
|
|
console.log('Other games modal shown'); |
|
|
} else { |
|
|
console.error('Other games modal not found'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function closeOtherGamesModal() { |
|
|
document.getElementById('otherGamesModal').classList.add('hidden'); |
|
|
document.getElementById('otherGamesModal').classList.remove('flex'); |
|
|
} |
|
|
|
|
|
|
|
|
function backToCategoryModal() { |
|
|
closeOtherGamesModal(); |
|
|
document.getElementById('categoryModal').classList.remove('hidden'); |
|
|
document.getElementById('categoryModal').classList.add('flex'); |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('otherGamesModal').addEventListener('click', (e) => { |
|
|
if (e.target === e.currentTarget) { |
|
|
closeOtherGamesModal(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
function showCustomGameInput() { |
|
|
console.log('showCustomGameInput called'); |
|
|
const customGameInput = document.getElementById('customGameInput'); |
|
|
const customGameName = document.getElementById('customGameName'); |
|
|
|
|
|
if (!customGameInput) { |
|
|
console.error('customGameInput element not found'); |
|
|
alert('Error: Custom game input section not found. Please refresh the page and try again.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (!customGameName) { |
|
|
console.error('customGameName element not found'); |
|
|
alert('Error: Custom game name input not found. Please refresh the page and try again.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
customGameInput.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
customGameName.value = ''; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
customGameName.focus(); |
|
|
customGameName.select(); |
|
|
}, 150); |
|
|
|
|
|
console.log('Custom game input shown and focused'); |
|
|
} |
|
|
|
|
|
|
|
|
function hideCustomGameInput() { |
|
|
console.log('hideCustomGameInput called'); |
|
|
const customGameInput = document.getElementById('customGameInput'); |
|
|
const customGameName = document.getElementById('customGameName'); |
|
|
|
|
|
if (customGameInput) { |
|
|
customGameInput.classList.add('hidden'); |
|
|
console.log('Custom game input hidden'); |
|
|
} |
|
|
|
|
|
if (customGameName) { |
|
|
customGameName.value = ''; |
|
|
console.log('Custom game name input cleared'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function showDeleteCustomGameInput() { |
|
|
console.log('showDeleteCustomGameInput called'); |
|
|
|
|
|
|
|
|
const customGameCards = document.querySelectorAll('.custom-game-card'); |
|
|
customGameCards.forEach(card => { |
|
|
card.classList.add('delete-mode'); |
|
|
}); |
|
|
|
|
|
|
|
|
showDeleteModeMessage(); |
|
|
|
|
|
console.log('Delete mode activated for', customGameCards.length, 'custom games'); |
|
|
} |
|
|
|
|
|
|
|
|
function hideDeleteCustomGameInput() { |
|
|
console.log('hideDeleteCustomGameInput called'); |
|
|
|
|
|
|
|
|
const customGameCards = document.querySelectorAll('.custom-game-card'); |
|
|
customGameCards.forEach(card => { |
|
|
card.classList.remove('delete-mode'); |
|
|
}); |
|
|
|
|
|
|
|
|
hideDeleteModeMessage(); |
|
|
|
|
|
console.log('Delete mode deactivated'); |
|
|
} |
|
|
|
|
|
|
|
|
function showDeleteModeMessage() { |
|
|
|
|
|
let overlay = document.getElementById('deleteModeOverlay'); |
|
|
if (!overlay) { |
|
|
overlay = document.createElement('div'); |
|
|
overlay.id = 'deleteModeOverlay'; |
|
|
overlay.className = 'fixed top-0 left-0 right-0 bg-red-500/90 text-white text-center py-3 z-50 transform -translate-y-full transition-transform duration-300'; |
|
|
overlay.innerHTML = ` |
|
|
<div class="flex items-center justify-center space-x-4"> |
|
|
<i class="fas fa-trash text-xl"></i> |
|
|
<span class="font-semibold">Delete Mode Active - Click minus buttons to delete custom games</span> |
|
|
<button onclick="hideDeleteCustomGameInput()" class="bg-white/20 hover:bg-white/30 px-4 py-1 rounded-full text-sm transition-colors"> |
|
|
<i class="fas fa-times mr-1"></i>Cancel |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
document.body.appendChild(overlay); |
|
|
} |
|
|
|
|
|
// Show overlay |
|
|
setTimeout(() => { |
|
|
overlay.classList.remove('-translate-y-full'); |
|
|
overlay.classList.add('translate-y-0'); |
|
|
}, 100); |
|
|
} |
|
|
|
|
|
|
|
|
function hideDeleteModeMessage() { |
|
|
const overlay = document.getElementById('deleteModeOverlay'); |
|
|
if (overlay) { |
|
|
overlay.classList.remove('translate-y-0'); |
|
|
overlay.classList.add('-translate-y-full'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function deleteCustomGame(gameName) { |
|
|
if (confirm(`Are you sure you want to delete the custom game "${gameName}"?\n\nThis will remove it from all dropdowns. If any products are using this category, you'll need to reassign them first.`)) { |
|
|
console.log('Deleting custom game:', gameName); |
|
|
|
|
|
try { |
|
|
// Show loading state |
|
|
const deleteBtn = event.target.closest('.delete-btn'); |
|
|
if (deleteBtn) { |
|
|
deleteBtn.innerHTML = '<i class="fas fa-spinner fa-spin text-sm"></i>'; |
|
|
deleteBtn.disabled = true; |
|
|
} |
|
|
|
|
|
// Call backend API |
|
|
const response = await fetch(`/custom-games/${encodeURIComponent(gameName)}`, { |
|
|
method: 'DELETE', |
|
|
headers: { |
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', |
|
|
'Content-Type': 'application/json', |
|
|
'Accept': 'application/json' |
|
|
} |
|
|
}); |
|
|
|
|
|
const result = await response.json(); |
|
|
|
|
|
if (result.success) { |
|
|
// Remove from product game select dropdown |
|
|
const gameSelect = document.getElementById('productGame'); |
|
|
if (gameSelect) { |
|
|
const optionToRemove = Array.from(gameSelect.options).find(option => option.value === gameName); |
|
|
if (optionToRemove) { |
|
|
optionToRemove.remove(); |
|
|
console.log('Removed from product select:', gameName); |
|
|
} |
|
|
|
|
|
// Check if Custom Games optgroup is empty and remove it |
|
|
const customOptgroup = gameSelect.querySelector('optgroup[label="Custom Games"]'); |
|
|
if (customOptgroup && customOptgroup.children.length === 0) { |
|
|
customOptgroup.remove(); |
|
|
console.log('Removed empty Custom Games optgroup'); |
|
|
} |
|
|
} |
|
|
|
|
|
// Remove from localStorage |
|
|
localStorage.removeItem(`customGame_${gameName}`); |
|
|
|
|
|
// Remove the card from the modal with animation |
|
|
const customGameCards = document.querySelectorAll('.custom-game-card'); |
|
|
customGameCards.forEach(card => { |
|
|
const button = card.querySelector('button[onclick*="' + gameName + '"]'); |
|
|
if (button) { |
|
|
// Add fade out animation |
|
|
card.style.transition = 'all 0.3s ease'; |
|
|
card.style.transform = 'scale(0.8)'; |
|
|
card.style.opacity = '0'; |
|
|
|
|
|
setTimeout(() => { |
|
|
card.remove(); |
|
|
console.log('Removed card for:', gameName); |
|
|
|
|
|
// Check if there are any custom games left |
|
|
const remainingCards = document.querySelectorAll('.custom-game-card'); |
|
|
if (remainingCards.length === 0) { |
|
|
hideDeleteCustomGameInput(); |
|
|
} |
|
|
}, 300); |
|
|
} |
|
|
}); |
|
|
|
|
|
// Show success message with better styling |
|
|
showNotification(result.message, 'success'); |
|
|
|
|
|
} else { |
|
|
// Show error message |
|
|
showNotification(result.message, 'error'); |
|
|
|
|
|
// Reset delete button |
|
|
if (deleteBtn) { |
|
|
deleteBtn.innerHTML = '<i class="fas fa-minus text-sm"></i>'; |
|
|
deleteBtn.disabled = false; |
|
|
} |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Error deleting custom game:', error); |
|
|
showNotification('Failed to delete custom game. Please try again.', 'error'); |
|
|
|
|
|
// Reset delete button |
|
|
const deleteBtn = event.target.closest('.delete-btn'); |
|
|
if (deleteBtn) { |
|
|
deleteBtn.innerHTML = '<i class="fas fa-minus text-sm"></i>'; |
|
|
deleteBtn.disabled = false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// Show notification function |
|
|
function showNotification(message, type = 'info') { |
|
|
// Remove existing notifications |
|
|
const existingNotifications = document.querySelectorAll('.custom-notification'); |
|
|
existingNotifications.forEach(notification => notification.remove()); |
|
|
|
|
|
// Create notification element |
|
|
const notification = document.createElement('div'); |
|
|
notification.className = `custom-notification fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg transform translate-x-full transition-all duration-300 ${ |
|
|
type === 'success' ? 'bg-green-500/90 text-white' : |
|
|
type === 'error' ? 'bg-red-500/90 text-white' : |
|
|
'bg-blue-500/90 text-white' |
|
|
}`; |
|
|
|
|
|
notification.innerHTML = ` |
|
|
<div class="flex items-center space-x-3"> |
|
|
<i class="fas ${ |
|
|
type === 'success' ? 'fa-check-circle' : |
|
|
type === 'error' ? 'fa-exclamation-circle' : |
|
|
'fa-info-circle' |
|
|
}"></i> |
|
|
<span>${message}</span> |
|
|
<button onclick="this.parentElement.parentElement.remove()" class="ml-2 hover:bg-white/20 rounded p-1"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
document.body.appendChild(notification); |
|
|
|
|
|
// Animate in |
|
|
setTimeout(() => { |
|
|
notification.classList.remove('translate-x-full'); |
|
|
notification.classList.add('translate-x-0'); |
|
|
}, 100); |
|
|
|
|
|
// Auto remove after 5 seconds |
|
|
setTimeout(() => { |
|
|
notification.classList.add('translate-x-full'); |
|
|
setTimeout(() => notification.remove(), 300); |
|
|
}, 5000); |
|
|
} |
|
|
|
|
|
// Quick custom game functions (same as main custom game functions) |
|
|
function showQuickCustomGameInput() { |
|
|
console.log('showQuickCustomGameInput called'); |
|
|
const quickCustomGameInput = document.getElementById('quickCustomGameInput'); |
|
|
const quickCustomGameName = document.getElementById('quickCustomGameName'); |
|
|
|
|
|
if (!quickCustomGameInput) { |
|
|
console.error('quickCustomGameInput element not found'); |
|
|
alert('Error: Quick custom game input section not found. Please refresh the page and try again.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (!quickCustomGameName) { |
|
|
console.error('quickCustomGameName element not found'); |
|
|
alert('Error: Quick custom game name input not found. Please refresh the page and try again.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
// Show the input section |
|
|
quickCustomGameInput.classList.remove('hidden'); |
|
|
|
|
|
// Clear any previous input |
|
|
quickCustomGameName.value = ''; |
|
|
|
|
|
// Focus on the input with a small delay to ensure visibility |
|
|
setTimeout(() => { |
|
|
quickCustomGameName.focus(); |
|
|
quickCustomGameName.select(); |
|
|
}, 150); |
|
|
|
|
|
console.log('Quick custom game input shown and focused'); |
|
|
} |
|
|
|
|
|
function hideQuickCustomGameInput() { |
|
|
console.log('hideQuickCustomGameInput called'); |
|
|
const quickCustomGameInput = document.getElementById('quickCustomGameInput'); |
|
|
const quickCustomGameName = document.getElementById('quickCustomGameName'); |
|
|
|
|
|
if (quickCustomGameInput) { |
|
|
quickCustomGameInput.classList.add('hidden'); |
|
|
console.log('Quick custom game input hidden'); |
|
|
} |
|
|
|
|
|
if (quickCustomGameName) { |
|
|
quickCustomGameName.value = ''; |
|
|
console.log('Quick custom game name input cleared'); |
|
|
} |
|
|
} |
|
|
|
|
|
async function createQuickCustomGame() { |
|
|
console.log('createQuickCustomGame called'); |
|
|
const quickCustomGameNameInput = document.getElementById('quickCustomGameName'); |
|
|
|
|
|
if (!quickCustomGameNameInput) { |
|
|
console.error('Quick custom game name input not found'); |
|
|
alert('Error: Quick custom game input field not found. Please refresh the page and try again.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const customGameName = quickCustomGameNameInput.value.trim(); |
|
|
console.log('Quick custom game name:', customGameName); |
|
|
|
|
|
if (!customGameName) { |
|
|
alert('Please enter a game name'); |
|
|
quickCustomGameNameInput.focus(); |
|
|
return; |
|
|
} |
|
|
|
|
|
// Validate game name length |
|
|
if (customGameName.length < 2) { |
|
|
alert('Game name must be at least 2 characters long'); |
|
|
quickCustomGameNameInput.focus(); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (customGameName.length > 50) { |
|
|
alert('Game name must be less than 50 characters'); |
|
|
quickCustomGameNameInput.focus(); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
// Generate random styling |
|
|
const gameStyle = getRandomGameStyle(customGameName); |
|
|
|
|
|
// Call backend API to store custom game |
|
|
const response = await fetch('/custom-games', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', |
|
|
'Content-Type': 'application/json', |
|
|
'Accept': 'application/json' |
|
|
}, |
|
|
body: JSON.stringify({ |
|
|
name: customGameName, |
|
|
icon: gameStyle.icon, |
|
|
color_gradient: gameStyle.gradient |
|
|
}) |
|
|
}); |
|
|
|
|
|
const result = await response.json(); |
|
|
|
|
|
if (result.success) { |
|
|
// Add the custom game to the select dropdown |
|
|
const gameSelect = document.getElementById('productGame'); |
|
|
if (gameSelect) { |
|
|
let customOptgroup = gameSelect.querySelector('optgroup[label="Custom Games"]'); |
|
|
|
|
|
// Create optgroup if it doesn't exist |
|
|
if (!customOptgroup) { |
|
|
customOptgroup = document.createElement('optgroup'); |
|
|
customOptgroup.label = 'Custom Games'; |
|
|
customOptgroup.id = 'customGamesOptgroup'; |
|
|
gameSelect.appendChild(customOptgroup); |
|
|
console.log('Created new Custom Games optgroup'); |
|
|
} |
|
|
|
|
|
|
|
|
const newOption = document.createElement('option'); |
|
|
newOption.value = customGameName; |
|
|
newOption.textContent = customGameName; |
|
|
customOptgroup.appendChild(newOption); |
|
|
console.log('Added new quick custom game option:', customGameName); |
|
|
} |
|
|
|
|
|
|
|
|
addCustomGameCardToModal(result.data); |
|
|
|
|
|
|
|
|
hideQuickCustomGameInput(); |
|
|
|
|
|
|
|
|
showCustomGameSuccessPopup(customGameName); |
|
|
|
|
|
|
|
|
showNotification(result.message, 'success'); |
|
|
} else { |
|
|
showNotification(result.message, 'error'); |
|
|
quickCustomGameNameInput.focus(); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Error creating custom game:', error); |
|
|
showNotification('Failed to create custom game. Please try again.', 'error'); |
|
|
quickCustomGameNameInput.focus(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const customGameColors = [ |
|
|
'from-red-500 to-pink-500', |
|
|
'from-orange-500 to-red-500', |
|
|
'from-yellow-500 to-orange-500', |
|
|
'from-green-500 to-teal-500', |
|
|
'from-teal-500 to-cyan-500', |
|
|
'from-blue-500 to-indigo-500', |
|
|
'from-indigo-500 to-purple-500', |
|
|
'from-purple-500 to-pink-500', |
|
|
'from-pink-500 to-rose-500', |
|
|
'from-emerald-500 to-green-500', |
|
|
'from-cyan-500 to-blue-500', |
|
|
'from-violet-500 to-purple-500' |
|
|
]; |
|
|
|
|
|
const customGameIcons = [ |
|
|
'fas fa-gamepad', |
|
|
'fas fa-dice', |
|
|
'fas fa-chess', |
|
|
'fas fa-puzzle-piece', |
|
|
'fas fa-trophy', |
|
|
'fas fa-crown', |
|
|
'fas fa-gem', |
|
|
'fas fa-fire', |
|
|
'fas fa-bolt', |
|
|
'fas fa-magic', |
|
|
'fas fa-dragon', |
|
|
'fas fa-shield', |
|
|
'fas fa-sword', |
|
|
'fas fa-heart', |
|
|
'fas fa-star', |
|
|
'fas fa-moon', |
|
|
'fas fa-sun', |
|
|
'fas fa-leaf', |
|
|
'fas fa-snowflake', |
|
|
'fas fa-mountain' |
|
|
]; |
|
|
|
|
|
|
|
|
function getRandomGameStyle(gameName) { |
|
|
|
|
|
let hash = 0; |
|
|
for (let i = 0; i < gameName.length; i++) { |
|
|
hash = ((hash << 5) - hash + gameName.charCodeAt(i)) & 0xffffffff; |
|
|
} |
|
|
|
|
|
const colorIndex = Math.abs(hash) % customGameColors.length; |
|
|
const iconIndex = Math.abs(hash >> 8) % customGameIcons.length; |
|
|
|
|
|
return { |
|
|
gradient: customGameColors[colorIndex], |
|
|
icon: customGameIcons[iconIndex] |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
async function createCustomGame() { |
|
|
console.log('createCustomGame called'); |
|
|
const customGameNameInput = document.getElementById('customGameName'); |
|
|
|
|
|
if (!customGameNameInput) { |
|
|
console.error('Custom game name input not found'); |
|
|
alert('Error: Custom game input field not found. Please refresh the page and try again.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const customGameName = customGameNameInput.value.trim(); |
|
|
console.log('Custom game name:', customGameName); |
|
|
|
|
|
if (!customGameName) { |
|
|
alert('Please enter a game name'); |
|
|
customGameNameInput.focus(); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (customGameName.length < 2) { |
|
|
alert('Game name must be at least 2 characters long'); |
|
|
customGameNameInput.focus(); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (customGameName.length > 50) { |
|
|
alert('Game name must be less than 50 characters'); |
|
|
customGameNameInput.focus(); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
const gameStyle = getRandomGameStyle(customGameName); |
|
|
|
|
|
|
|
|
const response = await fetch('/custom-games', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', |
|
|
'Content-Type': 'application/json', |
|
|
'Accept': 'application/json' |
|
|
}, |
|
|
body: JSON.stringify({ |
|
|
name: customGameName, |
|
|
icon: gameStyle.icon, |
|
|
color_gradient: gameStyle.gradient |
|
|
}) |
|
|
}); |
|
|
|
|
|
const result = await response.json(); |
|
|
|
|
|
if (result.success) { |
|
|
|
|
|
const gameSelect = document.getElementById('productGame'); |
|
|
if (gameSelect) { |
|
|
let customOptgroup = gameSelect.querySelector('optgroup[label="Custom Games"]'); |
|
|
|
|
|
|
|
|
if (!customOptgroup) { |
|
|
customOptgroup = document.createElement('optgroup'); |
|
|
customOptgroup.label = 'Custom Games'; |
|
|
customOptgroup.id = 'customGamesOptgroup'; |
|
|
gameSelect.appendChild(customOptgroup); |
|
|
console.log('Created new Custom Games optgroup'); |
|
|
} |
|
|
|
|
|
|
|
|
const newOption = document.createElement('option'); |
|
|
newOption.value = customGameName; |
|
|
newOption.textContent = customGameName; |
|
|
customOptgroup.appendChild(newOption); |
|
|
console.log('Added new custom game option:', customGameName); |
|
|
} |
|
|
|
|
|
|
|
|
addCustomGameCardToModal(result.data); |
|
|
|
|
|
|
|
|
hideCustomGameInput(); |
|
|
|
|
|
|
|
|
showCustomGameSuccessPopup(customGameName); |
|
|
|
|
|
|
|
|
showNotification(result.message, 'success'); |
|
|
} else { |
|
|
showNotification(result.message, 'error'); |
|
|
customGameNameInput.focus(); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Error creating custom game:', error); |
|
|
showNotification('Failed to create custom game. Please try again.', 'error'); |
|
|
customGameNameInput.focus(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function addCustomGameCardToModal(customGameData) { |
|
|
const otherGamesModal = document.getElementById('otherGamesModal'); |
|
|
if (!otherGamesModal) return; |
|
|
|
|
|
const gameGrid = otherGamesModal.querySelector('.grid'); |
|
|
if (!gameGrid) return; |
|
|
|
|
|
|
|
|
const gradientParts = customGameData.color_gradient.split(' '); |
|
|
const primaryColor = gradientParts[0].replace('from-', '').replace('-500', ''); |
|
|
|
|
|
|
|
|
const colorMap = { |
|
|
'red': ['from-red-500/20 to-pink-500/20', 'border-red-500/30', 'hover:border-red-500/50'], |
|
|
'pink': ['from-pink-500/20 to-rose-500/20', 'border-pink-500/30', 'hover:border-pink-500/50'], |
|
|
'rose': ['from-rose-500/20 to-pink-500/20', 'border-rose-500/30', 'hover:border-rose-500/50'], |
|
|
'purple': ['from-purple-500/20 to-blue-500/20', 'border-purple-500/30', 'hover:border-purple-500/50'], |
|
|
'blue': ['from-blue-500/20 to-indigo-500/20', 'border-blue-500/30', 'hover:border-blue-500/50'], |
|
|
'green': ['from-green-500/20 to-teal-500/20', 'border-green-500/30', 'hover:border-green-500/50'], |
|
|
'orange': ['from-orange-500/20 to-red-500/20', 'border-orange-500/30', 'hover:border-orange-500/50'], |
|
|
'yellow': ['from-yellow-500/20 to-orange-500/20', 'border-yellow-500/30', 'hover:border-yellow-500/50'], |
|
|
'teal': ['from-teal-500/20 to-cyan-500/20', 'border-teal-500/30', 'hover:border-teal-500/50'], |
|
|
'cyan': ['from-cyan-500/20 to-blue-500/20', 'border-cyan-500/30', 'hover:border-cyan-500/50'], |
|
|
'indigo': ['from-indigo-500/20 to-purple-500/20', 'border-indigo-500/30', 'hover:border-indigo-500/50'], |
|
|
'violet': ['from-violet-500/20 to-purple-500/20', 'border-violet-500/30', 'hover:border-violet-500/50'], |
|
|
'emerald': ['from-emerald-500/20 to-green-500/20', 'border-emerald-500/30', 'hover:border-emerald-500/50'] |
|
|
}; |
|
|
|
|
|
const colors = colorMap[primaryColor] || colorMap['purple']; |
|
|
|
|
|
|
|
|
const newCard = document.createElement('div'); |
|
|
newCard.className = 'relative custom-game-card'; |
|
|
newCard.innerHTML = ` |
|
|
<button onclick="openNewProductModal('${customGameData.name}')" class="w-full p-6 bg-gradient-to-r ${colors[0]} border ${colors[1]} rounded-xl text-left ${colors[2]} transition-all duration-300 group"> |
|
|
<div class="flex flex-col items-center text-center"> |
|
|
<div class="w-12 h-12 bg-gradient-to-r ${customGameData.color_gradient} rounded-lg flex items-center justify-center mb-3 group-hover:scale-110 transition-transform duration-300"> |
|
|
<i class="${customGameData.icon} text-white text-xl"></i> |
|
|
</div> |
|
|
<div class="text-white font-bold text-lg mb-1">${customGameData.name}</div> |
|
|
<div class="${colors[0].includes('purple') ? 'text-purple-300' : 'text-' + primaryColor + '-300'} text-sm">Custom game</div> |
|
|
</div> |
|
|
</button> |
|
|
<button onclick="deleteCustomGame('${customGameData.name}')" class="delete-btn absolute -top-2 -right-2 w-8 h-8 bg-red-500 hover:bg-red-600 text-white rounded-full flex items-center justify-center opacity-0 invisible transition-all duration-300 transform scale-0 z-10 shadow-lg"> |
|
|
<i class="fas fa-minus text-sm"></i> |
|
|
</button> |
|
|
`; |
|
|
|
|
|
// Find the delete button (last item) and insert before it |
|
|
const deleteButton = gameGrid.querySelector('button[onclick*="showDeleteCustomGameInput"]'); |
|
|
if (deleteButton && deleteButton.parentElement) { |
|
|
gameGrid.insertBefore(newCard, deleteButton.parentElement); |
|
|
} else { |
|
|
gameGrid.appendChild(newCard); |
|
|
} |
|
|
|
|
|
// Add animation |
|
|
newCard.style.opacity = '0'; |
|
|
newCard.style.transform = 'scale(0.8)'; |
|
|
setTimeout(() => { |
|
|
newCard.style.transition = 'all 0.3s ease'; |
|
|
newCard.style.opacity = '1'; |
|
|
newCard.style.transform = 'scale(1)'; |
|
|
}, 100); |
|
|
} |
|
|
|
|
|
// Show custom game success popup |
|
|
function showCustomGameSuccessPopup(gameName) { |
|
|
console.log('showCustomGameSuccessPopup called with:', gameName); // Debug log |
|
|
|
|
|
const successNameElement = document.getElementById('customGameSuccessName'); |
|
|
const gameNameButtonElement = document.getElementById('gameNameInButton'); |
|
|
const successModal = document.getElementById('customGameSuccessModal'); |
|
|
const addItemButton = document.getElementById('addItemForGame'); |
|
|
|
|
|
if (successNameElement && gameNameButtonElement && successModal && addItemButton) { |
|
|
successNameElement.textContent = gameName; |
|
|
gameNameButtonElement.textContent = gameName; |
|
|
|
|
|
// Set up the "Add Item" button click handler |
|
|
addItemButton.onclick = function() { |
|
|
closeCustomGameSuccessModal(); |
|
|
openNewProductModal(gameName); |
|
|
}; |
|
|
|
|
|
successModal.classList.remove('hidden'); |
|
|
successModal.classList.add('flex'); |
|
|
console.log('Success popup shown for game:', gameName); // Debug log |
|
|
} else { |
|
|
console.error('Success popup elements not found:'); // Debug log |
|
|
console.log('successNameElement:', successNameElement); |
|
|
console.log('gameNameButtonElement:', gameNameButtonElement); |
|
|
console.log('successModal:', successModal); |
|
|
console.log('addItemButton:', addItemButton); |
|
|
|
|
|
// Fallback alert if modal elements are missing |
|
|
alert(`Custom game "${gameName}" has been added successfully!`); |
|
|
} |
|
|
} |
|
|
|
|
|
// Close custom game success modal |
|
|
function closeCustomGameSuccessModal() { |
|
|
document.getElementById('customGameSuccessModal').classList.add('hidden'); |
|
|
document.getElementById('customGameSuccessModal').classList.remove('flex'); |
|
|
} |
|
|
|
|
|
// Show add more category (go back to category modal) |
|
|
function showAddMoreCategory() { |
|
|
closeCustomGameSuccessModal(); |
|
|
document.getElementById('categoryModal').classList.remove('hidden'); |
|
|
document.getElementById('categoryModal').classList.add('flex'); |
|
|
} |
|
|
|
|
|
// Handle Enter key in custom game input |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
const customGameInput = document.getElementById('customGameName'); |
|
|
if (customGameInput) { |
|
|
customGameInput.addEventListener('keypress', function(e) { |
|
|
if (e.key === 'Enter') { |
|
|
createCustomGame(); |
|
|
} |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
// Open new product modal with pre-selected category |
|
|
function openNewProductModal(gameCategory) { |
|
|
// Close both modals |
|
|
closeCategoryModal(); |
|
|
closeOtherGamesModal(); |
|
|
|
|
|
// Reset form for new product |
|
|
document.getElementById('productForm').reset(); |
|
|
document.getElementById('currentImagePreview').classList.add('hidden'); |
|
|
document.getElementById('productImage').setAttribute('required', 'required'); |
|
|
|
|
|
// Set the selected game category |
|
|
document.getElementById('productGame').value = gameCategory; |
|
|
|
|
|
// Set default values for specifications |
|
|
document.getElementById('productType').value = 'Digital Account'; |
|
|
document.getElementById('productPlatform').value = 'Multi-Platform'; |
|
|
document.getElementById('productRegion').value = 'Global'; |
|
|
document.getElementById('deliveryMethod').value = 'Instant Digital'; |
|
|
document.getElementById('productWarranty').value = 'Lifetime Support'; |
|
|
document.getElementById('supportLevel').value = '24/7 Available'; |
|
|
|
|
|
// Update modal title based on category |
|
|
let modalTitle = 'Add New Product'; |
|
|
if (gameCategory === 'Genshin') { |
|
|
modalTitle = 'Add Genshin Impact Product'; |
|
|
} else if (gameCategory === 'Starrail') { |
|
|
modalTitle = 'Add Honkai: Star Rail Product'; |
|
|
} else if (gameCategory === 'WutheringWave') { |
|
|
modalTitle = 'Add Wuthering Waves Product'; |
|
|
} else if (gameCategory === 'ZenlessZoneZero') { |
|
|
modalTitle = 'Add Zenless Zone Zero Product'; |
|
|
} else if (gameCategory === 'Arknights') { |
|
|
modalTitle = 'Add Arknights Product'; |
|
|
} else if (gameCategory === 'AzurLane') { |
|
|
modalTitle = 'Add Azur Lane Product'; |
|
|
} else { |
|
|
modalTitle = `Add ${gameCategory} Product`; |
|
|
} |
|
|
|
|
|
openModal(modalTitle); |
|
|
} |
|
|
|
|
|
// Close modal when clicking outside |
|
|
document.getElementById('productModal').addEventListener('click', (e) => { |
|
|
if (e.target === e.currentTarget) { |
|
|
closeModal(); |
|
|
} |
|
|
}); |
|
|
|
|
|
// Close success modal when clicking outside |
|
|
document.getElementById('customGameSuccessModal').addEventListener('click', (e) => { |
|
|
if (e.target === e.currentTarget) { |
|
|
closeCustomGameSuccessModal(); |
|
|
} |
|
|
}); |
|
|
|
|
|
// File upload functionality |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
const fileInput = document.getElementById('productImage'); |
|
|
const uploadArea = document.getElementById('fileUploadArea'); |
|
|
const uploadContent = document.getElementById('uploadContent'); |
|
|
const fileInfo = document.getElementById('fileInfo'); |
|
|
const fileName = document.getElementById('fileName'); |
|
|
const fileSize = document.getElementById('fileSize'); |
|
|
const removeBtn = document.getElementById('removeFile'); |
|
|
|
|
|
// Handle file input change |
|
|
fileInput.addEventListener('change', function(e) { |
|
|
const file = e.target.files[0]; |
|
|
if (file) { |
|
|
handleFile(file); |
|
|
} else { |
|
|
resetFileDisplay(); |
|
|
} |
|
|
}); |
|
|
|
|
|
// Handle drag and drop |
|
|
uploadArea.addEventListener('dragover', function(e) { |
|
|
e.preventDefault(); |
|
|
uploadArea.classList.add('dragover'); |
|
|
}); |
|
|
|
|
|
uploadArea.addEventListener('dragleave', function(e) { |
|
|
e.preventDefault(); |
|
|
uploadArea.classList.remove('dragover'); |
|
|
}); |
|
|
|
|
|
uploadArea.addEventListener('drop', function(e) { |
|
|
e.preventDefault(); |
|
|
uploadArea.classList.remove('dragover'); |
|
|
|
|
|
const files = e.dataTransfer.files; |
|
|
if (files.length > 0) { |
|
|
const file = files[0]; |
|
|
if (file.type.startsWith('image/')) { |
|
|
fileInput.files = files; |
|
|
handleFile(file); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
// Handle remove file |
|
|
removeBtn.addEventListener('click', function(e) { |
|
|
e.stopPropagation(); |
|
|
fileInput.value = ''; |
|
|
resetFileDisplay(); |
|
|
}); |
|
|
|
|
|
function handleFile(file) { |
|
|
// Validate file size (10MB) |
|
|
if (file.size > 10 * 1024 * 1024) { |
|
|
alert('File size must be less than 10MB'); |
|
|
fileInput.value = ''; |
|
|
return; |
|
|
} |
|
|
|
|
|
// Validate file type |
|
|
if (!file.type.startsWith('image/')) { |
|
|
alert('Please select an image file'); |
|
|
fileInput.value = ''; |
|
|
return; |
|
|
} |
|
|
|
|
|
// Show file info |
|
|
fileName.textContent = file.name; |
|
|
fileSize.textContent = formatFileSize(file.size); |
|
|
uploadContent.classList.add('hidden'); |
|
|
fileInfo.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function resetFileDisplay() { |
|
|
uploadContent.classList.remove('hidden'); |
|
|
fileInfo.classList.add('hidden'); |
|
|
fileName.textContent = ''; |
|
|
fileSize.textContent = ''; |
|
|
} |
|
|
|
|
|
function formatFileSize(bytes) { |
|
|
if (bytes === 0) return '0 Bytes'; |
|
|
const k = 1024; |
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
|
|
} |
|
|
}); |
|
|
</script> |
|
|
</x-app-layout> |