fix desing as per requirment

This commit is contained in:
subhajit 2026-07-20 17:21:25 +05:30
parent 7aa4e67962
commit 2c5eb1634a
12 changed files with 841 additions and 247 deletions

View File

@ -53,10 +53,13 @@ public function storeApp(Request $request)
'name' => 'required|string|max:255',
'url' => 'required|url|max:255',
'icon_svg' => 'nullable|string',
'logo' => 'nullable|file|mimes:jpg,jpeg,png,webp,svg|max:2048',
'logo' => 'nullable|file|mimes:jpg,jpeg,png,webp,svg,avif,gif,bmp,ico|max:5120',
'color' => 'nullable|string|max:20',
'desc' => 'nullable|string',
'tag' => 'nullable|string|max:255',
], [
'logo.mimes' => 'Invalid file format. Only image formats (PNG, JPG, WEBP, SVG, AVIF, GIF, BMP) are allowed.',
'logo.max' => 'File size exceeds maximum limit of 5MB. Please upload an image under 5MB.',
]);
$logoPath = null;
@ -105,10 +108,13 @@ public function updateApp(Request $request, $id)
'name' => 'required|string|max:255',
'url' => 'required|url|max:255',
'icon_svg' => 'nullable|string',
'logo' => 'nullable|file|mimes:jpg,jpeg,png,webp,svg|max:2048',
'logo' => 'nullable|file|mimes:jpg,jpeg,png,webp,svg,avif,gif,bmp,ico|max:5120',
'color' => 'nullable|string|max:20',
'desc' => 'nullable|string',
'tag' => 'nullable|string|max:255',
], [
'logo.mimes' => 'Invalid file format. Only image formats (PNG, JPG, WEBP, SVG, AVIF, GIF, BMP) are allowed.',
'logo.max' => 'File size exceeds maximum limit of 5MB. Please upload an image under 5MB.',
]);
$logoPath = $app->logo;

View File

@ -165,10 +165,13 @@ public function storePersonalApp(Request $request)
$request->validate([
'name' => 'required|string|max:255',
'url' => 'required|url|max:255',
'logo' => 'nullable|file|mimes:jpg,jpeg,png,webp,svg|max:2048',
'logo' => 'nullable|file|mimes:jpg,jpeg,png,webp,svg,avif,gif,bmp,ico|max:5120',
'color' => 'nullable|string|max:20',
'desc' => 'nullable|string',
'tag' => 'nullable|string|max:255',
], [
'logo.mimes' => 'Invalid file format. Only image formats (PNG, JPG, WEBP, SVG, AVIF, GIF, BMP) are allowed.',
'logo.max' => 'File size exceeds maximum limit of 5MB. Please upload an image under 5MB.',
]);
$logoPath = null;
@ -201,10 +204,13 @@ public function updatePersonalApp(Request $request, $id)
$request->validate([
'name' => 'required|string|max:255',
'url' => 'required|url|max:255',
'logo' => 'nullable|file|mimes:jpg,jpeg,png,webp,svg|max:2048',
'logo' => 'nullable|file|mimes:jpg,jpeg,png,webp,svg,avif,gif,bmp,ico|max:5120',
'color' => 'nullable|string|max:20',
'desc' => 'nullable|string',
'tag' => 'nullable|string|max:255',
], [
'logo.mimes' => 'Invalid file format. Only image formats (PNG, JPG, WEBP, SVG, AVIF, GIF, BMP) are allowed.',
'logo.max' => 'File size exceeds maximum limit of 5MB. Please upload an image under 5MB.',
]);
$logoPath = $app->logo;

