feat(auth): add Login Page Configuration Panel with Left/Center/Right layout and styling controls

This commit is contained in:
DevSoftTech Server 2026-10-27 21:28:44 +05:30
parent aac71afc70
commit 2ed2bfa99c
7 changed files with 842 additions and 34 deletions

View File

@ -1433,6 +1433,8 @@ $routes->group('setting', static function($routes) {
$routes->post('admin/ipd-examination-fields/save', 'Setting\IpdExaminationFields::save', ['filter' => ['permission:ipd_discharge.master.manage', 'csrf']]); $routes->post('admin/ipd-examination-fields/save', 'Setting\IpdExaminationFields::save', ['filter' => ['permission:ipd_discharge.master.manage', 'csrf']]);
$routes->post('admin/ipd-examination-fields/toggle/(:num)', 'Setting\IpdExaminationFields::toggle/$1', ['filter' => ['permission:ipd_discharge.master.manage', 'csrf']]); $routes->post('admin/ipd-examination-fields/toggle/(:num)', 'Setting\IpdExaminationFields::toggle/$1', ['filter' => ['permission:ipd_discharge.master.manage', 'csrf']]);
$routes->get('admin/hospital-profile', 'Setting\\HospitalProfile::index'); $routes->get('admin/hospital-profile', 'Setting\\HospitalProfile::index');
$routes->get('admin/login-settings', 'Setting\\HospitalProfile::loginSettings');
$routes->post('admin/login-settings/save', 'Setting\\HospitalProfile::saveLoginSettings');
$routes->get('admin/system-ops', 'SystemOps::index', ['filter' => $settingsAdminFilter]); $routes->get('admin/system-ops', 'SystemOps::index', ['filter' => $settingsAdminFilter]);
$routes->get('admin/system-ops/panel', 'SystemOps::panel', ['filter' => $settingsAdminFilter]); $routes->get('admin/system-ops/panel', 'SystemOps::panel', ['filter' => $settingsAdminFilter]);
$routes->get('admin/system-ops/diagnose', 'SystemOps::diagnose', ['filter' => $settingsAdminFilter]); $routes->get('admin/system-ops/diagnose', 'SystemOps::diagnose', ['filter' => $settingsAdminFilter]);

View File

@ -211,6 +211,132 @@ class HospitalProfile extends BaseController
]); ]);
} }
public function loginSettings()
{
if (! $this->canManageHospitalProfile()) {
return $this->response->setStatusCode(403)->setBody('Access denied');
}
$wallpaper = $this->readSettingValue('H_login_wallpaper');
$logo = $this->readSettingValue('H_logo');
$presets = [
[
'file' => 'hospital_wallpaper_20261027_203745_bf800456.jpg',
'title' => 'Client Deep Teal (Current)',
'desc' => 'Dark teal background with smooth geometric accents',
],
[
'file' => 'hospital_wallpaper_soft_teal_leaves.jpg',
'title' => 'Johri Soft Teal Botanical',
'desc' => 'Ivory canvas with organic soft teal curved leaves (Brand Guide Option 02)',
],
[
'file' => 'hospital_wallpaper_modern_facade.jpg',
'title' => 'Modern Hospital Facade',
'desc' => 'High-end architectural medical tower with landscaped grounds',
],
];
return view('Setting/Admin/login_settings', [
'hospital_name' => $this->readSettingValue('H_Name'),
'hospital_logo' => $logo,
'hospital_logo_url' => $logo !== '' ? base_url('assets/images/' . rawurlencode($logo)) : '',
'hospital_wallpaper' => $wallpaper,
'hospital_wallpaper_url' => $wallpaper !== '' ? base_url('assets/images/' . rawurlencode($wallpaper)) : '',
'login_panel_position' => $this->readSettingValue('LOGIN_PANEL_POSITION') ?: 'right',
'login_hero_theme' => $this->readSettingValue('LOGIN_HERO_THEME') ?: 'light',
'login_logo_style' => $this->readSettingValue('LOGIN_LOGO_STYLE') ?: '3d_box',
'login_hero_overlay' => $this->readSettingValue('LOGIN_HERO_OVERLAY') ?: 'dark',
'login_show_subtitle' => $this->readSettingValue('LOGIN_SHOW_SUBTITLE') !== '' ? $this->readSettingValue('LOGIN_SHOW_SUBTITLE') : '1',
'login_show_contact_meta' => $this->readSettingValue('LOGIN_SHOW_CONTACT_META') !== '' ? $this->readSettingValue('LOGIN_SHOW_CONTACT_META') : '1',
'login_show_pwa_card' => $this->readSettingValue('LOGIN_SHOW_PWA_CARD') !== '' ? $this->readSettingValue('LOGIN_SHOW_PWA_CARD') : '1',
'presets' => $presets,
]);
}
public function saveLoginSettings()
{
if (! $this->request->isAJAX()) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Invalid request']);
}
if (! $this->canManageHospitalProfile()) {
return $this->response->setStatusCode(403)->setJSON(['update' => 0, 'error_text' => 'Access denied']);
}
$panelPos = strtolower(trim((string) $this->request->getPost('login_panel_position')));
if (! in_array($panelPos, ['left', 'center', 'right'], true)) {
$panelPos = 'right';
}
$heroTheme = strtolower(trim((string) $this->request->getPost('login_hero_theme')));
if (! in_array($heroTheme, ['light', 'dark'], true)) {
$heroTheme = 'light';
}
$logoStyle = strtolower(trim((string) $this->request->getPost('login_logo_style')));
if (! in_array($logoStyle, ['3d_box', 'glass', 'transparent'], true)) {
$logoStyle = '3d_box';
}
$heroOverlay = strtolower(trim((string) $this->request->getPost('login_hero_overlay')));
if (! in_array($heroOverlay, ['dark', 'light', 'none'], true)) {
$heroOverlay = 'dark';
}
$showSubtitle = $this->request->getPost('login_show_subtitle') === '1' ? '1' : '0';
$showContactMeta = $this->request->getPost('login_show_contact_meta') === '1' ? '1' : '0';
$showPwaCard = $this->request->getPost('login_show_pwa_card') === '1' ? '1' : '0';
$this->upsertSettingValue('LOGIN_PANEL_POSITION', $panelPos);
$this->upsertSettingValue('LOGIN_HERO_THEME', $heroTheme);
$this->upsertSettingValue('LOGIN_LOGO_STYLE', $logoStyle);
$this->upsertSettingValue('LOGIN_HERO_OVERLAY', $heroOverlay);
$this->upsertSettingValue('LOGIN_SHOW_SUBTITLE', $showSubtitle);
$this->upsertSettingValue('LOGIN_SHOW_CONTACT_META', $showContactMeta);
$this->upsertSettingValue('LOGIN_SHOW_PWA_CARD', $showPwaCard);
// Check if preset selected
$presetWallpaper = trim((string) $this->request->getPost('preset_wallpaper'));
if ($presetWallpaper !== '') {
$presetFile = basename($presetWallpaper);
if (is_file(FCPATH . 'assets/images/' . $presetFile)) {
$this->upsertSettingValue('H_login_wallpaper', $presetFile);
}
}
// Process new wallpaper upload if present
$wpFile = $this->request->getFile('hospital_wallpaper');
if ($wpFile && $wpFile->isValid() && ! $wpFile->hasMoved()) {
$wallpaperResult = $this->processLogoUpload(
$wpFile,
'hospital_wallpaper',
'H_login_wallpaper',
'Login wallpaper'
);
if (! $wallpaperResult['ok']) {
return $this->response->setJSON([
'update' => 0,
'error_text' => $wallpaperResult['error'],
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
}
$currentWallpaper = $this->readSettingValue('H_login_wallpaper');
return $this->response->setJSON([
'update' => 1,
'error_text' => 'Login page settings saved successfully.',
'wallpaper_url' => $currentWallpaper !== '' ? base_url('assets/images/' . rawurlencode($currentWallpaper)) : '',
'wallpaper_name' => $currentWallpaper,
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
public function deleteLogo() public function deleteLogo()
{ {
if (! $this->request->isAJAX()) { if (! $this->request->isAJAX()) {

View File

@ -84,6 +84,15 @@
</div> </div>
</div> </div>
<div class="col-12">
<div class="alert alert-info py-2 px-3 small d-flex align-items-center justify-content-between flex-wrap gap-2 mb-0">
<div>
<i class="bi bi-palette-fill me-1 text-primary"></i> <strong>Login Page Customizer:</strong> Set Left, Center, or Right panel layout, 3D/glass logo styling, dark/light font colors, and preview presets.
</div>
<a href="javascript:openAdminSubPage('<?= base_url('setting/admin/login-settings') ?>','Login Page Settings');" class="btn btn-sm btn-primary">Open Login Page Customizer &rarr;</a>
</div>
</div>
<div class="col-12 mt-3"><hr><h6 class="mb-2">Pharmacy Information</h6></div> <div class="col-12 mt-3"><hr><h6 class="mb-2">Pharmacy Information</h6></div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Pharmacy Name</label> <label class="form-label">Pharmacy Name</label>

View File

@ -114,6 +114,12 @@
<span>Hospital Profile</span> <span>Hospital Profile</span>
</a> </a>
</div> </div>
<div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/login-settings') ?>','Login Page Settings');">
<i class="bi bi-palette" style="color:#059669;"></i>
<span>Login Settings</span>
</a>
</div>
<div class="col-6 col-md-2 col-lg-2"> <div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/insurance') ?>','Insurance Master');"> <a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/insurance') ?>','Insurance Master');">
<i class="bi bi-shield-check"></i> <i class="bi bi-shield-check"></i>

View File

@ -0,0 +1,472 @@
<div class="card shadow-sm border-0">
<div class="card-header bg-white border-bottom py-3 d-flex align-items-center justify-content-between flex-wrap gap-2">
<div>
<h4 class="card-title mb-1 fw-bold text-dark"><i class="bi bi-palette2 text-primary me-2"></i>Login Page Customizer</h4>
<div class="text-muted small">Configure layout positioning (Left, Center, Right), logo styling, color themes, and wallpapers for the software login page.</div>
</div>
<div class="d-flex align-items-center gap-2">
<a href="<?= base_url('login') ?>" target="_blank" class="btn btn-outline-primary btn-sm">
<i class="bi bi-box-arrow-up-right me-1"></i> Preview Live Login Page
</a>
</div>
</div>
<div class="card-body p-4">
<?= csrf_field() ?>
<style>
.layout-choice-card {
border: 2px solid #e2e8f0;
border-radius: 12px;
padding: 14px;
cursor: pointer;
transition: all 0.2s ease;
background: #f8fafc;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.layout-choice-card:hover {
border-color: #94a3b8;
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06);
}
.layout-choice-card.active {
border-color: #0d6efd;
background: #eff6ff;
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.15);
}
.layout-wireframe {
height: 70px;
border-radius: 6px;
background: #e2e8f0;
display: flex;
gap: 4px;
padding: 5px;
margin-bottom: 10px;
overflow: hidden;
}
.wire-hero {
background: #0d3b66;
border-radius: 4px;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #ffffff;
font-size: 8px;
}
.wire-form {
width: 38%;
background: #ffffff;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 3px;
padding: 4px;
}
.wire-input-bar {
height: 4px;
width: 80%;
background: #cbd5e1;
border-radius: 2px;
}
.wire-center-card {
width: 55%;
margin: auto;
background: #ffffff;
border-radius: 4px;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
display: flex;
flex-direction: column;
align-items: center;
padding: 6px 4px;
gap: 3px;
}
.preset-thumb-card {
border: 2px solid #e2e8f0;
border-radius: 10px;
padding: 8px;
cursor: pointer;
transition: all 0.2s ease;
background: #ffffff;
}
.preset-thumb-card:hover {
border-color: #0d6efd;
transform: translateY(-2px);
}
.preset-thumb-card.active {
border-color: #0d6efd;
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.2);
}
</style>
<form id="form_login_settings">
<!-- 1. Layout & Placement Selection -->
<div class="mb-4">
<label class="form-label fw-bold text-dark fs-6 mb-1">
<i class="bi bi-layout-sidebar-inset text-primary me-1"></i> 1. Login Panel Placement
</label>
<div class="text-muted small mb-3">Choose whether the login credentials form appears on the Left, Center, or Right side of the screen.</div>
<div class="row g-3">
<!-- Left -->
<div class="col-md-4">
<div class="layout-choice-card <?= ($login_panel_position ?? 'right') === 'left' ? 'active' : '' ?>" data-position="left">
<div class="layout-wireframe">
<div class="wire-form">
<div class="wire-input-bar" style="background:#0d6efd; width:50%;"></div>
<div class="wire-input-bar"></div>
<div class="wire-input-bar"></div>
<div class="wire-input-bar" style="background:#0d6efd; height:5px;"></div>
</div>
<div class="wire-hero">
<span>Branding</span>
</div>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<span class="fw-bold d-block text-dark">Left Panel</span>
<span class="text-muted small" style="font-size: 11px;">Form on Left, Wallpaper on Right</span>
</div>
<input type="radio" name="login_panel_position" value="left" class="form-check-input" <?= ($login_panel_position ?? 'right') === 'left' ? 'checked' : '' ?>>
</div>
</div>
</div>
<!-- Center -->
<div class="col-md-4">
<div class="layout-choice-card <?= ($login_panel_position ?? 'right') === 'center' ? 'active' : '' ?>" data-position="center">
<div class="layout-wireframe" style="background: #0d3b66;">
<div class="wire-center-card">
<div class="wire-input-bar" style="background:#0d6efd; width:50%;"></div>
<div class="wire-input-bar"></div>
<div class="wire-input-bar"></div>
<div class="wire-input-bar" style="background:#0d6efd; height:5px;"></div>
</div>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<span class="fw-bold d-block text-dark">Center Modal</span>
<span class="text-muted small" style="font-size: 11px;">Full wallpaper, Centered floating card</span>
</div>
<input type="radio" name="login_panel_position" value="center" class="form-check-input" <?= ($login_panel_position ?? 'right') === 'center' ? 'checked' : '' ?>>
</div>
</div>
</div>
<!-- Right (Default) -->
<div class="col-md-4">
<div class="layout-choice-card <?= ($login_panel_position ?? 'right') === 'right' ? 'active' : '' ?>" data-position="right">
<div class="layout-wireframe">
<div class="wire-hero">
<span>Branding</span>
</div>
<div class="wire-form">
<div class="wire-input-bar" style="background:#0d6efd; width:50%;"></div>
<div class="wire-input-bar"></div>
<div class="wire-input-bar"></div>
<div class="wire-input-bar" style="background:#0d6efd; height:5px;"></div>
</div>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<span class="fw-bold d-block text-dark">Right Panel <span class="badge bg-secondary ms-1">Standard</span></span>
<span class="text-muted small" style="font-size: 11px;">Wallpaper on Left, Form on Right</span>
</div>
<input type="radio" name="login_panel_position" value="right" class="form-check-input" <?= ($login_panel_position ?? 'right') === 'right' ? 'checked' : '' ?>>
</div>
</div>
</div>
</div>
</div>
<hr class="my-4">
<!-- 2. Logo Presentation & 3D Plaque -->
<div class="mb-4">
<label class="form-label fw-bold text-dark fs-6 mb-1">
<i class="bi bi-box text-primary me-1"></i> 2. Hospital Logo Container Style
</label>
<div class="text-muted small mb-3">Select how the transparent hospital logo is mounted so it always stands out clearly.</div>
<div class="row g-3">
<div class="col-md-4">
<label class="p-3 border rounded-3 w-100 h-100 d-flex align-items-center gap-3 cursor-pointer bg-light">
<input type="radio" name="login_logo_style" value="3d_box" class="form-check-input flex-shrink-0" <?= ($login_logo_style ?? '3d_box') === '3d_box' ? 'checked' : '' ?>>
<div>
<span class="fw-bold d-block text-dark">3D Raised Plaque</span>
<span class="text-muted small" style="font-size: 12px;">Curved corners, tactile bevel highlight, deep 3D drop shadow (Recommended for dark/photo wallpapers).</span>
</div>
</label>
</div>
<div class="col-md-4">
<label class="p-3 border rounded-3 w-100 h-100 d-flex align-items-center gap-3 cursor-pointer bg-light">
<input type="radio" name="login_logo_style" value="glass" class="form-check-input flex-shrink-0" <?= ($login_logo_style ?? '3d_box') === 'glass' ? 'checked' : '' ?>>
<div>
<span class="fw-bold d-block text-dark">Frosted Glass Plate</span>
<span class="text-muted small" style="font-size: 12px;">Semi-transparent frosted glass with backdrop blur and sleek borders.</span>
</div>
</label>
</div>
<div class="col-md-4">
<label class="p-3 border rounded-3 w-100 h-100 d-flex align-items-center gap-3 cursor-pointer bg-light">
<input type="radio" name="login_logo_style" value="transparent" class="form-check-input flex-shrink-0" <?= ($login_logo_style ?? '3d_box') === 'transparent' ? 'checked' : '' ?>>
<div>
<span class="fw-bold d-block text-dark">Seamless / Direct</span>
<span class="text-muted small" style="font-size: 12px;">No box/mount. Logo rests directly on the wallpaper (Best for white/ivory canvas backgrounds).</span>
</div>
</label>
</div>
</div>
</div>
<hr class="my-4">
<!-- 3. Text & Typography Theme -->
<div class="mb-4">
<label class="form-label fw-bold text-dark fs-6 mb-1">
<i class="bi bi-type text-primary me-1"></i> 3. Typography &amp; Heading Color Theme
</label>
<div class="text-muted small mb-3">Select text color scheme so heading and labels never blend into or drown in the background.</div>
<div class="row g-3">
<div class="col-md-6">
<label class="p-3 border rounded-3 w-100 h-100 d-flex align-items-center gap-3 cursor-pointer" style="background:#0f273d;">
<input type="radio" name="login_hero_theme" value="light" class="form-check-input flex-shrink-0" <?= ($login_hero_theme ?? 'light') === 'light' ? 'checked' : '' ?>>
<div>
<span class="fw-bold d-block text-white">Radiant White (Dark Backgrounds)</span>
<span class="text-white-50 small" style="font-size: 12px;">White font, crisp dark contour outline, and soft ambient glow. Highly visible against teal, blue, or dark photos.</span>
</div>
</label>
</div>
<div class="col-md-6">
<label class="p-3 border rounded-3 w-100 h-100 d-flex align-items-center gap-3 cursor-pointer" style="background:#f8fafc;">
<input type="radio" name="login_hero_theme" value="dark" class="form-check-input flex-shrink-0" <?= ($login_hero_theme ?? 'light') === 'dark' ? 'checked' : '' ?>>
<div>
<span class="fw-bold d-block text-dark">Architectural Navy (Light Backgrounds)</span>
<span class="text-muted small" style="font-size: 12px;">Deep corporate navy font (`#0b2545`). Pristine contrast on ivory, white, or botanical wallpapers.</span>
</div>
</label>
</div>
</div>
</div>
<hr class="my-4">
<!-- 4. Hero Background Overlay Vignette -->
<div class="mb-4">
<label class="form-label fw-bold text-dark fs-6 mb-1">
<i class="bi bi-brightness-high text-primary me-1"></i> 4. Background Overlay Shading
</label>
<div class="text-muted small mb-3">Controls the subtle gradient shading overlaid onto the wallpaper to balance text readability.</div>
<div class="row g-3">
<div class="col-md-4">
<label class="p-3 border rounded-3 w-100 h-100 d-flex align-items-center gap-3 cursor-pointer bg-light">
<input type="radio" name="login_hero_overlay" value="dark" class="form-check-input flex-shrink-0" <?= ($login_hero_overlay ?? 'dark') === 'dark' ? 'checked' : '' ?>>
<div>
<span class="fw-bold d-block text-dark">Dark Vignette (Default)</span>
<span class="text-muted small" style="font-size: 12px;">Deepens contrast naturally towards the bottom text area.</span>
</div>
</label>
</div>
<div class="col-md-4">
<label class="p-3 border rounded-3 w-100 h-100 d-flex align-items-center gap-3 cursor-pointer bg-light">
<input type="radio" name="login_hero_overlay" value="light" class="form-check-input flex-shrink-0" <?= ($login_hero_overlay ?? 'dark') === 'light' ? 'checked' : '' ?>>
<div>
<span class="fw-bold d-block text-dark">Light Ambient Wash</span>
<span class="text-muted small" style="font-size: 12px;">Soft daylight sheen, ideal for botanical or ivory branding.</span>
</div>
</label>
</div>
<div class="col-md-4">
<label class="p-3 border rounded-3 w-100 h-100 d-flex align-items-center gap-3 cursor-pointer bg-light">
<input type="radio" name="login_hero_overlay" value="none" class="form-check-input flex-shrink-0" <?= ($login_hero_overlay ?? 'dark') === 'none' ? 'checked' : '' ?>>
<div>
<span class="fw-bold d-block text-dark">None / Pure Wallpaper</span>
<span class="text-muted small" style="font-size: 12px;">No shading filter. Wallpaper shows in 100% natural colors.</span>
</div>
</label>
</div>
</div>
</div>
<hr class="my-4">
<!-- 5. Display Toggles -->
<div class="mb-4">
<label class="form-label fw-bold text-dark fs-6 mb-1">
<i class="bi bi-eye text-primary me-1"></i> 5. Show / Hide Content Elements
</label>
<div class="text-muted small mb-3">Turn on or off specific branding elements on the login page.</div>
<div class="row g-3">
<div class="col-md-4">
<div class="form-check form-switch p-3 border rounded-3 bg-light d-flex align-items-center justify-content-between">
<label class="form-check-label fw-semibold text-dark mb-0" for="toggle_subtitle">Show Tagline Subtitle</label>
<input class="form-check-input ms-2" type="checkbox" id="toggle_subtitle" name="login_show_subtitle" value="1" <?= ($login_show_subtitle ?? '1') === '1' ? 'checked' : '' ?>>
</div>
</div>
<div class="col-md-4">
<div class="form-check form-switch p-3 border rounded-3 bg-light d-flex align-items-center justify-content-between">
<label class="form-check-label fw-semibold text-dark mb-0" for="toggle_contact">Show Address &amp; Phone</label>
<input class="form-check-input ms-2" type="checkbox" id="toggle_contact" name="login_show_contact_meta" value="1" <?= ($login_show_contact_meta ?? '1') === '1' ? 'checked' : '' ?>>
</div>
</div>
<div class="col-md-4">
<div class="form-check form-switch p-3 border rounded-3 bg-light d-flex align-items-center justify-content-between">
<label class="form-check-label fw-semibold text-dark mb-0" for="toggle_pwa">Show Mobile PWA Card</label>
<input class="form-check-input ms-2" type="checkbox" id="toggle_pwa" name="login_show_pwa_card" value="1" <?= ($login_show_pwa_card ?? '1') === '1' ? 'checked' : '' ?>>
</div>
</div>
</div>
</div>
<hr class="my-4">
<!-- 6. Wallpaper Selection & Upload -->
<div class="mb-4">
<label class="form-label fw-bold text-dark fs-6 mb-1">
<i class="bi bi-image text-primary me-1"></i> 6. Wallpaper Library &amp; Upload
</label>
<div class="text-muted small mb-3">Select from curated wallpapers or upload a new custom background.</div>
<div class="row g-3 mb-3">
<?php if (!empty($presets)): ?>
<?php foreach ($presets as $p): ?>
<?php $isCurrent = ($hospital_wallpaper ?? '') === $p['file']; ?>
<div class="col-md-4">
<div class="preset-thumb-card <?= $isCurrent ? 'active' : '' ?>" data-preset-file="<?= esc($p['file']) ?>">
<div style="height: 100px; border-radius: 6px; overflow: hidden; background: #e2e8f0; margin-bottom: 6px;">
<img src="<?= base_url('assets/images/' . rawurlencode($p['file'])) ?>" alt="<?= esc($p['title']) ?>" style="width: 100%; height: 100%; object-fit: cover;">
</div>
<div class="fw-bold small text-dark d-flex align-items-center justify-content-between">
<span><?= esc($p['title']) ?></span>
<?php if ($isCurrent): ?>
<span class="badge bg-primary">Active</span>
<?php endif; ?>
</div>
<div class="text-muted small" style="font-size: 11px;"><?= esc($p['desc'] ?? ($p['subtitle'] ?? '')) ?></div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<input type="hidden" id="preset_wallpaper" name="preset_wallpaper" value="">
<div class="row g-3 align-items-center">
<div class="col-md-7">
<label class="form-label fw-semibold text-dark">Upload New Custom Wallpaper</label>
<input type="file" class="form-control" id="custom_hospital_wallpaper" name="hospital_wallpaper" accept=".png,.jpg,.jpeg,.webp">
<div class="form-text">Recommended size: 1920x1080 (PNG, JPG, WEBP).</div>
</div>
<div class="col-md-5">
<label class="form-label fw-semibold text-dark">Current Wallpaper Active</label>
<div id="active_wallpaper_display" class="border rounded p-2 text-center bg-light">
<?php if (!empty($hospital_wallpaper_url ?? '')): ?>
<img src="<?= esc((string) $hospital_wallpaper_url) ?>" alt="Wallpaper" style="max-height:60px; max-width:100%; object-fit:cover; border-radius:4px;">
<div class="small text-muted mt-1 text-truncate"><?= esc((string) ($hospital_wallpaper ?? '')) ?></div>
<?php else: ?>
<span class="text-muted small">No wallpaper active</span>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Submit Controls -->
<div class="d-flex align-items-center gap-2 flex-wrap pt-3 border-top">
<button type="button" class="btn btn-primary px-4 py-2 fw-semibold" id="btn_save_login_settings">
<i class="bi bi-check-circle me-1"></i> Save Configuration
</button>
<a href="<?= base_url('login') ?>" target="_blank" class="btn btn-outline-secondary px-3 py-2">
<i class="bi bi-eye me-1"></i> Open Login Page
</a>
</div>
<div id="login_settings_msg" class="mt-3"></div>
</form>
</div>
</div>
<script>
(function() {
// 1. Layout card click handler
$('.layout-choice-card').on('click', function() {
$('.layout-choice-card').removeClass('active');
$(this).addClass('active');
$(this).find('input[type="radio"]').prop('checked', true);
});
// 2. Preset wallpaper click handler
$('.preset-thumb-card').on('click', function() {
$('.preset-thumb-card').removeClass('active');
$(this).addClass('active');
var file = $(this).data('preset-file');
$('#preset_wallpaper').val(file);
});
// 3. Save AJAX
$('#btn_save_login_settings').on('click', function() {
var $btn = $(this);
var $msg = $('#login_settings_msg');
$msg.empty();
$btn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1" role="status"></span> Saving...');
var formEl = document.getElementById('form_login_settings');
var fd = new FormData(formEl);
// Append CSRF token
var csrfName = $('input[name="csrf_test_name"]').attr('name') || 'csrf_test_name';
var csrfVal = $('input[name="csrf_test_name"]').val();
if (csrfVal) {
fd.append(csrfName, csrfVal);
}
$.ajax({
url: '<?= base_url('setting/admin/login-settings/save') ?>',
type: 'POST',
data: fd,
processData: false,
contentType: false,
dataType: 'json',
success: function(resp) {
$btn.prop('disabled', false).html('<i class="bi bi-check-circle me-1"></i> Save Configuration');
if (resp.csrfName && resp.csrfHash) {
$('input[name="' + resp.csrfName + '"]').val(resp.csrfHash);
}
if (resp.update === 1) {
$msg.html('<div class="alert alert-success d-flex align-items-center py-2 px-3"><i class="bi bi-check-circle-fill me-2 fs-5"></i><div><strong>Saved!</strong> ' + (resp.error_text || 'Login page configuration updated successfully.') + ' <a href="<?= base_url('login') ?>" target="_blank" class="alert-link ms-2">View Login Page &rarr;</a></div></div>');
if (resp.wallpaper_url) {
$('#active_wallpaper_display').html('<img src="' + resp.wallpaper_url + '" alt="Wallpaper" style="max-height:60px; max-width:100%; object-fit:cover; border-radius:4px;"><div class="small text-muted mt-1 text-truncate">' + resp.wallpaper_name + '</div>');
}
} else {
$msg.html('<div class="alert alert-danger py-2 px-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>' + (resp.error_text || 'An error occurred while saving.') + '</div>');
}
},
error: function(xhr) {
$btn.prop('disabled', false).html('<i class="bi bi-check-circle me-1"></i> Save Configuration');
$msg.html('<div class="alert alert-danger py-2 px-3"><i class="bi bi-x-circle-fill me-2"></i>Failed to save settings. Please try again.</div>');
}
});
});
})();
</script>

View File

@ -10,6 +10,15 @@ $hospitalHfrId = hospital_setting_value('H_HFR_ID', '');
$hospitalLogo = hospital_setting_value('H_logo', ''); $hospitalLogo = hospital_setting_value('H_logo', '');
$hospitalWallpaper = hospital_setting_value('H_login_wallpaper', ''); $hospitalWallpaper = hospital_setting_value('H_login_wallpaper', '');
// Login Customizer Settings
$loginPanelPosition = hospital_setting_value('LOGIN_PANEL_POSITION', 'right'); // 'left', 'center', 'right'
$loginHeroTheme = hospital_setting_value('LOGIN_HERO_THEME', 'light'); // 'light', 'dark'
$loginLogoStyle = hospital_setting_value('LOGIN_LOGO_STYLE', '3d_box'); // '3d_box', 'glass', 'transparent'
$loginHeroOverlay = hospital_setting_value('LOGIN_HERO_OVERLAY', 'dark'); // 'dark', 'light', 'none'
$loginShowSubtitle = hospital_setting_value('LOGIN_SHOW_SUBTITLE', '1') !== '0';
$loginShowContactMeta = hospital_setting_value('LOGIN_SHOW_CONTACT_META', '1') !== '0';
$loginShowPwaCard = hospital_setting_value('LOGIN_SHOW_PWA_CARD', '1') !== '0';
$hasLogo = false; $hasLogo = false;
$logoUrl = ''; $logoUrl = '';
if ($hospitalLogo !== '' && is_file(FCPATH . 'assets/images/' . $hospitalLogo)) { if ($hospitalLogo !== '' && is_file(FCPATH . 'assets/images/' . $hospitalLogo)) {
@ -88,6 +97,56 @@ $footerVersion = hms_footer_version();
background-color: #f1f5f9; background-color: #f1f5f9;
} }
/* Layout Positions */
.login-page-wrapper.panel-right {
flex-direction: row;
}
.login-page-wrapper.panel-left {
flex-direction: row-reverse;
}
.login-page-wrapper.panel-left .login-hero-section {
border-right: none;
border-left: 1px solid #e2e8f0;
}
.login-page-wrapper.panel-left .login-form-section {
border-left: none;
border-right: 1px solid #e2e8f0;
box-shadow: 10px 0 40px rgba(0, 0, 0, 0.05);
}
.login-page-wrapper.panel-center {
flex-direction: column;
justify-content: center;
align-items: center;
padding: 2.5rem 1rem;
position: relative;
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
}
.login-page-wrapper.panel-center .login-hero-section {
display: none !important;
}
.login-page-wrapper.panel-center .login-form-section {
width: 100%;
max-width: 480px;
min-height: auto;
border: 1px solid rgba(255, 255, 255, 0.6);
border-radius: 20px;
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.28);
background: rgba(255, 255, 255, 0.96);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
padding: 2.25rem 2rem 1.5rem;
margin: auto;
position: relative;
z-index: 5;
}
.login-page-wrapper.panel-center .login-form-container {
max-width: 100%;
}
/* Left Hero/Branding section */ /* Left Hero/Branding section */
.login-hero-section { .login-hero-section {
flex: 1; flex: 1;
@ -108,14 +167,30 @@ $footerVersion = hms_footer_version();
color: #ffffff; color: #ffffff;
} }
.login-hero-section.has-wallpaper::before { /* Overlay Shading Options */
.login-hero-section.has-wallpaper.overlay-dark::before,
.login-page-wrapper.panel-center.overlay-dark::before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; left: 0; right: 0; bottom: 0; top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(180deg, rgba(7, 24, 43, 0.18) 0%, rgba(7, 24, 43, 0.42) 50%, rgba(7, 24, 43, 0.8) 100%); background: linear-gradient(180deg, rgba(7, 24, 43, 0.2) 0%, rgba(7, 24, 43, 0.45) 50%, rgba(7, 24, 43, 0.82) 100%);
z-index: 1; z-index: 1;
} }
.login-hero-section.has-wallpaper.overlay-light::before,
.login-page-wrapper.panel-center.overlay-light::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.28) 0%, rgba(255, 255, 255, 0.58) 50%, rgba(255, 255, 255, 0.88) 100%);
z-index: 1;
}
.login-hero-section.has-wallpaper.overlay-none::before,
.login-page-wrapper.panel-center.overlay-none::before {
display: none;
}
.login-hero-inner { .login-hero-inner {
position: relative; position: relative;
z-index: 2; z-index: 2;
@ -125,11 +200,16 @@ $footerVersion = hms_footer_version();
height: 100%; height: 100%;
} }
/* 3D Format Box for Hospital Logo with curved corners */ /* Hospital Logo Wrap Styles */
.login-hero-logo-wrap { .login-hero-logo-wrap {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
/* Style: 3D Raised Plaque (Default) */
.login-hero-logo-wrap.style-3d_box {
padding: 16px 26px; padding: 16px 26px;
border-radius: 20px; border-radius: 20px;
background: linear-gradient(150deg, #ffffff 0%, #edf6f7 100%); background: linear-gradient(150deg, #ffffff 0%, #edf6f7 100%);
@ -139,10 +219,8 @@ $footerVersion = hms_footer_version();
0 4px 10px rgba(0, 0, 0, 0.22), 0 4px 10px rgba(0, 0, 0, 0.22),
inset 0 2px 1px rgba(255, 255, 255, 1), inset 0 2px 1px rgba(255, 255, 255, 1),
inset 0 -2px 4px rgba(7, 34, 53, 0.08); inset 0 -2px 4px rgba(7, 34, 53, 0.08);
transition: transform 0.25s ease, box-shadow 0.25s ease;
} }
.login-hero-logo-wrap.style-3d_box:hover {
.login-hero-logo-wrap:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: box-shadow:
0 20px 42px rgba(0, 0, 0, 0.42), 0 20px 42px rgba(0, 0, 0, 0.42),
@ -151,6 +229,32 @@ $footerVersion = hms_footer_version();
inset 0 -2px 4px rgba(7, 34, 53, 0.08); inset 0 -2px 4px rgba(7, 34, 53, 0.08);
} }
/* Style: Frosted Glass Plate */
.login-hero-logo-wrap.style-glass {
padding: 16px 26px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.5);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
}
.login-hero-logo-wrap.style-glass:hover {
transform: translateY(-2px);
background: rgba(255, 255, 255, 0.35);
}
/* Style: Seamless / Direct */
.login-hero-logo-wrap.style-transparent {
padding: 6px 0;
background: transparent;
border: none;
box-shadow: none;
}
.login-hero-logo-wrap.style-transparent .login-hero-logo-img {
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.45));
}
.login-hero-logo-img { .login-hero-logo-img {
max-height: 85px; max-height: 85px;
max-width: 260px; max-width: 260px;
@ -161,12 +265,16 @@ $footerVersion = hms_footer_version();
transition: transform 0.2s ease; transition: transform 0.2s ease;
} }
/* Radiant, crisp heading on dark background with dark outline and ambient glow */ /* Heading & Typography Base */
.login-hero-title { .login-hero-title {
font-size: clamp(2.2rem, 3.8vw, 3.4rem); font-size: clamp(2.2rem, 3.8vw, 3.4rem);
font-weight: 800; font-weight: 800;
line-height: 1.18; line-height: 1.18;
letter-spacing: -0.015em; letter-spacing: -0.015em;
}
/* Theme: Radiant White (Dark Backgrounds) */
.login-page-wrapper.theme-light .login-hero-title {
color: #ffffff; color: #ffffff;
-webkit-text-stroke: 1.2px #061c2e; -webkit-text-stroke: 1.2px #061c2e;
paint-order: stroke fill; paint-order: stroke fill;
@ -175,20 +283,40 @@ $footerVersion = hms_footer_version();
0 8px 24px rgba(0, 0, 0, 0.6), 0 8px 24px rgba(0, 0, 0, 0.6),
0 0 32px rgba(45, 212, 191, 0.3); 0 0 32px rgba(45, 212, 191, 0.3);
} }
.login-page-wrapper.theme-light .login-hero-subtitle {
.login-hero-subtitle {
font-size: 1.15rem; font-size: 1.15rem;
color: #e2e8f0; color: #e2e8f0;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7); text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
max-width: 620px; max-width: 620px;
} }
.login-page-wrapper.theme-light .login-hero-meta {
.login-hero-meta {
color: #f1f5f9; color: #f1f5f9;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85); text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85);
font-weight: 500; font-weight: 500;
} }
/* Theme: Architectural Navy (Light / Ivory Backgrounds) */
.login-page-wrapper.theme-dark .login-hero-title {
color: #0c4a6e;
-webkit-text-stroke: 0.8px rgba(255, 255, 255, 0.95);
paint-order: stroke fill;
text-shadow:
0 2px 8px rgba(255, 255, 255, 0.9),
0 1px 4px rgba(12, 74, 110, 0.25);
}
.login-page-wrapper.theme-dark .login-hero-subtitle {
font-size: 1.15rem;
color: #1e3a5f;
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.8);
font-weight: 500;
max-width: 620px;
}
.login-page-wrapper.theme-dark .login-hero-meta {
color: #0f172a;
text-shadow: none;
font-weight: 600;
}
/* Hero Blank / Neutral State (First time / No Wallpaper) */ /* Hero Blank / Neutral State (First time / No Wallpaper) */
.login-hero-section.no-wallpaper { .login-hero-section.no-wallpaper {
background: linear-gradient(135deg, #f8fafc 0%, #eef2f6 100%); background: linear-gradient(135deg, #f8fafc 0%, #eef2f6 100%);
@ -206,7 +334,7 @@ $footerVersion = hms_footer_version();
text-shadow: none; text-shadow: none;
} }
/* Right Side Form Panel */ /* Form Panel Section */
.login-form-section { .login-form-section {
width: 460px; width: 460px;
max-width: 100%; max-width: 100%;
@ -230,7 +358,7 @@ $footerVersion = hms_footer_version();
@media (max-width: 991.98px) { @media (max-width: 991.98px) {
.login-page-wrapper { .login-page-wrapper {
flex-direction: column; flex-direction: column !important;
} }
.login-hero-section { .login-hero-section {
min-height: auto; min-height: auto;
@ -241,11 +369,14 @@ $footerVersion = hms_footer_version();
display: none; display: none;
} }
.login-form-section { .login-form-section {
width: 100%; width: 100% !important;
min-height: auto; max-width: 100% !important;
border-left: none; min-height: auto !important;
box-shadow: none; border-left: none !important;
padding: 1.5rem 1rem; border-right: none !important;
box-shadow: none !important;
padding: 1.5rem 1rem !important;
border-radius: 0 !important;
} }
} }
</style> </style>
@ -253,16 +384,16 @@ $footerVersion = hms_footer_version();
<body> <body>
<main class="login-page-wrapper"> <main class="login-page-wrapper panel-<?= esc($loginPanelPosition) ?> theme-<?= esc($loginHeroTheme) ?><?= ($loginPanelPosition === 'center' && $hasWallpaper) ? ' overlay-' . esc($loginHeroOverlay) : '' ?>"<?= ($loginPanelPosition === 'center' && $hasWallpaper) ? ' style="background-image: url(\'' . esc($wallpaperUrl) . '\');"' : '' ?>>
<?php if ($hasWallpaper): ?> <?php if ($hasWallpaper): ?>
<!-- Left Branding / Wallpaper Hero Section --> <!-- Left Branding / Wallpaper Hero Section -->
<section class="login-hero-section has-wallpaper" style="background-image: url('<?= esc($wallpaperUrl) ?>');"> <section class="login-hero-section has-wallpaper overlay-<?= esc($loginHeroOverlay) ?>" style="background-image: url('<?= esc($wallpaperUrl) ?>');">
<div class="login-hero-inner"> <div class="login-hero-inner">
<!-- Top Header / Hospital Logo (Logo only, prominent with soft drop shadow) --> <!-- Top Header / Hospital Logo (Logo only, prominent with soft drop shadow) -->
<div> <div>
<?php if ($hasLogo): ?> <?php if ($hasLogo): ?>
<div class="login-hero-logo-wrap"> <div class="login-hero-logo-wrap style-<?= esc($loginLogoStyle) ?>">
<img src="<?= esc($logoUrl) ?>" alt="<?= esc($hospitalName) ?> Logo" class="login-hero-logo-img"> <img src="<?= esc($logoUrl) ?>" alt="<?= esc($hospitalName) ?> Logo" class="login-hero-logo-img">
</div> </div>
<?php endif; ?> <?php endif; ?>
@ -276,11 +407,13 @@ $footerVersion = hms_footer_version();
<h1 class="login-hero-title mb-3">Hospital Information Management System</h1> <h1 class="login-hero-title mb-3">Hospital Information Management System</h1>
<?php endif; ?> <?php endif; ?>
<?php if ($loginShowSubtitle): ?>
<p class="login-hero-subtitle mb-4"> <p class="login-hero-subtitle mb-4">
Secure Healthcare Delivery, Digital EMR &amp; Clinical Management Portal Secure Healthcare Delivery, Digital EMR &amp; Clinical Management Portal
</p> </p>
<?php endif; ?>
<?php if (!empty($hospitalAddress1) || !empty($hospitalPhone) || !empty($hospitalHfrId)): ?> <?php if ($loginShowContactMeta && (!empty($hospitalAddress1) || !empty($hospitalPhone) || !empty($hospitalHfrId))): ?>
<div class="d-flex flex-wrap gap-3 align-items-center text-white small login-hero-meta"> <div class="d-flex flex-wrap gap-3 align-items-center text-white small login-hero-meta">
<?php if (!empty($hospitalAddress1)): ?> <?php if (!empty($hospitalAddress1)): ?>
<span class="d-inline-flex align-items-center gap-1"> <span class="d-inline-flex align-items-center gap-1">
@ -314,10 +447,28 @@ $footerVersion = hms_footer_version();
</section> </section>
<?php endif; ?> <?php endif; ?>
<!-- Right Side Login Form Section --> <!-- Form Section -->
<section class="login-form-section"> <section class="login-form-section">
<div class="login-form-container"> <div class="login-form-container">
<?php if ($loginPanelPosition === 'center'): ?>
<!-- Centered Hospital Branding Showcase -->
<div class="text-center mb-3">
<?php if ($hasLogo): ?>
<div class="mb-2">
<div class="login-hero-logo-wrap style-<?= esc($loginLogoStyle) ?>">
<img src="<?= esc($logoUrl) ?>" alt="<?= esc($hospitalName) ?> Logo" class="login-hero-logo-img" style="max-height: 70px;">
</div>
</div>
<?php endif; ?>
<?php if (!empty($hospitalName)): ?>
<h4 class="fw-bold text-dark mb-1" style="font-size: 1.35rem;"><?= esc($hospitalName) ?></h4>
<?php endif; ?>
<?php if ($loginShowSubtitle): ?>
<p class="text-muted small mb-0" style="font-size: 12px;">Secure Healthcare Delivery &amp; Clinical Management Portal</p>
<?php endif; ?>
</div>
<?php else: ?>
<!-- System / E-Atria Branding Header --> <!-- System / E-Atria Branding Header -->
<div class="text-center mb-2"> <div class="text-center mb-2">
<a href="/" class="logo d-inline-flex align-items-center justify-content-center text-decoration-none"> <a href="/" class="logo d-inline-flex align-items-center justify-content-center text-decoration-none">
@ -325,6 +476,7 @@ $footerVersion = hms_footer_version();
<span class="fs-4 fw-bold text-primary ms-2">E-Atria</span> <span class="fs-4 fw-bold text-primary ms-2">E-Atria</span>
</a> </a>
</div> </div>
<?php endif; ?>
<!-- Login Card --> <!-- Login Card -->
<div class="card mb-2 border-0 shadow-sm" style="border-radius: 12px; background: #ffffff;"> <div class="card mb-2 border-0 shadow-sm" style="border-radius: 12px; background: #ffffff;">
@ -447,6 +599,7 @@ $footerVersion = hms_footer_version();
</div> </div>
</div> </div>
<?php if ($loginShowPwaCard): ?>
<!-- Mobile Application QR & PWA Installation Card --> <!-- Mobile Application QR & PWA Installation Card -->
<div class="card border-0 shadow-sm mt-1 mb-2 w-100 text-center" style="background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); border-radius: 12px; color: white;"> <div class="card border-0 shadow-sm mt-1 mb-2 w-100 text-center" style="background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); border-radius: 12px; color: white;">
<div class="card-body p-3"> <div class="card-body p-3">
@ -463,6 +616,7 @@ $footerVersion = hms_footer_version();
</button> </button>
</div> </div>
</div> </div>
<?php endif; ?>
<div class="text-center mt-1 mb-2"> <div class="text-center mt-1 mb-2">
<a href="<?= base_url('HMS_FEATURES_PRESENTATION.html') ?>" target="_blank" rel="noopener" <a href="<?= base_url('HMS_FEATURES_PRESENTATION.html') ?>" target="_blank" rel="noopener"

View File

@ -53,19 +53,58 @@ final class HospitalProfileWallpaperTest extends CIUnitTestCase
$this->assertStringContainsString('renderWallpaper', $html); $this->assertStringContainsString('renderWallpaper', $html);
} }
public function testLoginViewRendersWithRightSideLayoutAndKeyElements(): void public function testLoginSettingsViewRendersAllConfigurationControls(): void
{ {
$html = view('auth/login'); $viewData = [
'login_panel_position' => 'left',
'login_hero_theme' => 'light',
'login_logo_style' => '3d_box',
'login_hero_overlay' => 'dark',
'login_show_subtitle' => '1',
'login_show_contact_meta' => '1',
'login_show_pwa_card' => '1',
'hospital_wallpaper' => 'hospital_wallpaper_soft_teal_leaves.jpg',
'hospital_wallpaper_url' => 'http://localhost:8080/assets/images/hospital_wallpaper_soft_teal_leaves.jpg',
'presets' => [
[
'file' => 'hospital_wallpaper_20261027_203745_bf800456.jpg',
'title' => 'Client Deep Teal',
'subtitle' => 'Original curated rich hospital theme',
'tag' => 'Active Default',
'url' => 'http://localhost:8080/assets/images/hospital_wallpaper_20261027_203745_bf800456.jpg',
],
],
'hospital_logo' => 'logo.png',
'hospital_logo_url' => 'http://localhost:8080/assets/images/logo.png',
'hospital_name' => 'Johri Hospital',
];
// Check right side layout container $html = view('Setting/Admin/login_settings', $viewData);
$this->assertStringContainsString('login-page-wrapper', $html);
$this->assertStringContainsString('login-form-section', $html);
$this->assertStringContainsString('login-hero-section', $html);
// Verify key login elements exist in the right panel // Layout position controls
$this->assertStringContainsString('floatingUsernameInput', $html); $this->assertStringContainsString('name="login_panel_position"', $html);
$this->assertStringContainsString('floatingPasswordInput', $html); $this->assertStringContainsString('value="left"', $html);
$this->assertStringContainsString('captchaInput', $html); $this->assertStringContainsString('value="center"', $html);
$this->assertStringContainsString('loginMobileAppsModal', $html); $this->assertStringContainsString('value="right"', $html);
// Logo style controls
$this->assertStringContainsString('name="login_logo_style"', $html);
$this->assertStringContainsString('value="3d_box"', $html);
$this->assertStringContainsString('value="glass"', $html);
$this->assertStringContainsString('value="transparent"', $html);
// Typography theme & overlay
$this->assertStringContainsString('name="login_hero_theme"', $html);
$this->assertStringContainsString('name="login_hero_overlay"', $html);
// Feature switches
$this->assertStringContainsString('name="login_show_subtitle"', $html);
$this->assertStringContainsString('name="login_show_contact_meta"', $html);
$this->assertStringContainsString('name="login_show_pwa_card"', $html);
// Presets and save button
$this->assertStringContainsString('Client Deep Teal', $html);
$this->assertStringContainsString('id="btn_save_login_settings"', $html);
} }
} }