@sonicjs-cms/core 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/{chunk-O46XKBFM.js → chunk-3LZ6TLPC.js} +14 -24
  2. package/dist/chunk-3LZ6TLPC.js.map +1 -0
  3. package/dist/{chunk-SRCY43RN.cjs → chunk-3NVJ6W27.cjs} +2 -2
  4. package/dist/chunk-3NVJ6W27.cjs.map +1 -0
  5. package/dist/{chunk-ALOS2CBJ.cjs → chunk-3SPQ3J4N.cjs} +14 -24
  6. package/dist/chunk-3SPQ3J4N.cjs.map +1 -0
  7. package/dist/chunk-4BJGEGX5.cjs +236 -0
  8. package/dist/chunk-4BJGEGX5.cjs.map +1 -0
  9. package/dist/{chunk-EGFHFM4N.cjs → chunk-5APKEYFK.cjs} +5 -5
  10. package/dist/{chunk-EGFHFM4N.cjs.map → chunk-5APKEYFK.cjs.map} +1 -1
  11. package/dist/{chunk-5FDDDD4J.cjs → chunk-5B3VMVEX.cjs} +559 -390
  12. package/dist/chunk-5B3VMVEX.cjs.map +1 -0
  13. package/dist/chunk-CDBVZEWR.js.map +1 -1
  14. package/dist/{chunk-BITQ4MFX.js → chunk-EAELJXRV.js} +93 -115
  15. package/dist/chunk-EAELJXRV.js.map +1 -0
  16. package/dist/chunk-FICTAGD4.js +59 -0
  17. package/dist/chunk-FICTAGD4.js.map +1 -0
  18. package/dist/{chunk-FVMV5DKA.cjs → chunk-HJZOA2O5.cjs} +93 -115
  19. package/dist/chunk-HJZOA2O5.cjs.map +1 -0
  20. package/dist/chunk-LEG4KNFP.cjs.map +1 -1
  21. package/dist/{chunk-5XTB4FE5.js → chunk-LH4Z7QID.js} +2 -2
  22. package/dist/chunk-LH4Z7QID.js.map +1 -0
  23. package/dist/chunk-M6FPVS7E.js +214 -0
  24. package/dist/chunk-M6FPVS7E.js.map +1 -0
  25. package/dist/{chunk-QSF34IYQ.js → chunk-PPUKPNTP.js} +401 -232
  26. package/dist/chunk-PPUKPNTP.js.map +1 -0
  27. package/dist/chunk-RCQ2HIQD.cjs +61 -0
  28. package/dist/chunk-RCQ2HIQD.cjs.map +1 -0
  29. package/dist/{chunk-P2PTTBO5.js → chunk-RYQCT2IV.js} +3 -3
  30. package/dist/{chunk-P2PTTBO5.js.map → chunk-RYQCT2IV.js.map} +1 -1
  31. package/dist/{chunk-NK6FN5R5.cjs → chunk-UL32L2KV.cjs} +3 -60
  32. package/dist/chunk-UL32L2KV.cjs.map +1 -0
  33. package/dist/{chunk-OL2OE3VJ.js → chunk-XJETEIRU.js} +4 -60
  34. package/dist/chunk-XJETEIRU.js.map +1 -0
  35. package/dist/index.cjs +145 -130
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.js +19 -15
  38. package/dist/index.js.map +1 -1
  39. package/dist/middleware.cjs +26 -21
  40. package/dist/middleware.js +2 -1
  41. package/dist/plugins.cjs +7 -7
  42. package/dist/plugins.js +1 -1
  43. package/dist/routes.cjs +26 -25
  44. package/dist/routes.js +6 -5
  45. package/dist/services.cjs +7 -7
  46. package/dist/services.js +1 -1
  47. package/dist/templates.cjs +18 -18
  48. package/dist/templates.js +2 -2
  49. package/dist/utils.cjs +16 -15
  50. package/dist/utils.js +2 -1
  51. package/package.json +1 -1
  52. package/dist/chunk-5FDDDD4J.cjs.map +0 -1
  53. package/dist/chunk-5XTB4FE5.js.map +0 -1
  54. package/dist/chunk-ALOS2CBJ.cjs.map +0 -1
  55. package/dist/chunk-BITQ4MFX.js.map +0 -1
  56. package/dist/chunk-FVMV5DKA.cjs.map +0 -1
  57. package/dist/chunk-KM4AJFXI.cjs +0 -101
  58. package/dist/chunk-KM4AJFXI.cjs.map +0 -1
  59. package/dist/chunk-NK6FN5R5.cjs.map +0 -1
  60. package/dist/chunk-O46XKBFM.js.map +0 -1
  61. package/dist/chunk-OL2OE3VJ.js.map +0 -1
  62. package/dist/chunk-QSF34IYQ.js.map +0 -1
  63. package/dist/chunk-SRCY43RN.cjs.map +0 -1
  64. package/dist/chunk-TY3NHEBN.js +0 -80
  65. package/dist/chunk-TY3NHEBN.js.map +0 -1
@@ -1,8 +1,9 @@
1
- import { getCacheService, CACHE_CONFIGS, getLogger } from './chunk-5XTB4FE5.js';
2
- import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity, requirePermission } from './chunk-TY3NHEBN.js';
1
+ import { getCacheService, CACHE_CONFIGS, getLogger } from './chunk-LH4Z7QID.js';
2
+ import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-M6FPVS7E.js';
3
3
  import { PluginService, MigrationService } from './chunk-CDBVZEWR.js';
4
- import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderFAQList, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-O46XKBFM.js';
5
- import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-OL2OE3VJ.js';
4
+ import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderFAQList, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-3LZ6TLPC.js';
5
+ import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-XJETEIRU.js';
6
+ import { metricsTracker } from './chunk-FICTAGD4.js';
6
7
  import { Hono } from 'hono';
7
8
  import { cors } from 'hono/cors';
8
9
  import { z } from 'zod';