BIN
public/info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
public/youtube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -7,3 +7,35 @@ @theme {
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
}
/* Modern Thin White Custom Scrollbars */
html, body, * {
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.4) rgba(15, 23, 42, 0.35);
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.35);
border-radius: 999px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.4);
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.15);
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.85);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
}
::-webkit-scrollbar-corner {
background: transparent;
}

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,37 @@
--success-color: #10b981;
}
/* Modern Thin White Scrollbars */
html, body, *, ::-webkit-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.4) rgba(15, 23, 42, 0.35);
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.35);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.4);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.15);
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.85);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
}
::-webkit-scrollbar-corner {
background: transparent;
}
* {
box-sizing: border-box;
margin: 0;

View File

@ -73,6 +73,58 @@
pointer-events: auto !important;
}
/* Drag and Drop Facilities Styling */
.service-card-glass {
cursor: grab;
user-select: none;
-webkit-user-select: none;
}
.service-card-glass:active {
cursor: grabbing;
}
.service-card-glass.dragging {
opacity: 0.35;
transform: scale(0.94);
border: 2px dashed rgba(255, 255, 255, 0.6) !important;
box-shadow: 0 0 20px rgba(99, 102, 241, 0.4);
}
.service-card-glass.drag-over {
border: 2px solid #38bdf8 !important;
transform: translateY(-6px) scale(1.04);
box-shadow: 0 12px 30px rgba(56, 189, 248, 0.35);
}
/* Modern Ultra-Thin White Custom Scrollbars */
html, body, *, ::-webkit-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.4) rgba(15, 23, 42, 0.35);
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.35);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.4);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.15);
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.85);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
}
::-webkit-scrollbar-corner {
background: transparent;
}
/* Personal Modal Styling */
.personal-modal {
position: fixed;
@ -130,7 +182,7 @@
font-family: 'Outfit', sans-serif;
font-size: 1.2rem;
font-weight: 700;
color: #a5b4fc;
color: #ffffff;
}
.modal-close {
@ -714,9 +766,7 @@
font-size: 1.4rem;
font-weight: 700;
margin-bottom: 12px;
background: linear-gradient(135deg, #a5b4fc 0%, #38bdf8 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: #ffffff;
}
.sso-subtitle {
@ -848,9 +898,9 @@
<section class="services-container">
<h2 class="section-title">Authorized Corporate Services</h2>
<div class="services-grid">
<div class="services-grid" id="corporate-services-grid">
@foreach($services as $service)
<div class="service-card-glass" onclick="launchSSO('{{ $service['name'] }}', '{{ route('sso.launch', $service['id']) }}')" style="--service-color: {{ $service['color'] ?? '#4f46e5' }}">
<div class="service-card-glass" draggable="true" data-id="corp-{{ $service['id'] }}" onclick="launchSSO('{{ $service['name'] }}', '{{ route('sso.launch', $service['id']) }}')" style="--service-color: {{ $service['color'] ?? '#4f46e5' }}">
<!-- Logo Highlight Wrapper -->
<div class="service-logo-wrapper">
@if($service['logo'])
@ -905,20 +955,19 @@
</button>
</div>
<div class="services-grid">
<div class="services-grid" id="personal-services-grid">
@forelse($personalApps as $pApp)
<div class="service-card-glass personal-app-card" style="--service-color: {{ $pApp->color ?? '#4f46e5' }};">
<div class="service-card-glass personal-app-card" draggable="true" data-id="personal-{{ $pApp->id }}" style="--service-color: {{ $pApp->color ?? '#4f46e5' }};">
<!-- Relative Wrapper for Logo + Actions -->
<div style="position: relative; display: inline-block;">
<!-- Delete & Edit overlay buttons on hover -->
<div class="personal-app-actions" style="position: absolute; top: -6px; right: -6px; display: flex; gap: 6px; z-index: 100; opacity: 0; transition: opacity 0.2s ease; pointer-events: none;">
<button onclick="event.stopPropagation(); openEditPersonalAppModal({{ $pApp->id }}, '{{ addslashes($pApp->name) }}', '{{ addslashes($pApp->url) }}', '{{ addslashes($pApp->color) }}', '{{ addslashes($pApp->tag) }}', '{{ addslashes($pApp->desc) }}', '{{ $pApp->logo }}')"
style="pointer-events: auto; background: #6366f1; border: 1.5px solid rgba(255, 255, 255, 0.2); color: white; border-radius: 50%; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.6); transition: transform 0.2s;"
style="pointer-events: auto; background: #06b6d4; border: 1.5px solid rgba(255, 255, 255, 0.2); color: white; border-radius: 50%; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.6); transition: transform 0.2s;"
onmouseover="this.style.transform='scale(1.15)'" onmouseout="this.style.transform='scale(1)'" title="Edit App">
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
<svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor">
<path d="M14.06,9L15,9.94L5.92,19H5V18.06L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z" />
</svg>
</button>
<form action="{{ route('dashboard.personal-apps.destroy', $pApp->id) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this custom app?')" style="display: inline;">
@ -927,9 +976,8 @@
<button type="submit" onclick="event.stopPropagation();"
style="pointer-events: auto; background: #ef4444; border: 1.5px solid rgba(255, 255, 255, 0.2); color: white; border-radius: 50%; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.6); transition: transform 0.2s;"
onmouseover="this.style.transform='scale(1.15)'" onmouseout="this.style.transform='scale(1)'" title="Delete App">
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor">
<path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />
</svg>
</button>
</form>
@ -985,20 +1033,23 @@
<h3 class="modal-title">Add Custom Application</h3>
<button class="modal-close" onclick="closePersonalModal('add-personal-app-modal')">&times;</button>
</div>
<form action="{{ route('dashboard.personal-apps.store') }}" method="POST" enctype="multipart/form-data">
<form action="{{ route('dashboard.personal-apps.store') }}" method="POST" enctype="multipart/form-data" class="validated-form">
@csrf
<div class="form-group">
<label class="form-label">Application Name *</label>
<label class="form-label">Application Name <span style="color: #ef4444; font-weight: bold;">*</span></label>
<input type="text" name="name" required placeholder="e.g. My Github, Internal Wiki" class="form-input">
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">This field is required.</div>
</div>
<div class="form-group">
<label class="form-label">Redirect URL *</label>
<label class="form-label">Redirect URL <span style="color: #ef4444; font-weight: bold;">*</span></label>
<input type="url" name="url" required placeholder="https://github.com" class="form-input">
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">Please enter a valid URL.</div>
</div>
<div class="form-group">
<label class="form-label">Custom Logo Image (Optional)</label>
<input type="file" name="logo" class="form-input" style="padding: 6px 12px;">
<div style="font-size:0.65rem; color:var(--text-muted); margin-top:4px;">Supported: PNG, JPG, WEBP, SVG (Max 2MB)</div>
<label class="form-label">Custom Logo Image (Optional - Max 5MB)</label>
<input type="file" name="logo" class="form-input logo-upload-5mb" style="padding: 6px 12px;">
<div class="file-size-error" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">File size exceeds maximum limit of 5MB.</div>
<div style="font-size:0.65rem; color:var(--text-muted); margin-top:4px;">Supported: PNG, JPG, WEBP, SVG (Max 5MB)</div>
</div>
<div class="form-group">
<label class="form-label">Brand Color (Hex Code)</label>
@ -1030,19 +1081,22 @@
<h3 class="modal-title">Edit Custom Application</h3>
<button class="modal-close" onclick="closePersonalModal('edit-personal-app-modal')">&times;</button>
</div>
<form id="edit-personal-app-form" method="POST" enctype="multipart/form-data">
<form id="edit-personal-app-form" method="POST" enctype="multipart/form-data" class="validated-form">
@csrf
<div class="form-group">
<label class="form-label">Application Name *</label>
<label class="form-label">Application Name <span style="color: #ef4444; font-weight: bold;">*</span></label>
<input type="text" id="edit-papp-name" name="name" required class="form-input">
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">This field is required.</div>
</div>
<div class="form-group">
<label class="form-label">Redirect URL *</label>
<label class="form-label">Redirect URL <span style="color: #ef4444; font-weight: bold;">*</span></label>
<input type="url" id="edit-papp-url" name="url" required class="form-input">
<div class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">Please enter a valid URL.</div>
</div>
<div class="form-group">
<label class="form-label">Custom Logo Image (Optional)</label>
<input type="file" name="logo" class="form-input" style="padding: 6px 12px;">
<label class="form-label">Custom Logo Image (Optional - Max 5MB)</label>
<input type="file" name="logo" class="form-input logo-upload-5mb" style="padding: 6px 12px;">
<div class="file-size-error" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">File size exceeds maximum limit of 5MB.</div>
<div id="edit-papp-logo-status" style="font-size:0.65rem; margin-top:4px;"></div>
</div>
<div class="form-group">
@ -1115,7 +1169,7 @@ function closePersonalModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
// Theme Switcher Logic
// Theme Switcher & Drag-and-Drop Facilities
document.addEventListener('DOMContentLoaded', () => {
const themeToggleBtn = document.getElementById('theme-toggle');
const sunIcon = themeToggleBtn.querySelector('.theme-icon-light');
@ -1140,6 +1194,130 @@ function updateIcons() {
localStorage.setItem('theme', newTheme);
updateIcons();
});
// Bidirectional Drag and Drop facilities for frontend services
function initDragAndDrop(gridId, storageKey) {
const grid = document.getElementById(gridId);
if (!grid) return;
// Restore saved order from localStorage
const savedOrder = JSON.parse(localStorage.getItem(storageKey) || '[]');
if (savedOrder.length > 0) {
const cards = Array.from(grid.children);
const cardMap = new Map();
cards.forEach(card => {
const id = card.getAttribute('data-id');
if (id) cardMap.set(id, card);
});
savedOrder.forEach(id => {
const card = cardMap.get(id);
if (card) grid.appendChild(card);
});
}
let draggedCard = null;
grid.querySelectorAll('.service-card-glass').forEach(card => {
card.addEventListener('dragstart', (e) => {
draggedCard = card;
card.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', card.getAttribute('data-id') || '');
});
card.addEventListener('dragend', () => {
card.classList.remove('dragging');
grid.querySelectorAll('.service-card-glass').forEach(c => c.classList.remove('drag-over'));
draggedCard = null;
// Persist new layout order
const newOrder = Array.from(grid.children)
.map(c => c.getAttribute('data-id'))
.filter(Boolean);
localStorage.setItem(storageKey, JSON.stringify(newOrder));
});
card.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const targetCard = e.target.closest('.service-card-glass');
if (targetCard && draggedCard && targetCard !== draggedCard && targetCard.parentNode === grid) {
targetCard.classList.add('drag-over');
const rect = targetCard.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// Realtime bidirectional placement (left/right/up/down)
const isAfter = (mouseX > rect.width / 2) || (mouseY > rect.height / 2);
if (isAfter) {
grid.insertBefore(draggedCard, targetCard.nextSibling);
} else {
grid.insertBefore(draggedCard, targetCard);
}
}
});
card.addEventListener('dragleave', () => {
card.classList.remove('drag-over');
});
card.addEventListener('drop', (e) => {
e.preventDefault();
card.classList.remove('drag-over');
});
});
}
initDragAndDrop('corporate-services-grid', 'user_corp_services_order');
initDragAndDrop('personal-services-grid', 'user_personal_services_order');
// 5MB Logo upload & explicit format validation (JPG, PNG, WEBP, SVG, AVIF, GIF, BMP)
const allowedImageFormats = ['jpg', 'jpeg', 'png', 'webp', 'svg', 'avif', 'gif', 'bmp', 'ico'];
document.querySelectorAll('.logo-upload-5mb').forEach(input => {
input.addEventListener('change', function() {
const file = this.files[0];
const errorDiv = this.parentElement.querySelector('.file-size-error');
if (file) {
const ext = file.name.split('.').pop().toLowerCase();
if (!allowedImageFormats.includes(ext)) {
if (errorDiv) {
errorDiv.textContent = 'Invalid file format ".' + ext + '". Only PNG, JPG, WEBP, SVG, AVIF, GIF, and BMP image formats are allowed.';
errorDiv.style.display = 'block';
}
this.value = '';
return;
}
if (file.size > 5 * 1024 * 1024) {
if (errorDiv) {
errorDiv.textContent = 'File size (' + (file.size / (1024*1024)).toFixed(2) + 'MB) exceeds maximum limit of 5MB. Please upload an image under 5MB.';
errorDiv.style.display = 'block';
}
this.value = '';
return;
}
if (errorDiv) errorDiv.style.display = 'none';
}
});
});
// Form validation
document.querySelectorAll('form.validated-form').forEach(form => {
form.addEventListener('submit', function(e) {
let isValid = true;
form.querySelectorAll('[required]').forEach(input => {
const errorDiv = input.parentElement.querySelector('.field-error-msg');
if (!input.value || !input.value.trim()) {
isValid = false;
input.style.borderColor = '#ef4444';
if (errorDiv) errorDiv.style.display = 'block';
} else {
input.style.borderColor = 'var(--card-border)';
if (errorDiv) errorDiv.style.display = 'none';
}
});
if (!isValid) e.preventDefault();
});
});
});
</script>
</body>

