updates for admin area

This commit is contained in:
Mark Gregory
2026-09-12 21:18:19 +01:00
parent ab3134de13
commit 5e2f4a1fa7
42 changed files with 894 additions and 210 deletions
+7
View File
@@ -14,3 +14,10 @@
@media(max-width:1100px){.dj-cards{grid-template-columns:repeat(3,minmax(0,1fr))}.steps{grid-template-columns:repeat(2,1fr)}.profile-grid{grid-template-columns:minmax(0,1fr) 330px}.admin-stats{grid-template-columns:repeat(2,1fr)}.role-grid{grid-template-columns:repeat(2,1fr)}}
@media(max-width:900px){.shell{width:min(100% - 28px,1240px)}.site-header{height:68px}.nav{height:68px}.mobile-menu-button{display:block}.nav-panel{display:none;position:absolute;top:68px;left:14px;right:14px;margin:0;padding:14px;flex-direction:column;align-items:stretch;gap:12px;background:#0e1425;border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow)}.nav-panel.open{display:flex}.navlinks,.nav-actions{align-items:stretch;flex-direction:column;gap:2px}.navlinks a,.nav-actions>a,.nav-actions .text-button{padding:11px 10px;text-align:left}.nav-actions{padding-top:8px;border-top:1px solid var(--line)}.nav-actions .button{width:100%}.hero{min-height:auto}.hero-copy{padding:74px 0}.searchbox,.filterbar{grid-template-columns:1fr 1fr}.searchbox .button,.filterbar .button{grid-column:1/-1}.dj-cards{grid-template-columns:repeat(2,minmax(0,1fr))}.split-feature{grid-template-columns:1fr;gap:36px}.profile-panel{grid-template-columns:100px 1fr}.profile-price{grid-column:1/-1;padding:18px 0 0;border-left:0;border-top:1px solid #e2e8f0;display:grid;grid-template-columns:auto auto 1fr auto;gap:8px;align-items:center}.profile-price .button{margin:0}.profile-grid{grid-template-columns:1fr}.enquiry-card{position:static}.account-shell{grid-template-columns:1fr}.account-sidebar{position:static}.account-nav{display:flex;gap:4px;overflow-x:auto}.account-nav a{white-space:nowrap}.dashboard-grid{grid-template-columns:1fr}.admin-shell{display:block}.admin-sidebar{position:fixed;left:0;top:0;width:270px;transform:translateX(-105%);transition:transform .2s ease}.admin-sidebar.open{transform:none}.admin-overlay{position:fixed;inset:0;background:rgba(11,16,32,.55);z-index:70}.admin-overlay.open{display:block}.admin-menu,.admin-close{display:inline-grid;place-items:center}.admin-topbar{padding:0 18px}.admin-page{padding:22px 18px}.admin-flash{margin:14px 18px 0}.admin-grid{grid-template-columns:1fr}.settings-grid{grid-template-columns:1fr}}
@media(max-width:640px){.shell{width:min(100% - 22px,1240px)}.brand{font-size:21px}.hero-shade{background:linear-gradient(180deg,rgba(7,9,20,.78),rgba(7,9,20,.97))}.hero-copy{padding:58px 0}.hero h1{font-size:44px}.hero-copy>p{font-size:16px}.searchbox,.filterbar{grid-template-columns:1fr;padding:8px}.searchbox .button,.filterbar .button{grid-column:auto}.hero-proof{gap:9px 16px;font-size:11px}.section{padding:58px 0}.section-head,.directory-heading,.page-heading,.pricing-box{align-items:flex-start;flex-direction:column}.section-head .button{width:100%}.dj-cards{grid-template-columns:1fr}.card-img{aspect-ratio:16/10}.steps{grid-template-columns:1fr}.feature-list{grid-template-columns:1fr}.pricing-box{padding:28px}.pricing-box .button{width:100%}.footer-grid{grid-template-columns:1fr;gap:28px}.cookie{left:10px;right:10px;bottom:10px;flex-direction:column;align-items:stretch}.cookie-actions{display:grid;grid-template-columns:1fr 1fr}.profile-hero{height:230px}.profile-shell{width:100%}.profile-panel{margin:-34px 11px 0;padding:18px;grid-template-columns:72px 1fr;gap:14px;border-radius:17px}.avatar{width:72px;height:72px}.profile-title h1{font-size:27px}.profile-summary p{font-size:13px}.profile-price{grid-template-columns:auto 1fr}.profile-price strong{font-size:30px}.profile-price small{display:none}.profile-price .button{grid-column:1/-1}.profile-grid{padding:28px 11px 0}.directory-page{padding:44px 0 60px}.directory-heading h1{font-size:42px}.account-page{padding:36px 0 58px}.account-person{display:none}.account-sidebar{border-radius:12px}.account-stats{grid-template-columns:1fr 1fr}.account-stat:last-child{grid-column:1/-1}.panel{padding:16px}.admin-topbar .button{display:none}.admin-page{padding:18px 12px}.admin-stats{grid-template-columns:1fr 1fr;gap:10px}.admin-stat{padding:15px}.admin-stat strong{font-size:24px}.settings-list>div{grid-template-columns:1fr}.role-grid{grid-template-columns:1fr}.inline-controls{align-items:stretch;flex-direction:column}.inline-controls select,.inline-controls .admin-button{width:100%}.auth-card{padding:23px}.auth-page{padding:38px 12px}}
/* Admin CRUD */
.crud-heading{gap:18px}.crud-heading .admin-button{flex:0 0 auto}.crud-toolbar{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:16px;padding:14px 16px}.crud-search{display:flex;gap:8px;align-items:center;flex:1}.crud-search input{width:min(420px,100%);height:38px;padding:0 12px;border:1px solid #d8deea;border-radius:9px;background:#fff;color:#172033;font:inherit;font-size:13px}.record-count{font-size:12px;font-weight:800;color:var(--admin-muted);white-space:nowrap}.admin-button.primary{background:#6d28d9}.danger-button{display:inline-flex;align-items:center;justify-content:center;min-height:33px;padding:0 11px;border:0;border-radius:8px;background:#fee2e2;color:#b91c1c;font:inherit;font-size:11px;font-weight:900;cursor:pointer}.row-actions{display:flex;align-items:center;gap:7px;justify-content:flex-end}.row-actions form{margin:0}.actions-col{text-align:right!important}.crud-table td:last-child{text-align:right}.crud-pagination{margin-top:16px}.crud-form-card{max-width:980px}.crud-form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:17px}.crud-field{display:grid;gap:7px}.crud-field>span,.crud-check strong{font-size:12px;font-weight:900;color:#263248}.crud-field input,.crud-field select,.crud-field textarea{width:100%;padding:10px 11px;border:1px solid #d5dce7;border-radius:9px;background:#fff;color:#172033;font:inherit;font-size:13px;outline:none}.crud-field input:focus,.crud-field select:focus,.crud-field textarea:focus{border-color:#8b5cf6;box-shadow:0 0 0 3px rgba(139,92,246,.10)}.crud-field textarea{resize:vertical;line-height:1.55}.crud-field small{color:var(--admin-muted);font-size:11px}.crud-field .field-error{color:#b91c1c}.full-field{grid-column:1/-1}.crud-check{display:flex;align-items:center;gap:9px;padding:11px 12px;border:1px solid #e0e5ee;border-radius:9px;background:#fafbfe}.crud-check input[type=checkbox]{width:18px;height:18px}.crud-form-actions{display:flex;align-items:center;gap:9px;padding-top:20px;margin-top:20px;border-top:1px solid var(--admin-line)}.admin-error{max-width:980px;margin-bottom:16px;padding:14px 16px;border:1px solid #fecaca;border-radius:10px;background:#fff1f2;color:#991b1b;font-size:13px}.admin-error ul{margin:8px 0 0 18px}.admin-flash.error{background:#fff1f2;color:#991b1b;border-color:#fecaca}
@media(max-width:760px){.crud-toolbar,.crud-search{align-items:stretch;flex-direction:column}.crud-toolbar .record-count{align-self:flex-start}.crud-search input{width:100%}.crud-form-grid{grid-template-columns:1fr}.full-field{grid-column:auto}.crud-heading .admin-button,.crud-heading .toggle-button{width:100%}.crud-form-actions{align-items:stretch;flex-direction:column}.crud-form-actions>*{width:100%}.crud-table{min-width:760px!important}}
.crud-heading-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}@media(max-width:640px){.crud-heading-actions{width:100%;align-items:stretch;flex-direction:column}}
.legal-shell{max-width:850px}.legal-shell h1{font-size:clamp(38px,6vw,64px);margin:10px 0 8px}.legal-meta{color:var(--muted);font-size:12px;margin-bottom:30px}.legal-copy{color:#cbd5e1;font-size:17px;line-height:1.85;white-space:normal}
+55
View File
@@ -0,0 +1,55 @@
@extends('layouts.admin')
@section('title', ($record ? 'Edit ' : 'Add ').$definition['singular'])
@section('page-title', $definition['title'])
@section('content')
<div class="page-heading admin-heading crud-heading">
<div><div class="eyebrow">{{ $record ? 'Edit record' : 'New record' }}</div><h1>{{ $record ? 'Edit '.$definition['singular'] : 'Add '.$definition['singular'] }}</h1><p>Complete the fields below and save your changes.</p></div>
<a class="toggle-button" href="{{ route('admin.resources.index', $resource) }}">← Back to {{ strtolower($definition['title']) }}</a>
</div>
@if($errors->any())
<div class="admin-error"><strong>Please correct the following:</strong><ul>@foreach($errors->all() as $error)<li>{{ $error }}</li>@endforeach</ul></div>
@endif
<div class="admin-card crud-form-card">
<form method="post" action="{{ $record ? route('admin.resources.update', [$resource, $record->getKey()]) : route('admin.resources.store', $resource) }}" class="crud-form">
@csrf
@if($record) @method('PUT') @endif
<div class="crud-form-grid">
@foreach($definition['fields'] as $name => $field)
@php
$raw = old($name);
if ($raw === null && $record) {
if ($name === 'role' && method_exists($record, 'getRoleNames')) $raw = $record->getRoleNames()->first();
elseif ($name === 'filters' && is_array($record->filters ?? null)) $raw = json_encode($record->filters, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
else $raw = data_get($record, $name);
}
if ($raw instanceof \Carbon\CarbonInterface && ($field['type'] ?? '') === 'datetime-local') $raw = $raw->format('Y-m-d\TH:i');
if ($raw instanceof \Carbon\CarbonInterface && ($field['type'] ?? '') === 'date') $raw = $raw->format('Y-m-d');
@endphp
@if(($field['type'] ?? 'text') === 'checkbox')
<label class="crud-check full-field"><input type="hidden" name="{{ $name }}" value="0"><input type="checkbox" name="{{ $name }}" value="1" @checked((bool)$raw)><span><strong>{{ $field['label'] }}</strong></span></label>
@else
<label class="crud-field {{ !empty($field['large']) || ($field['type'] ?? '') === 'textarea' ? 'full-field' : '' }}">
<span>{{ $field['label'] }}</span>
@if(($field['type'] ?? '') === 'select')
<select name="{{ $name }}">
@if(!empty($field['nullable']))<option value="">— None —</option>@endif
@php $selectOptions = isset($field['options']) ? ($options[$field['options']] ?? []) : ($field['values'] ?? []); @endphp
@foreach($selectOptions as $value => $label)<option value="{{ $value }}" @selected((string)$raw === (string)$value)>{{ $label }}</option>@endforeach
</select>
@elseif(($field['type'] ?? '') === 'textarea')
<textarea name="{{ $name }}" rows="{{ !empty($field['large']) ? 14 : 5 }}">{{ $raw }}</textarea>
@else
<input type="{{ $field['type'] ?? 'text' }}" name="{{ $name }}" value="{{ ($field['type'] ?? '') === 'password' ? '' : $raw }}" @if(isset($field['step'])) step="{{ $field['step'] }}" @endif @if(isset($field['min'])) min="{{ $field['min'] }}" @endif @if(isset($field['max'])) max="{{ $field['max'] }}" @endif>
@endif
@if(!empty($field['help']))<small>{{ $field['help'] }}</small>@endif
@error($name)<small class="field-error">{{ $message }}</small>@enderror
</label>
@endif
@endforeach
</div>
<div class="crud-form-actions"><button class="admin-button primary" type="submit">{{ $record ? 'Save changes' : 'Create '.$definition['singular'] }}</button><a class="toggle-button" href="{{ route('admin.resources.index', $resource) }}">Cancel</a></div>
</form>
</div>
@endsection
@@ -0,0 +1,62 @@
@extends('layouts.admin')
@section('title', $definition['title'])
@section('page-title', $definition['title'])
@section('content')
<div class="page-heading admin-heading crud-heading">
<div>
<div class="eyebrow">Management</div>
<h1>{{ $definition['title'] }}</h1>
<p>Create, edit and remove {{ strtolower($definition['title']) }} records.</p>
</div>
<div class="crud-heading-actions">
@if($resource === 'roles')<a class="toggle-button" href="{{ route('admin.resources.index', 'permissions') }}">Manage permissions</a>@endif
@if($resource === 'permissions')<a class="toggle-button" href="{{ route('admin.resources.index', 'roles') }}">Manage roles</a>@endif
@if($resource === 'settings')<form method="post" action="{{ route('admin.settings.clear-cache') }}">@csrf<button class="toggle-button" type="submit">Clear application cache</button></form>@endif
<a class="admin-button primary" href="{{ route('admin.resources.create', $resource) }}">+ Add {{ strtolower($definition['singular']) }}</a>
</div>
</div>
<div class="admin-card crud-toolbar">
<form method="get" action="{{ route('admin.resources.index', $resource) }}" class="crud-search">
<input type="search" name="q" value="{{ request('q') }}" placeholder="Search {{ strtolower($definition['title']) }}…">
<button class="admin-button" type="submit">Search</button>
@if(request('q'))<a class="toggle-button" href="{{ route('admin.resources.index', $resource) }}">Clear</a>@endif
</form>
<span class="record-count">{{ number_format($records->total()) }} records</span>
</div>
<div class="admin-card table-card">
<div class="responsive-table">
<table class="crud-table">
<thead><tr>@foreach($definition['columns'] as $label)<th>{{ $label }}</th>@endforeach<th class="actions-col">Actions</th></tr></thead>
<tbody>
@forelse($records as $record)
<tr>
@foreach($definition['columns'] as $path => $label)
@php
$value = $path === 'roles_text' ? $record->getRoleNames()->join(', ') : data_get($record, $path);
if ($value instanceof \Carbon\CarbonInterface) $value = $value->format('j M Y H:i');
elseif (is_bool($value)) $value = $value ? 'Yes' : 'No';
elseif (in_array($path, ['amount','total','deposit','price_from'], true) && $value !== null) $value = '£'.number_format((float)$value, 2);
@endphp
<td data-label="{{ $label }}">{{ filled($value) ? $value : '—' }}</td>
@endforeach
<td data-label="Actions">
<div class="row-actions">
<a class="toggle-button" href="{{ route('admin.resources.edit', [$resource, $record->getKey()]) }}">Edit</a>
<form method="post" action="{{ route('admin.resources.destroy', [$resource, $record->getKey()]) }}" onsubmit="return confirm('Delete this {{ strtolower($definition['singular']) }}? This cannot be undone.')">
@csrf @method('DELETE')
<button class="danger-button" type="submit">Delete</button>
</form>
</div>
</td>
</tr>
@empty
<tr><td colspan="{{ count($definition['columns']) + 1 }}"><div class="feature-empty"><div class="feature-icon">+</div><h2>No {{ strtolower($definition['title']) }} yet</h2><p>Create the first record to start managing this area.</p><a class="admin-button primary" href="{{ route('admin.resources.create', $resource) }}">Add {{ strtolower($definition['singular']) }}</a></div></td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="crud-pagination">{{ $records->links() }}</div>
@endsection
+4 -4
View File
@@ -3,10 +3,10 @@
@section('page-title','Dashboard')
@section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Marketplace control centre</div><h1>Dashboard</h1><p>Live overview of users, DJs, bookings and moderation.</p></div></div>
<div class="admin-stats">@foreach($stats as $key=>$value)<a class="admin-stat" href="{{ $key === 'pending reviews' ? route('admin.section','reviews') : ($key === 'djs' ? route('admin.section','djs') : ($key === 'bookings' ? route('admin.section','bookings') : route('admin.section','users'))) }}"><span>{{ ucfirst($key) }}</span><strong>{{ number_format($value) }}</strong><small>Open section →</small></a>@endforeach</div>
<div class="admin-stats">@foreach($stats as $key=>$value)<a class="admin-stat" href="{{ $key === 'pending reviews' ? route('admin.resources.index','reviews') : ($key === 'djs' ? route('admin.resources.index','djs') : ($key === 'bookings' ? route('admin.resources.index','bookings') : route('admin.resources.index','users'))) }}"><span>{{ ucfirst($key) }}</span><strong>{{ number_format($value) }}</strong><small>Open section →</small></a>@endforeach</div>
<div class="admin-grid">
<section class="admin-card"><div class="panel-head"><h2>Newest users</h2><a href="{{ route('admin.section','users') }}">Manage</a></div>@forelse($recentUsers as $user)<div class="admin-list-row"><div><strong>{{ $user->name }}</strong><span>{{ $user->email }}</span></div><span>{{ $user->created_at?->diffForHumans() }}</span></div>@empty<div class="empty-state">No users yet.</div>@endforelse</section>
<section class="admin-card"><div class="panel-head"><h2>Latest enquiries</h2><a href="{{ route('admin.section','clients') }}">Clients</a></div>@forelse($recentEnquiries as $item)<div class="admin-list-row"><div><strong>{{ $item->user?->name }}</strong><span>{{ $item->djProfile?->stage_name }} · {{ $item->event_type }}</span></div><span class="status-pill">{{ ucfirst($item->status) }}</span></div>@empty<div class="empty-state">No enquiries yet.</div>@endforelse</section>
<section class="admin-card admin-card-wide"><div class="panel-head"><h2>Recent bookings</h2><a href="{{ route('admin.section','bookings') }}">View all</a></div><div class="responsive-table"><table><thead><tr><th>Client</th><th>DJ</th><th>Date</th><th>Total</th><th>Status</th></tr></thead><tbody>@forelse($recentBookings as $item)<tr><td>{{ $item->client?->name }}</td><td>{{ $item->djProfile?->stage_name }}</td><td>{{ optional($item->event_date)->format('j M Y') }}</td><td>£{{ number_format((float)$item->total,2) }}</td><td><span class="status-pill">{{ ucfirst($item->status) }}</span></td></tr>@empty<tr><td colspan="5">No bookings yet.</td></tr>@endforelse</tbody></table></div></section>
<section class="admin-card"><div class="panel-head"><h2>Newest users</h2><a href="{{ route('admin.resources.index','users') }}">Manage</a></div>@forelse($recentUsers as $user)<div class="admin-list-row"><div><strong>{{ $user->name }}</strong><span>{{ $user->email }}</span></div><span>{{ $user->created_at?->diffForHumans() }}</span></div>@empty<div class="empty-state">No users yet.</div>@endforelse</section>
<section class="admin-card"><div class="panel-head"><h2>Latest enquiries</h2><a href="{{ route('admin.resources.index','clients') }}">Clients</a></div>@forelse($recentEnquiries as $item)<div class="admin-list-row"><div><strong>{{ $item->user?->name }}</strong><span>{{ $item->djProfile?->stage_name }} · {{ $item->event_type }}</span></div><span class="status-pill">{{ ucfirst($item->status) }}</span></div>@empty<div class="empty-state">No enquiries yet.</div>@endforelse</section>
<section class="admin-card admin-card-wide"><div class="panel-head"><h2>Recent bookings</h2><a href="{{ route('admin.resources.index','bookings') }}">View all</a></div><div class="responsive-table"><table><thead><tr><th>Client</th><th>DJ</th><th>Date</th><th>Total</th><th>Status</th></tr></thead><tbody>@forelse($recentBookings as $item)<tr><td>{{ $item->client?->name }}</td><td>{{ $item->djProfile?->stage_name }}</td><td>{{ optional($item->event_date)->format('j M Y') }}</td><td>£{{ number_format((float)$item->total,2) }}</td><td><span class="status-pill">{{ ucfirst($item->status) }}</span></td></tr>@empty<tr><td colspan="5">No bookings yet.</td></tr>@endforelse</tbody></table></div></section>
</div>
@endsection
-38
View File
@@ -1,38 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{ ucwords(str_replace('-', ' ', $section)) }} — BeatBook Admin</title>
@vite(['resources/css/app.css'])
</head>
<body>
<div class="admin">
<aside class="sidebar">
<a class="brand" href="{{ route('admin.dashboard') }}"><b>◉</b> BeatBook</a>
<p class="muted">Administration</p>
<a href="{{ route('admin.dashboard') }}">Dashboard</a>
@foreach([
'users' => 'Users', 'djs' => 'DJs & Businesses', 'clients' => 'Clients',
'bookings' => 'Bookings', 'payments' => 'Payments', 'reviews' => 'Reviews',
'messages' => 'Messages', 'notifications' => 'Notifications', 'support' => 'Support',
'reports' => 'Reports', 'content' => 'Content', 'roles' => 'Roles & Permissions',
'privacy' => 'Privacy', 'settings' => 'Settings'
] as $key => $label)
<a href="{{ route('admin.section', $key) }}">{{ $label }}</a>
@endforeach
<a href="{{ route('home') }}">Back to site</a>
</aside>
<main class="admin-main">
<div class="eyebrow">Administration</div>
<h1>{{ ucwords(str_replace('-', ' ', $section)) }}</h1>
<p style="color:#64748b">This BeatBook administration section is connected and ready for its management workflow.</p>
<div class="stats">
@foreach($counts as $label => $value)
<div class="stat"><span>{{ ucfirst($label) }}</span><strong>{{ number_format($value) }}</strong></div>
@endforeach
</div>
</main>
</div>
</body>
</html>
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Bookings') @section('page-title','Bookings') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Events</div><h1>Bookings</h1><p>All marketplace bookings and event values.</p></div><span class="metric-chip">{{ number_format($counts['bookings']) }} bookings</span></div>
<div class="admin-card table-card"><div class="responsive-table"><table><thead><tr><th>Client</th><th>DJ</th><th>Event date</th><th>Total</th><th>Deposit</th><th>Status</th></tr></thead><tbody>@forelse($bookings as $item)<tr><td>{{ $item->client?->name }}</td><td>{{ $item->djProfile?->stage_name }}</td><td>{{ optional($item->event_date)->format('j M Y') }}</td><td>£{{ number_format((float)$item->total,2) }}</td><td>£{{ number_format((float)$item->deposit,2) }}</td><td><span class="status-pill">{{ ucfirst($item->status) }}</span></td></tr>@empty<tr><td colspan="6">No bookings yet.</td></tr>@endforelse</tbody></table></div></div>{{ $bookings->links() }}
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Clients') @section('page-title','Clients') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Customers</div><h1>Clients</h1><p>Review client activity across enquiries and bookings.</p></div><span class="metric-chip">{{ number_format($counts['clients']) }} clients</span></div>
<div class="admin-card table-card"><div class="responsive-table"><table><thead><tr><th>Client</th><th>Phone</th><th>Enquiries</th><th>Bookings</th><th>Joined</th></tr></thead><tbody>@forelse($clients as $client)<tr><td><strong>{{ $client->name }}</strong><small>{{ $client->email }}</small></td><td>{{ $client->phone ?: '—' }}</td><td>{{ $client->enquiries_count }}</td><td>{{ $client->bookings_count }}</td><td>{{ $client->created_at?->format('j M Y') }}</td></tr>@empty<tr><td colspan="5">No clients yet.</td></tr>@endforelse</tbody></table></div></div>{{ $clients->links() }}
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Content') @section('page-title','Content') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Website</div><h1>Content</h1><p>Public-site content and marketplace presentation.</p></div></div>
<div class="settings-grid"><a class="admin-card action-card" href="{{ route('home') }}"><h2>Homepage</h2><p>Review hero, featured DJs, how it works and DJ proposition.</p><span>Open page →</span></a><a class="admin-card action-card" href="{{ route('djs.index') }}"><h2>DJ directory</h2><p>Review public listings, filtering and profile presentation.</p><span>Open directory →</span></a><a class="admin-card action-card" href="{{ route('privacy') }}"><h2>Legal content</h2><p>Review privacy, cookie and terms content.</p><span>Open privacy →</span></a></div>
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','DJs & businesses') @section('page-title','DJs & businesses') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Marketplace supply</div><h1>DJs & businesses</h1><p>Control visibility and featured placement.</p></div><span class="metric-chip">{{ number_format($counts['djs']) }} profiles</span></div>
<div class="admin-card table-card"><div class="responsive-table"><table><thead><tr><th>DJ</th><th>Owner</th><th>City</th><th>Price</th><th>Published</th><th>Featured</th></tr></thead><tbody>@forelse($djs as $dj)<tr><td><strong>{{ $dj->stage_name }}</strong><small>{{ $dj->business?->name ?: 'Independent' }}</small></td><td>{{ $dj->user?->email }}</td><td>{{ $dj->city }}</td><td>{{ $dj->price_from ? '£'.number_format((float)$dj->price_from) : '—' }}</td><td><form method="post" action="{{ route('admin.djs.published',$dj) }}">@csrf @method('PATCH')<button class="toggle-button {{ $dj->is_published ? 'on' : '' }}" type="submit">{{ $dj->is_published ? 'Published' : 'Hidden' }}</button></form></td><td><form method="post" action="{{ route('admin.djs.featured',$dj) }}">@csrf @method('PATCH')<button class="toggle-button {{ $dj->is_featured ? 'on' : '' }}" type="submit">{{ $dj->is_featured ? 'Featured' : 'Standard' }}</button></form></td></tr>@empty<tr><td colspan="6">No DJ profiles yet.</td></tr>@endforelse</tbody></table></div></div>{{ $djs->links() }}
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Messages') @section('page-title','Messages') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Communication</div><h1>Messages</h1><p>Marketplace messaging will appear here once conversations are created.</p></div></div>
<div class="admin-card"><div class="feature-empty"><div class="feature-icon">✉</div><h2>No conversations yet</h2><p>Enquiry messaging can be added here without exposing private contact details between clients and DJs.</p><a class="admin-button" href="{{ route('admin.section','clients') }}">View clients</a></div></div>
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Notifications') @section('page-title','Notifications') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Delivery</div><h1>Notifications</h1><p>System alerts and delivery health.</p></div></div>
<div class="settings-grid"><div class="admin-card"><h2>Notification channels</h2><div class="settings-list"><div><strong>Email</strong><span>Laravel mail channel</span><span class="status-pill">Configured</span></div><div><strong>Database</strong><span>In-app notifications</span><span class="status-pill">Ready</span></div></div></div><div class="admin-card"><h2>Recommended events</h2><p class="admin-muted">Booking confirmation, new enquiry, review request, payment receipt and support replies can be delivered from this area.</p></div></div>
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Payments') @section('page-title','Payments') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Finance</div><h1>Payments</h1><p>Payment state and booking value overview.</p></div></div>
<div class="admin-card table-card"><div class="responsive-table"><table><thead><tr><th>Booking</th><th>Client</th><th>DJ</th><th>Total</th><th>Deposit</th><th>Payment</th></tr></thead><tbody>@forelse($bookings as $item)<tr><td>{{ Str::limit($item->id,10,'…') }}</td><td>{{ $item->client?->name }}</td><td>{{ $item->djProfile?->stage_name }}</td><td>£{{ number_format((float)$item->total,2) }}</td><td>£{{ number_format((float)$item->deposit,2) }}</td><td><span class="status-pill">{{ $item->paid_at ? 'Paid' : 'Outstanding' }}</span></td></tr>@empty<tr><td colspan="6">No payment records yet.</td></tr>@endforelse</tbody></table></div></div>{{ $bookings->links() }}
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Privacy') @section('page-title','Privacy') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Compliance</div><h1>Privacy</h1><p>Quick access to the public legal policies and privacy controls.</p></div></div>
<div class="settings-grid"><a class="admin-card action-card" href="{{ route('privacy') }}"><h2>Privacy policy</h2><p>Review the current public privacy policy.</p><span>Open policy →</span></a><a class="admin-card action-card" href="{{ route('cookies') }}"><h2>Cookie policy</h2><p>Review necessary, analytics and marketing cookie information.</p><span>Open policy →</span></a><a class="admin-card action-card" href="{{ route('terms') }}"><h2>Terms</h2><p>Review marketplace terms shown to users.</p><span>Open terms →</span></a></div>
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Reports') @section('page-title','Reports') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Performance</div><h1>Reports</h1><p>Marketplace totals calculated from live BeatBook data.</p></div></div>
<div class="admin-stats"><div class="admin-stat"><span>Booking value</span><strong>£{{ number_format((float)$report['booking_value'],2) }}</strong></div><div class="admin-stat"><span>Deposits</span><strong>£{{ number_format((float)$report['deposits'],2) }}</strong></div><div class="admin-stat"><span>Published DJs</span><strong>{{ $report['published_djs'] }}</strong></div><div class="admin-stat"><span>Pending reviews</span><strong>{{ $report['pending_reviews'] }}</strong></div></div>
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Reviews') @section('page-title','Reviews') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Trust & safety</div><h1>Reviews</h1><p>Moderate feedback before it appears publicly.</p></div><span class="metric-chip">{{ number_format($counts['reviews']) }} reviews</span></div>
<div class="admin-card table-card"><div class="responsive-table"><table><thead><tr><th>Reviewer</th><th>DJ</th><th>Rating</th><th>Review</th><th>Status</th><th>Moderate</th></tr></thead><tbody>@forelse($reviews as $review)<tr><td>{{ $review->user?->name }}</td><td>{{ $review->djProfile?->stage_name }}</td><td>★ {{ $review->rating }}/5</td><td><strong>{{ $review->title ?: 'Review' }}</strong><small>{{ Str::limit($review->body,80) }}</small></td><td><span class="status-pill">{{ ucfirst($review->status) }}</span></td><td><form class="inline-controls" method="post" action="{{ route('admin.reviews.status',$review) }}">@csrf @method('PATCH')<select name="status"><option value="pending" @selected($review->status==='pending')>Pending</option><option value="published" @selected($review->status==='published')>Published</option><option value="rejected" @selected($review->status==='rejected')>Rejected</option></select><button class="admin-button">Save</button></form></td></tr>@empty<tr><td colspan="6">No reviews yet.</td></tr>@endforelse</tbody></table></div></div>{{ $reviews->links() }}
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Roles & permissions') @section('page-title','Roles & permissions') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Access control</div><h1>Roles & permissions</h1><p>Roles currently available to BeatBook accounts.</p></div></div>
<div class="role-grid">@foreach($roles as $role)<div class="admin-card"><span class="role-badge">{{ $role->name }}</span><strong class="role-count">{{ $role->users_count }}</strong><p>{{ Str::plural('user',$role->users_count) }}</p></div>@endforeach</div>
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Settings') @section('page-title','Settings') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Application</div><h1>Settings</h1><p>Operational controls for the BeatBook installation.</p></div></div>
<div class="settings-grid"><div class="admin-card"><h2>Environment</h2><div class="settings-list"><div><strong>Application</strong><span>{{ config('app.name') }}</span></div><div><strong>Environment</strong><span>{{ app()->environment() }}</span></div><div><strong>URL</strong><span>{{ config('app.url') }}</span></div></div></div><div class="admin-card"><h2>Maintenance</h2><p class="admin-muted">Clear application cache after configuration or content changes.</p><form method="post" action="{{ route('admin.settings.clear-cache') }}">@csrf<button class="admin-button" type="submit">Clear application cache</button></form></div></div>
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Support') @section('page-title','Support') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">Customer care</div><h1>Support</h1><p>Central area for customer and DJ support cases.</p></div></div>
<div class="admin-card"><div class="feature-empty"><div class="feature-icon">?</div><h2>No open support cases</h2><p>The support queue is ready for a ticket model and inbound support form when you enable that workflow.</p></div></div>
@endsection
@@ -1,4 +0,0 @@
@extends('layouts.admin') @section('title','Users') @section('page-title','Users') @section('content')
<div class="page-heading admin-heading"><div><div class="eyebrow">People</div><h1>Users</h1><p>Manage accounts and assign marketplace roles.</p></div><span class="metric-chip">{{ number_format($counts['users']) }} users</span></div>
<div class="admin-card table-card"><div class="responsive-table"><table><thead><tr><th>User</th><th>Role</th><th>Joined</th><th>Change role</th></tr></thead><tbody>@foreach($users as $user)<tr><td><strong>{{ $user->name }}</strong><small>{{ $user->email }}</small></td><td>{{ $user->getRoleNames()->join(', ') ?: 'No role' }}</td><td>{{ $user->created_at?->format('j M Y') }}</td><td><form class="inline-controls" method="post" action="{{ route('admin.users.role',$user) }}">@csrf @method('PATCH')<select name="role">@foreach(['client','dj','support','moderator','admin','super-admin'] as $role)<option value="{{ $role }}" @selected($user->hasRole($role))>{{ ucfirst($role) }}</option>@endforeach</select><button class="admin-button" type="submit">Save</button></form></td></tr>@endforeach</tbody></table></div></div>{{ $users->links() }}
@endsection
+9
View File
@@ -0,0 +1,9 @@
@extends('layouts.app')
@section('title', $page->title)
@section('content')
<main class="section legal-page"><div class="shell legal-shell">
<div class="eyebrow">BeatBook</div><h1>{{ $page->title }}</h1>
@if($page->excerpt)<p class="legal-meta">{{ $page->excerpt }}</p>@endif
<div class="legal-copy">{!! nl2br(e($page->body ?? '')) !!}</div>
</div></main>
@endsection
+3 -2
View File
@@ -18,12 +18,12 @@
<nav class="admin-nav">
<a class="{{ request()->routeIs('admin.dashboard') ? 'active' : '' }}" href="{{ route('admin.dashboard') }}">Overview</a>
@foreach([
'users' => 'Users', 'djs' => 'DJs & businesses', 'clients' => 'Clients', 'bookings' => 'Bookings',
'users' => 'Users', 'clients' => 'Clients', 'djs' => 'DJs', 'businesses' => 'Businesses', 'bookings' => 'Bookings',
'payments' => 'Payments', 'reviews' => 'Reviews', 'messages' => 'Messages', 'notifications' => 'Notifications',
'support' => 'Support', 'reports' => 'Reports', 'content' => 'Content', 'roles' => 'Roles & permissions',
'privacy' => 'Privacy', 'settings' => 'Settings'
] as $key => $label)
<a class="{{ request()->is('admin/'.$key) ? 'active' : '' }}" href="{{ route('admin.section', $key) }}">{{ $label }}</a>
<a class="{{ request()->is('admin/'.$key) || request()->is('admin/'.$key.'/*') ? 'active' : '' }}" href="{{ route('admin.resources.index', $key) }}">{{ $label }}</a>
@endforeach
</nav>
<div class="admin-sidebar-foot">
@@ -40,6 +40,7 @@
<a class="button small" href="{{ route('home') }}">Public site</a>
</header>
@if(session('success'))<div class="admin-flash">{{ session('success') }}</div>@endif
@if(session('error'))<div class="admin-flash error">{{ session('error') }}</div>@endif
<div class="admin-page">@yield('content')</div>
</main>
</div>
+10
View File
@@ -0,0 +1,10 @@
@extends('layouts.app')
@section('title', $document?->title ?? $fallbackTitle)
@section('content')
<main class="section legal-page"><div class="shell legal-shell">
<div class="eyebrow">Legal</div>
<h1>{{ $document?->title ?? $fallbackTitle }}</h1>
@if($document)<p class="legal-meta">Version {{ $document->version }} · Updated {{ $document->updated_at?->format('j F Y') }}</p>@endif
<div class="legal-copy">{!! nl2br(e($document?->body ?? $fallbackBody)) !!}</div>
</div></main>
@endsection