@@ -2030,9 +2031,36 @@ function renderRegisterPage(data) {
2030
2031
  </html>
2031
2032
  `;
2032
2033
  }
2033
-
2034
- // src/services/auth-validation.ts
2035
- var authValidationService = {};
2034
+ var authValidationService = {
2035
+ /**
2036
+ * Build registration schema dynamically based on auth settings
2037
+ * For now, returns a static schema with standard fields
2038
+ */
2039
+ async buildRegistrationSchema(_db) {
2040
+ return z.object({
2041
+ email: z.string().email("Valid email is required"),
2042
+ password: z.string().min(8, "Password must be at least 8 characters"),
2043
+ username: z.string().min(3, "Username must be at least 3 characters").optional(),
2044
+ firstName: z.string().min(1, "First name is required").optional(),
2045
+ lastName: z.string().min(1, "Last name is required").optional()
2046
+ });
2047
+ },
2048
+ /**
2049
+ * Generate default values for optional fields
2050
+ */
2051
+ generateDefaultValue(field, data) {
2052
+ switch (field) {
2053
+ case "username":
2054
+ return data.email ? data.email.split("@")[0] : `user${Date.now()}`;
2055
+ case "firstName":
2056
+ return "User";
2057
+ case "lastName":
2058
+ return data.email ? data.email.split("@")[0] : "Account";
2059
+ default:
2060
+ return "";
2061
+ }
2062
+ }
2063
+ };
2036
2064
 
2037
2065
  // src/routes/auth.ts
2038
2066
  var authRoutes = new Hono();
@@ -4704,6 +4732,7 @@ var isPluginActive2 = () => false;
4704
4732
 
4705
4733
  // src/routes/admin-content.ts
4706
4734
  var adminContentRoutes = new Hono();
4735
+ adminContentRoutes.use("*", requireAuth());
4707
4736
  async function getCollectionFields(db, collectionId) {
4708
4737
  const cache = getCacheService(CACHE_CONFIGS.collection);
4709
4738
  return cache.getOrSet(
@@ -5836,170 +5865,157 @@ ${JSON.stringify(data, null, 2)}
5836
5865
  var admin_content_default = adminContentRoutes;
5837
5866
 
5838
5867
  // src/templates/pages/admin-profile.template.ts
5868
+ init_admin_layout_catalyst_template();
5839
5869
  function renderProfilePage(data) {
5840
5870
  const pageContent = `
5841
- <div class="w-full px-4 sm:px-6 lg:px-8 py-6">
5871
+ <div class="space-y-8">
5842
5872
  <!-- Header -->
5843
- <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6">
5873
+ <div class="sm:flex sm:items-center sm:justify-between">
5844
5874
  <div>
5845
- <h1 class="text-2xl font-semibold text-white">User Profile</h1>
5846
- <p class="mt-2 text-sm text-gray-300">Manage your account settings and preferences</p>
5875
+ <h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">User Profile</h1>
5876
+ <p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">
5877
+ Manage your account settings and preferences
5878
+ </p>
5847
5879
  </div>
5848
5880
  </div>
5849
5881
 
5850
- <!-- Breadcrumb -->
5851
- <nav class="flex mb-6" aria-label="Breadcrumb">
5852
- <ol class="flex items-center space-x-3">
5853
- <li>
5854
- <a href="/admin" class="text-gray-300 hover:text-white transition-colors">
5855
- <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
5856
- <path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
5857
- </svg>
5858
- </a>
5859
- </li>
5860
- <li class="flex items-center">
5861
- <svg class="h-5 w-5 text-gray-400 mx-2" fill="currentColor" viewBox="0 0 20 20">
5862
- <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
5863
- </svg>
5864
- <span class="text-sm font-medium text-gray-200">Profile</span>
5865
- </li>
5866
- </ol>
5867
- </nav>
5868
-
5869
5882
  <!-- Alert Messages -->
5870
5883
  ${data.error ? renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
5871
5884
  ${data.success ? renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
5872
5885
 
5873
5886
  <!-- Profile Form -->
5874
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
5887
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
5875
5888
  <!-- Main Profile Form -->
5876
5889
  <div class="lg:col-span-2">
5877
- <div class="backdrop-blur-xl bg-white/10 rounded-3xl border border-white/20 shadow-2xl overflow-hidden">
5890
+ <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10">
5878
5891
  <!-- Form Header -->
5879
- <div class="relative px-8 py-6 border-b border-white/10">
5880
- <div class="absolute inset-0 bg-gradient-to-r from-blue-600/10 via-purple-600/10 to-pink-600/10"></div>
5881
- <div class="relative flex items-center gap-3">
5882
- <div class="w-12 h-12 rounded-xl bg-white/10 backdrop-blur-sm flex items-center justify-center">
5883
- <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5892
+ <div class="px-6 py-5 border-b border-zinc-950/5 dark:border-white/5">
5893
+ <div class="flex items-center gap-x-3">
5894
+ <div class="flex h-10 w-10 items-center justify-center rounded-lg bg-zinc-950 dark:bg-white">
5895
+ <svg class="h-5 w-5 text-white dark:text-zinc-950" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5884
5896
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
5885
5897
  </svg>
5886
5898
  </div>
5887
5899
  <div>
5888
- <h2 class="text-xl font-semibold text-white">Profile Information</h2>
5889
- <p class="text-sm text-gray-300">Update your account details and preferences</p>
5900
+ <h2 class="text-base font-semibold text-zinc-950 dark:text-white">Profile Information</h2>
5901
+ <p class="text-sm text-zinc-500 dark:text-zinc-400">Update your account details</p>
5890
5902
  </div>
5891
5903
  </div>
5892
5904
  </div>
5893
5905
 
5894
5906
  <!-- Form Content -->
5895
- <form id="profile-form" hx-put="/admin/profile" hx-target="#form-messages" class="p-8 space-y-6">
5907
+ <form id="profile-form" hx-put="/admin/profile" hx-target="#form-messages" class="p-6 space-y-6">
5896
5908
  <div id="form-messages"></div>
5897
5909
 
5898
5910
  <!-- Basic Information -->
5899
5911
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
5900
5912
  <div>
5901
- <label class="block text-sm font-medium text-gray-300 mb-2">First Name</label>
5902
- <input
5903
- type="text"
5904
- name="first_name"
5913
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">First Name</label>
5914
+ <input
5915
+ type="text"
5916
+ name="first_name"
5905
5917
  value="${data.profile.first_name}"
5906
5918
  required
5907
- class="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl text-white focus:outline-none focus:bg-white/10 focus:border-white/30 focus:ring-2 focus:ring-white/20 transition-all"
5919
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
5908
5920
  placeholder="Enter your first name"
5909
5921
  >
5910
5922
  </div>
5911
5923
  <div>
5912
- <label class="block text-sm font-medium text-gray-300 mb-2">Last Name</label>
5913
- <input
5914
- type="text"
5915
- name="last_name"
5924
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Last Name</label>
5925
+ <input
5926
+ type="text"
5927
+ name="last_name"
5916
5928
  value="${data.profile.last_name}"
5917
5929
  required
5918
- class="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl text-white focus:outline-none focus:bg-white/10 focus:border-white/30 focus:ring-2 focus:ring-white/20 transition-all"
5930
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
5919
5931
  placeholder="Enter your last name"
5920
5932
  >
5921
5933
  </div>
5922
5934
  </div>
5923
5935
 
5924
5936
  <div>
5925
- <label class="block text-sm font-medium text-gray-300 mb-2">Username</label>
5926
- <input
5927
- type="text"
5928
- name="username"
5937
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Username</label>
5938
+ <input
5939
+ type="text"
5940
+ name="username"
5929
5941
  value="${data.profile.username}"
5930
5942
  required
5931
- class="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl text-white focus:outline-none focus:bg-white/10 focus:border-white/30 focus:ring-2 focus:ring-white/20 transition-all"
5943
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
5932
5944
  placeholder="Enter your username"
5933
5945
  >
5934
5946
  </div>
5935
5947
 
5936
5948
  <div>
5937
- <label class="block text-sm font-medium text-gray-300 mb-2">Email Address</label>
5938
- <input
5939
- type="email"
5940
- name="email"
5949
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Email Address</label>
5950
+ <input
5951
+ type="email"
5952
+ name="email"
5941
5953
  value="${data.profile.email}"
5942
5954
  required
5943
- class="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl text-white focus:outline-none focus:bg-white/10 focus:border-white/30 focus:ring-2 focus:ring-white/20 transition-all"
5955
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
5944
5956
  placeholder="Enter your email address"
5945
5957
  >
5946
5958
  </div>
5947
5959
 
5948
5960
  <div>
5949
- <label class="block text-sm font-medium text-gray-300 mb-2">Phone Number</label>
5950
- <input
5951
- type="tel"
5952
- name="phone"
5961
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Phone Number</label>
5962
+ <input
5963
+ type="tel"
5964
+ name="phone"
5953
5965
  value="${data.profile.phone || ""}"
5954
- class="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl text-white focus:outline-none focus:bg-white/10 focus:border-white/30 focus:ring-2 focus:ring-white/20 transition-all"
5966
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
5955
5967
  placeholder="Enter your phone number"
5956
5968
  >
5957
5969
  </div>
5958
5970
 
5959
5971
  <div>
5960
- <label class="block text-sm font-medium text-gray-300 mb-2">Bio</label>
5961
- <textarea
5962
- name="bio"
5972
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Bio</label>
5973
+ <textarea
5974
+ name="bio"
5963
5975
  rows="3"
5964
- class="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl text-white focus:outline-none focus:bg-white/10 focus:border-white/30 focus:ring-2 focus:ring-white/20 transition-all resize-none"
5976
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow resize-none"
5965
5977
  placeholder="Tell us about yourself..."
5966
5978
  >${data.profile.bio || ""}</textarea>
5967
5979
  </div>
5968
5980
 
5969
5981
  <!-- Preferences -->
5970
- <div class="pt-6 border-t border-white/10">
5971
- <h3 class="text-lg font-semibold text-white mb-4">Preferences</h3>
5972
-
5982
+ <div class="pt-6 border-t border-zinc-950/5 dark:border-white/5">
5983
+ <h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-4">Preferences</h3>
5984
+
5973
5985
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
5974
5986
  <div>
5975
- <label class="block text-sm font-medium text-gray-300 mb-2">Timezone</label>
5976
- <select
5977
- name="timezone"
5978
- class="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl text-white focus:outline-none focus:bg-white/10 focus:border-white/30 focus:ring-2 focus:ring-white/20 transition-all"
5979
- >
5980
- ${data.timezones.map((tz) => `
5981
- <option value="${tz.value}" ${tz.value === data.profile.timezone ? "selected" : ""}>${tz.label}</option>
5982
- `).join("")}
5983
- </select>
5987
+ <label for="timezone" class="block text-sm/6 font-medium text-zinc-950 dark:text-white mb-2">Timezone</label>
5988
+ <div class="grid grid-cols-1">
5989
+ <select id="timezone" name="timezone" class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white/5 dark:bg-white/5 py-1.5 pl-3 pr-8 text-base text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-zinc-500/30 dark:outline-zinc-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-zinc-500 dark:focus-visible:outline-zinc-400 sm:text-sm/6">
5990
+ ${data.timezones.map((tz) => `
5991
+ <option value="${tz.value}" ${tz.value === data.profile.timezone ? "selected" : ""}>${tz.label}</option>
5992
+ `).join("")}
5993
+ </select>
5994
+ <svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-zinc-600 dark:text-zinc-400 sm:size-4">
5995
+ <path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
5996
+ </svg>
5997
+ </div>
5984
5998
  </div>
5985
5999
  <div>
5986
- <label class="block text-sm font-medium text-gray-300 mb-2">Language</label>
5987
- <select
5988
- name="language"
5989
- class="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl text-white focus:outline-none focus:bg-white/10 focus:border-white/30 focus:ring-2 focus:ring-white/20 transition-all"
5990
- >
5991
- ${data.languages.map((lang) => `
5992
- <option value="${lang.value}" ${lang.value === data.profile.language ? "selected" : ""}>${lang.label}</option>
5993
- `).join("")}
5994
- </select>
6000
+ <label for="language" class="block text-sm/6 font-medium text-zinc-950 dark:text-white mb-2">Language</label>
6001
+ <div class="grid grid-cols-1">
6002
+ <select id="language" name="language" class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white/5 dark:bg-white/5 py-1.5 pl-3 pr-8 text-base text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-zinc-500/30 dark:outline-zinc-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-zinc-500 dark:focus-visible:outline-zinc-400 sm:text-sm/6">
6003
+ ${data.languages.map((lang) => `
6004
+ <option value="${lang.value}" ${lang.value === data.profile.language ? "selected" : ""}>${lang.label}</option>
6005
+ `).join("")}
6006
+ </select>
6007
+ <svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-zinc-600 dark:text-zinc-400 sm:size-4">
6008
+ <path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
6009
+ </svg>
6010
+ </div>
5995
6011
  </div>
5996
6012
  </div>
5997
6013
  </div>
5998
6014
 
5999
6015
  <!-- Notifications -->
6000
- <div class="pt-6 border-t border-white/10">
6001
- <h3 class="text-lg font-semibold text-white mb-4">Notifications</h3>
6002
-
6016
+ <div class="pt-6 border-t border-zinc-950/5 dark:border-white/5">
6017
+ <h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-4">Notifications</h3>
6018
+
6003
6019
  <div class="space-y-5">
6004
6020
  <div class="flex gap-3">
6005
6021
  <div class="flex h-6 shrink-0 items-center">
@@ -6010,27 +6026,30 @@ function renderProfilePage(data) {
6010
6026
  name="email_notifications"
6011
6027
  value="1"
6012
6028
  ${data.profile.email_notifications ? "checked" : ""}
6013
- class="col-start-1 row-start-1 appearance-none rounded border border-zinc-950/10 dark:border-white/10 bg-white dark:bg-white/5 checked:border-indigo-500 checked:bg-indigo-500 indeterminate:border-indigo-500 indeterminate:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500 disabled:border-zinc-950/5 dark:disabled:border-white/5 disabled:bg-zinc-950/10 dark:disabled:bg-white/10 disabled:checked:bg-zinc-950/10 dark:disabled:checked:bg-white/10 forced-colors:appearance-auto"
6029
+ class="col-start-1 row-start-1 appearance-none rounded border border-zinc-950/10 dark:border-white/10 bg-white dark:bg-white/5 checked:border-indigo-500 checked:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500 disabled:border-zinc-950/5 dark:disabled:border-white/5 disabled:bg-zinc-950/10 dark:disabled:bg-white/10 forced-colors:appearance-auto"
6014
6030
  />
6015
6031
  <svg viewBox="0 0 14 14" fill="none" class="pointer-events-none col-start-1 row-start-1 size-3.5 self-center justify-self-center stroke-white group-has-[:disabled]:stroke-zinc-950/25 dark:group-has-[:disabled]:stroke-white/25">
6016
6032
  <path d="M3 8L6 11L11 3.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-[:checked]:opacity-100" />
6017
- <path d="M3 7H11" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-[:indeterminate]:opacity-100" />
6018
6033
  </svg>
6019
6034
  </div>
6020
6035
  </div>
6021
6036
  <div class="text-sm/6">
6022
6037
  <label for="email_notifications" class="font-medium text-zinc-950 dark:text-white">Email notifications</label>
6038
+ <p class="text-zinc-500 dark:text-zinc-400">Receive email updates about new features and product announcements.</p>
6023
6039
  </div>
6024
6040
  </div>
6025
6041
  </div>
6026
6042
  </div>
6027
6043
 
6028
6044
  <!-- Submit Button -->
6029
- <div class="pt-6 border-t border-white/10">
6030
- <button
6045
+ <div class="pt-6 border-t border-zinc-950/5 dark:border-white/5">
6046
+ <button
6031
6047
  type="submit"
6032
- class="w-full px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-semibold rounded-xl hover:from-blue-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-transparent transition-all"
6048
+ class="inline-flex justify-center items-center gap-x-2 rounded-lg bg-zinc-950 dark:bg-white px-4 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-950 dark:focus-visible:outline-white transition-colors"
6033
6049
  >
6050
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6051
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
6052
+ </svg>
6034
6053
  Update Profile
6035
6054
  </button>
6036
6055
  </div>
@@ -6041,86 +6060,96 @@ function renderProfilePage(data) {
6041
6060
  <!-- Profile Sidebar -->
6042
6061
  <div class="lg:col-span-1 space-y-6">
6043
6062
  <!-- Avatar -->
6044
- <div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl p-6">
6045
- <h3 class="text-lg font-semibold text-white mb-4">Profile Picture</h3>
6046
-
6063
+ <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6">
6064
+ <h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-4">Profile Picture</h3>
6065
+
6047
6066
  <div class="text-center">
6048
- <div class="w-24 h-24 rounded-full mx-auto mb-4 overflow-hidden bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
6067
+ <div class="w-24 h-24 rounded-full mx-auto mb-4 overflow-hidden bg-gradient-to-br from-cyan-400 to-purple-400 flex items-center justify-center ring-4 ring-zinc-950/5 dark:ring-white/10">
6049
6068
  ${data.profile.avatar_url ? `<img src="${data.profile.avatar_url}" alt="Profile picture" class="w-full h-full object-cover">` : `<span class="text-2xl font-bold text-white">${data.profile.first_name.charAt(0)}${data.profile.last_name.charAt(0)}</span>`}
6050
6069
  </div>
6051
-
6070
+
6052
6071
  <form id="avatar-form" hx-post="/admin/profile/avatar" hx-target="#avatar-messages" hx-encoding="multipart/form-data">
6053
- <input
6054
- type="file"
6055
- name="avatar"
6072
+ <input
6073
+ type="file"
6074
+ name="avatar"
6056
6075
  accept="image/*"
6057
6076
  class="hidden"
6058
6077
  id="avatar-input"
6059
6078
  onchange="document.getElementById('avatar-form').dispatchEvent(new Event('submit'))"
6060
6079
  >
6061
- <label
6080
+ <label
6062
6081
  for="avatar-input"
6063
- class="inline-block px-4 py-2 bg-white/10 text-white rounded-xl border border-white/20 hover:bg-white/20 transition-all cursor-pointer"
6082
+ class="inline-flex items-center gap-x-2 rounded-lg bg-white dark:bg-zinc-800 px-4 py-2.5 text-sm font-semibold text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors cursor-pointer"
6064
6083
  >
6084
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6085
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
6086
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
6087
+ </svg>
6065
6088
  Change Picture
6066
6089
  </label>
6067
6090
  </form>
6068
-
6091
+
6069
6092
  <div id="avatar-messages" class="mt-3"></div>
6070
6093
  </div>
6071
6094
  </div>
6072
6095
 
6073
6096
  <!-- Account Info -->
6074
- <div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl p-6">
6075
- <h3 class="text-lg font-semibold text-white mb-4">Account Information</h3>
6076
-
6077
- <div class="space-y-3 text-sm">
6097
+ <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6">
6098
+ <h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-4">Account Information</h3>
6099
+
6100
+ <dl class="space-y-3 text-sm">
6078
6101
  <div>
6079
- <span class="text-gray-400">Role:</span>
6080
- <span class="text-white ml-2 capitalize">${data.profile.role}</span>
6102
+ <dt class="text-zinc-500 dark:text-zinc-400">Role</dt>
6103
+ <dd class="mt-1">
6104
+ <span class="inline-flex items-center rounded-md bg-blue-50 dark:bg-blue-500/10 px-2 py-1 text-xs font-medium text-blue-700 dark:text-blue-400 ring-1 ring-inset ring-blue-700/10 dark:ring-blue-400/20 capitalize">
6105
+ ${data.profile.role}
6106
+ </span>
6107
+ </dd>
6081
6108
  </div>
6082
6109
  <div>
6083
- <span class="text-gray-400">Member Since:</span>
6084
- <span class="text-white ml-2">${new Date(data.profile.created_at).toLocaleDateString()}</span>
6110
+ <dt class="text-zinc-500 dark:text-zinc-400">Member Since</dt>
6111
+ <dd class="mt-1 text-zinc-950 dark:text-white">${new Date(data.profile.created_at).toLocaleDateString()}</dd>
6085
6112
  </div>
6086
6113
  ${data.profile.last_login_at ? `
6087
6114
  <div>
6088
- <span class="text-gray-400">Last Login:</span>
6089
- <span class="text-white ml-2">${new Date(data.profile.last_login_at).toLocaleDateString()}</span>
6115
+ <dt class="text-zinc-500 dark:text-zinc-400">Last Login</dt>
6116
+ <dd class="mt-1 text-zinc-950 dark:text-white">${new Date(data.profile.last_login_at).toLocaleDateString()}</dd>
6090
6117
  </div>
6091
6118
  ` : ""}
6092
6119
  <div>
6093
- <span class="text-gray-400">Two-Factor Auth:</span>
6094
- <span class="text-white ml-2">${data.profile.two_factor_enabled ? "Enabled" : "Disabled"}</span>
6120
+ <dt class="text-zinc-500 dark:text-zinc-400">Two-Factor Auth</dt>
6121
+ <dd class="mt-1">
6122
+ ${data.profile.two_factor_enabled ? '<span class="inline-flex items-center rounded-md bg-green-50 dark:bg-green-500/10 px-2 py-1 text-xs font-medium text-green-700 dark:text-green-400 ring-1 ring-inset ring-green-600/20 dark:ring-green-500/20">Enabled</span>' : '<span class="inline-flex items-center rounded-md bg-zinc-50 dark:bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-400 ring-1 ring-inset ring-zinc-500/10 dark:ring-zinc-400/20">Disabled</span>'}
6123
+ </dd>
6095
6124
  </div>
6096
- </div>
6125
+ </dl>
6097
6126
  </div>
6098
6127
 
6099
6128
  <!-- Security Actions -->
6100
- <div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl p-6">
6101
- <h3 class="text-lg font-semibold text-white mb-4">Security</h3>
6102
-
6103
- <div class="space-y-3">
6104
- <button
6129
+ <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6">
6130
+ <h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-4">Security</h3>
6131
+
6132
+ <div class="space-y-2">
6133
+ <button
6105
6134
  type="button"
6106
6135
  onclick="showChangePasswordModal()"
6107
- class="w-full text-left px-3 py-2 text-sm text-gray-300 hover:text-white hover:bg-white/10 rounded-xl transition-all"
6136
+ class="w-full text-left flex items-center gap-x-3 px-3 py-2 text-sm text-zinc-950 dark:text-white hover:bg-zinc-50 dark:hover:bg-zinc-800/50 rounded-lg transition-colors"
6108
6137
  >
6109
- <svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6138
+ <svg class="w-4 h-4 text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6110
6139
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-3.586l4.293-4.293A6 6 0 0119 9z"/>
6111
6140
  </svg>
6112
- Change Password
6141
+ <span class="font-medium">Change Password</span>
6113
6142
  </button>
6114
-
6115
- <button
6143
+
6144
+ <button
6116
6145
  type="button"
6117
6146
  onclick="toggle2FA()"
6118
- class="w-full text-left px-3 py-2 text-sm text-gray-300 hover:text-white hover:bg-white/10 rounded-xl transition-all"
6147
+ class="w-full text-left flex items-center gap-x-3 px-3 py-2 text-sm text-zinc-950 dark:text-white hover:bg-zinc-50 dark:hover:bg-zinc-800/50 rounded-lg transition-colors"
6119
6148
  >
6120
- <svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6149
+ <svg class="w-4 h-4 text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6121
6150
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
6122
6151
  </svg>
6123
- ${data.profile.two_factor_enabled ? "Disable" : "Enable"} 2FA
6152
+ <span class="font-medium">${data.profile.two_factor_enabled ? "Disable" : "Enable"} 2FA</span>
6124
6153
  </button>
6125
6154
  </div>
6126
6155
  </div>
@@ -6129,67 +6158,73 @@ function renderProfilePage(data) {
6129
6158
  </div>
6130
6159
 
6131
6160
  <!-- Change Password Modal -->
6132
- <div id="password-modal" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 hidden">
6133
- <div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl w-full max-w-md mx-4">
6134
- <div class="relative px-6 py-4 border-b border-white/10">
6135
- <div class="absolute inset-0 bg-gradient-to-r from-blue-600/10 via-purple-600/10 to-pink-600/10"></div>
6136
- <div class="relative flex items-center justify-between">
6137
- <h3 class="text-lg font-semibold text-white">Change Password</h3>
6138
- <button onclick="closePasswordModal()" class="text-gray-300 hover:text-white">
6139
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6161
+ <div id="password-modal" class="fixed inset-0 bg-zinc-950/50 backdrop-blur-sm flex items-center justify-center z-50 hidden">
6162
+ <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-2xl ring-1 ring-zinc-950/5 dark:ring-white/10 w-full max-w-md mx-4">
6163
+ <div class="px-6 py-5 border-b border-zinc-950/5 dark:border-white/5">
6164
+ <div class="flex items-center justify-between">
6165
+ <h3 class="text-base font-semibold text-zinc-950 dark:text-white">Change Password</h3>
6166
+ <button onclick="closePasswordModal()" class="text-zinc-500 dark:text-zinc-400 hover:text-zinc-950 dark:hover:text-white transition-colors">
6167
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6140
6168
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
6141
6169
  </svg>
6142
6170
  </button>
6143
6171
  </div>
6144
6172
  </div>
6145
-
6173
+
6146
6174
  <form id="password-form" hx-post="/admin/profile/password" hx-target="#password-messages" class="p-6 space-y-4">
6147
6175
  <div id="password-messages"></div>
6148
-
6176
+
6149
6177
  <div>
6150
- <label class="block text-sm font-medium text-gray-300 mb-2">Current Password</label>
6151
- <input
6152
- type="password"
6153
- name="current_password"
6178
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Current Password</label>
6179
+ <input
6180
+ type="password"
6181
+ name="current_password"
6154
6182
  required
6155
- class="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl text-white focus:outline-none focus:bg-white/10 focus:border-white/30 focus:ring-2 focus:ring-white/20 transition-all"
6183
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
6184
+ placeholder="Enter current password"
6156
6185
  >
6157
6186
  </div>
6158
6187
 
6159
6188
  <div>
6160
- <label class="block text-sm font-medium text-gray-300 mb-2">New Password</label>
6161
- <input
6162
- type="password"
6163
- name="new_password"
6189
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">New Password</label>
6190
+ <input
6191
+ type="password"
6192
+ name="new_password"
6164
6193
  required
6165
6194
  minlength="8"
6166
- class="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl text-white focus:outline-none focus:bg-white/10 focus:border-white/30 focus:ring-2 focus:ring-white/20 transition-all"
6195
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
6196
+ placeholder="Enter new password"
6167
6197
  >
6198
+ <p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Must be at least 8 characters</p>
6168
6199
  </div>
6169
6200
 
6170
6201
  <div>
6171
- <label class="block text-sm font-medium text-gray-300 mb-2">Confirm New Password</label>
6172
- <input
6173
- type="password"
6174
- name="confirm_password"
6202
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Confirm New Password</label>
6203
+ <input
6204
+ type="password"
6205
+ name="confirm_password"
6175
6206
  required
6176
6207
  minlength="8"
6177
- class="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl text-white focus:outline-none focus:bg-white/10 focus:border-white/30 focus:ring-2 focus:ring-white/20 transition-all"
6208
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
6209
+ placeholder="Confirm new password"
6178
6210
  >
6179
6211
  </div>
6180
6212
 
6181
- <div class="flex justify-end space-x-3 pt-4 border-t border-white/10">
6182
- <button
6183
- type="button"
6213
+ <div class="flex justify-end gap-x-3 pt-4 border-t border-zinc-950/5 dark:border-white/5">
6214
+ <button
6215
+ type="button"
6184
6216
  onclick="closePasswordModal()"
6185
- class="px-4 py-2 bg-white/10 text-white rounded-xl border border-white/20 hover:bg-white/20 transition-all"
6217
+ class="rounded-lg bg-white dark:bg-zinc-800 px-4 py-2.5 text-sm font-semibold text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors"
6186
6218
  >
6187
6219
  Cancel
6188
6220
  </button>
6189
- <button
6221
+ <button
6190
6222
  type="submit"
6191
- class="px-4 py-2 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-medium rounded-xl hover:from-blue-700 hover:to-purple-700 transition-all"
6223
+ class="inline-flex items-center gap-x-2 rounded-lg bg-zinc-950 dark:bg-white px-4 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors"
6192
6224
  >
6225
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6226
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
6227
+ </svg>
6193
6228
  Update Password
6194
6229
  </button>
6195
6230
  </div>
@@ -6232,9 +6267,10 @@ function renderProfilePage(data) {
6232
6267
  pageTitle: "Profile",
6233
6268
  currentPath: "/admin/profile",
6234
6269
  user: data.user,
6270
+ version: data.version,
6235
6271
  content: pageContent
6236
6272
  };
6237
- return renderAdminLayout(layoutData);
6273
+ return renderAdminLayoutCatalyst(layoutData);
6238
6274
  }
6239
6275
 
6240
6276
  // src/templates/components/alert.template.ts
@@ -7653,6 +7689,9 @@ function renderUsersListPage(data) {
7653
7689
  // src/routes/admin-users.ts
7654
7690
  var userRoutes = new Hono();
7655
7691
  userRoutes.use("*", requireAuth());
7692
+ userRoutes.get("/", (c) => {
7693
+ return c.redirect("/admin/dashboard");
7694
+ });
7656
7695
  var TIMEZONES = [
7657
7696
  { value: "UTC", label: "UTC" },
7658
7697
  { value: "America/New_York", label: "Eastern Time" },
@@ -7975,7 +8014,7 @@ userRoutes.post("/profile/password", async (c) => {
7975
8014
  }));
7976
8015
  }
7977
8016
  });
7978
- userRoutes.get("/users", requirePermission("users.read"), async (c) => {
8017
+ userRoutes.get("/users", async (c) => {
7979
8018
  const db = c.env.DB;
7980
8019
  const user = c.get("user");
7981
8020
  try {
@@ -8094,7 +8133,7 @@ userRoutes.get("/users", requirePermission("users.read"), async (c) => {
8094
8133
  }), 500);
8095
8134
  }
8096
8135
  });
8097
- userRoutes.get("/users/new", requirePermission("users.create"), async (c) => {
8136
+ userRoutes.get("/users/new", async (c) => {
8098
8137
  const user = c.get("user");
8099
8138
  try {
8100
8139
  const pageData = {
@@ -8115,7 +8154,7 @@ userRoutes.get("/users/new", requirePermission("users.create"), async (c) => {
8115
8154
  }), 500);
8116
8155
  }
8117
8156
  });
8118
- userRoutes.post("/users/new", requirePermission("users.create"), async (c) => {
8157
+ userRoutes.post("/users/new", async (c) => {
8119
8158
  const db = c.env.DB;
8120
8159
  const user = c.get("user");
8121
8160
  try {
@@ -8215,7 +8254,7 @@ userRoutes.post("/users/new", requirePermission("users.create"), async (c) => {
8215
8254
  }));
8216
8255
  }
8217
8256
  });
8218
- userRoutes.get("/users/:id", requirePermission("users.read"), async (c) => {
8257
+ userRoutes.get("/users/:id", async (c) => {
8219
8258
  if (c.req.path.endsWith("/edit")) {
8220
8259
  return c.notFound();
8221
8260
  }
@@ -8266,7 +8305,7 @@ userRoutes.get("/users/:id", requirePermission("users.read"), async (c) => {
8266
8305
  return c.json({ error: "Failed to fetch user" }, 500);
8267
8306
  }
8268
8307
  });
8269
- userRoutes.get("/users/:id/edit", requirePermission("users.update"), async (c) => {
8308
+ userRoutes.get("/users/:id/edit", async (c) => {
8270
8309
  const db = c.env.DB;
8271
8310
  const user = c.get("user");
8272
8311
  const userId = c.req.param("id");
@@ -8320,7 +8359,7 @@ userRoutes.get("/users/:id/edit", requirePermission("users.update"), async (c) =
8320
8359
  }), 500);
8321
8360
  }
8322
8361
  });
8323
- userRoutes.put("/users/:id", requirePermission("users.update"), async (c) => {
8362
+ userRoutes.put("/users/:id", async (c) => {
8324
8363
  const db = c.env.DB;
8325
8364
  const user = c.get("user");
8326
8365
  const userId = c.req.param("id");
@@ -8406,7 +8445,7 @@ userRoutes.put("/users/:id", requirePermission("users.update"), async (c) => {
8406
8445
  }));
8407
8446
  }
8408
8447
  });
8409
- userRoutes.delete("/users/:id", requirePermission("users.delete"), async (c) => {
8448
+ userRoutes.delete("/users/:id", async (c) => {
8410
8449
  const db = c.env.DB;
8411
8450
  const user = c.get("user");
8412
8451
  const userId = c.req.param("id");
@@ -8467,7 +8506,7 @@ userRoutes.delete("/users/:id", requirePermission("users.delete"), async (c) =>
8467
8506
  return c.json({ error: "Failed to delete user" }, 500);
8468
8507
  }
8469
8508
  });
8470
- userRoutes.post("/invite-user", requirePermission("users.create"), async (c) => {
8509
+ userRoutes.post("/invite-user", async (c) => {
8471
8510
  const db = c.env.DB;
8472
8511
  const user = c.get("user");
8473
8512
  try {
@@ -8543,7 +8582,7 @@ userRoutes.post("/invite-user", requirePermission("users.create"), async (c) =>
8543
8582
  return c.json({ error: "Failed to send user invitation" }, 500);
8544
8583
  }
8545
8584
  });
8546
- userRoutes.post("/resend-invitation/:id", requirePermission("users.create"), async (c) => {
8585
+ userRoutes.post("/resend-invitation/:id", async (c) => {
8547
8586
  const db = c.env.DB;
8548
8587
  const user = c.get("user");
8549
8588
  const userId = c.req.param("id");
@@ -8592,7 +8631,7 @@ userRoutes.post("/resend-invitation/:id", requirePermission("users.create"), asy
8592
8631
  return c.json({ error: "Failed to resend invitation" }, 500);
8593
8632
  }
8594
8633
  });
8595
- userRoutes.delete("/cancel-invitation/:id", requirePermission("users.delete"), async (c) => {
8634
+ userRoutes.delete("/cancel-invitation/:id", async (c) => {
8596
8635
  const db = c.env.DB;
8597
8636
  const user = c.get("user");
8598
8637
  const userId = c.req.param("id");
@@ -8626,7 +8665,7 @@ userRoutes.delete("/cancel-invitation/:id", requirePermission("users.delete"), a
8626
8665
  return c.json({ error: "Failed to cancel invitation" }, 500);
8627
8666
  }
8628
8667
  });
8629
- userRoutes.get("/activity-logs", requirePermission("activity.read"), async (c) => {
8668
+ userRoutes.get("/activity-logs", async (c) => {
8630
8669
  const db = c.env.DB;
8631
8670
  const user = c.get("user");
8632
8671
  try {
@@ -8732,7 +8771,7 @@ userRoutes.get("/activity-logs", requirePermission("activity.read"), async (c) =
8732
8771
  return c.html(renderActivityLogsPage(pageData));
8733
8772
  }
8734
8773
  });
8735
- userRoutes.get("/activity-logs/export", requirePermission("activity.read"), async (c) => {
8774
+ userRoutes.get("/activity-logs/export", async (c) => {
8736
8775
  const db = c.env.DB;
8737
8776
  const user = c.get("user");
8738
8777
  try {
@@ -10133,6 +10172,7 @@ var fileValidationSchema2 = z.object({
10133
10172
  // 50MB max
10134
10173
  });
10135
10174
  var adminMediaRoutes = new Hono();
10175
+ adminMediaRoutes.use("*", requireAuth());
10136
10176
  adminMediaRoutes.get("/", async (c) => {
10137
10177
  try {
10138
10178
  const user = c.get("user");
@@ -12152,6 +12192,7 @@ function formatTimestamp(timestamp) {
12152
12192
 
12153
12193
  // src/routes/admin-plugins.ts
12154
12194
  var adminPluginRoutes = new Hono();
12195
+ adminPluginRoutes.use("*", requireAuth());
12155
12196
  adminPluginRoutes.get("/", async (c) => {
12156
12197
  try {
12157
12198
  const user = c.get("user");
@@ -13261,6 +13302,7 @@ function renderLogConfigPage(data) {
13261
13302
 
13262
13303
  // src/routes/admin-logs.ts
13263
13304
  var adminLogsRoutes = new Hono();
13305
+ adminLogsRoutes.use("*", requireAuth());
13264
13306
  adminLogsRoutes.get("/", async (c) => {
13265
13307
  try {
13266
13308
  const user = c.get("user");
@@ -15743,7 +15785,7 @@ function renderAnalyticsChart() {
15743
15785
 
15744
15786
  <!-- Hidden div to trigger HTMX polling -->
15745
15787
  <div
15746
- hx-get="/admin/api/metrics"
15788
+ hx-get="/admin/dashboard/api/metrics"
15747
15789
  hx-trigger="every 1s"
15748
15790
  hx-swap="none"
15749
15791
  style="display: none;"
@@ -15846,9 +15888,12 @@ function renderAnalyticsChart() {
15846
15888
 
15847
15889
  // Listen for metrics updates from HTMX
15848
15890
  window.addEventListener('htmx:afterRequest', function(event) {
15849
- if (event.detail.pathInfo.requestPath === '/admin/api/metrics') {
15891
+ console.log('[Dashboard] HTMX request completed:', event.detail.pathInfo.requestPath);
15892
+
15893
+ if (event.detail.pathInfo.requestPath === '/admin/dashboard/api/metrics') {
15850
15894
  try {
15851
15895
  const metrics = JSON.parse(event.detail.xhr.responseText);
15896
+ console.log('[Dashboard] Metrics received:', metrics);
15852
15897
 
15853
15898
  // Update current RPS display
15854
15899
  const rpsElement = document.getElementById('current-rps');
@@ -15869,8 +15914,9 @@ function renderAnalyticsChart() {
15869
15914
  chart.data.labels = newLabels;
15870
15915
 
15871
15916
  chart.update('none'); // Update without animation for smoother real-time updates
15917
+ console.log('[Dashboard] Chart updated with RPS:', metrics.requestsPerSecond);
15872
15918
  } catch (e) {
15873
- console.error('Error updating metrics:', e);
15919
+ console.error('[Dashboard] Error updating metrics:', e);
15874
15920
  }
15875
15921
  }
15876
15922
  });
@@ -16217,7 +16263,7 @@ router.get("/", async (c) => {
16217
16263
  return c.html(renderDashboardPage(pageData));
16218
16264
  }
16219
16265
  });
16220
- router.get("/dashboard/stats", async (c) => {
16266
+ router.get("/stats", async (c) => {
16221
16267
  try {
16222
16268
  const db = c.env.DB;
16223
16269
  let collectionsCount = 0;
@@ -16267,7 +16313,7 @@ router.get("/dashboard/stats", async (c) => {
16267
16313
  return c.html('<div class="text-red-500">Failed to load statistics</div>');
16268
16314
  }
16269
16315
  });
16270
- router.get("/dashboard/storage", async (c) => {
16316
+ router.get("/storage", async (c) => {
16271
16317
  try {
16272
16318
  const db = c.env.DB;
16273
16319
  let databaseSize = 0;
@@ -16292,7 +16338,7 @@ router.get("/dashboard/storage", async (c) => {
16292
16338
  return c.html('<div class="text-red-500">Failed to load storage information</div>');
16293
16339
  }
16294
16340
  });
16295
- router.get("/dashboard/recent-activity", async (c) => {
16341
+ router.get("/recent-activity", async (c) => {
16296
16342
  try {
16297
16343
  const db = c.env.DB;
16298
16344
  const limit = parseInt(c.req.query("limit") || "5");
@@ -16344,25 +16390,14 @@ router.get("/dashboard/recent-activity", async (c) => {
16344
16390
  }
16345
16391
  });
16346
16392
  router.get("/api/metrics", async (c) => {
16347
- try {
16348
- const { metricsTracker } = await import('../utils/metrics-tracker');
16349
- return c.json({
16350
- requestsPerSecond: metricsTracker.getRequestsPerSecond(),
16351
- totalRequests: metricsTracker.getTotalRequests(),
16352
- averageRPS: metricsTracker.getAverageRPS(),
16353
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
16354
- });
16355
- } catch (error) {
16356
- console.error("Error fetching metrics:", error);
16357
- return c.json({
16358
- requestsPerSecond: 0,
16359
- totalRequests: 0,
16360
- averageRPS: 0,
16361
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
16362
- });
16363
- }
16393
+ return c.json({
16394
+ requestsPerSecond: metricsTracker.getRequestsPerSecond(),
16395
+ totalRequests: metricsTracker.getTotalRequests(),
16396
+ averageRPS: Number(metricsTracker.getAverageRPS().toFixed(2)),
16397
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16398
+ });
16364
16399
  });
16365
- router.get("/dashboard/system-status", async (c) => {
16400
+ router.get("/system-status", async (c) => {
16366
16401
  try {
16367
16402
  const html9 = `
16368
16403
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
@@ -17683,7 +17718,8 @@ function renderCollectionFormPage(data) {
17683
17718
 
17684
17719
  // src/routes/admin-collections.ts
17685
17720
  var adminCollectionsRoutes = new Hono();
17686
- adminCollectionsRoutes.get("/collections", async (c) => {
17721
+ adminCollectionsRoutes.use("*", requireAuth());
17722
+ adminCollectionsRoutes.get("/", async (c) => {
17687
17723
  try {
17688
17724
  const user = c.get("user");
17689
17725
  const db = c.env.DB;
@@ -17738,7 +17774,7 @@ adminCollectionsRoutes.get("/collections", async (c) => {
17738
17774
  return c.html(html`<p>Error loading collections</p>`);
17739
17775
  }
17740
17776
  });
17741
- adminCollectionsRoutes.get("/collections/new", (c) => {
17777
+ adminCollectionsRoutes.get("/new", (c) => {
17742
17778
  const user = c.get("user");
17743
17779
  const formData = {
17744
17780
  isEdit: false,
@@ -17751,7 +17787,7 @@ adminCollectionsRoutes.get("/collections/new", (c) => {
17751
17787
  };
17752
17788
  return c.html(renderCollectionFormPage(formData));
17753
17789
  });
17754
- adminCollectionsRoutes.post("/collections", async (c) => {
17790
+ adminCollectionsRoutes.post("/", async (c) => {
17755
17791
  try {
17756
17792
  const formData = await c.req.formData();
17757
17793
  const name = formData.get("name");
@@ -17870,7 +17906,7 @@ adminCollectionsRoutes.post("/collections", async (c) => {
17870
17906
  }
17871
17907
  }
17872
17908
  });
17873
- adminCollectionsRoutes.get("/collections/:id", async (c) => {
17909
+ adminCollectionsRoutes.get("/:id", async (c) => {
17874
17910
  try {
17875
17911
  const id = c.req.param("id");
17876
17912
  const user = c.get("user");
@@ -17938,7 +17974,7 @@ adminCollectionsRoutes.get("/collections/:id", async (c) => {
17938
17974
  return c.html(renderCollectionFormPage(formData));
17939
17975
  }
17940
17976
  });
17941
- adminCollectionsRoutes.put("/collections/:id", async (c) => {
17977
+ adminCollectionsRoutes.put("/:id", async (c) => {
17942
17978
  try {
17943
17979
  const id = c.req.param("id");
17944
17980
  const formData = await c.req.formData();
@@ -17972,7 +18008,7 @@ adminCollectionsRoutes.put("/collections/:id", async (c) => {
17972
18008
  `);
17973
18009
  }
17974
18010
  });
17975
- adminCollectionsRoutes.delete("/collections/:id", async (c) => {
18011
+ adminCollectionsRoutes.delete("/:id", async (c) => {
17976
18012
  try {
17977
18013
  const id = c.req.param("id");
17978
18014
  const db = c.env.DB;
@@ -18003,7 +18039,7 @@ adminCollectionsRoutes.delete("/collections/:id", async (c) => {
18003
18039
  `);
18004
18040
  }
18005
18041
  });
18006
- adminCollectionsRoutes.post("/collections/:id/fields", async (c) => {
18042
+ adminCollectionsRoutes.post("/:id/fields", async (c) => {
18007
18043
  try {
18008
18044
  const collectionId = c.req.param("id");
18009
18045
  const formData = await c.req.formData();
@@ -18056,7 +18092,7 @@ adminCollectionsRoutes.post("/collections/:id/fields", async (c) => {
18056
18092
  return c.json({ success: false, error: "Failed to add field." });
18057
18093
  }
18058
18094
  });
18059
- adminCollectionsRoutes.put("/collections/:collectionId/fields/:fieldId", async (c) => {
18095
+ adminCollectionsRoutes.put("/:collectionId/fields/:fieldId", async (c) => {
18060
18096
  try {
18061
18097
  const fieldId = c.req.param("fieldId");
18062
18098
  const formData = await c.req.formData();
@@ -18080,7 +18116,7 @@ adminCollectionsRoutes.put("/collections/:collectionId/fields/:fieldId", async (
18080
18116
  return c.json({ success: false, error: "Failed to update field." });
18081
18117
  }
18082
18118
  });
18083
- adminCollectionsRoutes.delete("/collections/:collectionId/fields/:fieldId", async (c) => {
18119
+ adminCollectionsRoutes.delete("/:collectionId/fields/:fieldId", async (c) => {
18084
18120
  try {
18085
18121
  const fieldId = c.req.param("fieldId");
18086
18122
  const db = c.env.DB;
@@ -18092,7 +18128,7 @@ adminCollectionsRoutes.delete("/collections/:collectionId/fields/:fieldId", asyn
18092
18128
  return c.json({ success: false, error: "Failed to delete field." });
18093
18129
  }
18094
18130
  });
18095
- adminCollectionsRoutes.post("/collections/:collectionId/fields/reorder", async (c) => {
18131
+ adminCollectionsRoutes.post("/:collectionId/fields/reorder", async (c) => {
18096
18132
  try {
18097
18133
  const body = await c.req.json();
18098
18134
  const fieldIds = body.fieldIds;
@@ -18213,7 +18249,7 @@ function renderSettingsPage(data) {
18213
18249
  // Migration functions
18214
18250
  window.refreshMigrationStatus = async function() {
18215
18251
  try {
18216
- const response = await fetch('/admin/api/migrations/status');
18252
+ const response = await fetch('/admin/settings/api/migrations/status');
18217
18253
  const result = await response.json();
18218
18254
 
18219
18255
  if (result.success) {
@@ -18241,7 +18277,7 @@ function renderSettingsPage(data) {
18241
18277
  btn.innerHTML = 'Running...';
18242
18278
 
18243
18279
  try {
18244
- const response = await fetch('/admin/api/migrations/run', {
18280
+ const response = await fetch('/admin/settings/api/migrations/run', {
18245
18281
  method: 'POST'
18246
18282
  });
18247
18283
  const result = await response.json();
@@ -18262,7 +18298,7 @@ function renderSettingsPage(data) {
18262
18298
 
18263
18299
  window.validateSchema = async function() {
18264
18300
  try {
18265
- const response = await fetch('/admin/api/migrations/validate');
18301
+ const response = await fetch('/admin/settings/api/migrations/validate');
18266
18302
  const result = await response.json();
18267
18303
 
18268
18304
  if (result.success) {
@@ -18340,7 +18376,7 @@ function renderSettingsPage(data) {
18340
18376
  // Database Tools functions
18341
18377
  window.refreshDatabaseStats = async function() {
18342
18378
  try {
18343
- const response = await fetch('/admin/database-tools/api/stats');
18379
+ const response = await fetch('/admin/settings/api/database-tools/stats');
18344
18380
  const result = await response.json();
18345
18381
 
18346
18382
  if (result.success) {
@@ -18361,7 +18397,7 @@ function renderSettingsPage(data) {
18361
18397
  btn.innerHTML = 'Creating Backup...';
18362
18398
 
18363
18399
  try {
18364
- const response = await fetch('/admin/database-tools/api/backup', {
18400
+ const response = await fetch('/admin/settings/api/database-tools/backup', {
18365
18401
  method: 'POST'
18366
18402
  });
18367
18403
  const result = await response.json();
@@ -18400,7 +18436,7 @@ function renderSettingsPage(data) {
18400
18436
  btn.innerHTML = 'Truncating...';
18401
18437
 
18402
18438
  try {
18403
- const response = await fetch('/admin/database-tools/api/truncate', {
18439
+ const response = await fetch('/admin/settings/api/database-tools/truncate', {
18404
18440
  method: 'POST',
18405
18441
  headers: {
18406
18442
  'Content-Type': 'application/json'
@@ -18431,7 +18467,7 @@ function renderSettingsPage(data) {
18431
18467
 
18432
18468
  window.validateDatabase = async function() {
18433
18469
  try {
18434
- const response = await fetch('/admin/database-tools/api/validate');
18470
+ const response = await fetch('/admin/settings/api/database-tools/validate');
18435
18471
  const result = await response.json();
18436
18472
 
18437
18473
  if (result.success) {
@@ -19285,7 +19321,7 @@ function renderMigrationSettings(settings) {
19285
19321
  if (typeof refreshMigrationStatus === 'undefined') {
19286
19322
  window.refreshMigrationStatus = async function() {
19287
19323
  try {
19288
- const response = await fetch('/admin/api/migrations/status');
19324
+ const response = await fetch('/admin/settings/api/migrations/status');
19289
19325
  const result = await response.json();
19290
19326
 
19291
19327
  if (result.success) {
@@ -19539,6 +19575,7 @@ function renderDatabaseToolsSettings(settings) {
19539
19575
 
19540
19576
  // src/routes/admin-settings.ts
19541
19577
  var adminSettingsRoutes = new Hono();
19578
+ adminSettingsRoutes.use("*", requireAuth());
19542
19579
  function getMockSettings(user) {
19543
19580
  return {
19544
19581
  general: {
@@ -19597,10 +19634,10 @@ function getMockSettings(user) {
19597
19634
  }
19598
19635
  };
19599
19636
  }
19600
- adminSettingsRoutes.get("/settings", (c) => {
19637
+ adminSettingsRoutes.get("/", (c) => {
19601
19638
  return c.redirect("/admin/settings/general");
19602
19639
  });
19603
- adminSettingsRoutes.get("/settings/general", (c) => {
19640
+ adminSettingsRoutes.get("/general", (c) => {
19604
19641
  const user = c.get("user");
19605
19642
  const pageData = {
19606
19643
  user: user ? {
@@ -19614,7 +19651,7 @@ adminSettingsRoutes.get("/settings/general", (c) => {
19614
19651
  };
19615
19652
  return c.html(renderSettingsPage(pageData));
19616
19653
  });
19617
- adminSettingsRoutes.get("/settings/appearance", (c) => {
19654
+ adminSettingsRoutes.get("/appearance", (c) => {
19618
19655
  const user = c.get("user");
19619
19656
  const pageData = {
19620
19657
  user: user ? {
@@ -19628,7 +19665,7 @@ adminSettingsRoutes.get("/settings/appearance", (c) => {
19628
19665
  };
19629
19666
  return c.html(renderSettingsPage(pageData));
19630
19667
  });
19631
- adminSettingsRoutes.get("/settings/security", (c) => {
19668
+ adminSettingsRoutes.get("/security", (c) => {
19632
19669
  const user = c.get("user");
19633
19670
  const pageData = {
19634
19671
  user: user ? {
@@ -19642,7 +19679,7 @@ adminSettingsRoutes.get("/settings/security", (c) => {
19642
19679
  };
19643
19680
  return c.html(renderSettingsPage(pageData));
19644
19681
  });
19645
- adminSettingsRoutes.get("/settings/notifications", (c) => {
19682
+ adminSettingsRoutes.get("/notifications", (c) => {
19646
19683
  const user = c.get("user");
19647
19684
  const pageData = {
19648
19685
  user: user ? {
@@ -19656,7 +19693,7 @@ adminSettingsRoutes.get("/settings/notifications", (c) => {
19656
19693
  };
19657
19694
  return c.html(renderSettingsPage(pageData));
19658
19695
  });
19659
- adminSettingsRoutes.get("/settings/storage", (c) => {
19696
+ adminSettingsRoutes.get("/storage", (c) => {
19660
19697
  const user = c.get("user");
19661
19698
  const pageData = {
19662
19699
  user: user ? {
@@ -19670,7 +19707,7 @@ adminSettingsRoutes.get("/settings/storage", (c) => {
19670
19707
  };
19671
19708
  return c.html(renderSettingsPage(pageData));
19672
19709
  });
19673
- adminSettingsRoutes.get("/settings/migrations", (c) => {
19710
+ adminSettingsRoutes.get("/migrations", (c) => {
19674
19711
  const user = c.get("user");
19675
19712
  const pageData = {
19676
19713
  user: user ? {
@@ -19684,7 +19721,7 @@ adminSettingsRoutes.get("/settings/migrations", (c) => {
19684
19721
  };
19685
19722
  return c.html(renderSettingsPage(pageData));
19686
19723
  });
19687
- adminSettingsRoutes.get("/settings/database-tools", (c) => {
19724
+ adminSettingsRoutes.get("/database-tools", (c) => {
19688
19725
  const user = c.get("user");
19689
19726
  const pageData = {
19690
19727
  user: user ? {
@@ -19757,7 +19794,139 @@ adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
19757
19794
  }, 500);
19758
19795
  }
19759
19796
  });
19760
- adminSettingsRoutes.post("/settings", async (c) => {
19797
+ adminSettingsRoutes.get("/api/database-tools/stats", async (c) => {
19798
+ try {
19799
+ const db = c.env.DB;
19800
+ const tablesQuery = await db.prepare(`
19801
+ SELECT name FROM sqlite_master
19802
+ WHERE type='table'
19803
+ AND name NOT LIKE 'sqlite_%'
19804
+ AND name NOT LIKE '_cf_%'
19805
+ ORDER BY name
19806
+ `).all();
19807
+ const tables = tablesQuery.results || [];
19808
+ let totalRows = 0;
19809
+ const tableStats = await Promise.all(
19810
+ tables.map(async (table) => {
19811
+ try {
19812
+ const countResult = await db.prepare(`SELECT COUNT(*) as count FROM ${table.name}`).first();
19813
+ const rowCount = countResult?.count || 0;
19814
+ totalRows += rowCount;
19815
+ return {
19816
+ name: table.name,
19817
+ rowCount
19818
+ };
19819
+ } catch (error) {
19820
+ console.error(`Error counting rows in ${table.name}:`, error);
19821
+ return {
19822
+ name: table.name,
19823
+ rowCount: 0
19824
+ };
19825
+ }
19826
+ })
19827
+ );
19828
+ const estimatedSizeBytes = totalRows * 1024;
19829
+ const databaseSizeMB = (estimatedSizeBytes / (1024 * 1024)).toFixed(2);
19830
+ return c.json({
19831
+ success: true,
19832
+ data: {
19833
+ totalTables: tables.length,
19834
+ totalRows,
19835
+ databaseSize: `${databaseSizeMB} MB (estimated)`,
19836
+ tables: tableStats
19837
+ }
19838
+ });
19839
+ } catch (error) {
19840
+ console.error("Error fetching database stats:", error);
19841
+ return c.json({
19842
+ success: false,
19843
+ error: "Failed to fetch database statistics"
19844
+ }, 500);
19845
+ }
19846
+ });
19847
+ adminSettingsRoutes.get("/api/database-tools/validate", async (c) => {
19848
+ try {
19849
+ const db = c.env.DB;
19850
+ const integrityResult = await db.prepare("PRAGMA integrity_check").first();
19851
+ const isValid = integrityResult?.integrity_check === "ok";
19852
+ return c.json({
19853
+ success: true,
19854
+ data: {
19855
+ valid: isValid,
19856
+ message: isValid ? "Database integrity check passed" : "Database integrity check failed"
19857
+ }
19858
+ });
19859
+ } catch (error) {
19860
+ console.error("Error validating database:", error);
19861
+ return c.json({
19862
+ success: false,
19863
+ error: "Failed to validate database"
19864
+ }, 500);
19865
+ }
19866
+ });
19867
+ adminSettingsRoutes.post("/api/database-tools/backup", async (c) => {
19868
+ try {
19869
+ const user = c.get("user");
19870
+ if (!user || user.role !== "admin") {
19871
+ return c.json({
19872
+ success: false,
19873
+ error: "Unauthorized. Admin access required."
19874
+ }, 403);
19875
+ }
19876
+ return c.json({
19877
+ success: true,
19878
+ message: "Database backup feature coming soon. Use Cloudflare Dashboard for backups."
19879
+ });
19880
+ } catch (error) {
19881
+ console.error("Error creating backup:", error);
19882
+ return c.json({
19883
+ success: false,
19884
+ error: "Failed to create backup"
19885
+ }, 500);
19886
+ }
19887
+ });
19888
+ adminSettingsRoutes.post("/api/database-tools/truncate", async (c) => {
19889
+ try {
19890
+ const user = c.get("user");
19891
+ if (!user || user.role !== "admin") {
19892
+ return c.json({
19893
+ success: false,
19894
+ error: "Unauthorized. Admin access required."
19895
+ }, 403);
19896
+ }
19897
+ const body = await c.req.json();
19898
+ const tablesToTruncate = body.tables || [];
19899
+ if (!Array.isArray(tablesToTruncate) || tablesToTruncate.length === 0) {
19900
+ return c.json({
19901
+ success: false,
19902
+ error: "No tables specified for truncation"
19903
+ }, 400);
19904
+ }
19905
+ const db = c.env.DB;
19906
+ const results = [];
19907
+ for (const tableName of tablesToTruncate) {
19908
+ try {
19909
+ await db.prepare(`DELETE FROM ${tableName}`).run();
19910
+ results.push({ table: tableName, success: true });
19911
+ } catch (error) {
19912
+ console.error(`Error truncating ${tableName}:`, error);
19913
+ results.push({ table: tableName, success: false, error: String(error) });
19914
+ }
19915
+ }
19916
+ return c.json({
19917
+ success: true,
19918
+ message: `Truncated ${results.filter((r) => r.success).length} of ${tablesToTruncate.length} tables`,
19919
+ results
19920
+ });
19921
+ } catch (error) {
19922
+ console.error("Error truncating tables:", error);
19923
+ return c.json({
19924
+ success: false,
19925
+ error: "Failed to truncate tables"
19926
+ }, 500);
19927
+ }
19928
+ });
19929
+ adminSettingsRoutes.post("/", async (c) => {
19761
19930
  try {
19762
19931
  const formData = await c.req.formData();
19763
19932
  return c.html(html`
@@ -19809,5 +19978,5 @@ var ROUTES_INFO = {
19809
19978
  };
19810
19979
 
19811
19980
  export { ROUTES_INFO, adminCheckboxRoutes, adminCollectionsRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_code_examples_default, admin_content_default, admin_faq_default, admin_testimonials_default, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, router, userRoutes };
19812
- //# sourceMappingURL=chunk-QSF34IYQ.js.map
19813
- //# sourceMappingURL=chunk-QSF34IYQ.js.map
19981
+ //# sourceMappingURL=chunk-PPUKPNTP.js.map
19982
+ //# sourceMappingURL=chunk-PPUKPNTP.js.map