630 lines
31 KiB
HTML
630 lines
31 KiB
HTML
{% load static i18n %}
|
|
{% get_current_language as LANGUAGE_CODE %}
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_CODE == 'ar' %}rtl{% else %}ltr{% endif %}">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<meta name="description" content="{% trans 'King Abdullah Academic University Hospital - Portal' %}">
|
|
<title>{% block title %}{% trans 'KAAUH Portal' %}{% endblock %}</title>
|
|
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
|
|
<script>
|
|
tailwind.config = {
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
'temple-red': '#9d2235',
|
|
'temple-dark': '#1a1a1a',
|
|
'temple-cream': '#f8f7f2',
|
|
'dashboard-blue': '#4e73df',
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
|
|
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
/* Smooth scrolling */
|
|
html {
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
/* Touch-friendly tap highlight */
|
|
* {
|
|
-webkit-tap-highlight-color: rgba(157, 34, 53, 0.1);
|
|
}
|
|
|
|
/* Smooth transitions */
|
|
.smooth-transition {
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
/* CRITICAL: Improved touch targets - minimum 48x48px for better mobile UX */
|
|
button,
|
|
a:not(.no-touch-target),
|
|
.touch-target {
|
|
min-height: 48px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* Prevent text selection on buttons */
|
|
button {
|
|
-webkit-user-select: none;
|
|
user-select: none;
|
|
}
|
|
|
|
/* Custom scrollbar - desktop only */
|
|
@media (min-width: 1024px) {
|
|
.sidebar-scroll::-webkit-scrollbar { width: 4px; }
|
|
.sidebar-scroll::-webkit-scrollbar-thumb {
|
|
background: #333;
|
|
border-radius: 10px;
|
|
}
|
|
}
|
|
|
|
/* Backdrop blur support */
|
|
@supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {
|
|
.backdrop-blur-custom {
|
|
-webkit-backdrop-filter: blur(10px);
|
|
backdrop-filter: blur(10px);
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
}
|
|
}
|
|
|
|
/* Safe area padding for notched devices */
|
|
@supports (padding: env(safe-area-inset-bottom)) {
|
|
.safe-area-padding-bottom {
|
|
padding-bottom: calc(1rem + env(safe-area-inset-bottom));
|
|
}
|
|
.safe-area-padding-top {
|
|
padding-top: calc(0.75rem + env(safe-area-inset-top));
|
|
}
|
|
}
|
|
|
|
/* Sticky header optimization */
|
|
.sticky-header {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 40;
|
|
-webkit-backface-visibility: hidden;
|
|
backface-visibility: hidden;
|
|
}
|
|
|
|
/* Alert animation */
|
|
.alert {
|
|
animation: slideInDown 0.3s ease-out;
|
|
}
|
|
|
|
@keyframes slideInDown {
|
|
from {
|
|
transform: translateY(-100%);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
/* Mobile-optimized spacing */
|
|
.mobile-spaced {
|
|
padding-left: 0.75rem;
|
|
padding-right: 0.75rem;
|
|
}
|
|
|
|
@media (min-width: 640px) {
|
|
.mobile-spaced {
|
|
padding-left: 1rem;
|
|
padding-right: 1rem;
|
|
}
|
|
}
|
|
|
|
/* Optimized badge positioning */
|
|
.nav-badge {
|
|
font-size: 0.625rem;
|
|
line-height: 1;
|
|
padding: 0.25rem 0.375rem;
|
|
min-width: 1.25rem;
|
|
text-align: center;
|
|
}
|
|
|
|
/* Better visibility for active states */
|
|
.nav-link.active {
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Mobile-friendly footer */
|
|
@media (max-width: 640px) {
|
|
footer {
|
|
font-size: 0.75rem;
|
|
padding: 1rem;
|
|
}
|
|
}
|
|
|
|
/* Better button sizing on mobile */
|
|
@media (max-width: 640px) {
|
|
.mobile-btn {
|
|
padding: 0.875rem 1rem;
|
|
font-size: 0.9375rem;
|
|
}
|
|
}
|
|
|
|
/* Header actions responsive sizing */
|
|
.header-action {
|
|
min-width: 44px;
|
|
min-height: 44px;
|
|
padding: 0.625rem;
|
|
}
|
|
|
|
@media (min-width: 640px) {
|
|
.header-action {
|
|
min-width: 48px;
|
|
min-height: 48px;
|
|
}
|
|
}
|
|
|
|
/* User avatar responsive sizing */
|
|
.user-avatar {
|
|
width: 2.25rem;
|
|
height: 2.25rem;
|
|
}
|
|
|
|
@media (min-width: 640px) {
|
|
.user-avatar {
|
|
width: 2.5rem;
|
|
height: 2.5rem;
|
|
}
|
|
}
|
|
|
|
/* Sidebar header responsive */
|
|
.sidebar-header {
|
|
padding: 1rem;
|
|
}
|
|
|
|
@media (min-width: 640px) {
|
|
.sidebar-header {
|
|
padding: 1.25rem;
|
|
}
|
|
}
|
|
|
|
/* Language button responsive */
|
|
.lang-btn {
|
|
padding: 0.625rem 0.75rem;
|
|
}
|
|
|
|
@media (min-width: 640px) {
|
|
.lang-btn {
|
|
padding: 0.625rem 1rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'image/favicon/apple-touch-icon.png'%}">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'image/favicon/favicon-32x32.png'%}">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'image/favicon/favicon-16x16.png'%}">
|
|
<link rel="manifest" href="{% static 'image/favicon/site.webmanifest'%}">
|
|
|
|
{% block customCSS %}{% endblock %}
|
|
</head>
|
|
<body class="bg-temple-cream text-gray-800 flex min-h-screen overflow-x-hidden">
|
|
|
|
<!-- Mobile Sidebar Backdrop -->
|
|
<div id="sidebar-backdrop"
|
|
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 hidden lg:hidden backdrop-blur-custom smooth-transition"
|
|
onclick="closeMobileSidebar()"
|
|
role="button"
|
|
aria-label="{% trans 'Close sidebar' %}">
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<aside id="sidebar"
|
|
class="fixed inset-y-0 left-0 z-50 bg-temple-dark text-gray-400 transform -translate-x-full lg:translate-x-0 lg:w-64 smooth-transition border-r border-gray-800"
|
|
role="navigation"
|
|
aria-label="{% trans 'Main navigation' %}">
|
|
<div class="flex flex-col h-full">
|
|
<!-- Sidebar Header -->
|
|
<div class="sidebar-header flex items-center gap-3 text-white safe-area-padding-top border-b border-gray-800">
|
|
<div class="bg-temple-red p-2.5 rounded-lg shrink-0">
|
|
<i data-lucide="shield-check" class="w-5 h-5 sm:w-6 sm:h-6 text-white"></i>
|
|
</div>
|
|
<div class="min-w-0 flex-1">
|
|
<span class="text-lg sm:text-xl font-bold tracking-tight block">KAAUH</span>
|
|
<div class="text-[10px] sm:text-xs text-gray-400 mt-0.5 truncate leading-tight">
|
|
{% if request.user.user_type == 'agency' %}{% trans "Agency Portal" %}{% else %}{% trans "Applicant Portal" %}{% endif %}
|
|
</div>
|
|
</div>
|
|
<button onclick="closeMobileSidebar()"
|
|
class="lg:hidden ml-2 text-gray-400 hover:text-white transition touch-target"
|
|
aria-label="{% trans 'Close menu' %}">
|
|
<i data-lucide="x" class="w-5 h-5"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Navigation Menu -->
|
|
<nav class="flex-1 overflow-y-auto sidebar-scroll momentum-scroll px-4 pb-4">
|
|
<div class="section-header text-[10px] uppercase tracking-widest text-gray-600 font-bold mb-3 px-2">
|
|
{% trans "Main" %}
|
|
</div>
|
|
<ul class="space-y-1.5">
|
|
{% if request.user.user_type == 'agency' %}
|
|
<li>
|
|
<a href="{% url 'agency_portal_dashboard' %}"
|
|
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'agency_portal_dashboard' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name == 'agency_portal_dashboard' %}page{% endif %}">
|
|
<i data-lucide="layout-grid" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'agency_portal_dashboard' %}text-temple-red{% endif %}"></i>
|
|
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Dashboard" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'agency_portal_persons_list' %}"
|
|
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'agency_portal_persons_list' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name == 'agency_portal_persons_list' %}page{% endif %}">
|
|
<i data-lucide="users" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'agency_portal_persons_list' %}text-temple-red{% endif %}"></i>
|
|
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Applicants" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'kaauh_career' %}"
|
|
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'kaauh_career' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name == 'kaauh_career' %}page{% endif %}">
|
|
<i data-lucide="briefcase" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'kaauh_career' %}text-temple-red{% endif %}"></i>
|
|
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Careers" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'message_list' %}"
|
|
class="nav-link flex items-center rounded-lg transition touch-target relative {% if request.resolver_match.url_name == 'message_list' or request.resolver_match.url_name|slice:':8' == 'message_' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if 'message_' in request.resolver_match.url_name %}page{% endif %}">
|
|
<i data-lucide="mail" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if 'message_' in request.resolver_match.url_name %}text-temple-red{% endif %}"></i>
|
|
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Messages" %}</span>
|
|
{% if request.user.get_unread_message_count > 0 %}
|
|
<span class="ml-auto bg-temple-red text-[10px] sm:text-xs text-white rounded-full font-medium nav-badge px-2 py-0.5">{{ request.user.get_unread_message_count }}</span>
|
|
{% endif %}
|
|
</a>
|
|
</li>
|
|
{% else %}
|
|
<li>
|
|
<a href="{% url 'applicant_portal_dashboard' %}"
|
|
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'applicant_portal_dashboard' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name == 'applicant_portal_dashboard' %}page{% endif %}">
|
|
<i data-lucide="layout-grid" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'applicant_portal_dashboard' %}text-temple-red{% endif %}"></i>
|
|
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Dashboard" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'kaauh_career' %}"
|
|
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'kaauh_career' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name == 'kaauh_career' %}page{% endif %}">
|
|
<i data-lucide="briefcase" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'kaauh_career' %}text-temple-red{% endif %}"></i>
|
|
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Careers" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'message_list' %}"
|
|
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'message_list' or request.resolver_match.url_name|slice:':8' == 'message_' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if 'message_' in request.resolver_match.url_name %}page{% endif %}">
|
|
<i data-lucide="mail" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if 'message_' in request.resolver_match.url_name %}text-temple-red{% endif %}"></i>
|
|
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Messages" %}</span>
|
|
{% if request.user.get_unread_message_count > 0 %}
|
|
<span class="ml-auto bg-temple-red text-[10px] sm:text-xs text-white rounded-full font-medium nav-badge px-2 py-0.5">{{ request.user.get_unread_message_count }}</span>
|
|
{% endif %}
|
|
</a>
|
|
</li>
|
|
|
|
{% endif %}
|
|
</ul>
|
|
|
|
<!-- Account Section -->
|
|
<div class="section-header text-[10px] uppercase tracking-widest text-gray-600 font-bold mt-6 mb-3 px-2">
|
|
{% trans "Account" %}
|
|
</div>
|
|
<ul class="space-y-1">
|
|
<li>
|
|
<a href="{% url 'user_detail' request.user.pk %}"
|
|
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'user_detail' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name == 'user_detail' %}page{% endif %}">
|
|
<i data-lucide="user-circle" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'user_detail' %}text-temple-red{% endif %}"></i>
|
|
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "My Profile" %}</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
|
|
<!-- Logout Button -->
|
|
{% if request.user.is_authenticated %}
|
|
<div class="p-4 border-t border-gray-800 mt-auto">
|
|
<form method="post" action="{% url 'account_logout'%}">
|
|
{% csrf_token %}
|
|
<button type="submit"
|
|
class="mobile-btn w-full flex items-center justify-center gap-2 bg-gray-800 hover:bg-gray-700 text-white rounded-lg smooth-transition touch-target"
|
|
title="{% trans 'Sign Out' %}">
|
|
<i data-lucide="log-out" class="w-4 h-4 shrink-0"></i>
|
|
<span class="font-medium">{% trans "Sign Out" %}</span>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Powered By Footer -->
|
|
<div class="p-4 border-t border-gray-800 text-center safe-area-padding-bottom">
|
|
<a href="https://tenhal.sa/" class="text-decoration-none block no-touch-target" target="_blank" rel="noopener noreferrer">
|
|
<div class="text-[10px] text-gray-600 uppercase tracking-widest leading-relaxed">
|
|
{% trans "POWERED BY" %} <span class="text-white font-bold">TENHAL</span>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main Content -->
|
|
<main class="flex-1 lg:ml-64 flex flex-col min-h-screen smooth-transition">
|
|
|
|
<!-- Header -->
|
|
<header class="sticky-header bg-white border-b px-3 sm:px-4 lg:px-6 py-2.5 sm:py-3 flex justify-between items-center shadow-sm safe-area-padding-top">
|
|
<div class="flex items-center flex-1 min-w-0">
|
|
<!-- Mobile Menu Toggle -->
|
|
<button id="menu-toggle"
|
|
class="lg:hidden mr-2 sm:mr-3 p-2.5 text-gray-600 hover:bg-gray-100 active:bg-gray-200 rounded-lg smooth-transition touch-target shrink-0"
|
|
aria-label="{% trans 'Open menu' %}"
|
|
aria-expanded="false"
|
|
aria-controls="sidebar">
|
|
<i data-lucide="menu" class="w-5 h-5"></i>
|
|
</button>
|
|
|
|
<!-- Logo/Title -->
|
|
<div class="hospital-text min-w-0 flex flex-wrap items-baseline gap-x-2 mobile-spaced">
|
|
<span class="text-temple-red font-bold text-sm sm:text-base whitespace-nowrap">
|
|
{% if request.user.user_type == 'agency' %}{% trans "Agency Portal" %}{% else %}{% trans "Applicant Portal" %}{% endif %}
|
|
</span>
|
|
<span class="text-gray-400 text-xs sm:text-sm hidden sm:inline">|</span>
|
|
<span class="text-gray-600 text-xs sm:text-sm whitespace-nowrap">KAAUH</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Header Actions -->
|
|
<div class="flex items-center gap-1 sm:gap-2 ml-1 sm:ml-2 shrink-0">
|
|
<!-- Messages Icon -->
|
|
<a href="{% url 'message_list' %}"
|
|
class="header-action relative text-gray-500 hover:text-temple-red smooth-transition rounded-lg hover:bg-gray-50"
|
|
aria-label="{% trans 'Messages' %}{% if request.user.get_unread_message_count > 0 %} ({{ request.user.get_unread_message_count }} {% trans 'unread' %}){% endif %}">
|
|
<i data-lucide="mail" class="w-5 h-5"></i>
|
|
{% if request.user.get_unread_message_count > 0 %}
|
|
<span class="absolute top-1 right-1 bg-temple-red border-2 border-white w-2.5 h-2.5 rounded-full" aria-hidden="true"></span>
|
|
{% endif %}
|
|
</a>
|
|
|
|
<!-- Language Switcher -->
|
|
{% if LANGUAGE_CODE == 'en' %}
|
|
<form action="{% url 'set_language' %}" method="post" class="relative">
|
|
{% csrf_token %}
|
|
<input name="language" type="hidden" value="ar">
|
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
|
<button class="lang-btn flex items-center gap-1.5 sm:gap-2 bg-gray-100 hover:bg-gray-200 active:bg-gray-300 rounded-lg smooth-transition touch-target"
|
|
aria-label="{% trans 'Switch to Arabic' %}">
|
|
<span class="text-base" aria-hidden="true">🇸🇦</span>
|
|
<span class="hidden sm:inline text-sm">{% trans "العربية" %}</span>
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<form action="{% url 'set_language' %}" method="post" class="relative">
|
|
{% csrf_token %}
|
|
<input name="language" type="hidden" value="en">
|
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
|
<button class="lang-btn flex items-center gap-1.5 sm:gap-2 bg-gray-100 hover:bg-gray-200 active:bg-gray-300 rounded-lg smooth-transition touch-target"
|
|
aria-label="{% trans 'Switch to English' %}">
|
|
<span class="text-base" aria-hidden="true">🇺🇸</span>
|
|
<span class="hidden sm:inline text-sm">English</span>
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
|
|
<!-- User Menu Dropdown -->
|
|
<div class="dropdown relative">
|
|
<button class="header-action flex items-center gap-2 rounded-lg hover:bg-gray-50"
|
|
id="user-menu-button"
|
|
aria-haspopup="true"
|
|
aria-expanded="false"
|
|
aria-label="{% trans 'User menu' %}">
|
|
{% if user.profile_image %}
|
|
<img src="{{ user.profile_image.url }}"
|
|
alt="{{ user.get_full_name|default:user.username }}"
|
|
class="user-avatar rounded-full border-2 border-gray-200 shadow-sm object-cover">
|
|
{% else %}
|
|
<div class="user-avatar rounded-full bg-temple-red flex items-center justify-center text-white font-bold text-[12px] sm:text-sm border-2 border-gray-200">
|
|
{{ user.username|first|upper }}
|
|
</div>
|
|
{% endif %}
|
|
</button>
|
|
|
|
<div class="hidden absolute right-0 mt-2 w-56 sm:w-64 bg-white rounded-xl shadow-lg border border-gray-100 py-2 z-50 dropdown-menu"
|
|
id="user-menu-dropdown"
|
|
role="menu"
|
|
aria-labelledby="user-menu-button">
|
|
<div class="px-4 py-3 border-b border-gray-100">
|
|
<p class="text-sm font-bold text-gray-800 truncate">{{ user.get_full_name|default:user.username }}</p>
|
|
<p class="text-xs text-gray-500 truncate mt-1">{{ user.email }}</p>
|
|
</div>
|
|
<a href="{% url 'user_detail' request.user.pk %}"
|
|
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 transition touch-target"
|
|
role="menuitem">
|
|
<div class="flex items-center gap-2">
|
|
<i data-lucide="user-circle" class="w-4 h-4 inline-block mr-2 align-text-bottom"></i>
|
|
{% trans "Profile" %}
|
|
</div>
|
|
</a>
|
|
<hr class="my-2 border-gray-100">
|
|
<form method="post" action="{% url 'account_logout'%}">
|
|
{% csrf_token %}
|
|
<button type="submit"
|
|
class="no-touch-target w-full text-left px-4 py-2.5 text-sm text-red-600 hover:bg-red-50 active:bg-red-100 transition"
|
|
role="menuitem">
|
|
<i data-lucide="log-out" class="w-4 h-4 inline-block mr-2 align-text-bottom"></i>
|
|
{% trans "Sign Out" %}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Messages/Alerts -->
|
|
<div class="p-4 sm:p-6 lg:p-8 space-y-4 flex-1 overflow-x-hidden">
|
|
{% if messages %}
|
|
<div class="space-y-2.5" role="alert" aria-live="polite">
|
|
{% for message in messages %}
|
|
<div class="alert p-4 rounded-lg shadow-sm border flex items-start gap-3 smooth-transition {% if message.tags == 'error' %}bg-red-50 border-red-200 text-red-800{% elif message.tags == 'success' %}bg-green-50 border-green-200 text-green-800{% elif message.tags == 'warning' %}bg-yellow-50 border-yellow-200 text-yellow-800{% else %}bg-blue-50 border-blue-200 text-blue-800{% endif %}">
|
|
<span class="flex-1 text-sm leading-relaxed">{{ message }}</span>
|
|
<button type="button"
|
|
class="text-gray-400 hover:text-gray-600 p-1.5 rounded hover:bg-gray-100 transition touch-target shrink-0"
|
|
onclick="this.parentElement.parentElement.remove()"
|
|
aria-label="{% trans 'Dismiss' %}">
|
|
<i data-lucide="x" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Page Content -->
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<footer class="mt-auto py-4 border-t bg-white safe-area-padding-bottom">
|
|
<div class="px-4 text-center">
|
|
<div class="text-xs sm:text-sm text-gray-400">
|
|
© {% now "Y" %} KAAUH. {% trans "All rights reserved." %}
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</main>
|
|
|
|
<!-- Delete Modal -->
|
|
{% include 'includes/delete_modal.html' %}
|
|
|
|
<script>
|
|
// Initialize Lucide icons
|
|
lucide.createIcons();
|
|
|
|
// Mobile Sidebar Toggle
|
|
const menuToggle = document.getElementById('menu-toggle');
|
|
const sidebar = document.getElementById('sidebar');
|
|
const sidebarBackdrop = document.getElementById('sidebar-backdrop');
|
|
|
|
function openMobileSidebar() {
|
|
sidebar.classList.remove('-translate-x-full');
|
|
sidebar.classList.add('translate-x-0');
|
|
sidebarBackdrop.classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
menuToggle?.setAttribute('aria-expanded', 'true');
|
|
}
|
|
|
|
function closeMobileSidebar() {
|
|
sidebar.classList.add('-translate-x-full');
|
|
sidebar.classList.remove('translate-x-0');
|
|
sidebarBackdrop.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
menuToggle?.setAttribute('aria-expanded', 'false');
|
|
}
|
|
|
|
// Make functions available globally
|
|
window.openMobileSidebar = openMobileSidebar;
|
|
window.closeMobileSidebar = closeMobileSidebar;
|
|
|
|
if (menuToggle) {
|
|
menuToggle.addEventListener('click', () => {
|
|
if (sidebar.classList.contains('-translate-x-full')) {
|
|
openMobileSidebar();
|
|
} else {
|
|
closeMobileSidebar();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Close sidebar on escape key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && !sidebar.classList.contains('-translate-x-full')) {
|
|
closeMobileSidebar();
|
|
}
|
|
});
|
|
|
|
// User Dropdown Toggle
|
|
const userMenuButton = document.getElementById('user-menu-button');
|
|
const userMenuDropdown = document.getElementById('user-menu-dropdown');
|
|
|
|
if (userMenuButton && userMenuDropdown) {
|
|
userMenuButton.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const isHidden = userMenuDropdown.classList.contains('hidden');
|
|
userMenuDropdown.classList.toggle('hidden');
|
|
userMenuButton.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
|
|
|
|
// Reinitialize icons when dropdown opens
|
|
if (!isHidden) {
|
|
setTimeout(() => lucide.createIcons(), 10);
|
|
}
|
|
});
|
|
|
|
// Close dropdown when clicking outside
|
|
document.addEventListener('click', (e) => {
|
|
if (!userMenuButton.contains(e.target) && !userMenuDropdown.contains(e.target)) {
|
|
userMenuDropdown.classList.add('hidden');
|
|
userMenuButton.setAttribute('aria-expanded', 'false');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Close dropdown on escape key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && !userMenuDropdown.classList.contains('hidden')) {
|
|
userMenuDropdown.classList.add('hidden');
|
|
userMenuButton.setAttribute('aria-expanded', 'false');
|
|
userMenuButton.focus();
|
|
}
|
|
});
|
|
|
|
// Performance: Debounce scroll events
|
|
let ticking = false;
|
|
function onScroll() {
|
|
if (!ticking) {
|
|
window.requestAnimationFrame(() => {
|
|
ticking = false;
|
|
});
|
|
ticking = true;
|
|
}
|
|
}
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
|
|
// Handle window resize for mobile
|
|
let resizeTimer;
|
|
window.addEventListener('resize', () => {
|
|
clearTimeout(resizeTimer);
|
|
resizeTimer = setTimeout(() => {
|
|
if (window.innerWidth >= 1024 && !sidebar.classList.contains('-translate-x-full')) {
|
|
closeMobileSidebar();
|
|
}
|
|
}, 250);
|
|
});
|
|
</script>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js"></script>
|
|
{% block customJS %}{% endblock %}
|
|
</body>
|
|
</html> |