Initial commit

This commit is contained in:
root
2026-05-09 17:28:23 +02:00
commit 9d73f82529
5575 changed files with 281989 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
@extends('layouts.app')
@section('title', __('radio.become_dj') . ' - ' . config('app.name'))
@section('content')
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-white">
<h2 class="text-2xl font-bold mb-4">{{ __('radio.become_dj') }}</h2>
@if(session('success'))
<div class="bg-green-500 text-white p-4 rounded mb-4">
{{ session('success') }}
</div>
@endif
@if($errors->any())
<div class="bg-red-500 text-white p-4 rounded mb-4">
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@if(isset($hasPendingApplication) && $hasPendingApplication)
<div class="bg-blue-500 text-white p-4 rounded">
{{ __('radio.application_pending') }}
</div>
@else
<form action="{{ route('radio.apply.store') }}" method="POST">
@csrf
<div class="grid grid-cols-1 gap-6">
<div>
<label for="real_name" class="block text-sm font-medium text-gray-300">{{ __('radio.real_name') }}</label>
<input type="text" name="real_name" id="real_name" required class="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white">
</div>
<div>
<label for="age" class="block text-sm font-medium text-gray-300">{{ __('radio.age') }}</label>
<input type="number" name="age" id="age" required min="13" max="99" class="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white">
</div>
<div>
<label for="discord_username" class="block text-sm font-medium text-gray-300">{{ __('radio.discord') }}</label>
<input type="text" name="discord_username" id="discord_username" class="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white">
</div>
<div>
<label for="experience" class="block text-sm font-medium text-gray-300">{{ __('radio.experience') }}</label>
<textarea name="experience" id="experience" rows="3" class="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"></textarea>
</div>
<div>
<label for="music_style" class="block text-sm font-medium text-gray-300">{{ __('radio.music_style') }}</label>
<textarea name="music_style" id="music_style" rows="2" class="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"></textarea>
</div>
<div>
<label for="availability" class="block text-sm font-medium text-gray-300">{{ __('radio.availability') }}</label>
<textarea name="availability" id="availability" required rows="2" class="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"></textarea>
</div>
<div>
<label for="motivation" class="block text-sm font-medium text-gray-300">{{ __('radio.motivation') }}</label>
<textarea name="motivation" id="motivation" required rows="4" class="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"></textarea>
</div>
<div>
<button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded">
{{ __('radio.send_application') }}
</button>
</div>
</div>
</form>
@endif
</div>
</div>
</div>
@endsection
+45
View File
@@ -0,0 +1,45 @@
@extends('layouts.app')
@section('title', __('radio.contests') . ' - ' . config('app.name'))
@section('content')
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-white">
<h2 class="text-2xl font-bold mb-6">{{ __('radio.active_contests') }}</h2>
@if(session('error'))
<div class="bg-red-500 text-white p-4 rounded mb-4">
{{ session('error') }}
</div>
@endif
@if($contests->isEmpty())
<div class="bg-gray-700 p-6 rounded-lg text-center">
<p class="text-gray-300">{{ __('radio.no_contests') }}</p>
</div>
@else
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach($contests as $contest)
<div class="bg-gray-700 rounded-lg overflow-hidden shadow-lg">
<div class="p-6">
<h3 class="text-xl font-bold mb-2">{{ $contest->title }}</h3>
<p class="text-gray-300 mb-4 line-clamp-3">{{ $contest->description }}</p>
<div class="flex justify-between items-center text-sm text-gray-400 mb-4">
<span>{{ __('radio.ends_in', ['time' => $contest->ends_at->diffForHumans()]) }}</span>
<span>{{ $contest->entryCount() }} {{ __('radio.participants') }}</span>
</div>
<a href="{{ route('radio.contests.show', $contest) }}" class="block w-full bg-indigo-600 hover:bg-indigo-700 text-center text-white font-bold py-2 px-4 rounded transition duration-200">
{{ __('radio.view_participate') }}
</a>
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
</div>
@endsection
+37
View File
@@ -0,0 +1,37 @@
@extends('layouts.app')
@section('title', $contest->title . ' - ' . __('radio.contests') . ' - ' . config('app.name'))
@section('content')
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-white">
<div class="mb-6">
<a href="{{ route('radio.contests.index') }}" class="text-indigo-400 hover:text-indigo-300 flex items-center">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
{{ __('radio.back_overview') }}
</a>
</div>
<h1 class="text-3xl font-bold mb-4">{{ $contest->title }}</h1>
<div class="prose prose-invert max-w-none mb-8">
{!! nl2br(e($contest->description)) !!}
</div>
<div class="bg-gray-700 p-6 rounded-lg mb-8">
<h3 class="text-xl font-semibold mb-4">{{ __('radio.information') }}</h3>
<ul class="space-y-2 text-gray-300">
<li><strong>{{ __('radio.start_date') }}:</strong> {{ $contest->starts_at->format('d-m-Y H:i') }}</li>
<li><strong>{{ __('radio.end_date') }}:</strong> {{ $contest->ends_at->format('d-m-Y H:i') }}</li>
<li><strong>{{ __('radio.participants') }}:</strong> {{ $contest->entryCount() }}</li>
</ul>
</div>
<div class="bg-gray-700 p-6 rounded-lg">
<p class="text-center text-gray-300">{{ __('radio.participate_button') }} form placeholder.</p>
</div>
</div>
</div>
</div>
@endsection
+45
View File
@@ -0,0 +1,45 @@
@extends('layouts.app')
@section('title', __('radio.giveaways') . ' - ' . config('app.name'))
@section('content')
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-white">
<h2 class="text-2xl font-bold mb-6">{{ __('radio.active_giveaways') }}</h2>
@if(session('error'))
<div class="bg-red-500 text-white p-4 rounded mb-4">
{{ session('error') }}
</div>
@endif
@if($giveaways->isEmpty())
<div class="bg-gray-700 p-6 rounded-lg text-center">
<p class="text-gray-300">{{ __('radio.no_giveaways') }}</p>
</div>
@else
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach($giveaways as $giveaway)
<div class="bg-gray-700 rounded-lg overflow-hidden shadow-lg">
<div class="p-6">
<h3 class="text-xl font-bold mb-2">{{ $giveaway->title }}</h3>
<p class="text-gray-300 mb-4 line-clamp-3">{{ $giveaway->description }}</p>
<div class="flex justify-between items-center text-sm text-gray-400 mb-4">
<span>{{ __('radio.ends_in', ['time' => $giveaway->ends_at->diffForHumans()]) }}</span>
<span>{{ $giveaway->participantCount() }} {{ __('radio.participants') }}</span>
</div>
<a href="{{ route('radio.giveaways.show', $giveaway) }}" class="block w-full bg-indigo-600 hover:bg-indigo-700 text-center text-white font-bold py-2 px-4 rounded transition duration-200">
{{ __('radio.view_participate') }}
</a>
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
</div>
@endsection
+38
View File
@@ -0,0 +1,38 @@
@extends('layouts.app')
@section('title', $giveaway->title . ' - ' . __('radio.giveaways') . ' - ' . config('app.name'))
@section('content')
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-white">
<div class="mb-6">
<a href="{{ route('radio.giveaways.index') }}" class="text-indigo-400 hover:text-indigo-300 flex items-center">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
{{ __('radio.back_overview') }}
</a>
</div>
<h1 class="text-3xl font-bold mb-4">{{ $giveaway->title }}</h1>
<div class="prose prose-invert max-w-none mb-8">
{!! nl2br(e($giveaway->description)) !!}
</div>
<div class="bg-gray-700 p-6 rounded-lg mb-8">
<h3 class="text-xl font-semibold mb-4">{{ __('radio.information') }}</h3>
<ul class="space-y-2 text-gray-300">
<li><strong>{{ __('radio.start_date') }}:</strong> {{ $giveaway->starts_at->format('d-m-Y H:i') }}</li>
<li><strong>{{ __('radio.end_date') }}:</strong> {{ $giveaway->ends_at->format('d-m-Y H:i') }}</li>
<li><strong>{{ __('radio.participants') }}:</strong> {{ $giveaway->participantCount() }}</li>
<li><strong>{{ __('radio.prize_value') }}:</strong> {{ $giveaway->prize_value }} Credits</li>
</ul>
</div>
<div class="bg-gray-700 p-6 rounded-lg">
<p class="text-center text-gray-300">{{ __('radio.participate_button') }} button placeholder.</p>
</div>
</div>
</div>
</div>
@endsection
+264
View File
@@ -0,0 +1,264 @@
@extends('layouts.app')
@section('title', __('radio.music') . ' - ' . config('app.name'))
@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/inter@4.x/400-700.css">
<style>
:root {
--radio-primary: {{ $primaryColor ?? '#eeb425' }};
--radio-secondary: {{ $secondaryColor ?? '#1a1a2e' }};
--radio-text: {{ $textColor ?? '#ffffff' }};
--radio-accent: {{ $accentColor ?? '#ff6b6b' }};
}
.radio-container {
background: linear-gradient(135deg, var(--radio-secondary) 0%, #0f0f1a 100%);
min-height: 100vh;
}
.radio-player {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.now-playing-card {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.stat-card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.animate-pulse-slow {
animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.live-badge {
background: linear-gradient(90deg, #ef4444, #f97316);
animation: livePulse 2s infinite;
}
@keyframes livePulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); }
50% { box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); }
}
</style>
@endpush
@section('content')
<div class="radio-container text-white">
@if(!$enabled)
<!-- Radio Disabled State -->
<div class="min-h-screen flex items-center justify-center p-6">
<div class="text-center max-w-md">
<div class="mb-6">
<svg class="w-24 h-24 mx-auto text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</div>
<h1 class="text-3xl font-bold mb-4">{{ __('radio.offline') }}</h1>
<p class="text-gray-400 mb-6">{{ __('radio.offline_message') }}</p>
@if($offlineMessage)
<div class="bg-gray-800 rounded-lg p-4">
<p class="text-gray-300">{{ $offlineMessage }}</p>
</div>
@endif
</div>
</div>
@else
<!-- Radio Player Section -->
<div class="max-w-6xl mx-auto px-4 py-8">
<!-- Header -->
<div class="flex items-center justify-between mb-8">
<div>
<h1 class="text-3xl font-bold flex items-center gap-3">
<span class="live-badge inline-flex items-center px-3 py-1 rounded-full text-xs font-bold">
<span class="w-2 h-2 bg-white rounded-full mr-2"></span>
{{ __('radio.live') }}
</span>
{{ __('radio.music') }}
</h1>
<p class="text-gray-400 mt-1">{{ __('radio.music_desc') }}</p>
</div>
<!-- Stats -->
<div class="flex gap-6">
@if($showListeners)
<div class="stat-card rounded-lg px-4 py-2 flex items-center gap-2">
<svg class="w-5 h-5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<span id="listenerCount" class="font-semibold">--</span>
<span class="text-gray-400 text-sm">{{ __('radio.listeners') }}</span>
</div>
@endif
<a href="{{ route('radio.leaderboard') }}" class="stat-card rounded-lg px-4 py-2 flex items-center gap-2 hover:bg-white/10 transition">
<svg class="w-5 h-5 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
<span class="font-semibold">{{ __('radio.top_100') }}</span>
</a>
</div>
</div>
<!-- Main Player -->
<div class="radio-player rounded-2xl p-6 mb-8">
<div class="grid md:grid-cols-3 gap-6 items-center">
<!-- Album Art / DJ Avatar -->
<div class="flex justify-center">
<div id="djAvatar" class="w-32 h-32 rounded-full bg-gradient-to-br from-amber-400 to-amber-600 flex items-center justify-center text-4xl">
@if($currentDJ && $currentDJ->user)
<img src="{{ $currentDJ->user->avatar_url ?? asset('images/default-avatar.png') }}" alt="{{ $currentDJ->user->username }}" class="w-full h-full rounded-full object-cover">
@else
🎵
@endif
</div>
</div>
<!-- Track Info -->
<div class="text-center">
<div id="nowPlayingInfo">
<p class="text-gray-400 text-sm mb-1">{{ __('radio.now_playing') }}</p>
<h2 id="trackTitle" class="text-xl font-bold mb-2">
@if($nowPlaying && $nowPlaying['title'])
{{ $nowPlaying['title'] }}
@else
--
@endif
</h2>
@if($nowPlaying && $nowPlaying['artist'])
<p id="trackArtist" class="text-gray-400">{{ $nowPlaying['artist'] }}</p>
@endif
</div>
<!-- DJ Info -->
@if($showCurrentDJ && $currentDJ && $currentDJ->user)
<div class="mt-4 pt-4 border-t border-white/10">
<p class="text-gray-400 text-sm mb-1">{{ __('radio.host') }}</p>
<p class="font-semibold text-amber-400">{{ $currentDJ->user->username }}</p>
</div>
@endif
</div>
</div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const player = document.getElementById('radioPlayer');
const playBtn = document.getElementById('playPauseBtn');
const playIcon = document.getElementById('playIcon');
const pauseIcon = document.getElementById('pauseIcon');
const volumeSlider = document.getElementById('volumeSlider');
const listenerCount = document.getElementById('listenerCount');
// Play/Pause
playBtn.addEventListener('click', function() {
if (player.paused) {
player.play().catch(function(e) {
console.log('Autoplay blocked:', e);
});
playIcon.classList.add('hidden');
pauseIcon.classList.remove('hidden');
} else {
player.pause();
playIcon.classList.remove('hidden');
pauseIcon.classList.add('hidden');
}
});
// Volume
volumeSlider.addEventListener('input', function() {
player.volume = this.value / 100;
});
// Initial volume
player.volume = volumeSlider.value / 100;
// Update listeners count
function updateListeners() {
fetch('/api/radio/listeners')
.then(r => r.json())
.then(data => {
if (listenerCount) {
listenerCount.textContent = data.count.toLocaleString();
}
})
.catch(() => {
if (listenerCount) listenerCount.textContent = '--';
});
}
// Update now playing
function updateNowPlaying() {
fetch('/api/radio/now-playing')
.then(r => r.json())
.then(data => {
const titleEl = document.getElementById('trackTitle');
const artistEl = document.getElementById('trackArtist');
if (titleEl && data.title) titleEl.textContent = data.title;
if (artistEl && data.artist) artistEl.textContent = data.artist;
})
.catch(() => {});
}
// Auto-update listeners every 30 seconds
updateListeners();
setInterval(updateListeners, 30000);
// Auto-update now playing every 15 seconds
updateNowPlaying();
setInterval(updateNowPlaying, 15000);
// Load user points if logged in
if (document.body.classList.contains('authenticated')) {
loadUserPoints();
setInterval(loadUserPoints, 60000); // Update every minute
}
});
function loadUserPoints() {
fetch('/api/radio/points/user')
.then(response => {
if (!response.ok) {
throw new Error('Not authenticated');
}
return response.json();
})
.then(data => {
const pointsCount = document.getElementById('userPointsCount');
const userRank = document.getElementById('userRank');
const pointsHistory = document.getElementById('pointsHistory');
if (pointsCount) pointsCount.textContent = data.points || 0;
if (userRank) userRank.textContent = data.rank || '--';
if (pointsHistory && data.history && data.history.length > 0) {
pointsHistory.innerHTML = data.history.map(item => `
<div class="flex justify-between items-center py-1 border-b border-gray-700 text-xs">
<span class="text-gray-300">${item.reason}</span>
<span class="${item.points > 0 ? 'text-green-400' : 'text-red-400'}">
${item.points > 0 ? '+' : ''}${item.points}
</span>
</div>
`).join('');
} else if (pointsHistory) {
pointsHistory.innerHTML = '<p class="text-xs text-gray-500">{{ __("radio.no_history") }}</p>';
}
})
.catch(error => {
console.log('User points not available:', error.message);
// Silently fail if not authenticated or other error
});
}
</script>
@endpush
+318
View File
@@ -0,0 +1,318 @@
<x-app-layout>
@push('title', 'Radio Leaderboard')
<div class="col-span-12 lg:col-span-9 lg:w-[96%]">
<div class="flex flex-col gap-y-4">
<x-content.content-card icon="trophy" classes="border dark:border-gray-900">
<x-slot:title>
Radio Leaderboard
</x-slot:title>
<x-slot:under-title>
Wie zijn de meest toegewijde luisteraars?
</x-slot:under-title>
<div class="px-2 text-sm space-y-4 dark:text-gray-200">
<!-- Period Navigation -->
<div class="flex flex-wrap gap-2 mb-6">
<a href="{{ route('radio.leaderboard', ['period' => 'all']) }}"
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-300 transform hover:scale-105
{{ $period === 'all'
? 'bg-gradient-to-r from-yellow-400 to-yellow-500 text-gray-900 shadow-lg shadow-yellow-500/30'
: 'bg-gray-700/50 text-gray-200 hover:bg-gray-600' }}">
🌟 Alles
</a>
<a href="{{ route('radio.leaderboard', ['period' => 'weekly']) }}"
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-300 transform hover:scale-105
{{ $period === 'weekly'
? 'bg-gradient-to-r from-yellow-400 to-yellow-500 text-gray-900 shadow-lg shadow-yellow-500/30'
: 'bg-gray-700/50 text-gray-200 hover:bg-gray-600' }}">
📅 Deze Week
</a>
<a href="{{ route('radio.leaderboard', ['period' => 'monthly']) }}"
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-300 transform hover:scale-105
{{ $period === 'monthly'
? 'bg-gradient-to-r from-yellow-400 to-yellow-500 text-gray-900 shadow-lg shadow-yellow-500/30'
: 'bg-gray-700/50 text-gray-200 hover:bg-gray-600' }}">
📆 Deze Maand
</a>
</div>
<!-- Top 3 Podium -->
@if(!empty($users) && count($users) >= 3)
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
<!-- 2nd Place -->
<div class="order-1 md:order-1">
<div class="bg-gradient-to-b from-gray-700 to-gray-800 rounded-xl p-6 text-center border border-gray-600/50 shadow-xl transform hover:scale-105 transition-all duration-300 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-b from-gray-500/10 to-transparent"></div>
<div class="relative">
<div class="absolute -top-6 left-1/2 -translate-x-1/2">
<div class="w-16 h-16 rounded-full bg-gradient-to-br from-gray-300 to-gray-400 flex items-center justify-center text-2xl shadow-lg">
🥈
</div>
</div>
<div class="mt-8 mb-3">
<img src="{{ $users[1]['avatar'] ?? 'https://ui-avatars.com/api/?name=' . urlencode($users[1]['username']) . '&size=128&background=6b7280&color=ffffff' }}"
alt="{{ $users[1]['username'] }}"
class="w-24 h-24 rounded-full mx-auto border-4 border-gray-500 shadow-lg">
</div>
<h4 class="font-bold text-xl text-white mb-1">{{ $users[1]['username'] }}</h4>
<div class="inline-flex items-center gap-1 px-4 py-1.5 rounded-full bg-gray-600 text-gray-200 font-medium text-sm">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
{{ number_format($users[1]['points']) }}
</div>
</div>
</div>
</div>
<!-- 1st Place -->
<div class="order-2">
<div class="bg-gradient-to-b from-yellow-500/20 to-gray-800 rounded-xl p-6 text-center border-2 border-yellow-500/50 shadow-xl shadow-yellow-500/20 transform hover:scale-105 transition-all duration-300 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-b from-yellow-500/10 to-transparent"></div>
<div class="absolute -top-8 left-1/2 -translate-x-1/2">
<div class="w-20 h-20 rounded-full bg-gradient-to-br from-yellow-300 to-yellow-500 flex items-center justify-center text-3xl shadow-lg shadow-yellow-500/50 animate-pulse">
🥇
</div>
</div>
<div class="mt-10 mb-3">
<img src="{{ $users[0]['avatar'] ?? 'https://ui-avatars.com/api/?name=' . urlencode($users[0]['username']) . '&size=128&background=eeb425&color=000000' }}"
alt="{{ $users[0]['username'] }}"
class="w-28 h-28 rounded-full mx-auto border-4 border-yellow-400 shadow-lg shadow-yellow-500/30">
</div>
<h4 class="font-bold text-xl text-yellow-400 mb-1">{{ $users[0]['username'] }}</h4>
<div class="inline-flex items-center gap-1 px-4 py-1.5 rounded-full bg-yellow-500/20 text-yellow-400 font-medium text-sm">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
{{ number_format($users[0]['points']) }}
</div>
</div>
</div>
<!-- 3rd Place -->
<div class="order-3 md:order-3">
<div class="bg-gradient-to-b from-orange-700/50 to-gray-800 rounded-xl p-6 text-center border border-orange-600/30 shadow-xl transform hover:scale-105 transition-all duration-300 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-b from-orange-500/10 to-transparent"></div>
<div class="relative">
<div class="absolute -top-6 left-1/2 -translate-x-1/2">
<div class="w-14 h-14 rounded-full bg-gradient-to-br from-orange-400 to-orange-600 flex items-center justify-center text-xl shadow-lg">
🥉
</div>
</div>
<div class="mt-6 mb-3">
<img src="{{ $users[2]['avatar'] ?? 'https://ui-avatars.com/api/?name=' . urlencode($users[2]['username']) . '&size=128&background=ea580c&color=ffffff' }}"
alt="{{ $users[2]['username'] }}"
class="w-20 h-20 rounded-full mx-auto border-4 border-orange-500 shadow-lg">
</div>
<h4 class="font-bold text-lg text-orange-400 mb-1">{{ $users[2]['username'] }}</h4>
<div class="inline-flex items-center gap-1 px-4 py-1.5 rounded-full bg-orange-500/20 text-orange-400 font-medium text-sm">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
{{ number_format($users[2]['points']) }}
</div>
</div>
</div>
</div>
</div>
@endif
<!-- Leaderboard List -->
<div class="bg-gray-800/50 backdrop-blur-sm rounded-xl overflow-hidden border border-gray-700/50 shadow-xl">
<div class="px-6 py-4 border-b border-gray-700/50 bg-gradient-to-r from-gray-800/80 to-gray-800/40">
<h4 class="font-bold text-white flex items-center gap-3">
<span class="w-8 h-8 rounded-lg bg-[#eeb425]/20 flex items-center justify-center">
<svg class="w-5 h-5 text-[#eeb425]" fill="currentColor" viewBox="0 0 24 24">
<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/>
</svg>
</span>
Alle Luisteraars
<span class="ml-auto text-sm font-normal text-gray-400">({{ count($users) }})</span>
</h4>
</div>
<div class="max-h-[500px] overflow-y-auto custom-scrollbar">
@forelse($users as $user)
<div class="flex items-center gap-4 px-6 py-3 border-b border-gray-700/30 last:border-b-0 hover:bg-gray-700/30 transition-all duration-200 group">
<!-- Rank -->
<div class="w-8 flex-shrink-0">
@if($user['rank'] <= 3)
<div class="flex items-center justify-center w-8 h-8 rounded-full font-bold text-sm
{{ $user['rank'] === 1 ? 'bg-gradient-to-br from-yellow-400 to-yellow-600 text-gray-900 shadow-lg shadow-yellow-500/30' :
($user['rank'] === 2 ? 'bg-gradient-to-br from-gray-300 to-gray-500 text-gray-900' :
'bg-gradient-to-br from-orange-400 to-orange-600 text-white') }}">
{{ $user['rank'] }}
</div>
@else
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-gray-700/50 text-gray-400 text-sm font-medium">
{{ $user['rank'] }}
</div>
@endif
</div>
<!-- Avatar -->
<div class="relative flex-shrink-0">
<img src="{{ $user['avatar'] ?? 'https://ui-avatars.com/api/?name=' . urlencode($user['username']) . '&size=64&background=374151&color=eeb425' }}"
alt="{{ $user['username'] }}"
class="w-12 h-12 rounded-full border-2 border-gray-600 group-hover:border-[#eeb425] transition-colors duration-200">
@if($user['rank'] <= 3)
<div class="absolute -top-1 -right-1 w-5 h-5 rounded-full flex items-center justify-center text-xs
{{ $user['rank'] === 1 ? 'bg-yellow-400' : ($user['rank'] === 2 ? 'bg-gray-300' : 'bg-orange-400') }}">
{{ $user['rank'] }}
</div>
@endif
</div>
<!-- Username -->
<div class="flex-1 min-w-0">
<div class="font-semibold text-white group-hover:text-[#eeb425] transition-colors duration-200 truncate">{{ $user['username'] }}</div>
@if($user['rank'] <= 3)
<div class="text-xs">
<span class="{{ $user['rank'] === 1 ? 'text-yellow-400' : ($user['rank'] === 2 ? 'text-gray-300' : 'text-orange-400') }}">
{{ $user['rank'] === 1 ? '👑 Kampioen' : ($user['rank'] === 2 ? '🥈 Runner-up' : '🥉 Top 3') }}
</span>
</div>
@endif
</div>
<!-- Points -->
<div class="flex items-center gap-3 flex-shrink-0">
<div class="px-3 py-1.5 rounded-lg bg-gradient-to-r from-[#eeb425]/10 to-[#eeb425]/5 border border-[#eeb425]/20">
<span class="text-[#eeb425] font-bold">{{ number_format($user['points']) }}</span>
<span class="text-[#eeb425]/60 text-xs ml-1">pts</span>
</div>
@if($user['rank'] === 1)
<span class="text-2xl">🏆</span>
@elseif($user['rank'] <= 10)
<span class="text-lg"></span>
@endif
</div>
</div>
@empty
<div class="text-center py-16 px-4">
<div class="relative inline-block mb-4">
<div class="w-24 h-24 rounded-full bg-gray-700/50 flex items-center justify-center text-5xl">
🎵
</div>
<div class="absolute -bottom-2 -right-2 w-10 h-10 rounded-full bg-[#eeb425] flex items-center justify-center text-xl">
🤔
</div>
</div>
<h4 class="text-xl font-bold text-white mb-2">{{ __('radio.no_listeners') }}</h4>
<p class="text-gray-400 mb-6 max-w-sm mx-auto">{{ __('radio.listen_to_earn') }}</p>
<a href="{{ route('radio.index') }}"
class="inline-flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-[#eeb425] to-yellow-500 text-gray-900 rounded-xl font-semibold hover:from-yellow-400 hover:to-yellow-400 transition-all duration-300 transform hover:scale-105 shadow-lg shadow-yellow-500/30">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</svg>
{{ __('radio.start_listening') }}
</a>
</div>
@endforelse
</div>
</div>
</div>
</x-content.content-card>
</div>
</div>
<!-- Sidebar -->
<div class="col-span-12 lg:col-span-3 lg:w-[110%] space-y-4 lg:-ml-[32px]">
<x-content.content-card icon="info" classes="border dark:border-gray-900 bg-gradient-to-br from-gray-800/50 to-gray-900/50">
<x-slot:title>
{{ __('radio.leaderboard_info') }}
</x-slot:title>
<x-slot:under-title>
{{ __('radio.how_it_works') }}
</x-slot:under-title>
<div class="px-2 text-sm space-y-4 dark:text-gray-200">
<p class="text-gray-300">{{ __('radio.points_auto') }}</p>
<div class="space-y-3 text-xs">
<div class="flex items-center gap-3 p-3 rounded-lg bg-green-500/10 border border-green-500/20">
<span class="w-3 h-3 rounded-full bg-green-500 shadow-lg shadow-green-500/50"></span>
<span class="text-green-400">{{ __('radio.points_per_minute') }}</span>
</div>
<div class="flex items-center gap-3 p-3 rounded-lg bg-yellow-500/10 border border-yellow-500/20">
<span class="w-3 h-3 rounded-full bg-yellow-500 shadow-lg shadow-yellow-500/50"></span>
<span class="text-yellow-400">{{ __('radio.max_points_per_day') }}</span>
</div>
<div class="flex items-center gap-3 p-3 rounded-lg bg-blue-500/10 border border-blue-500/20">
<span class="w-3 h-3 rounded-full bg-blue-500 shadow-lg shadow-blue-500/50"></span>
<span class="text-blue-400">{{ __('radio.leaderboard_updated') }}</span>
</div>
</div>
</div>
</x-content.content-card>
<x-content.content-card icon="trophy" classes="border dark:border-gray-900 bg-gradient-to-br from-[#eeb425]/10 to-transparent">
<x-slot:title>
{{ __('radio.quick_stats') }}
</x-slot:title>
<x-slot:under-title>
{{ __('radio.current_stand') }}
</x-slot:under-title>
<div class="px-2 text-sm space-y-4 dark:text-gray-200">
<div class="flex justify-between items-center p-3 rounded-lg bg-gray-700/30">
<span class="text-gray-400">{{ __('radio.active_listeners') }}</span>
<span class="font-bold text-[#eeb425] text-lg">{{ count($users) }}</span>
</div>
@if(!empty($users))
<div class="flex justify-between items-center p-3 rounded-lg bg-yellow-500/10 border border-yellow-500/20">
<span class="text-gray-400">{{ __('radio.current_leader') }}</span>
<span class="font-bold text-yellow-400 flex items-center gap-2">
👑 {{ $users[0]['username'] }}
</span>
</div>
@endif
<div class="flex justify-between items-center p-3 rounded-lg bg-gray-700/30">
<span class="text-gray-400">{{ __('radio.current_period') }}</span>
<span class="font-semibold text-white">
{{ $period === 'weekly' ? __('radio.this_week') : ($period === 'monthly' ? __('radio.this_month') : __('radio.total')) }}
</span>
</div>
</div>
</x-content.content-card>
<!-- Top 3 Compact -->
@if(!empty($users) && count($users) >= 3)
<x-content.content-card icon="trophy" classes="border dark:border-gray-900">
<x-slot:title>
Top 3
</x-slot:title>
<x-slot:under-title>
De beste luisteraars
</x-slot:under-title>
<div class="px-2 space-y-3">
@foreach(array_slice($users, 0, 3) as $index => $user)
<div class="flex items-center gap-3 p-3 rounded-lg {{ $index === 0 ? 'bg-yellow-500/10 border border-yellow-500/20' : 'bg-gray-700/30' }}">
<div class="w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm
{{ $index === 0 ? 'bg-gradient-to-br from-yellow-400 to-yellow-600 text-gray-900' :
($index === 1 ? 'bg-gradient-to-br from-gray-300 to-gray-500 text-gray-900' :
'bg-gradient-to-br from-orange-400 to-orange-600 text-white') }}">
{{ $index + 1 }}
</div>
<img src="{{ $user['avatar'] ?? 'https://ui-avatars.com/api/?name=' . urlencode($user['username']) . '&size=64&background=374151&color=eeb425' }}"
alt="{{ $user['username'] }}"
class="w-10 h-10 rounded-full border-2 border-gray-600">
<div class="flex-1 min-w-0">
<div class="font-semibold text-white truncate">{{ $user['username'] }}</div>
<div class="text-xs text-[#eeb425]">{{ number_format($user['points']) }} pts</div>
</div>
<span class="text-xl">
{{ $index === 0 ? '🏆' : ($index === 1 ? '🥈' : '🥉') }}
</span>
</div>
@endforeach
</div>
</x-content.content-card>
@endif
</div>
</x-app-layout>
+83
View File
@@ -0,0 +1,83 @@
@extends('layouts.app')
@section('title', __('radio.requests') . ' - ' . config('app.name'))
@section('content')
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-white">
<h2 class="text-2xl font-bold mb-4">{{ __('radio.requests') }}</h2>
@if(session('success'))
<div class="bg-green-500 text-white p-4 rounded mb-4">
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="bg-red-500 text-white p-4 rounded mb-4">
{{ session('error') }}
</div>
@endif
<form action="{{ route('radio.requests.store') }}" method="POST" class="mb-8">
@csrf
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="artist" class="block text-sm font-medium text-gray-300">{{ __('radio.artist') }}</label>
<input type="text" name="artist" id="artist" required class="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white focus:border-indigo-500 focus:ring-indigo-500">
</div>
<div>
<label for="title" class="block text-sm font-medium text-gray-300">{{ __('radio.title') }}</label>
<input type="text" name="title" id="title" required class="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white focus:border-indigo-500 focus:ring-indigo-500">
</div>
</div>
<div class="mt-4">
<label for="message" class="block text-sm font-medium text-gray-300">{{ __('radio.message') }}</label>
<input type="text" name="message" id="message" class="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white focus:border-indigo-500 focus:ring-indigo-500">
</div>
<div class="mt-4">
<button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded">
{{ __('radio.submit_request') }}
</button>
</div>
</form>
@if(isset($recentRequests) && count($recentRequests) > 0)
<h3 class="text-xl font-bold mb-4">{{ __('radio.queue') }}</h3>
<div class="overflow-x-auto">
<table class="min-w-full bg-gray-700 rounded-lg overflow-hidden">
<thead class="bg-gray-600">
<tr>
<th class="py-3 px-4 text-left">{{ __('radio.artist') }}</th>
<th class="py-3 px-4 text-left">{{ __('radio.title') }}</th>
<th class="py-3 px-4 text-left">{{ __('radio.requested_by') }}</th>
<th class="py-3 px-4 text-left">{{ __('radio.votes') }}</th>
<th class="py-3 px-4 text-left">{{ __('radio.vote') }}</th>
</tr>
</thead>
<tbody>
@foreach($recentRequests as $request)
<tr class="border-b border-gray-600">
<td class="py-3 px-4">{{ $request->artist }}</td>
<td class="py-3 px-4">{{ $request->title }}</td>
<td class="py-3 px-4">{{ $request->user->username ?? __('radio.unknown') }}</td>
<td class="py-3 px-4">{{ $request->votes }}</td>
<td class="py-3 px-4">
<form action="{{ route('radio.requests.vote', $request) }}" method="POST">
@csrf
<button type="submit" class="text-indigo-400 hover:text-indigo-300">{{ __('radio.vote') }}</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<p class="text-gray-400">{{ __('radio.no_requests') }}</p>
@endif
</div>
</div>
</div>
@endsection
+44
View File
@@ -0,0 +1,44 @@
@extends('layouts.app')
@section('title', __('radio.timetable') . ' - ' . config('app.name'))
@section('content')
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-white">
<h2 class="text-2xl font-bold mb-6">{{ __('radio.timetable') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-7 gap-4">
@php
$days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
@endphp
@foreach($days as $day)
<div class="bg-gray-700 rounded-lg overflow-hidden">
<div class="bg-gray-600 p-3 text-center font-bold">
{{ __('radio.days.' . $day) }}
</div>
<div class="p-4 space-y-3">
@if(isset($schedule[$day]) && count($schedule[$day]) > 0)
@foreach($schedule[$day] as $slot)
<div class="bg-gray-800 p-3 rounded border border-gray-600">
<div class="text-sm text-indigo-400 font-semibold">
{{ $slot->start_time->format('H:i') }} - {{ $slot->end_time->format('H:i') }}
</div>
<div class="font-bold">{{ $slot->user->username ?? __('radio.unknown') }}</div>
<div class="text-xs text-gray-400 truncate">{{ $slot->show_name }}</div>
</div>
@endforeach
@else
<div class="text-center text-gray-500 text-sm py-4">
{{ __('radio.no_shows') }}
</div>
@endif
</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
@endsection
+71
View File
@@ -0,0 +1,71 @@
@extends('layouts.app')
@section('title', __('radio.shouts') . ' - ' . config('app.name'))
@section('content')
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-white">
<h2 class="text-2xl font-bold mb-4">{{ __('radio.shouts') }}</h2>
@if(session('success'))
<div class="bg-green-500 text-white p-4 rounded mb-4">
{{ session('success') }}
</div>
@endif
<form action="{{ route('radio.shouts.store') }}" method="POST" class="mb-8">
@csrf
<div>
<label for="message" class="block text-sm font-medium text-gray-300">{{ __('radio.shout_message') }}</label>
<textarea name="message" id="message" required rows="3" maxlength="280" placeholder="{{ __('radio.shout_placeholder') }}" class="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white focus:border-indigo-500 focus:ring-indigo-500"></textarea>
<p class="text-xs text-gray-400 mt-1">{{ __('radio.max_chars') }}</p>
</div>
<div class="mt-4">
<button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded">
{{ __('radio.send_shout') }}
</button>
</div>
</form>
<div id="shouts-container" class="space-y-4">
{{-- Shouts will be loaded via API --}}
<div class="text-center text-gray-400">{{ __('radio.loading') }}</div>
</div>
</div>
</div>
</div>
@push('scripts')
<script>
function loadShouts() {
fetch('/api/radio/shouts')
.then(response => response.json())
.then(data => {
const container = document.getElementById('shouts-container');
if (data.shouts && data.shouts.length > 0) {
container.innerHTML = data.shouts.map(shout => `
<div class="bg-gray-700 p-4 rounded-lg">
<div class="flex justify-between items-center mb-2">
<span class="font-bold text-indigo-400">${shout.username}</span>
<span class="text-xs text-gray-400">${shout.created_at}</span>
</div>
<p class="text-gray-300">${shout.message}</p>
</div>
`).join('');
} else {
container.innerHTML = "<p class='text-center text-gray-400'>{{ __('radio.no_shouts') }}</p>";
}
})
.catch(() => {
document.getElementById('shouts-container').innerHTML = "<p class='text-center text-red-400'>{{ __('radio.load_error') }}</p>";
});
}
document.addEventListener('DOMContentLoaded', function() {
loadShouts();
setInterval(loadShouts, 30000); // Reload every 30 seconds
});
</script>
@endpush
@endsection