fix desing as per requirment
This commit is contained in:
parent
7aa4e67962
commit
2c5eb1634a
@ -53,10 +53,13 @@ public function storeApp(Request $request)
|
|||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'url' => 'required|url|max:255',
|
'url' => 'required|url|max:255',
|
||||||
'icon_svg' => 'nullable|string',
|
'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',
|
'color' => 'nullable|string|max:20',
|
||||||
'desc' => 'nullable|string',
|
'desc' => 'nullable|string',
|
||||||
'tag' => 'nullable|string|max:255',
|
'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;
|
$logoPath = null;
|
||||||
@ -105,10 +108,13 @@ public function updateApp(Request $request, $id)
|
|||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'url' => 'required|url|max:255',
|
'url' => 'required|url|max:255',
|
||||||
'icon_svg' => 'nullable|string',
|
'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',
|
'color' => 'nullable|string|max:20',
|
||||||
'desc' => 'nullable|string',
|
'desc' => 'nullable|string',
|
||||||
'tag' => 'nullable|string|max:255',
|
'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;
|
$logoPath = $app->logo;
|
||||||
|
|||||||
@ -165,10 +165,13 @@ public function storePersonalApp(Request $request)
|
|||||||
$request->validate([
|
$request->validate([
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'url' => 'required|url|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',
|
'color' => 'nullable|string|max:20',
|
||||||
'desc' => 'nullable|string',
|
'desc' => 'nullable|string',
|
||||||
'tag' => 'nullable|string|max:255',
|
'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;
|
$logoPath = null;
|
||||||
@ -201,10 +204,13 @@ public function updatePersonalApp(Request $request, $id)
|
|||||||
$request->validate([
|
$request->validate([
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'url' => 'required|url|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',
|
'color' => 'nullable|string|max:20',
|
||||||
'desc' => 'nullable|string',
|
'desc' => 'nullable|string',
|
||||||
'tag' => 'nullable|string|max:255',
|
'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;
|
$logoPath = $app->logo;
|
||||||
|
|||||||
BIN
public/info.png
Normal file
BIN
public/info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 373 KiB |
BIN
public/uploads/logos/1784539919_6a5deb0f64426.jpg
Normal file
BIN
public/uploads/logos/1784539919_6a5deb0f64426.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
public/uploads/logos/1784545215_6a5dffbf2023f.png
Normal file
BIN
public/uploads/logos/1784545215_6a5dffbf2023f.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
public/youtube.png
Normal file
BIN
public/youtube.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@ -7,3 +7,35 @@ @theme {
|
|||||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||||
'Segoe UI Symbol', 'Noto Color 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
@ -22,6 +22,37 @@
|
|||||||
--success-color: #10b981;
|
--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;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@ -73,6 +73,58 @@
|
|||||||
pointer-events: auto !important;
|
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 Styling */
|
||||||
.personal-modal {
|
.personal-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -130,7 +182,7 @@
|
|||||||
font-family: 'Outfit', sans-serif;
|
font-family: 'Outfit', sans-serif;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #a5b4fc;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-close {
|
.modal-close {
|
||||||
@ -714,9 +766,7 @@
|
|||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
background: linear-gradient(135deg, #a5b4fc 0%, #38bdf8 100%);
|
color: #ffffff;
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sso-subtitle {
|
.sso-subtitle {
|
||||||
@ -848,9 +898,9 @@
|
|||||||
<section class="services-container">
|
<section class="services-container">
|
||||||
<h2 class="section-title">Authorized Corporate Services</h2>
|
<h2 class="section-title">Authorized Corporate Services</h2>
|
||||||
|
|
||||||
<div class="services-grid">
|
<div class="services-grid" id="corporate-services-grid">
|
||||||
@foreach($services as $service)
|
@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 -->
|
<!-- Logo Highlight Wrapper -->
|
||||||
<div class="service-logo-wrapper">
|
<div class="service-logo-wrapper">
|
||||||
@if($service['logo'])
|
@if($service['logo'])
|
||||||
@ -905,20 +955,19 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="services-grid">
|
<div class="services-grid" id="personal-services-grid">
|
||||||
@forelse($personalApps as $pApp)
|
@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 -->
|
<!-- Relative Wrapper for Logo + Actions -->
|
||||||
<div style="position: relative; display: inline-block;">
|
<div style="position: relative; display: inline-block;">
|
||||||
<!-- Delete & Edit overlay buttons on hover -->
|
<!-- 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;">
|
<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 }}')"
|
<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">
|
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">
|
<svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor">
|
||||||
<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="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" />
|
||||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</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;">
|
<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();"
|
<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;"
|
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">
|
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">
|
<svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor">
|
||||||
<polyline points="3 6 5 6 21 6"/>
|
<path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />
|
||||||
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@ -985,20 +1033,23 @@
|
|||||||
<h3 class="modal-title">Add Custom Application</h3>
|
<h3 class="modal-title">Add Custom Application</h3>
|
||||||
<button class="modal-close" onclick="closePersonalModal('add-personal-app-modal')">×</button>
|
<button class="modal-close" onclick="closePersonalModal('add-personal-app-modal')">×</button>
|
||||||
</div>
|
</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
|
@csrf
|
||||||
<div class="form-group">
|
<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">
|
<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>
|
||||||
<div class="form-group">
|
<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">
|
<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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Custom Logo Image (Optional)</label>
|
<label class="form-label">Custom Logo Image (Optional - Max 5MB)</label>
|
||||||
<input type="file" name="logo" class="form-input" style="padding: 6px 12px;">
|
<input type="file" name="logo" class="form-input logo-upload-5mb" 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>
|
<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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Brand Color (Hex Code)</label>
|
<label class="form-label">Brand Color (Hex Code)</label>
|
||||||
@ -1030,19 +1081,22 @@
|
|||||||
<h3 class="modal-title">Edit Custom Application</h3>
|
<h3 class="modal-title">Edit Custom Application</h3>
|
||||||
<button class="modal-close" onclick="closePersonalModal('edit-personal-app-modal')">×</button>
|
<button class="modal-close" onclick="closePersonalModal('edit-personal-app-modal')">×</button>
|
||||||
</div>
|
</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
|
@csrf
|
||||||
<div class="form-group">
|
<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">
|
<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>
|
||||||
<div class="form-group">
|
<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">
|
<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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Custom Logo Image (Optional)</label>
|
<label class="form-label">Custom Logo Image (Optional - Max 5MB)</label>
|
||||||
<input type="file" name="logo" class="form-input" style="padding: 6px 12px;">
|
<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 id="edit-papp-logo-status" style="font-size:0.65rem; margin-top:4px;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -1115,7 +1169,7 @@ function closePersonalModal(modalId) {
|
|||||||
document.getElementById(modalId).classList.remove('active');
|
document.getElementById(modalId).classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Theme Switcher Logic
|
// Theme Switcher & Drag-and-Drop Facilities
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const themeToggleBtn = document.getElementById('theme-toggle');
|
const themeToggleBtn = document.getElementById('theme-toggle');
|
||||||
const sunIcon = themeToggleBtn.querySelector('.theme-icon-light');
|
const sunIcon = themeToggleBtn.querySelector('.theme-icon-light');
|
||||||
@ -1140,6 +1194,130 @@ function updateIcons() {
|
|||||||
localStorage.setItem('theme', newTheme);
|
localStorage.setItem('theme', newTheme);
|
||||||
updateIcons();
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -8,8 +8,12 @@
|
|||||||
// Authentication & Portal landing
|
// Authentication & Portal landing
|
||||||
Route::get('/', [LoginController::class, 'showLoginForm']);
|
Route::get('/', [LoginController::class, 'showLoginForm']);
|
||||||
Route::get('/login', [LoginController::class, 'showLoginForm'])->name('login');
|
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::post('/logout', [LoginController::class, 'logout'])->name('logout');
|
||||||
Route::get('/logout', [LoginController::class, 'logout']); // For ease of development/testing via GET
|
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');
|
Route::delete('/dashboard/personal-apps/{id}', [DashboardController::class, 'destroyPersonalApp'])->name('dashboard.personal-apps.destroy');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Admin Control Panel (requires admin role)
|
// Admin Control Panel (requires admin role)
|
||||||
Route::middleware(['auth', 'role:admin'])->group(function () {
|
Route::middleware(['auth', 'role:admin'])->prefix('controlpannel')->group(function () {
|
||||||
Route::get('/admin/dashboard', [AdminController::class, 'index'])->name('admin.dashboard')->middleware('verify-ms-session');
|
Route::get('/', [AdminController::class, 'index'])->middleware('verify-ms-session');
|
||||||
Route::post('/admin/apps', [AdminController::class, 'storeApp'])->name('admin.apps.store');
|
Route::get('/dashboard', [AdminController::class, 'index'])->name('admin.dashboard')->middleware('verify-ms-session');
|
||||||
Route::post('/admin/apps/{id}/update', [AdminController::class, 'updateApp'])->name('admin.apps.update');
|
Route::post('/apps', [AdminController::class, 'storeApp'])->name('admin.apps.store');
|
||||||
Route::delete('/admin/apps/{id}', [AdminController::class, 'destroyApp'])->name('admin.apps.destroy');
|
Route::post('/apps/{id}/update', [AdminController::class, 'updateApp'])->name('admin.apps.update');
|
||||||
Route::post('/admin/users/{id}/toggle-block', [AdminController::class, 'toggleBlockUser'])->name('admin.users.toggle-block');
|
Route::delete('/apps/{id}', [AdminController::class, 'destroyApp'])->name('admin.apps.destroy');
|
||||||
Route::post('/admin/users/{id}/toggle-admin', [AdminController::class, 'toggleAdminPrivilege'])->name('admin.users.toggle-admin');
|
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
|
// Roles CRUD
|
||||||
Route::post('/admin/roles', [AdminController::class, 'storeRole'])->name('admin.roles.store');
|
Route::post('/roles', [AdminController::class, 'storeRole'])->name('admin.roles.store');
|
||||||
Route::post('/admin/roles/{id}/update', [AdminController::class, 'updateRole'])->name('admin.roles.update');
|
Route::post('/roles/{id}/update', [AdminController::class, 'updateRole'])->name('admin.roles.update');
|
||||||
Route::delete('/admin/roles/{id}', [AdminController::class, 'destroyRole'])->name('admin.roles.destroy');
|
Route::delete('/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('/settings/default-role', [AdminController::class, 'updateDefaultRole'])->name('admin.settings.default-role');
|
||||||
|
|
||||||
// User Roles & Overrides
|
// User Roles & Overrides
|
||||||
Route::post('/admin/users/{id}/roles', [AdminController::class, 'updateUserRoles'])->name('admin.users.roles.update');
|
Route::post('/users/{id}/roles', [AdminController::class, 'updateUserRoles'])->name('admin.users.roles.update');
|
||||||
Route::post('/admin/overrides', [AdminController::class, 'storeUserOverride'])->name('admin.overrides.store');
|
Route::post('/overrides', [AdminController::class, 'storeUserOverride'])->name('admin.overrides.store');
|
||||||
Route::delete('/admin/overrides/{id}', [AdminController::class, 'destroyUserOverride'])->name('admin.overrides.destroy');
|
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', '.*');
|
||||||
|
|||||||
@ -31,17 +31,17 @@ public function test_admin_can_access_admin_dashboard_but_not_standard_users():
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Unauthenticated user is redirected to login
|
// Unauthenticated user is redirected to login
|
||||||
$this->get('/admin/dashboard')
|
$this->get('/controlpannel/dashboard')
|
||||||
->assertRedirect(route('login'));
|
->assertRedirect(route('login'));
|
||||||
|
|
||||||
// Admin can access admin dashboard
|
// Admin can access admin dashboard
|
||||||
$this->actingAs($admin)
|
$this->actingAs($admin)
|
||||||
->get('/admin/dashboard')
|
->get('/controlpannel/dashboard')
|
||||||
->assertStatus(200);
|
->assertStatus(200);
|
||||||
|
|
||||||
// Standard user gets allowed standard user access to dashboard (our middleware handles this)
|
// Standard user gets allowed standard user access to dashboard (our middleware handles this)
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->get('/admin/dashboard')
|
->get('/controlpannel/dashboard')
|
||||||
->assertRedirect(route('dashboard'));
|
->assertRedirect(route('dashboard'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,7 +304,7 @@ public function test_user_multiple_roles_admin_link(): void
|
|||||||
|
|
||||||
// And they can access the Admin Dashboard
|
// And they can access the Admin Dashboard
|
||||||
$responseAdmin = $this->actingAs($user)
|
$responseAdmin = $this->actingAs($user)
|
||||||
->get('/admin/dashboard');
|
->get('/controlpannel/dashboard');
|
||||||
$responseAdmin->assertStatus(200);
|
$responseAdmin->assertStatus(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user