View File

@ -8,8 +8,12 @@
// Authentication & Portal landing
Route::get('/', [LoginController::class, 'showLoginForm']);
Route::get('/login', [LoginController::class, 'showLoginForm'])->name('login');
Route::get('/admin/login', [LoginController::class, 'showAdminLoginForm'])->name('admin.login');
Route::post('/admin/login', [LoginController::class, 'adminLogin'])->name('admin.login.submit');
// Control Panel Login
Route::get('/controlpannel/login', [LoginController::class, 'showAdminLoginForm'])->name('admin.login');
Route::post('/controlpannel/login', [LoginController::class, 'adminLogin'])->name('admin.login.submit');
Route::get('/admin/login', fn() => redirect()->route('admin.login'));
Route::post('/logout', [LoginController::class, 'logout'])->name('logout');
Route::get('/logout', [LoginController::class, 'logout']); // For ease of development/testing via GET
@ -35,24 +39,27 @@
Route::delete('/dashboard/personal-apps/{id}', [DashboardController::class, 'destroyPersonalApp'])->name('dashboard.personal-apps.destroy');
});
// Admin Control Panel (requires admin role)
Route::middleware(['auth', 'role:admin'])->group(function () {
Route::get('/admin/dashboard', [AdminController::class, 'index'])->name('admin.dashboard')->middleware('verify-ms-session');
Route::post('/admin/apps', [AdminController::class, 'storeApp'])->name('admin.apps.store');
Route::post('/admin/apps/{id}/update', [AdminController::class, 'updateApp'])->name('admin.apps.update');
Route::delete('/admin/apps/{id}', [AdminController::class, 'destroyApp'])->name('admin.apps.destroy');
Route::post('/admin/users/{id}/toggle-block', [AdminController::class, 'toggleBlockUser'])->name('admin.users.toggle-block');
Route::post('/admin/users/{id}/toggle-admin', [AdminController::class, 'toggleAdminPrivilege'])->name('admin.users.toggle-admin');
Route::middleware(['auth', 'role:admin'])->prefix('controlpannel')->group(function () {
Route::get('/', [AdminController::class, 'index'])->middleware('verify-ms-session');
Route::get('/dashboard', [AdminController::class, 'index'])->name('admin.dashboard')->middleware('verify-ms-session');
Route::post('/apps', [AdminController::class, 'storeApp'])->name('admin.apps.store');
Route::post('/apps/{id}/update', [AdminController::class, 'updateApp'])->name('admin.apps.update');
Route::delete('/apps/{id}', [AdminController::class, 'destroyApp'])->name('admin.apps.destroy');
Route::post('/users/{id}/toggle-block', [AdminController::class, 'toggleBlockUser'])->name('admin.users.toggle-block');
Route::post('/users/{id}/toggle-admin', [AdminController::class, 'toggleAdminPrivilege'])->name('admin.users.toggle-admin');
// Roles CRUD
Route::post('/admin/roles', [AdminController::class, 'storeRole'])->name('admin.roles.store');
Route::post('/admin/roles/{id}/update', [AdminController::class, 'updateRole'])->name('admin.roles.update');
Route::delete('/admin/roles/{id}', [AdminController::class, 'destroyRole'])->name('admin.roles.destroy');
Route::post('/admin/settings/default-role', [AdminController::class, 'updateDefaultRole'])->name('admin.settings.default-role');
Route::post('/roles', [AdminController::class, 'storeRole'])->name('admin.roles.store');
Route::post('/roles/{id}/update', [AdminController::class, 'updateRole'])->name('admin.roles.update');
Route::delete('/roles/{id}', [AdminController::class, 'destroyRole'])->name('admin.roles.destroy');
Route::post('/settings/default-role', [AdminController::class, 'updateDefaultRole'])->name('admin.settings.default-role');
// User Roles & Overrides
Route::post('/admin/users/{id}/roles', [AdminController::class, 'updateUserRoles'])->name('admin.users.roles.update');
Route::post('/admin/overrides', [AdminController::class, 'storeUserOverride'])->name('admin.overrides.store');
Route::delete('/admin/overrides/{id}', [AdminController::class, 'destroyUserOverride'])->name('admin.overrides.destroy');
Route::post('/users/{id}/roles', [AdminController::class, 'updateUserRoles'])->name('admin.users.roles.update');
Route::post('/overrides', [AdminController::class, 'storeUserOverride'])->name('admin.overrides.store');
Route::delete('/overrides/{id}', [AdminController::class, 'destroyUserOverride'])->name('admin.overrides.destroy');
});
// Legacy /admin redirect to /controlpannel/dashboard
Route::get('/admin/{any?}', fn() => redirect()->route('admin.dashboard'))->where('any', '.*');

View File

@ -31,17 +31,17 @@ public function test_admin_can_access_admin_dashboard_but_not_standard_users():
]);
// Unauthenticated user is redirected to login
$this->get('/admin/dashboard')
$this->get('/controlpannel/dashboard')
->assertRedirect(route('login'));
// Admin can access admin dashboard
$this->actingAs($admin)
->get('/admin/dashboard')
->get('/controlpannel/dashboard')
->assertStatus(200);
// Standard user gets allowed standard user access to dashboard (our middleware handles this)
$this->actingAs($user)
->get('/admin/dashboard')
->get('/controlpannel/dashboard')
->assertRedirect(route('dashboard'));
}
@ -304,7 +304,7 @@ public function test_user_multiple_roles_admin_link(): void
// And they can access the Admin Dashboard
$responseAdmin = $this->actingAs($user)
->get('/admin/dashboard');
->get('/controlpannel/dashboard');
$responseAdmin->assertStatus(200);
}