updates for admin area
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# Admin appearance modes
|
||||
|
||||
The BeatBook administration backend now supports three appearance modes:
|
||||
|
||||
- Light
|
||||
- System (follows the operating system appearance and updates live)
|
||||
- Dark
|
||||
|
||||
The switcher is in the administration top bar. The preference is stored in browser localStorage under `beatbook-admin-theme` and is applied in the document head before Vite assets load to prevent a light/dark flash during navigation.
|
||||
|
||||
The theme affects the admin shell, sidebar, header, cards, CRUD tables, form controls, search fields, badges, flash/error messages, buttons and date/time inputs. It does not alter the public BeatBook site.
|
||||
@@ -0,0 +1,32 @@
|
||||
# BeatBook Admin CRUD
|
||||
|
||||
This revision replaces the read-only/placeholder administration sections with persistent CRUD management.
|
||||
|
||||
## CRUD resources
|
||||
|
||||
- Users
|
||||
- Clients
|
||||
- DJs
|
||||
- Businesses
|
||||
- Bookings
|
||||
- Payments
|
||||
- Reviews
|
||||
- Messages
|
||||
- Notifications
|
||||
- Support tickets
|
||||
- Saved reports
|
||||
- Content pages
|
||||
- Roles
|
||||
- Permissions
|
||||
- Privacy/legal documents
|
||||
- Application settings
|
||||
|
||||
## New persistence
|
||||
|
||||
Migration `2026_09_12_000004_create_admin_management_tables.php` adds tables for payments, messages, admin notifications, support tickets, saved reports, content pages, legal documents and application settings.
|
||||
|
||||
Run `php artisan migrate` on an existing installation. Do not use `migrate:fresh` unless you intentionally want to erase existing data.
|
||||
|
||||
Published Privacy, Cookie and Terms records are used by the corresponding public legal pages. Published Content records are available at `/pages/{slug}`.
|
||||
|
||||
Notification records are currently managed content; creating a record does not yet dispatch email/push notifications. Saved reports are definitions; a later reporting engine can execute/export them.
|
||||
@@ -0,0 +1,26 @@
|
||||
# BeatBook UI / Backend Rework
|
||||
|
||||
## Completed
|
||||
- Replaced the generic admin placeholder with real section views for users, DJs, clients, bookings, payments, reviews, messages, notifications, support, reports, content, roles, privacy and settings.
|
||||
- Added admin actions for role changes, DJ publish/feature controls, review moderation and cache clearing.
|
||||
- Replaced the non-clickable client dashboard with account routes for overview, enquiries, bookings and editable account details.
|
||||
- Added responsive admin navigation with a mobile drawer.
|
||||
- Added responsive public navigation and mobile menu.
|
||||
- Rebuilt home, DJ directory and DJ profile layouts for desktop, tablet and mobile.
|
||||
- Added sorting to the DJ directory.
|
||||
- Added route coverage tests for account and admin pages.
|
||||
- Made auth tests explicitly bypass request-forgery middleware during automated tests only.
|
||||
- Restored a clean Laravel .gitignore and ensured composer.lock is not ignored.
|
||||
- Updated Laravel 13 request-forgery middleware API usage.
|
||||
|
||||
## Notes
|
||||
Messages, notifications and support now have proper destinations and admin presentation, but the project currently has no dedicated conversation/support database schema. Their pages therefore show deliberate empty states until those workflows/models are added.
|
||||
|
||||
After applying the files run:
|
||||
|
||||
composer install
|
||||
npm install
|
||||
npm run build
|
||||
php artisan optimize:clear
|
||||
php artisan test
|
||||
./vendor/bin/pint
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# Security baseline
|
||||
- Enforce HTTPS/HSTS in production.
|
||||
- Keep private/verification uploads outside public storage; authorize every download.
|
||||
- Validate MIME and image decoding; strip metadata where appropriate.
|
||||
- Use policies for every tenant-owned resource.
|
||||
- Enable 2FA for admin/DJ accounts.
|
||||
- Verify Stripe webhook signatures and make handlers idempotent.
|
||||
- Rate-limit auth, enquiry, review and messaging endpoints.
|
||||
- Audit privileged actions; hash IP addresses if retained.
|
||||
- Never store card details.
|
||||
- Run queues under Supervisor/systemd and keep dependencies patched.
|
||||
- Replace placeholder legal policies before launch.
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"resources/css/app.css": {
|
||||
"file": "assets/app-DbTvU8vz.css",
|
||||
"file": "assets/app-ByTEIgcV.css",
|
||||
"src": "resources/css/app.css",
|
||||
"isEntry": true,
|
||||
"name": "app",
|
||||
@@ -9,7 +9,7 @@
|
||||
]
|
||||
},
|
||||
"resources/js/app.js": {
|
||||
"file": "assets/app-fQMWg8Nx.js",
|
||||
"file": "assets/app-Cl06EWgF.js",
|
||||
"name": "app",
|
||||
"src": "resources/js/app.js",
|
||||
"isEntry": true
|
||||
|
||||
@@ -21,3 +21,112 @@
|
||||
|
||||
.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}
|
||||
|
||||
/* Admin appearance */
|
||||
.admin-body{
|
||||
--admin-surface:#fff;
|
||||
--admin-surface-2:#fafbfe;
|
||||
--admin-surface-3:#f0f3f8;
|
||||
--admin-input:#fff;
|
||||
--admin-input-border:#d5dce7;
|
||||
--admin-sidebar:#0b1020;
|
||||
--admin-sidebar-hover:#17203a;
|
||||
--admin-sidebar-line:#202a42;
|
||||
--admin-sidebar-text:#aab7ca;
|
||||
--admin-accent:#6d28d9;
|
||||
--admin-accent-soft:#ede9fe;
|
||||
--admin-accent-text:#6d28d9;
|
||||
--admin-success-bg:#dcfce7;
|
||||
--admin-success-text:#166534;
|
||||
--admin-success-line:#bbf7d0;
|
||||
--admin-danger-bg:#fff1f2;
|
||||
--admin-danger-text:#991b1b;
|
||||
--admin-danger-line:#fecaca;
|
||||
--admin-shadow:0 4px 18px rgba(31,45,75,.04);
|
||||
background:var(--admin-bg);
|
||||
color:var(--admin-ink);
|
||||
color-scheme:light;
|
||||
}
|
||||
html[data-admin-theme="dark"] .admin-body{
|
||||
--admin-bg:#0b1020;
|
||||
--admin-ink:#e8edf6;
|
||||
--admin-muted:#98a6ba;
|
||||
--admin-line:#263149;
|
||||
--admin-surface:#111827;
|
||||
--admin-surface-2:#151e2f;
|
||||
--admin-surface-3:#1b2639;
|
||||
--admin-input:#0f1726;
|
||||
--admin-input-border:#344159;
|
||||
--admin-sidebar:#070b15;
|
||||
--admin-sidebar-hover:#17213a;
|
||||
--admin-sidebar-line:#202b43;
|
||||
--admin-sidebar-text:#aebbd0;
|
||||
--admin-accent:#8b5cf6;
|
||||
--admin-accent-soft:#2b2145;
|
||||
--admin-accent-text:#c4b5fd;
|
||||
--admin-success-bg:#10291d;
|
||||
--admin-success-text:#86efac;
|
||||
--admin-success-line:#1f5138;
|
||||
--admin-danger-bg:#35151d;
|
||||
--admin-danger-text:#fda4af;
|
||||
--admin-danger-line:#642334;
|
||||
--admin-shadow:0 7px 24px rgba(0,0,0,.22);
|
||||
color-scheme:dark;
|
||||
}
|
||||
.admin-sidebar{background:var(--admin-sidebar)}
|
||||
.admin-nav a,.admin-sidebar-foot a,.admin-sidebar-foot button{color:var(--admin-sidebar-text)}
|
||||
.admin-nav a:hover,.admin-nav a.active,.admin-sidebar-foot a:hover,.admin-sidebar-foot button:hover{background:var(--admin-sidebar-hover)}
|
||||
.admin-sidebar-foot{border-color:var(--admin-sidebar-line)}
|
||||
.admin-topbar,.admin-stat,.admin-card{background:var(--admin-surface);box-shadow:var(--admin-shadow)}
|
||||
.admin-topbar{border-color:var(--admin-line)}
|
||||
.admin-card .responsive-table th,.admin-card .responsive-table td{border-color:var(--admin-line);color:var(--admin-ink)}
|
||||
.admin-card .responsive-table th{color:var(--admin-muted)}
|
||||
.toggle-button{background:var(--admin-surface-3);color:var(--admin-ink)}
|
||||
.toggle-button.on{background:var(--admin-success-bg);color:var(--admin-success-text)}
|
||||
.inline-controls select,.crud-search input,.crud-field input,.crud-field select,.crud-field textarea{background:var(--admin-input);border-color:var(--admin-input-border);color:var(--admin-ink)}
|
||||
.crud-field>span,.crud-check strong{color:var(--admin-ink)}
|
||||
.crud-check{background:var(--admin-surface-2);border-color:var(--admin-line)}
|
||||
.admin-flash{background:var(--admin-success-bg);color:var(--admin-success-text);border-color:var(--admin-success-line)}
|
||||
.admin-flash.error,.admin-error{background:var(--admin-danger-bg);color:var(--admin-danger-text);border-color:var(--admin-danger-line)}
|
||||
.danger-button{background:var(--admin-danger-bg);color:var(--admin-danger-text)}
|
||||
.role-badge,.feature-icon{background:var(--admin-accent-soft);color:var(--admin-accent-text)}
|
||||
.action-card:hover{border-color:var(--admin-accent)}
|
||||
.action-card>span{color:var(--admin-accent-text)}
|
||||
.admin-theme-switcher{display:inline-flex;align-items:center;padding:3px;border:1px solid var(--admin-line);border-radius:10px;background:var(--admin-surface-2)}
|
||||
.admin-theme-switcher button{display:grid;place-items:center;width:32px;height:32px;padding:0;border:0;border-radius:7px;background:transparent;color:var(--admin-muted);font:inherit;font-size:15px;cursor:pointer;transition:background .15s ease,color .15s ease,box-shadow .15s ease}
|
||||
.admin-theme-switcher button:hover{color:var(--admin-ink)}
|
||||
.admin-theme-switcher button.active{background:var(--admin-surface);color:var(--admin-accent-text);box-shadow:0 1px 4px rgba(0,0,0,.12)}
|
||||
.admin-topbar-actions{display:flex;align-items:center;gap:10px}
|
||||
html[data-admin-theme="dark"] .admin-button{background:#29354a;color:#f8fafc}
|
||||
html[data-admin-theme="dark"] .admin-button.primary{background:#7c3aed}
|
||||
html[data-admin-theme="dark"] .admin-stat small{color:#c4b5fd}
|
||||
html[data-admin-theme="dark"] .crud-field input:focus,html[data-admin-theme="dark"] .crud-field select:focus,html[data-admin-theme="dark"] .crud-field textarea:focus{border-color:#8b5cf6;box-shadow:0 0 0 3px rgba(139,92,246,.18)}
|
||||
html[data-admin-theme="dark"] input[type="date"],html[data-admin-theme="dark"] input[type="datetime-local"],html[data-admin-theme="dark"] input[type="time"]{color-scheme:dark}
|
||||
@media(max-width:640px){.admin-topbar-actions{margin-left:auto}.admin-theme-switcher button{width:29px;height:29px}.admin-topbar-actions .button{display:none}}
|
||||
|
||||
/* Keep admin appearance controls and Public Site action on one row. */
|
||||
.admin-topbar > .admin-topbar-actions{
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
flex-wrap:nowrap;
|
||||
align-items:center;
|
||||
justify-content:flex-end;
|
||||
gap:10px;
|
||||
min-width:max-content;
|
||||
margin-left:auto;
|
||||
}
|
||||
.admin-topbar-actions .admin-theme-switcher,
|
||||
.admin-topbar-actions .button{
|
||||
flex:0 0 auto;
|
||||
white-space:nowrap;
|
||||
}
|
||||
@media(max-width:640px){
|
||||
.admin-topbar{padding:0 12px;gap:8px}
|
||||
.admin-topbar > div:not(.admin-topbar-actions){min-width:0;overflow:hidden}
|
||||
.admin-topbar > div:not(.admin-topbar-actions) strong,
|
||||
.admin-topbar > div:not(.admin-topbar-actions) span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.admin-topbar > .admin-topbar-actions{display:flex;gap:6px;margin-left:auto}
|
||||
.admin-theme-switcher{padding:2px}
|
||||
.admin-theme-switcher button{width:28px;height:28px}
|
||||
.admin-topbar-actions .button{display:inline-flex;min-height:32px;padding:0 10px;font-size:11px}
|
||||
}
|
||||
|
||||
@@ -28,3 +28,29 @@ const setAdminMenu = (open) => {
|
||||
document.querySelector('[data-admin-open]')?.addEventListener('click', () => setAdminMenu(true));
|
||||
document.querySelector('[data-admin-close]')?.addEventListener('click', () => setAdminMenu(false));
|
||||
adminOverlay?.addEventListener('click', () => setAdminMenu(false));
|
||||
|
||||
|
||||
const adminThemeButtons = document.querySelectorAll('[data-admin-theme]');
|
||||
const adminThemeMedia = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
const applyAdminTheme = (preference, persist = true) => {
|
||||
if (!['light', 'dark', 'system'].includes(preference)) preference = 'system';
|
||||
const resolved = preference === 'system' ? (adminThemeMedia.matches ? 'dark' : 'light') : preference;
|
||||
document.documentElement.dataset.adminTheme = resolved;
|
||||
document.documentElement.dataset.adminThemePreference = preference;
|
||||
if (persist) localStorage.setItem('beatbook-admin-theme', preference);
|
||||
|
||||
adminThemeButtons.forEach((button) => {
|
||||
const active = button.dataset.adminTheme === preference;
|
||||
button.classList.toggle('active', active);
|
||||
button.setAttribute('aria-pressed', String(active));
|
||||
});
|
||||
};
|
||||
|
||||
if (adminThemeButtons.length) {
|
||||
applyAdminTheme(localStorage.getItem('beatbook-admin-theme') || 'system', false);
|
||||
adminThemeButtons.forEach((button) => button.addEventListener('click', () => applyAdminTheme(button.dataset.adminTheme)));
|
||||
adminThemeMedia.addEventListener?.('change', () => {
|
||||
if ((localStorage.getItem('beatbook-admin-theme') || 'system') === 'system') applyAdminTheme('system', false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>@yield('title', 'Administration') — BeatBook</title>
|
||||
<script>
|
||||
(() => {
|
||||
const saved = localStorage.getItem('beatbook-admin-theme') || 'system';
|
||||
const dark = saved === 'dark' || (saved === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
document.documentElement.dataset.adminTheme = dark ? 'dark' : 'light';
|
||||
document.documentElement.dataset.adminThemePreference = saved;
|
||||
})();
|
||||
</script>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="admin-body">
|
||||
@@ -37,7 +45,14 @@
|
||||
<header class="admin-topbar">
|
||||
<button class="icon-button admin-menu" type="button" data-admin-open aria-label="Open menu">☰</button>
|
||||
<div><strong>@yield('page-title', 'Administration')</strong><span>{{ auth()->user()->name }}</span></div>
|
||||
<a class="button small" href="{{ route('home') }}">Public site</a>
|
||||
<div class="admin-topbar-actions">
|
||||
<div class="admin-theme-switcher" role="group" aria-label="Appearance">
|
||||
<button type="button" data-admin-theme="light" title="Light mode" aria-label="Use light mode">☀</button>
|
||||
<button type="button" data-admin-theme="system" title="System appearance" aria-label="Use system appearance">◐</button>
|
||||
<button type="button" data-admin-theme="dark" title="Dark mode" aria-label="Use dark mode">☾</button>
|
||||
</div>
|
||||
<a class="button small" href="{{ route('home') }}">Public site</a>
|
||||
</div>
|
||||
</header>
|
||||
@if(session('success'))<div class="admin-flash">{{ session('success') }}</div>@endif
|
||||
@if(session('error'))<div class="admin-flash error">{{ session('error') }}</div>@endif
|
||||
|
||||
Reference in New Issue
Block a user