暂无描述

base.html 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <!DOCTYPE html>
  2. {% load tailwind_tags public_urls static i18n %}
  3. {% get_current_language as LANGUAGE_CODE %}
  4. <html lang="{{ LANGUAGE_CODE }}" x-data="{ open:false }">
  5. <head>
  6. <meta charset="utf-8" />
  7. <meta name="viewport" content="width=device-width, initial-scale=1" />
  8. <title>{{ current_site.name|default:'Web Admin' }} — {% block title %}{% endblock %}</title>
  9. {% tailwind_css %}
  10. <script defer src="{% static 'alpinejs/dist/cdn.min.js' %}"></script>
  11. </head>
  12. <body class="bg-gray-50 text-gray-900">
  13. <div class="min-h-screen">
  14. <nav class="bg-white border-b border-gray-200">
  15. <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
  16. <div class="flex h-16 items-center justify-between">
  17. <div class="flex items-center gap-8">
  18. <a href="/webadmin/" class="text-lg font-semibold">{{ current_site.name|default:'Web Admin' }}</a>
  19. <div class="hidden md:flex items-center gap-4">
  20. <a class="hover:text-black/70" href="{% url 'admin_frontend:dashboard' %}">Dashboard</a>
  21. <a class="hover:text-black/70" href="{% url 'admin_frontend:profiles_list' %}">Profiles</a>
  22. <a class="hover:text-black/70" href="{% url 'admin_frontend:opportunities_list' %}">Opportunities</a>
  23. <a class="hover:text-black/70" href="{% url 'admin_frontend:intro_requests_list' %}">Intro Requests</a>
  24. <a class="hover:text-black/70" href="{% url 'admin_frontend:leaderboard' %}">Leaderboard</a>
  25. </div>
  26. </div>
  27. <div class="flex items-center gap-3">
  28. {% if request.user.is_authenticated %}
  29. <span class="text-sm text-gray-600">{{ request.user.username }}</span>
  30. <form method="post" action="{% url 'admin_frontend:logout' %}">
  31. {% csrf_token %}
  32. <button class="text-sm text-red-600 hover:text-red-700">Logout</button>
  33. </form>
  34. <form method="post" action="{% url 'set_language' %}" class="ml-2">
  35. {% csrf_token %}
  36. <input type="hidden" name="next" value="{{ request.get_full_path }}" />
  37. {% get_available_languages as langs %}
  38. {% get_current_language as LANGUAGE_CODE %}
  39. {% get_language_info_list for langs as languages %}
  40. <label for="lang" class="sr-only">Language</label>
  41. <select id="lang" name="language" class="text-sm border border-gray-300 rounded px-2 py-1 bg-white"
  42. onchange="this.form.submit()">
  43. {% for lang in languages %}
  44. <option value="{{ lang.code }}" {% if lang.code == LANGUAGE_CODE %}selected{% endif %}>{{ lang.name_local }}</option>
  45. {% endfor %}
  46. </select>
  47. </form>
  48. {% else %}
  49. <a class="text-sm text-blue-600 hover:text-blue-700" href="{% url 'admin_frontend:login' %}">Login</a>
  50. {% endif %}
  51. </div>
  52. </div>
  53. </div>
  54. </nav>
  55. <main class="mx-auto max-w-7xl p-4 sm:p-6 lg:p-8">
  56. {% if messages %}
  57. <div id="toasts" class="fixed bottom-4 right-4 z-50 space-y-2">
  58. {% for message in messages %}
  59. <div class="toast px-4 py-3 rounded border shadow {{ message.tags|default:'' }}">
  60. {% trans message %}
  61. </div>
  62. {% endfor %}
  63. </div>
  64. {% endif %}
  65. <div class="grid grid-cols-1 md:grid-cols-12 gap-6">
  66. <aside class="md:col-span-3">
  67. <nav class="bg-white rounded shadow p-4">
  68. <div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Navigation</div>
  69. {% app_menu 'admin_frontend' as menu_items %}
  70. <ul class="space-y-1">
  71. <div x-data="{ open: {% if '/webadmin/settings/' in request.path %}true{% else %}false{% endif %} }">
  72. <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
  73. <span class="truncate">{% trans "Settings" %}</span>
  74. <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  75. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
  76. </svg>
  77. </div>
  78. <div x-show="open" x-cloak class="pl-4">
  79. {% app_menu 'settings' as settings_menu_items %}
  80. <ul class="space-y-1">
  81. {% for it in settings_menu_items %}
  82. <li>
  83. <a href="{{ it.url }}" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 {% if request.path == it.url %}bg-blue-50 text-blue-700{% else %}text-gray-700{% endif %}">
  84. <span class="truncate">{{ it.label }}</span>
  85. </a>
  86. </li>
  87. {% endfor %}
  88. </ul>
  89. </div>
  90. </div>
  91. <div x-data="{ open: {% if '/webadmin/cms/' in request.path %}true{% else %}false{% endif %} }">
  92. <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
  93. <span class="truncate">{% trans "CMS" %}</span>
  94. <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  95. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
  96. </svg>
  97. </div>
  98. <div x-show="open" x-cloak class="pl-4">
  99. {% app_menu 'cms' as cms_menu_items %}
  100. <ul class="space-y-1">
  101. {% for it in cms_menu_items %}
  102. <li>
  103. <a href="{{ it.url }}" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 {% if request.path == it.url %}bg-blue-50 text-blue-700{% else %}text-gray-700{% endif %}">
  104. <span class="truncate">{{ it.label }}</span>
  105. </a>
  106. </li>
  107. {% endfor %}
  108. </ul>
  109. </div>
  110. </div>
  111. <div x-data="{ open: {% if '/webadmin/frontend/' in request.path %}true{% else %}false{% endif %} }">
  112. <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
  113. <span class="truncate">{% trans "Frontend" %}</span>
  114. <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  115. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
  116. </svg>
  117. </div>
  118. <div x-show="open" x-cloak class="pl-4">
  119. {% app_menu 'public_frontend_admin' as pf_menu_items %}
  120. <ul class="space-y-1">
  121. {% for it in pf_menu_items %}
  122. <li>
  123. <a href="{{ it.url }}" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 {% if request.path == it.url %}bg-blue-50 text-blue-700{% else %}text-gray-700{% endif %}">
  124. <span class="truncate">{{ it.label }}</span>
  125. </a>
  126. </li>
  127. {% endfor %}
  128. </ul>
  129. </div>
  130. </div>
  131. <div x-data="{ open: {% if '/webadmin/orgs/' in request.path %}true{% else %}false{% endif %} }">
  132. <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
  133. <span class="truncate">{% trans "Organization" %}</span>
  134. <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  135. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
  136. </svg>
  137. </div>
  138. <div x-show="open" x-cloak class="pl-4">
  139. {% app_menu 'orgs_admin' as org_menu_items %}
  140. <ul class="space-y-1">
  141. {% for it in org_menu_items %}
  142. <li>
  143. <a href="{{ it.url }}" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 {% if request.path == it.url %}bg-blue-50 text-blue-700{% else %}text-gray-700{% endif %}">
  144. <span class="truncate">{{ it.label }}</span>
  145. </a>
  146. </li>
  147. {% endfor %}
  148. </ul>
  149. </div>
  150. </div>
  151. <div x-data="{ open: {% if '/webadmin/billings/' in request.path %}true{% else %}false{% endif %} }">
  152. <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
  153. <span class="truncate">{% trans "Billing" %}</span>
  154. <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  155. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
  156. </svg>
  157. </div>
  158. <div x-show="open" x-cloak class="pl-4">
  159. {% app_menu 'billing' as billing_menu_items %}
  160. <ul class="space-y-1">
  161. {% for it in billing_menu_items %}
  162. <li>
  163. <a href="{{ it.url }}" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 {% if request.path == it.url %}bg-blue-50 text-blue-700{% else %}text-gray-700{% endif %}">
  164. <span class="truncate">{{ it.label }}</span>
  165. </a>
  166. </li>
  167. {% endfor %}
  168. </ul>
  169. </div>
  170. </div>
  171. <div x-data="{ open: {% if '/webadmin/recycle/' in request.path %}true{% else %}false{% endif %} }">
  172. <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
  173. <span class="truncate">{% trans "Recycle Ops" %}</span>
  174. <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  175. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
  176. </svg>
  177. </div>
  178. <div x-show="open" x-cloak class="pl-4">
  179. {% app_menu 'recycle_core' as recycle_menu_items %}
  180. <ul class="space-y-1">
  181. {% for it in recycle_menu_items %}
  182. <li>
  183. <a href="{{ it.url }}" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 {% if request.path == it.url %}bg-blue-50 text-blue-700{% else %}text-gray-700{% endif %}">
  184. <span class="truncate">{{ it.label }}</span>
  185. </a>
  186. </li>
  187. {% endfor %}
  188. </ul>
  189. </div>
  190. </div>
  191. </ul>
  192. </nav>
  193. </aside>
  194. <section class="md:col-span-9">
  195. {% block content %}{% endblock %}
  196. </section>
  197. </div>
  198. </main>
  199. <footer class="bg-white border-t border-gray-200">
  200. <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
  201. <div class="flex h-16 items-center justify-between">
  202. <p class="text-sm text-gray-600">&copy; {% now "Y" %} Lux Backend. All rights reserved.</p>
  203. </div>
  204. </div>
  205. </footer>
  206. </div>
  207. {% block scripts %}{% endblock %}
  208. <!-- Flowbite JS (components and interactivity) -->
  209. <script src="{% static 'flowbite/dist/flowbite.min.js' %}"></script>
  210. <script defer src="{% static "sortablejs/Sortable.min.js" %}"></script>
  211. <script>
  212. // Auto-fade toasts after 5 seconds
  213. window.addEventListener('DOMContentLoaded', () => {
  214. document.querySelectorAll('#toasts .toast').forEach((el) => {
  215. setTimeout(() => {
  216. el.style.opacity = '0';
  217. el.style.transition = 'opacity 0.5s ease';
  218. setTimeout(() => el.remove(), 600);
  219. }, 5000);
  220. });
  221. });
  222. </script>
  223. <style>
  224. /* Ensure system UI controls (date/time pickers) render with dark icons on light background */
  225. input[type="date"], input[type="time"], input[type="datetime-local"] { color-scheme: light; }
  226. /* Hide cloaked elements until Alpine initializes */
  227. [x-cloak] { display: none !important; }
  228. .success { background-color: #ecfdf5; border-color:#34d399; color:#065f46; }
  229. .error { background-color: #fef2f2; border-color:#fca5a5; color:#7f1d1d; }
  230. .info { background-color: #eff6ff; border-color:#93c5fd; color:#1e3a8a; }
  231. .toast { opacity: 1; }
  232. #div_id_is_verified .mb-3 { margin-bottom: 0px !important; }
  233. /* Fallback color for outline buttons when --btn-color is unset */
  234. </style>
  235. </body>
  236. </html>