@sonicjs-cms/core 2.0.0-alpha.11 → 2.0.0-alpha.12

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.
@@ -3,12 +3,11 @@
3
3
  var chunkAGOE25LF_cjs = require('./chunk-AGOE25LF.cjs');
4
4
  var chunkBUKT6HP5_cjs = require('./chunk-BUKT6HP5.cjs');
5
5
  var chunkRNR4HA23_cjs = require('./chunk-RNR4HA23.cjs');
6
- var chunkLEGKJ5DI_cjs = require('./chunk-LEGKJ5DI.cjs');
6
+ var chunkET5I4GBD_cjs = require('./chunk-ET5I4GBD.cjs');
7
7
  var chunkRGCQSFKC_cjs = require('./chunk-RGCQSFKC.cjs');
8
8
  var hono = require('hono');
9
9
  var cors = require('hono/cors');
10
10
  var zod = require('zod');
11
- var validator = require('hono/validator');
12
11
  var cookie = require('hono/cookie');
13
12
  var html = require('hono/html');
14
13
 
@@ -1301,22 +1300,6 @@ apiSystemRoutes.get("/env", (c) => {
1301
1300
  });
1302
1301
  });
1303
1302
  var api_system_default = apiSystemRoutes;
1304
- var zValidator = (target, schema, hook, options) => (
1305
- // @ts-expect-error not typed well
1306
- validator.validator(target, async (value, c) => {
1307
- let validatorValue = value;
1308
- const result = (
1309
- // @ts-expect-error z4.$ZodType has safeParseAsync
1310
- await schema.safeParseAsync(validatorValue)
1311
- );
1312
- if (!result.success) {
1313
- return c.json(result, 400);
1314
- }
1315
- return result.data;
1316
- })
1317
- );
1318
-
1319
- // src/routes/admin-api.ts
1320
1303
  var adminApiRoutes = new hono.Hono();
1321
1304
  adminApiRoutes.use("*", chunkBUKT6HP5_cjs.requireAuth());
1322
1305
  adminApiRoutes.use("*", chunkBUKT6HP5_cjs.requireRole(["admin", "editor"]));
@@ -1554,131 +1537,133 @@ adminApiRoutes.get("/collections/:id", async (c) => {
1554
1537
  return c.json({ error: "Failed to fetch collection" }, 500);
1555
1538
  }
1556
1539
  });
1557
- adminApiRoutes.post(
1558
- "/collections",
1559
- zValidator("json", createCollectionSchema),
1560
- async (c) => {
1561
- try {
1562
- const validatedData = c.req.valid("json");
1563
- const db = c.env.DB;
1564
- const user = c.get("user");
1565
- const existingStmt = db.prepare("SELECT id FROM collections WHERE name = ?");
1566
- const existing = await existingStmt.bind(validatedData.name).first();
1567
- if (existing) {
1568
- return c.json({ error: "A collection with this name already exists" }, 400);
1569
- }
1570
- const basicSchema = {
1571
- type: "object",
1572
- properties: {
1573
- title: {
1574
- type: "string",
1575
- title: "Title",
1576
- required: true
1577
- },
1578
- content: {
1579
- type: "string",
1580
- title: "Content",
1581
- format: "richtext"
1582
- },
1583
- status: {
1584
- type: "string",
1585
- title: "Status",
1586
- enum: ["draft", "published", "archived"],
1587
- default: "draft"
1588
- }
1540
+ adminApiRoutes.post("/collections", async (c) => {
1541
+ try {
1542
+ const body = await c.req.json();
1543
+ const validation = createCollectionSchema.safeParse(body);
1544
+ if (!validation.success) {
1545
+ return c.json({ error: "Validation failed", details: validation.error.errors }, 400);
1546
+ }
1547
+ const validatedData = validation.data;
1548
+ const db = c.env.DB;
1549
+ const user = c.get("user");
1550
+ const existingStmt = db.prepare("SELECT id FROM collections WHERE name = ?");
1551
+ const existing = await existingStmt.bind(validatedData.name).first();
1552
+ if (existing) {
1553
+ return c.json({ error: "A collection with this name already exists" }, 400);
1554
+ }
1555
+ const basicSchema = {
1556
+ type: "object",
1557
+ properties: {
1558
+ title: {
1559
+ type: "string",
1560
+ title: "Title",
1561
+ required: true
1589
1562
  },
1590
- required: ["title"]
1591
- };
1592
- const collectionId = crypto.randomUUID();
1593
- const now = Date.now();
1594
- const insertStmt = db.prepare(`
1563
+ content: {
1564
+ type: "string",
1565
+ title: "Content",
1566
+ format: "richtext"
1567
+ },
1568
+ status: {
1569
+ type: "string",
1570
+ title: "Status",
1571
+ enum: ["draft", "published", "archived"],
1572
+ default: "draft"
1573
+ }
1574
+ },
1575
+ required: ["title"]
1576
+ };
1577
+ const collectionId = crypto.randomUUID();
1578
+ const now = Date.now();
1579
+ const insertStmt = db.prepare(`
1595
1580
  INSERT INTO collections (id, name, display_name, description, schema, is_active, created_at, updated_at)
1596
1581
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1597
1582
  `);
1598
- await insertStmt.bind(
1599
- collectionId,
1600
- validatedData.name,
1601
- validatedData.display_name,
1602
- validatedData.description || null,
1603
- JSON.stringify(basicSchema),
1604
- 1,
1605
- // is_active
1606
- now,
1607
- now
1608
- ).run();
1609
- try {
1610
- await c.env.CACHE_KV.delete("cache:collections:all");
1611
- await c.env.CACHE_KV.delete(`cache:collection:${validatedData.name}`);
1612
- } catch (e) {
1613
- console.error("Error clearing cache:", e);
1614
- }
1615
- return c.json({
1616
- data: {
1617
- id: collectionId,
1618
- name: validatedData.name,
1619
- display_name: validatedData.display_name,
1620
- description: validatedData.description,
1621
- created_at: now
1622
- }
1623
- }, 201);
1624
- } catch (error) {
1625
- console.error("Error creating collection:", error);
1626
- return c.json({ error: "Failed to create collection" }, 500);
1583
+ await insertStmt.bind(
1584
+ collectionId,
1585
+ validatedData.name,
1586
+ validatedData.display_name,
1587
+ validatedData.description || null,
1588
+ JSON.stringify(basicSchema),
1589
+ 1,
1590
+ // is_active
1591
+ now,
1592
+ now
1593
+ ).run();
1594
+ try {
1595
+ await c.env.CACHE_KV.delete("cache:collections:all");
1596
+ await c.env.CACHE_KV.delete(`cache:collection:${validatedData.name}`);
1597
+ } catch (e) {
1598
+ console.error("Error clearing cache:", e);
1627
1599
  }
1600
+ return c.json({
1601
+ data: {
1602
+ id: collectionId,
1603
+ name: validatedData.name,
1604
+ display_name: validatedData.display_name,
1605
+ description: validatedData.description,
1606
+ created_at: now
1607
+ }
1608
+ }, 201);
1609
+ } catch (error) {
1610
+ console.error("Error creating collection:", error);
1611
+ return c.json({ error: "Failed to create collection" }, 500);
1628
1612
  }
1629
- );
1630
- adminApiRoutes.patch(
1631
- "/collections/:id",
1632
- zValidator("json", updateCollectionSchema),
1633
- async (c) => {
1634
- try {
1635
- const id = c.req.param("id");
1636
- const validatedData = c.req.valid("json");
1637
- const db = c.env.DB;
1638
- const checkStmt = db.prepare("SELECT * FROM collections WHERE id = ?");
1639
- const existing = await checkStmt.bind(id).first();
1640
- if (!existing) {
1641
- return c.json({ error: "Collection not found" }, 404);
1642
- }
1643
- const updateFields = [];
1644
- const updateParams = [];
1645
- if (validatedData.display_name !== void 0) {
1646
- updateFields.push("display_name = ?");
1647
- updateParams.push(validatedData.display_name);
1648
- }
1649
- if (validatedData.description !== void 0) {
1650
- updateFields.push("description = ?");
1651
- updateParams.push(validatedData.description);
1652
- }
1653
- if (validatedData.is_active !== void 0) {
1654
- updateFields.push("is_active = ?");
1655
- updateParams.push(validatedData.is_active ? 1 : 0);
1656
- }
1657
- if (updateFields.length === 0) {
1658
- return c.json({ error: "No fields to update" }, 400);
1659
- }
1660
- updateFields.push("updated_at = ?");
1661
- updateParams.push(Date.now());
1662
- updateParams.push(id);
1663
- const updateStmt = db.prepare(`
1613
+ });
1614
+ adminApiRoutes.patch("/collections/:id", async (c) => {
1615
+ try {
1616
+ const id = c.req.param("id");
1617
+ const body = await c.req.json();
1618
+ const validation = updateCollectionSchema.safeParse(body);
1619
+ if (!validation.success) {
1620
+ return c.json({ error: "Validation failed", details: validation.error.errors }, 400);
1621
+ }
1622
+ const validatedData = validation.data;
1623
+ const db = c.env.DB;
1624
+ const checkStmt = db.prepare("SELECT * FROM collections WHERE id = ?");
1625
+ const existing = await checkStmt.bind(id).first();
1626
+ if (!existing) {
1627
+ return c.json({ error: "Collection not found" }, 404);
1628
+ }
1629
+ const updateFields = [];
1630
+ const updateParams = [];
1631
+ if (validatedData.display_name !== void 0) {
1632
+ updateFields.push("display_name = ?");
1633
+ updateParams.push(validatedData.display_name);
1634
+ }
1635
+ if (validatedData.description !== void 0) {
1636
+ updateFields.push("description = ?");
1637
+ updateParams.push(validatedData.description);
1638
+ }
1639
+ if (validatedData.is_active !== void 0) {
1640
+ updateFields.push("is_active = ?");
1641
+ updateParams.push(validatedData.is_active ? 1 : 0);
1642
+ }
1643
+ if (updateFields.length === 0) {
1644
+ return c.json({ error: "No fields to update" }, 400);
1645
+ }
1646
+ updateFields.push("updated_at = ?");
1647
+ updateParams.push(Date.now());
1648
+ updateParams.push(id);
1649
+ const updateStmt = db.prepare(`
1664
1650
  UPDATE collections
1665
1651
  SET ${updateFields.join(", ")}
1666
1652
  WHERE id = ?
1667
1653
  `);
1668
- await updateStmt.bind(...updateParams).run();
1669
- try {
1670
- await c.env.CACHE_KV.delete("cache:collections:all");
1671
- await c.env.CACHE_KV.delete(`cache:collection:${existing.name}`);
1672
- } catch (e) {
1673
- console.error("Error clearing cache:", e);
1674
- }
1675
- return c.json({ message: "Collection updated successfully" });
1676
- } catch (error) {
1677
- console.error("Error updating collection:", error);
1678
- return c.json({ error: "Failed to update collection" }, 500);
1654
+ await updateStmt.bind(...updateParams).run();
1655
+ try {
1656
+ await c.env.CACHE_KV.delete("cache:collections:all");
1657
+ await c.env.CACHE_KV.delete(`cache:collection:${existing.name}`);
1658
+ } catch (e) {
1659
+ console.error("Error clearing cache:", e);
1679
1660
  }
1661
+ return c.json({ message: "Collection updated successfully" });
1662
+ } catch (error) {
1663
+ console.error("Error updating collection:", error);
1664
+ return c.json({ error: "Failed to update collection" }, 500);
1680
1665
  }
1681
- );
1666
+ });
1682
1667
  adminApiRoutes.delete("/collections/:id", async (c) => {
1683
1668
  try {
1684
1669
  const id = c.req.param("id");
@@ -1712,79 +1697,6 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
1712
1697
  });
1713
1698
  var admin_api_default = adminApiRoutes;
1714
1699
 
1715
- // src/templates/components/alert.template.ts
1716
- function renderAlert(data) {
1717
- const typeClasses = {
1718
- success: "bg-green-50 dark:bg-green-500/10 border border-green-600/20 dark:border-green-500/20",
1719
- error: "bg-error/10 border border-red-600/20 dark:border-red-500/20",
1720
- warning: "bg-amber-50 dark:bg-amber-500/10 border border-amber-600/20 dark:border-amber-500/20",
1721
- info: "bg-blue-50 dark:bg-blue-500/10 border border-blue-600/20 dark:border-blue-500/20"
1722
- };
1723
- const iconClasses = {
1724
- success: "text-green-600 dark:text-green-400",
1725
- error: "text-red-600 dark:text-red-400",
1726
- warning: "text-amber-600 dark:text-amber-400",
1727
- info: "text-blue-600 dark:text-blue-400"
1728
- };
1729
- const textClasses = {
1730
- success: "text-green-900 dark:text-green-300",
1731
- error: "text-red-900 dark:text-red-300",
1732
- warning: "text-amber-900 dark:text-amber-300",
1733
- info: "text-blue-900 dark:text-blue-300"
1734
- };
1735
- const messageTextClasses = {
1736
- success: "text-green-700 dark:text-green-400",
1737
- error: "text-red-700 dark:text-red-400",
1738
- warning: "text-amber-700 dark:text-amber-400",
1739
- info: "text-blue-700 dark:text-blue-400"
1740
- };
1741
- const icons = {
1742
- success: `<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />`,
1743
- error: `<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />`,
1744
- warning: `<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />`,
1745
- info: `<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />`
1746
- };
1747
- return `
1748
- <div class="rounded-lg p-4 ${typeClasses[data.type]} ${data.className || ""}" ${data.dismissible ? 'id="dismissible-alert"' : ""}>
1749
- <div class="flex">
1750
- ${data.icon !== false ? `
1751
- <div class="flex-shrink-0">
1752
- <svg class="h-5 w-5 ${iconClasses[data.type]}" viewBox="0 0 20 20" fill="currentColor">
1753
- ${icons[data.type]}
1754
- </svg>
1755
- </div>
1756
- ` : ""}
1757
- <div class="${data.icon !== false ? "ml-3" : ""}">
1758
- ${data.title ? `
1759
- <h3 class="text-sm font-semibold ${textClasses[data.type]}">
1760
- ${data.title}
1761
- </h3>
1762
- ` : ""}
1763
- <div class="${data.title ? "mt-1 text-sm" : "text-sm"} ${messageTextClasses[data.type]}">
1764
- <p>${data.message}</p>
1765
- </div>
1766
- </div>
1767
- ${data.dismissible ? `
1768
- <div class="ml-auto pl-3">
1769
- <div class="-mx-1.5 -my-1.5">
1770
- <button
1771
- type="button"
1772
- class="inline-flex rounded-md p-1.5 ${iconClasses[data.type]} hover:bg-opacity-20 focus:outline-none focus:ring-2 focus:ring-offset-2"
1773
- onclick="document.getElementById('dismissible-alert').remove()"
1774
- >
1775
- <span class="sr-only">Dismiss</span>
1776
- <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
1777
- <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
1778
- </svg>
1779
- </button>
1780
- </div>
1781
- </div>
1782
- ` : ""}
1783
- </div>
1784
- </div>
1785
- `;
1786
- }
1787
-
1788
1700
  // src/templates/pages/auth-login.template.ts
1789
1701
  function renderLoginPage(data, demoLoginActive = false) {
1790
1702
  return `
@@ -1842,8 +1754,8 @@ function renderLoginPage(data, demoLoginActive = false) {
1842
1754
  <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
1843
1755
  <div class="bg-zinc-900 shadow-sm ring-1 ring-white/10 rounded-xl px-6 py-8 sm:px-10">
1844
1756
  <!-- Alerts -->
1845
- ${data.error ? `<div class="mb-6">${renderAlert({ type: "error", message: data.error })}</div>` : ""}
1846
- ${data.message ? `<div class="mb-6">${renderAlert({ type: "success", message: data.message })}</div>` : ""}
1757
+ ${data.error ? `<div class="mb-6">${chunkET5I4GBD_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
1758
+ ${data.message ? `<div class="mb-6">${chunkET5I4GBD_cjs.renderAlert({ type: "success", message: data.message })}</div>` : ""}
1847
1759
 
1848
1760
  <!-- Form Response (HTMX target) -->
1849
1761
  <div id="form-response" class="mb-6"></div>
@@ -2007,7 +1919,7 @@ function renderRegisterPage(data) {
2007
1919
  <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
2008
1920
  <div class="bg-zinc-900 shadow-sm ring-1 ring-white/10 rounded-xl px-6 py-8 sm:px-10">
2009
1921
  <!-- Alerts -->
2010
- ${data.error ? `<div class="mb-6">${renderAlert({ type: "error", message: data.error })}</div>` : ""}
1922
+ ${data.error ? `<div class="mb-6">${chunkET5I4GBD_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
2011
1923
 
2012
1924
  <!-- Form -->
2013
1925
  <form
@@ -2424,58 +2336,59 @@ authRoutes.post(
2424
2336
  }
2425
2337
  }
2426
2338
  );
2427
- authRoutes.post(
2428
- "/login",
2429
- zValidator("json", loginSchema),
2430
- async (c) => {
2431
- try {
2432
- const { email, password } = c.req.valid("json");
2433
- const db = c.env.DB;
2434
- const normalizedEmail = email.toLowerCase();
2435
- const cache = chunkAGOE25LF_cjs.getCacheService(chunkAGOE25LF_cjs.CACHE_CONFIGS.user);
2436
- let user = await cache.get(cache.generateKey("user", `email:${normalizedEmail}`));
2437
- if (!user) {
2438
- user = await db.prepare("SELECT * FROM users WHERE email = ? AND is_active = 1").bind(normalizedEmail).first();
2439
- if (user) {
2440
- await cache.set(cache.generateKey("user", `email:${normalizedEmail}`), user);
2441
- await cache.set(cache.generateKey("user", user.id), user);
2442
- }
2443
- }
2444
- if (!user) {
2445
- return c.json({ error: "Invalid email or password" }, 401);
2446
- }
2447
- const isValidPassword = await chunkBUKT6HP5_cjs.AuthManager.verifyPassword(password, user.password_hash);
2448
- if (!isValidPassword) {
2449
- return c.json({ error: "Invalid email or password" }, 401);
2339
+ authRoutes.post("/login", async (c) => {
2340
+ try {
2341
+ const body = await c.req.json();
2342
+ const validation = loginSchema.safeParse(body);
2343
+ if (!validation.success) {
2344
+ return c.json({ error: "Validation failed", details: validation.error.errors }, 400);
2345
+ }
2346
+ const { email, password } = validation.data;
2347
+ const db = c.env.DB;
2348
+ const normalizedEmail = email.toLowerCase();
2349
+ const cache = chunkAGOE25LF_cjs.getCacheService(chunkAGOE25LF_cjs.CACHE_CONFIGS.user);
2350
+ let user = await cache.get(cache.generateKey("user", `email:${normalizedEmail}`));
2351
+ if (!user) {
2352
+ user = await db.prepare("SELECT * FROM users WHERE email = ? AND is_active = 1").bind(normalizedEmail).first();
2353
+ if (user) {
2354
+ await cache.set(cache.generateKey("user", `email:${normalizedEmail}`), user);
2355
+ await cache.set(cache.generateKey("user", user.id), user);
2450
2356
  }
2451
- const token = await chunkBUKT6HP5_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2452
- cookie.setCookie(c, "auth_token", token, {
2453
- httpOnly: true,
2454
- secure: true,
2455
- sameSite: "Strict",
2456
- maxAge: 60 * 60 * 24
2457
- // 24 hours
2458
- });
2459
- await db.prepare("UPDATE users SET last_login_at = ? WHERE id = ?").bind((/* @__PURE__ */ new Date()).getTime(), user.id).run();
2460
- await cache.delete(cache.generateKey("user", user.id));
2461
- await cache.delete(cache.generateKey("user", `email:${normalizedEmail}`));
2462
- return c.json({
2463
- user: {
2464
- id: user.id,
2465
- email: user.email,
2466
- username: user.username,
2467
- firstName: user.firstName,
2468
- lastName: user.lastName,
2469
- role: user.role
2470
- },
2471
- token
2472
- });
2473
- } catch (error) {
2474
- console.error("Login error:", error);
2475
- return c.json({ error: "Login failed" }, 500);
2476
2357
  }
2358
+ if (!user) {
2359
+ return c.json({ error: "Invalid email or password" }, 401);
2360
+ }
2361
+ const isValidPassword = await chunkBUKT6HP5_cjs.AuthManager.verifyPassword(password, user.password_hash);
2362
+ if (!isValidPassword) {
2363
+ return c.json({ error: "Invalid email or password" }, 401);
2364
+ }
2365
+ const token = await chunkBUKT6HP5_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2366
+ cookie.setCookie(c, "auth_token", token, {
2367
+ httpOnly: true,
2368
+ secure: true,
2369
+ sameSite: "Strict",
2370
+ maxAge: 60 * 60 * 24
2371
+ // 24 hours
2372
+ });
2373
+ await db.prepare("UPDATE users SET last_login_at = ? WHERE id = ?").bind((/* @__PURE__ */ new Date()).getTime(), user.id).run();
2374
+ await cache.delete(cache.generateKey("user", user.id));
2375
+ await cache.delete(cache.generateKey("user", `email:${normalizedEmail}`));
2376
+ return c.json({
2377
+ user: {
2378
+ id: user.id,
2379
+ email: user.email,
2380
+ username: user.username,
2381
+ firstName: user.firstName,
2382
+ lastName: user.lastName,
2383
+ role: user.role
2384
+ },
2385
+ token
2386
+ });
2387
+ } catch (error) {
2388
+ console.error("Login error:", error);
2389
+ return c.json({ error: "Login failed" }, 500);
2477
2390
  }
2478
- );
2391
+ });
2479
2392
  authRoutes.post("/logout", (c) => {
2480
2393
  cookie.setCookie(c, "auth_token", "", {
2481
2394
  httpOnly: true,
@@ -3225,7 +3138,7 @@ authRoutes.post("/reset-password", async (c) => {
3225
3138
  var auth_default = authRoutes;
3226
3139
 
3227
3140
  // src/templates/pages/admin-content-form.template.ts
3228
- chunkLEGKJ5DI_cjs.init_admin_layout_catalyst_template();
3141
+ chunkET5I4GBD_cjs.init_admin_layout_catalyst_template();
3229
3142
 
3230
3143
  // src/templates/components/dynamic-field.template.ts
3231
3144
  function renderDynamicField(field, options = {}) {
@@ -3564,98 +3477,18 @@ function escapeHtml2(text) {
3564
3477
  })[char] || char);
3565
3478
  }
3566
3479
 
3567
- // src/templates/components/confirmation-dialog.template.ts
3568
- function renderConfirmationDialog(options) {
3569
- const {
3570
- id,
3571
- title,
3572
- message,
3573
- confirmText = "Confirm",
3574
- cancelText = "Cancel",
3575
- confirmClass = "bg-red-500 hover:bg-red-400",
3576
- iconColor = "red",
3577
- onConfirm = ""
3578
- } = options;
3579
- const iconColorClasses = {
3580
- red: "bg-red-500/10 text-red-400",
3581
- yellow: "bg-yellow-500/10 text-yellow-400",
3582
- blue: "bg-blue-500/10 text-blue-400"
3583
- };
3584
- return `
3585
- <el-dialog>
3586
- <dialog
3587
- id="${id}"
3588
- aria-labelledby="${id}-title"
3589
- class="fixed inset-0 m-0 size-auto max-h-none max-w-none overflow-y-auto bg-transparent p-0 backdrop:bg-transparent"
3590
- >
3591
- <el-dialog-backdrop class="fixed inset-0 bg-gray-900/50 transition-opacity data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in"></el-dialog-backdrop>
3592
-
3593
- <div tabindex="0" class="flex min-h-full items-end justify-center p-4 text-center focus:outline focus:outline-0 sm:items-center sm:p-0">
3594
- <el-dialog-panel class="relative transform overflow-hidden rounded-lg bg-gray-800 px-4 pb-4 pt-5 text-left shadow-xl outline outline-1 -outline-offset-1 outline-white/10 transition-all data-[closed]:translate-y-4 data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in sm:my-8 sm:w-full sm:max-w-lg sm:p-6 data-[closed]:sm:translate-y-0 data-[closed]:sm:scale-95">
3595
- <div class="sm:flex sm:items-start">
3596
- <div class="mx-auto flex size-12 shrink-0 items-center justify-center rounded-full ${iconColorClasses[iconColor]} sm:mx-0 sm:size-10">
3597
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" data-slot="icon" aria-hidden="true" class="size-6">
3598
- <path d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" stroke-linecap="round" stroke-linejoin="round" />
3599
- </svg>
3600
- </div>
3601
- <div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
3602
- <h3 id="${id}-title" class="text-base font-semibold text-white">${title}</h3>
3603
- <div class="mt-2">
3604
- <p class="text-sm text-gray-400">${message}</p>
3605
- </div>
3606
- </div>
3607
- </div>
3608
- <div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
3609
- <button
3610
- type="button"
3611
- onclick="${onConfirm}; document.getElementById('${id}').close()"
3612
- command="close"
3613
- commandfor="${id}"
3614
- class="confirm-button inline-flex w-full justify-center rounded-md ${confirmClass} px-3 py-2 text-sm font-semibold text-white sm:ml-3 sm:w-auto"
3615
- >
3616
- ${confirmText}
3617
- </button>
3618
- <button
3619
- type="button"
3620
- command="close"
3621
- commandfor="${id}"
3622
- class="mt-3 inline-flex w-full justify-center rounded-md bg-white/10 px-3 py-2 text-sm font-semibold text-white ring-1 ring-inset ring-white/5 hover:bg-white/20 sm:mt-0 sm:w-auto"
3623
- >
3624
- ${cancelText}
3625
- </button>
3626
- </div>
3627
- </el-dialog-panel>
3628
- </div>
3629
- </dialog>
3630
- </el-dialog>
3631
- `;
3632
- }
3633
- function getConfirmationDialogScript() {
3634
- return `
3635
- <script src="https://cdn.jsdelivr.net/npm/@tailwindplus/elements@1" type="module"></script>
3636
- <script>
3637
- function showConfirmDialog(dialogId) {
3638
- const dialog = document.getElementById(dialogId);
3639
- if (dialog) {
3640
- dialog.showModal();
3641
- }
3642
- }
3643
- </script>
3644
- `;
3645
- }
3646
-
3647
- // src/templates/pages/admin-content-form.template.ts
3648
- function renderContentFormPage(data) {
3649
- const isEdit = data.isEdit || !!data.id;
3650
- const title = isEdit ? `Edit: ${data.title || "Content"}` : `New ${data.collection.display_name}`;
3651
- const backUrl = data.referrerParams ? `/admin/content?${data.referrerParams}` : `/admin/content?collection=${data.collection.id}`;
3652
- const coreFields = data.fields.filter((f) => ["title", "slug", "content"].includes(f.field_name));
3653
- const contentFields = data.fields.filter((f) => !["title", "slug", "content"].includes(f.field_name) && !f.field_name.startsWith("meta_"));
3654
- const metaFields = data.fields.filter((f) => f.field_name.startsWith("meta_"));
3655
- const getFieldValue = (fieldName) => {
3656
- if (fieldName === "title") return data.title || data.data?.[fieldName] || "";
3657
- if (fieldName === "slug") return data.slug || data.data?.[fieldName] || "";
3658
- return data.data?.[fieldName] || "";
3480
+ // src/templates/pages/admin-content-form.template.ts
3481
+ function renderContentFormPage(data) {
3482
+ const isEdit = data.isEdit || !!data.id;
3483
+ const title = isEdit ? `Edit: ${data.title || "Content"}` : `New ${data.collection.display_name}`;
3484
+ const backUrl = data.referrerParams ? `/admin/content?${data.referrerParams}` : `/admin/content?collection=${data.collection.id}`;
3485
+ const coreFields = data.fields.filter((f) => ["title", "slug", "content"].includes(f.field_name));
3486
+ const contentFields = data.fields.filter((f) => !["title", "slug", "content"].includes(f.field_name) && !f.field_name.startsWith("meta_"));
3487
+ const metaFields = data.fields.filter((f) => f.field_name.startsWith("meta_"));
3488
+ const getFieldValue = (fieldName) => {
3489
+ if (fieldName === "title") return data.title || data.data?.[fieldName] || "";
3490
+ if (fieldName === "slug") return data.slug || data.data?.[fieldName] || "";
3491
+ return data.data?.[fieldName] || "";
3659
3492
  };
3660
3493
  const coreFieldsHTML = coreFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
3661
3494
  value: getFieldValue(field.field_name),
@@ -3709,8 +3542,8 @@ function renderContentFormPage(data) {
3709
3542
  <!-- Form Content -->
3710
3543
  <div class="px-6 py-6">
3711
3544
  <div id="form-messages">
3712
- ${data.error ? renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
3713
- ${data.success ? renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
3545
+ ${data.error ? chunkET5I4GBD_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
3546
+ ${data.success ? chunkET5I4GBD_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
3714
3547
  </div>
3715
3548
 
3716
3549
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
@@ -3945,7 +3778,7 @@ function renderContentFormPage(data) {
3945
3778
  </div>
3946
3779
 
3947
3780
  <!-- Confirmation Dialogs -->
3948
- ${renderConfirmationDialog({
3781
+ ${chunkET5I4GBD_cjs.renderConfirmationDialog({
3949
3782
  id: "duplicate-content-confirm",
3950
3783
  title: "Duplicate Content",
3951
3784
  message: "Create a copy of this content?",
@@ -3956,7 +3789,7 @@ function renderContentFormPage(data) {
3956
3789
  onConfirm: "performDuplicateContent()"
3957
3790
  })}
3958
3791
 
3959
- ${renderConfirmationDialog({
3792
+ ${chunkET5I4GBD_cjs.renderConfirmationDialog({
3960
3793
  id: "delete-content-confirm",
3961
3794
  title: "Delete Content",
3962
3795
  message: "Are you sure you want to delete this content? This action cannot be undone.",
@@ -3967,7 +3800,7 @@ function renderContentFormPage(data) {
3967
3800
  onConfirm: `performDeleteContent('${data.id}')`
3968
3801
  })}
3969
3802
 
3970
- ${getConfirmationDialogScript()}
3803
+ ${chunkET5I4GBD_cjs.getConfirmationDialogScript()}
3971
3804
 
3972
3805
  <!-- TinyMCE CDN -->
3973
3806
  <script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/6/tinymce.min.js" referrerpolicy="origin"></script>
@@ -4255,387 +4088,11 @@ function renderContentFormPage(data) {
4255
4088
  content: pageContent,
4256
4089
  version: data.version
4257
4090
  };
4258
- return chunkLEGKJ5DI_cjs.renderAdminLayoutCatalyst(layoutData);
4259
- }
4260
-
4261
- // src/templates/pages/admin-content-list.template.ts
4262
- chunkLEGKJ5DI_cjs.init_admin_layout_catalyst_template();
4263
-
4264
- // src/templates/components/table.template.ts
4265
- function renderTable(data) {
4266
- const tableId = data.tableId || `table-${Math.random().toString(36).substr(2, 9)}`;
4267
- if (data.rows.length === 0) {
4268
- return `
4269
- <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-8 text-center">
4270
- <div class="text-zinc-500 dark:text-zinc-400">
4271
- <svg class="mx-auto h-12 w-12 text-zinc-400 dark:text-zinc-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
4272
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
4273
- </svg>
4274
- <p class="mt-2 text-sm text-zinc-500 dark:text-zinc-400">${data.emptyMessage || "No data available"}</p>
4275
- </div>
4276
- </div>
4277
- `;
4278
- }
4279
- return `
4280
- <div class="${data.className || ""}" id="${tableId}">
4281
- ${data.title ? `
4282
- <div class="px-4 sm:px-0 mb-4">
4283
- <h3 class="text-base font-semibold text-zinc-950 dark:text-white">${data.title}</h3>
4284
- </div>
4285
- ` : ""}
4286
- <div class="overflow-x-auto">
4287
- <table class="min-w-full sortable-table">
4288
- <thead>
4289
- <tr>
4290
- ${data.selectable ? `
4291
- <th class="px-4 py-3.5 text-center sm:pl-0">
4292
- <div class="flex items-center justify-center">
4293
- <div class="group grid size-4 grid-cols-1">
4294
- <input type="checkbox" id="select-all-${tableId}" class="col-start-1 row-start-1 appearance-none rounded border border-white/10 bg-white/5 checked:border-cyan-500 checked:bg-cyan-500 indeterminate:border-cyan-500 indeterminate:bg-cyan-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cyan-500 disabled:border-white/5 disabled:bg-white/10 disabled:checked:bg-white/10 forced-colors:appearance-auto row-checkbox" />
4295
- <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-white/25">
4296
- <path d="M3 8L6 11L11 3.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-[:checked]:opacity-100" />
4297
- <path d="M3 7H11" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-[:indeterminate]:opacity-100" />
4298
- </svg>
4299
- </div>
4300
- </div>
4301
- </th>
4302
- ` : ""}
4303
- ${data.columns.map((column, index) => {
4304
- const isFirst = index === 0 && !data.selectable;
4305
- const isLast = index === data.columns.length - 1;
4306
- return `
4307
- <th class="px-4 py-3.5 text-left text-sm font-semibold text-zinc-950 dark:text-white ${isFirst ? "sm:pl-0" : ""} ${isLast ? "sm:pr-0" : ""} ${column.className || ""}">
4308
- ${column.sortable ? `
4309
- <button
4310
- class="flex items-center gap-x-2 hover:text-zinc-700 dark:hover:text-zinc-300 transition-colors sort-btn text-left"
4311
- data-column="${column.key}"
4312
- data-sort-type="${column.sortType || "string"}"
4313
- data-sort-direction="none"
4314
- onclick="sortTable('${tableId}', '${column.key}', '${column.sortType || "string"}')"
4315
- >
4316
- <span>${column.label}</span>
4317
- <div class="sort-icons flex flex-col">
4318
- <svg class="w-3 h-3 sort-up opacity-30" fill="currentColor" viewBox="0 0 20 20">
4319
- <path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd" />
4320
- </svg>
4321
- <svg class="w-3 h-3 sort-down opacity-30 -mt-1" fill="currentColor" viewBox="0 0 20 20">
4322
- <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
4323
- </svg>
4324
- </div>
4325
- </button>
4326
- ` : column.label}
4327
- </th>
4328
- `;
4329
- }).join("")}
4330
- </tr>
4331
- </thead>
4332
- <tbody>
4333
- ${data.rows.map((row, rowIndex) => {
4334
- if (!row) return "";
4335
- const clickableClass = data.rowClickable ? "cursor-pointer" : "";
4336
- const clickHandler = data.rowClickable && data.rowClickUrl ? `onclick="window.location.href='${data.rowClickUrl(row)}'"` : "";
4337
- return `
4338
- <tr class="group border-t border-zinc-950/5 dark:border-white/5 hover:bg-gradient-to-r hover:from-cyan-50/50 hover:via-blue-50/30 hover:to-purple-50/50 dark:hover:from-cyan-900/20 dark:hover:via-blue-900/10 dark:hover:to-purple-900/20 hover:shadow-sm hover:shadow-cyan-500/5 dark:hover:shadow-cyan-400/5 transition-all duration-300 ${clickableClass}" ${clickHandler}>
4339
- ${data.selectable ? `
4340
- <td class="px-4 py-4 sm:pl-0" onclick="event.stopPropagation()">
4341
- <div class="flex items-center justify-center">
4342
- <div class="group grid size-4 grid-cols-1">
4343
- <input type="checkbox" value="${row.id || ""}" class="col-start-1 row-start-1 appearance-none rounded border border-white/10 bg-white/5 checked:border-cyan-500 checked:bg-cyan-500 indeterminate:border-cyan-500 indeterminate:bg-cyan-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cyan-500 disabled:border-white/5 disabled:bg-white/10 disabled:checked:bg-white/10 forced-colors:appearance-auto row-checkbox" />
4344
- <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-white/25">
4345
- <path d="M3 8L6 11L11 3.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-[:checked]:opacity-100" />
4346
- <path d="M3 7H11" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-[:indeterminate]:opacity-100" />
4347
- </svg>
4348
- </div>
4349
- </div>
4350
- </td>
4351
- ` : ""}
4352
- ${data.columns.map((column, colIndex) => {
4353
- const value = row[column.key];
4354
- const displayValue = column.render ? column.render(value, row) : value;
4355
- const stopPropagation = column.key === "actions" ? 'onclick="event.stopPropagation()"' : "";
4356
- const isFirst = colIndex === 0 && !data.selectable;
4357
- const isLast = colIndex === data.columns.length - 1;
4358
- return `
4359
- <td class="px-4 py-4 text-sm text-zinc-500 dark:text-zinc-400 ${isFirst ? "sm:pl-0 font-medium text-zinc-950 dark:text-white" : ""} ${isLast ? "sm:pr-0" : ""} ${column.className || ""}" ${stopPropagation}>
4360
- ${displayValue || ""}
4361
- </td>
4362
- `;
4363
- }).join("")}
4364
- </tr>
4365
- `;
4366
- }).join("")}
4367
- </tbody>
4368
- </table>
4369
- </div>
4370
-
4371
- <script>
4372
- // Table sorting functionality
4373
- window.sortTable = function(tableId, column, sortType) {
4374
- const tableContainer = document.getElementById(tableId);
4375
- const table = tableContainer.querySelector('.sortable-table');
4376
- const tbody = table.querySelector('tbody');
4377
- const rows = Array.from(tbody.querySelectorAll('tr'));
4378
- const headerBtn = table.querySelector(\`[data-column="\${column}"]\`);
4379
-
4380
- // Get current sort direction
4381
- let direction = headerBtn.getAttribute('data-sort-direction');
4382
-
4383
- // Reset all sort indicators
4384
- table.querySelectorAll('.sort-btn').forEach(btn => {
4385
- btn.setAttribute('data-sort-direction', 'none');
4386
- btn.querySelectorAll('.sort-up, .sort-down').forEach(icon => {
4387
- icon.classList.add('opacity-30');
4388
- icon.classList.remove('opacity-100', 'text-zinc-950', 'dark:text-white');
4389
- });
4390
- });
4391
-
4392
- // Determine new direction
4393
- if (direction === 'none' || direction === 'desc') {
4394
- direction = 'asc';
4395
- } else {
4396
- direction = 'desc';
4397
- }
4398
-
4399
- // Update current header
4400
- headerBtn.setAttribute('data-sort-direction', direction);
4401
- const upIcon = headerBtn.querySelector('.sort-up');
4402
- const downIcon = headerBtn.querySelector('.sort-down');
4403
-
4404
- if (direction === 'asc') {
4405
- upIcon.classList.remove('opacity-30');
4406
- upIcon.classList.add('opacity-100', 'text-zinc-950', 'dark:text-white');
4407
- downIcon.classList.add('opacity-30');
4408
- downIcon.classList.remove('opacity-100', 'text-zinc-950', 'dark:text-white');
4409
- } else {
4410
- downIcon.classList.remove('opacity-30');
4411
- downIcon.classList.add('opacity-100', 'text-zinc-950', 'dark:text-white');
4412
- upIcon.classList.add('opacity-30');
4413
- upIcon.classList.remove('opacity-100', 'text-zinc-950', 'dark:text-white');
4414
- }
4415
-
4416
- // Find column index (accounting for potential select column)
4417
- const headers = Array.from(table.querySelectorAll('th'));
4418
- const selectableOffset = table.querySelector('input[id^="select-all"]') ? 1 : 0;
4419
- const columnIndex = headers.findIndex(th => th.querySelector(\`[data-column="\${column}"]\`)) - selectableOffset;
4420
-
4421
- // Sort rows
4422
- rows.sort((a, b) => {
4423
- const aCell = a.children[columnIndex + selectableOffset];
4424
- const bCell = b.children[columnIndex + selectableOffset];
4425
-
4426
- if (!aCell || !bCell) return 0;
4427
-
4428
- let aValue = aCell.textContent.trim();
4429
- let bValue = bCell.textContent.trim();
4430
-
4431
- // Handle different sort types
4432
- switch (sortType) {
4433
- case 'number':
4434
- aValue = parseFloat(aValue.replace(/[^0-9.-]/g, '')) || 0;
4435
- bValue = parseFloat(bValue.replace(/[^0-9.-]/g, '')) || 0;
4436
- break;
4437
- case 'date':
4438
- aValue = new Date(aValue).getTime() || 0;
4439
- bValue = new Date(bValue).getTime() || 0;
4440
- break;
4441
- case 'boolean':
4442
- aValue = aValue.toLowerCase() === 'true' || aValue.toLowerCase() === 'published' || aValue.toLowerCase() === 'active';
4443
- bValue = bValue.toLowerCase() === 'true' || bValue.toLowerCase() === 'published' || bValue.toLowerCase() === 'active';
4444
- break;
4445
- default: // string
4446
- aValue = aValue.toLowerCase();
4447
- bValue = bValue.toLowerCase();
4448
- }
4449
-
4450
- if (aValue < bValue) return direction === 'asc' ? -1 : 1;
4451
- if (aValue > bValue) return direction === 'asc' ? 1 : -1;
4452
- return 0;
4453
- });
4454
-
4455
- // Re-append sorted rows
4456
- rows.forEach(row => tbody.appendChild(row));
4457
- };
4458
-
4459
- // Select all functionality
4460
- document.addEventListener('DOMContentLoaded', function() {
4461
- document.querySelectorAll('[id^="select-all"]').forEach(selectAll => {
4462
- selectAll.addEventListener('change', function() {
4463
- const tableId = this.id.replace('select-all-', '');
4464
- const table = document.getElementById(tableId);
4465
- if (table) {
4466
- const checkboxes = table.querySelectorAll('.row-checkbox');
4467
- checkboxes.forEach(checkbox => {
4468
- checkbox.checked = this.checked;
4469
- });
4470
- }
4471
- });
4472
- });
4473
- });
4474
- </script>
4475
- </div>
4476
- `;
4477
- }
4478
-
4479
- // src/templates/components/pagination.template.ts
4480
- function renderPagination(data) {
4481
- const shouldShowPagination = data.totalPages > 1 || data.showPageSizeSelector !== false && data.totalItems > 0;
4482
- if (!shouldShowPagination) {
4483
- return "";
4484
- }
4485
- const buildUrl = (page, limit) => {
4486
- const params = new URLSearchParams(data.queryParams || {});
4487
- params.set("page", page.toString());
4488
- if (data.itemsPerPage !== 20) {
4489
- params.set("limit", data.itemsPerPage.toString());
4490
- }
4491
- return `${data.baseUrl}?${params.toString()}`;
4492
- };
4493
- const buildPageSizeUrl = (limit) => {
4494
- const params = new URLSearchParams(data.queryParams || {});
4495
- params.set("page", "1");
4496
- params.set("limit", limit.toString());
4497
- return `${data.baseUrl}?${params.toString()}`;
4498
- };
4499
- const generatePageNumbers = () => {
4500
- const maxNumbers = data.maxPageNumbers || 5;
4501
- const half = Math.floor(maxNumbers / 2);
4502
- let start = Math.max(1, data.currentPage - half);
4503
- let end = Math.min(data.totalPages, start + maxNumbers - 1);
4504
- if (end - start + 1 < maxNumbers) {
4505
- start = Math.max(1, end - maxNumbers + 1);
4506
- }
4507
- const pages = [];
4508
- for (let i = start; i <= end; i++) {
4509
- pages.push(i);
4510
- }
4511
- return pages;
4512
- };
4513
- return `
4514
- <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-4 py-3 flex items-center justify-between mt-4">
4515
- ${data.totalPages > 1 ? `
4516
- <!-- Mobile Pagination -->
4517
- <div class="flex-1 flex justify-between sm:hidden">
4518
- ${data.currentPage > 1 ? `
4519
- <a href="${buildUrl(data.currentPage - 1)}" class="inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 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">
4520
- Previous
4521
- </a>
4522
- ` : `
4523
- <span class="inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-400 dark:text-zinc-600 shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 opacity-50 cursor-not-allowed">Previous</span>
4524
- `}
4525
-
4526
- ${data.currentPage < data.totalPages ? `
4527
- <a href="${buildUrl(data.currentPage + 1)}" class="inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 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">
4528
- Next
4529
- </a>
4530
- ` : `
4531
- <span class="inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-400 dark:text-zinc-600 shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 opacity-50 cursor-not-allowed">Next</span>
4532
- `}
4533
- </div>
4534
- ` : ""}
4535
-
4536
- <!-- Desktop Pagination -->
4537
- <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
4538
- <div class="flex items-center gap-4">
4539
- <p class="text-sm text-zinc-500 dark:text-zinc-400">
4540
- Showing <span class="font-medium text-zinc-950 dark:text-white">${data.startItem}</span> to
4541
- <span class="font-medium text-zinc-950 dark:text-white">${data.endItem}</span> of
4542
- <span class="font-medium text-zinc-950 dark:text-white">${data.totalItems}</span> results
4543
- </p>
4544
- ${data.showPageSizeSelector !== false ? `
4545
- <div class="flex items-center gap-2">
4546
- <label for="page-size" class="text-sm text-zinc-500 dark:text-zinc-400">Per page:</label>
4547
- <div class="grid grid-cols-1">
4548
- <select
4549
- id="page-size"
4550
- onchange="window.location.href = this.value"
4551
- 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-sm 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"
4552
- >
4553
- ${(data.pageSizeOptions || [10, 20, 50, 100]).map((size) => `
4554
- <option value="${buildPageSizeUrl(size)}" ${size === data.itemsPerPage ? "selected" : ""}>
4555
- ${size}
4556
- </option>
4557
- `).join("")}
4558
- </select>
4559
- <svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-4 self-center justify-self-end text-zinc-600 dark:text-zinc-400">
4560
- <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" />
4561
- </svg>
4562
- </div>
4563
- </div>
4564
- ` : ""}
4565
- </div>
4566
-
4567
- ${data.totalPages > 1 ? `
4568
- <div class="flex items-center gap-x-1">
4569
- <!-- Previous Button -->
4570
- ${data.currentPage > 1 ? `
4571
- <a href="${buildUrl(data.currentPage - 1)}"
4572
- class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 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">
4573
- Previous
4574
- </a>
4575
- ` : ""}
4576
-
4577
- <!-- Page Numbers -->
4578
- ${data.showPageNumbers !== false ? `
4579
- <!-- First page if not in range -->
4580
- ${(() => {
4581
- const pageNumbers = generatePageNumbers();
4582
- const firstPage = pageNumbers.length > 0 ? pageNumbers[0] : null;
4583
- return firstPage && firstPage > 1 ? `
4584
- <a href="${buildUrl(1)}"
4585
- class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 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">
4586
- 1
4587
- </a>
4588
- ${firstPage > 2 ? `
4589
- <span class="px-2 text-sm text-zinc-500 dark:text-zinc-400">...</span>
4590
- ` : ""}
4591
- ` : "";
4592
- })()}
4593
-
4594
- <!-- Page number buttons -->
4595
- ${generatePageNumbers().map((pageNum) => `
4596
- ${pageNum === data.currentPage ? `
4597
- <span class="rounded-lg bg-zinc-950 dark:bg-white px-3 py-2 text-sm font-semibold text-white dark:text-zinc-950">
4598
- ${pageNum}
4599
- </span>
4600
- ` : `
4601
- <a href="${buildUrl(pageNum)}"
4602
- class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 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">
4603
- ${pageNum}
4604
- </a>
4605
- `}
4606
- `).join("")}
4607
-
4608
- <!-- Last page if not in range -->
4609
- ${(() => {
4610
- const pageNumbers = generatePageNumbers();
4611
- const lastPageNum = pageNumbers.length > 0 ? pageNumbers.slice(-1)[0] : null;
4612
- return lastPageNum && lastPageNum < data.totalPages ? `
4613
- ${lastPageNum < data.totalPages - 1 ? `
4614
- <span class="px-2 text-sm text-zinc-500 dark:text-zinc-400">...</span>
4615
- ` : ""}
4616
- <a href="${buildUrl(data.totalPages)}"
4617
- class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 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">
4618
- ${data.totalPages}
4619
- </a>
4620
- ` : "";
4621
- })()}
4622
- ` : ""}
4623
-
4624
- <!-- Next Button -->
4625
- ${data.currentPage < data.totalPages ? `
4626
- <a href="${buildUrl(data.currentPage + 1)}"
4627
- class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 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">
4628
- Next
4629
- </a>
4630
- ` : ""}
4631
- </div>
4632
- ` : ""}
4633
- </div>
4634
- </div>
4635
- `;
4091
+ return chunkET5I4GBD_cjs.renderAdminLayoutCatalyst(layoutData);
4636
4092
  }
4637
4093
 
4638
4094
  // src/templates/pages/admin-content-list.template.ts
4095
+ chunkET5I4GBD_cjs.init_admin_layout_catalyst_template();
4639
4096
  function renderContentListPage(data) {
4640
4097
  const urlParams = new URLSearchParams();
4641
4098
  if (data.modelName && data.modelName !== "all") urlParams.set("model", data.modelName);
@@ -5031,8 +4488,8 @@ function renderContentListPage(data) {
5031
4488
 
5032
4489
  <!-- Content List -->
5033
4490
  <div id="content-list">
5034
- ${renderTable(tableData)}
5035
- ${renderPagination(paginationData)}
4491
+ ${chunkET5I4GBD_cjs.renderTable(tableData)}
4492
+ ${chunkET5I4GBD_cjs.renderPagination(paginationData)}
5036
4493
  </div>
5037
4494
 
5038
4495
  </div>
@@ -5241,7 +4698,7 @@ function renderContentListPage(data) {
5241
4698
  </script>
5242
4699
 
5243
4700
  <!-- Confirmation Dialog for Bulk Actions -->
5244
- ${renderConfirmationDialog({
4701
+ ${chunkET5I4GBD_cjs.renderConfirmationDialog({
5245
4702
  id: "bulk-action-confirm",
5246
4703
  title: "Confirm Bulk Action",
5247
4704
  message: "Are you sure you want to perform this action? This operation will affect multiple items.",
@@ -5253,7 +4710,7 @@ function renderContentListPage(data) {
5253
4710
  })}
5254
4711
 
5255
4712
  <!-- Confirmation Dialog Script -->
5256
- ${getConfirmationDialogScript()}
4713
+ ${chunkET5I4GBD_cjs.getConfirmationDialogScript()}
5257
4714
  `;
5258
4715
  const layoutData = {
5259
4716
  title: "Content Management",
@@ -5263,7 +4720,7 @@ function renderContentListPage(data) {
5263
4720
  version: data.version,
5264
4721
  content: pageContent
5265
4722
  };
5266
- return chunkLEGKJ5DI_cjs.renderAdminLayoutCatalyst(layoutData);
4723
+ return chunkET5I4GBD_cjs.renderAdminLayoutCatalyst(layoutData);
5267
4724
  }
5268
4725
 
5269
4726
  // src/templates/components/version-history.template.ts
@@ -6609,8 +6066,8 @@ function renderProfilePage(data) {
6609
6066
  </nav>
6610
6067
 
6611
6068
  <!-- Alert Messages -->
6612
- ${data.error ? renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
6613
- ${data.success ? renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
6069
+ ${data.error ? chunkET5I4GBD_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
6070
+ ${data.success ? chunkET5I4GBD_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
6614
6071
 
6615
6072
  <!-- Profile Form -->
6616
6073
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
@@ -6976,7 +6433,80 @@ function renderProfilePage(data) {
6976
6433
  user: data.user,
6977
6434
  content: pageContent
6978
6435
  };
6979
- return chunkLEGKJ5DI_cjs.renderAdminLayout(layoutData);
6436
+ return chunkET5I4GBD_cjs.renderAdminLayout(layoutData);
6437
+ }
6438
+
6439
+ // src/templates/components/alert.template.ts
6440
+ function renderAlert2(data) {
6441
+ const typeClasses = {
6442
+ success: "bg-green-50 dark:bg-green-500/10 border border-green-600/20 dark:border-green-500/20",
6443
+ error: "bg-error/10 border border-red-600/20 dark:border-red-500/20",
6444
+ warning: "bg-amber-50 dark:bg-amber-500/10 border border-amber-600/20 dark:border-amber-500/20",
6445
+ info: "bg-blue-50 dark:bg-blue-500/10 border border-blue-600/20 dark:border-blue-500/20"
6446
+ };
6447
+ const iconClasses = {
6448
+ success: "text-green-600 dark:text-green-400",
6449
+ error: "text-red-600 dark:text-red-400",
6450
+ warning: "text-amber-600 dark:text-amber-400",
6451
+ info: "text-blue-600 dark:text-blue-400"
6452
+ };
6453
+ const textClasses = {
6454
+ success: "text-green-900 dark:text-green-300",
6455
+ error: "text-red-900 dark:text-red-300",
6456
+ warning: "text-amber-900 dark:text-amber-300",
6457
+ info: "text-blue-900 dark:text-blue-300"
6458
+ };
6459
+ const messageTextClasses = {
6460
+ success: "text-green-700 dark:text-green-400",
6461
+ error: "text-red-700 dark:text-red-400",
6462
+ warning: "text-amber-700 dark:text-amber-400",
6463
+ info: "text-blue-700 dark:text-blue-400"
6464
+ };
6465
+ const icons = {
6466
+ success: `<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />`,
6467
+ error: `<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />`,
6468
+ warning: `<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />`,
6469
+ info: `<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />`
6470
+ };
6471
+ return `
6472
+ <div class="rounded-lg p-4 ${typeClasses[data.type]} ${data.className || ""}" ${data.dismissible ? 'id="dismissible-alert"' : ""}>
6473
+ <div class="flex">
6474
+ ${data.icon !== false ? `
6475
+ <div class="flex-shrink-0">
6476
+ <svg class="h-5 w-5 ${iconClasses[data.type]}" viewBox="0 0 20 20" fill="currentColor">
6477
+ ${icons[data.type]}
6478
+ </svg>
6479
+ </div>
6480
+ ` : ""}
6481
+ <div class="${data.icon !== false ? "ml-3" : ""}">
6482
+ ${data.title ? `
6483
+ <h3 class="text-sm font-semibold ${textClasses[data.type]}">
6484
+ ${data.title}
6485
+ </h3>
6486
+ ` : ""}
6487
+ <div class="${data.title ? "mt-1 text-sm" : "text-sm"} ${messageTextClasses[data.type]}">
6488
+ <p>${data.message}</p>
6489
+ </div>
6490
+ </div>
6491
+ ${data.dismissible ? `
6492
+ <div class="ml-auto pl-3">
6493
+ <div class="-mx-1.5 -my-1.5">
6494
+ <button
6495
+ type="button"
6496
+ class="inline-flex rounded-md p-1.5 ${iconClasses[data.type]} hover:bg-opacity-20 focus:outline-none focus:ring-2 focus:ring-offset-2"
6497
+ onclick="document.getElementById('dismissible-alert').remove()"
6498
+ >
6499
+ <span class="sr-only">Dismiss</span>
6500
+ <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
6501
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
6502
+ </svg>
6503
+ </button>
6504
+ </div>
6505
+ </div>
6506
+ ` : ""}
6507
+ </div>
6508
+ </div>
6509
+ `;
6980
6510
  }
6981
6511
 
6982
6512
  // src/templates/pages/admin-activity-logs.template.ts
@@ -7186,7 +6716,7 @@ function renderActivityLogsPage(data) {
7186
6716
  user: data.user,
7187
6717
  content: pageContent
7188
6718
  };
7189
- return chunkLEGKJ5DI_cjs.renderAdminLayout(layoutData);
6719
+ return chunkET5I4GBD_cjs.renderAdminLayout(layoutData);
7190
6720
  }
7191
6721
  function getActionBadgeClass(action) {
7192
6722
  if (action.includes("login") || action.includes("logout")) {
@@ -7206,17 +6736,99 @@ function formatAction(action) {
7206
6736
  }
7207
6737
 
7208
6738
  // src/templates/pages/admin-user-edit.template.ts
7209
- chunkLEGKJ5DI_cjs.init_admin_layout_catalyst_template();
7210
- function renderUserEditPage(data) {
7211
- const pageContent = `
7212
- <div>
7213
- <!-- Header -->
7214
- <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6">
7215
- <div>
7216
- <div class="flex items-center gap-3 mb-2">
7217
- <a href="/admin/users" class="text-zinc-500 dark:text-zinc-400 hover:text-zinc-950 dark:hover:text-white transition-colors">
7218
- <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
7219
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
6739
+ chunkET5I4GBD_cjs.init_admin_layout_catalyst_template();
6740
+
6741
+ // src/templates/components/confirmation-dialog.template.ts
6742
+ function renderConfirmationDialog2(options) {
6743
+ const {
6744
+ id,
6745
+ title,
6746
+ message,
6747
+ confirmText = "Confirm",
6748
+ cancelText = "Cancel",
6749
+ confirmClass = "bg-red-500 hover:bg-red-400",
6750
+ iconColor = "red",
6751
+ onConfirm = ""
6752
+ } = options;
6753
+ const iconColorClasses = {
6754
+ red: "bg-red-500/10 text-red-400",
6755
+ yellow: "bg-yellow-500/10 text-yellow-400",
6756
+ blue: "bg-blue-500/10 text-blue-400"
6757
+ };
6758
+ return `
6759
+ <el-dialog>
6760
+ <dialog
6761
+ id="${id}"
6762
+ aria-labelledby="${id}-title"
6763
+ class="fixed inset-0 m-0 size-auto max-h-none max-w-none overflow-y-auto bg-transparent p-0 backdrop:bg-transparent"
6764
+ >
6765
+ <el-dialog-backdrop class="fixed inset-0 bg-gray-900/50 transition-opacity data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in"></el-dialog-backdrop>
6766
+
6767
+ <div tabindex="0" class="flex min-h-full items-end justify-center p-4 text-center focus:outline focus:outline-0 sm:items-center sm:p-0">
6768
+ <el-dialog-panel class="relative transform overflow-hidden rounded-lg bg-gray-800 px-4 pb-4 pt-5 text-left shadow-xl outline outline-1 -outline-offset-1 outline-white/10 transition-all data-[closed]:translate-y-4 data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in sm:my-8 sm:w-full sm:max-w-lg sm:p-6 data-[closed]:sm:translate-y-0 data-[closed]:sm:scale-95">
6769
+ <div class="sm:flex sm:items-start">
6770
+ <div class="mx-auto flex size-12 shrink-0 items-center justify-center rounded-full ${iconColorClasses[iconColor]} sm:mx-0 sm:size-10">
6771
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" data-slot="icon" aria-hidden="true" class="size-6">
6772
+ <path d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" stroke-linecap="round" stroke-linejoin="round" />
6773
+ </svg>
6774
+ </div>
6775
+ <div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
6776
+ <h3 id="${id}-title" class="text-base font-semibold text-white">${title}</h3>
6777
+ <div class="mt-2">
6778
+ <p class="text-sm text-gray-400">${message}</p>
6779
+ </div>
6780
+ </div>
6781
+ </div>
6782
+ <div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
6783
+ <button
6784
+ type="button"
6785
+ onclick="${onConfirm}; document.getElementById('${id}').close()"
6786
+ command="close"
6787
+ commandfor="${id}"
6788
+ class="confirm-button inline-flex w-full justify-center rounded-md ${confirmClass} px-3 py-2 text-sm font-semibold text-white sm:ml-3 sm:w-auto"
6789
+ >
6790
+ ${confirmText}
6791
+ </button>
6792
+ <button
6793
+ type="button"
6794
+ command="close"
6795
+ commandfor="${id}"
6796
+ class="mt-3 inline-flex w-full justify-center rounded-md bg-white/10 px-3 py-2 text-sm font-semibold text-white ring-1 ring-inset ring-white/5 hover:bg-white/20 sm:mt-0 sm:w-auto"
6797
+ >
6798
+ ${cancelText}
6799
+ </button>
6800
+ </div>
6801
+ </el-dialog-panel>
6802
+ </div>
6803
+ </dialog>
6804
+ </el-dialog>
6805
+ `;
6806
+ }
6807
+ function getConfirmationDialogScript2() {
6808
+ return `
6809
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindplus/elements@1" type="module"></script>
6810
+ <script>
6811
+ function showConfirmDialog(dialogId) {
6812
+ const dialog = document.getElementById(dialogId);
6813
+ if (dialog) {
6814
+ dialog.showModal();
6815
+ }
6816
+ }
6817
+ </script>
6818
+ `;
6819
+ }
6820
+
6821
+ // src/templates/pages/admin-user-edit.template.ts
6822
+ function renderUserEditPage(data) {
6823
+ const pageContent = `
6824
+ <div>
6825
+ <!-- Header -->
6826
+ <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6">
6827
+ <div>
6828
+ <div class="flex items-center gap-3 mb-2">
6829
+ <a href="/admin/users" class="text-zinc-500 dark:text-zinc-400 hover:text-zinc-950 dark:hover:text-white transition-colors">
6830
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6831
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
7220
6832
  </svg>
7221
6833
  </a>
7222
6834
  <h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">Edit User</h1>
@@ -7245,8 +6857,8 @@ function renderUserEditPage(data) {
7245
6857
 
7246
6858
  <!-- Alert Messages -->
7247
6859
  <div id="form-messages">
7248
- ${data.error ? renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
7249
- ${data.success ? renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
6860
+ ${data.error ? chunkET5I4GBD_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
6861
+ ${data.success ? chunkET5I4GBD_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
7250
6862
  </div>
7251
6863
 
7252
6864
  <!-- User Edit Form -->
@@ -7519,7 +7131,7 @@ function renderUserEditPage(data) {
7519
7131
  </script>
7520
7132
 
7521
7133
  <!-- Confirmation Dialogs -->
7522
- ${renderConfirmationDialog({
7134
+ ${renderConfirmationDialog2({
7523
7135
  id: "delete-user-confirm",
7524
7136
  title: "Delete User",
7525
7137
  message: 'Are you sure you want to delete this user? Check the "Hard Delete" option to permanently remove all data from the database. This action cannot be undone!',
@@ -7530,7 +7142,7 @@ function renderUserEditPage(data) {
7530
7142
  onConfirm: "performDeleteUser()"
7531
7143
  })}
7532
7144
 
7533
- ${getConfirmationDialogScript()}
7145
+ ${getConfirmationDialogScript2()}
7534
7146
  `;
7535
7147
  const layoutData = {
7536
7148
  title: "Edit User",
@@ -7539,11 +7151,11 @@ function renderUserEditPage(data) {
7539
7151
  user: data.user,
7540
7152
  content: pageContent
7541
7153
  };
7542
- return chunkLEGKJ5DI_cjs.renderAdminLayoutCatalyst(layoutData);
7154
+ return chunkET5I4GBD_cjs.renderAdminLayoutCatalyst(layoutData);
7543
7155
  }
7544
7156
 
7545
7157
  // src/templates/pages/admin-user-new.template.ts
7546
- chunkLEGKJ5DI_cjs.init_admin_layout_catalyst_template();
7158
+ chunkET5I4GBD_cjs.init_admin_layout_catalyst_template();
7547
7159
  function renderUserNewPage(data) {
7548
7160
  const pageContent = `
7549
7161
  <div>
@@ -7582,8 +7194,8 @@ function renderUserNewPage(data) {
7582
7194
 
7583
7195
  <!-- Alert Messages -->
7584
7196
  <div id="form-messages">
7585
- ${data.error ? renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
7586
- ${data.success ? renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
7197
+ ${data.error ? chunkET5I4GBD_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
7198
+ ${data.success ? chunkET5I4GBD_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
7587
7199
  </div>
7588
7200
 
7589
7201
  <!-- User New Form -->
@@ -7827,11 +7439,11 @@ function renderUserNewPage(data) {
7827
7439
  user: data.user,
7828
7440
  content: pageContent
7829
7441
  };
7830
- return chunkLEGKJ5DI_cjs.renderAdminLayoutCatalyst(layoutData);
7442
+ return chunkET5I4GBD_cjs.renderAdminLayoutCatalyst(layoutData);
7831
7443
  }
7832
7444
 
7833
7445
  // src/templates/pages/admin-users-list.template.ts
7834
- chunkLEGKJ5DI_cjs.init_admin_layout_catalyst_template();
7446
+ chunkET5I4GBD_cjs.init_admin_layout_catalyst_template();
7835
7447
  function renderUsersListPage(data) {
7836
7448
  const columns = [
7837
7449
  {
@@ -7857,7 +7469,7 @@ function renderUsersListPage(data) {
7857
7469
  sortable: true,
7858
7470
  sortType: "string",
7859
7471
  render: (value, row) => {
7860
- const escapeHtml4 = (text) => text.replace(/[&<>"']/g, (char) => ({
7472
+ const escapeHtml7 = (text) => text.replace(/[&<>"']/g, (char) => ({
7861
7473
  "&": "&amp;",
7862
7474
  "<": "&lt;",
7863
7475
  ">": "&gt;",
@@ -7866,9 +7478,9 @@ function renderUsersListPage(data) {
7866
7478
  })[char] || char);
7867
7479
  const truncatedFirstName = row.firstName.length > 25 ? row.firstName.substring(0, 25) + "..." : row.firstName;
7868
7480
  const truncatedLastName = row.lastName.length > 25 ? row.lastName.substring(0, 25) + "..." : row.lastName;
7869
- const fullName = escapeHtml4(`${truncatedFirstName} ${truncatedLastName}`);
7481
+ const fullName = escapeHtml7(`${truncatedFirstName} ${truncatedLastName}`);
7870
7482
  const truncatedUsername = row.username.length > 100 ? row.username.substring(0, 100) + "..." : row.username;
7871
- const username = escapeHtml4(truncatedUsername);
7483
+ const username = escapeHtml7(truncatedUsername);
7872
7484
  const statusBadge = row.isActive ? '<span class="inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium bg-lime-50 dark:bg-lime-500/10 text-lime-700 dark:text-lime-300 ring-1 ring-inset ring-lime-700/10 dark:ring-lime-400/20 ml-2">Active</span>' : '<span class="inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium bg-red-50 dark:bg-red-500/10 text-red-700 dark:text-red-400 ring-1 ring-inset ring-red-700/10 dark:ring-red-500/20 ml-2">Inactive</span>';
7873
7485
  return `
7874
7486
  <div>
@@ -7884,14 +7496,14 @@ function renderUsersListPage(data) {
7884
7496
  sortable: true,
7885
7497
  sortType: "string",
7886
7498
  render: (value) => {
7887
- const escapeHtml4 = (text) => text.replace(/[&<>"']/g, (char) => ({
7499
+ const escapeHtml7 = (text) => text.replace(/[&<>"']/g, (char) => ({
7888
7500
  "&": "&amp;",
7889
7501
  "<": "&lt;",
7890
7502
  ">": "&gt;",
7891
7503
  '"': "&quot;",
7892
7504
  "'": "&#39;"
7893
7505
  })[char] || char);
7894
- const escapedEmail = escapeHtml4(value);
7506
+ const escapedEmail = escapeHtml7(value);
7895
7507
  return `<a href="mailto:${escapedEmail}" class="text-cyan-600 dark:text-cyan-400 hover:text-cyan-700 dark:hover:text-cyan-300 transition-colors">${escapedEmail}</a>`;
7896
7508
  }
7897
7509
  },
@@ -7982,8 +7594,8 @@ function renderUsersListPage(data) {
7982
7594
  </div>
7983
7595
 
7984
7596
  <!-- Alert Messages -->
7985
- ${data.error ? renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
7986
- ${data.success ? renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
7597
+ ${data.error ? chunkET5I4GBD_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
7598
+ ${data.success ? chunkET5I4GBD_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
7987
7599
 
7988
7600
  <!-- Stats -->
7989
7601
  <div class="mb-6">
@@ -8160,10 +7772,10 @@ function renderUsersListPage(data) {
8160
7772
  </div>
8161
7773
 
8162
7774
  <!-- Users Table -->
8163
- ${renderTable(tableData)}
7775
+ ${chunkET5I4GBD_cjs.renderTable(tableData)}
8164
7776
 
8165
7777
  <!-- Pagination -->
8166
- ${data.pagination ? renderPagination(data.pagination) : ""}
7778
+ ${data.pagination ? chunkET5I4GBD_cjs.renderPagination(data.pagination) : ""}
8167
7779
  </div>
8168
7780
 
8169
7781
  <script>
@@ -8213,7 +7825,7 @@ function renderUsersListPage(data) {
8213
7825
  </script>
8214
7826
 
8215
7827
  <!-- Confirmation Dialogs -->
8216
- ${renderConfirmationDialog({
7828
+ ${renderConfirmationDialog2({
8217
7829
  id: "toggle-user-status-confirm",
8218
7830
  title: "Toggle User Status",
8219
7831
  message: "Are you sure you want to activate/deactivate this user?",
@@ -8224,7 +7836,7 @@ function renderUsersListPage(data) {
8224
7836
  onConfirm: "performToggleUserStatus()"
8225
7837
  })}
8226
7838
 
8227
- ${getConfirmationDialogScript()}
7839
+ ${getConfirmationDialogScript2()}
8228
7840
  `;
8229
7841
  const layoutData = {
8230
7842
  title: "Users",
@@ -8234,7 +7846,7 @@ function renderUsersListPage(data) {
8234
7846
  version: data.version,
8235
7847
  content: pageContent
8236
7848
  };
8237
- return chunkLEGKJ5DI_cjs.renderAdminLayoutCatalyst(layoutData);
7849
+ return chunkET5I4GBD_cjs.renderAdminLayoutCatalyst(layoutData);
8238
7850
  }
8239
7851
 
8240
7852
  // src/routes/admin-users.ts
@@ -8345,7 +7957,7 @@ userRoutes.put("/profile", async (c) => {
8345
7957
  const language = formData.get("language")?.toString() || "en";
8346
7958
  const emailNotifications = formData.get("email_notifications") === "1";
8347
7959
  if (!firstName || !lastName || !username || !email) {
8348
- return c.html(renderAlert({
7960
+ return c.html(renderAlert2({
8349
7961
  type: "error",
8350
7962
  message: "First name, last name, username, and email are required.",
8351
7963
  dismissible: true
@@ -8353,7 +7965,7 @@ userRoutes.put("/profile", async (c) => {
8353
7965
  }
8354
7966
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
8355
7967
  if (!emailRegex.test(email)) {
8356
- return c.html(renderAlert({
7968
+ return c.html(renderAlert2({
8357
7969
  type: "error",
8358
7970
  message: "Please enter a valid email address.",
8359
7971
  dismissible: true
@@ -8365,9 +7977,9 @@ userRoutes.put("/profile", async (c) => {
8365
7977
  `);
8366
7978
  const existingUser = await checkStmt.bind(username, email, user.userId).first();
8367
7979
  if (existingUser) {
8368
- return c.html(renderAlert({
7980
+ return c.html(renderAlert2({
8369
7981
  type: "error",
8370
- message: "Username or email is already taken by another user.",
7982
+ message: "Username or email is already taken by another user!.",
8371
7983
  dismissible: true
8372
7984
  }));
8373
7985
  }
@@ -8401,14 +8013,14 @@ userRoutes.put("/profile", async (c) => {
8401
8013
  c.req.header("x-forwarded-for") || c.req.header("cf-connecting-ip"),
8402
8014
  c.req.header("user-agent")
8403
8015
  );
8404
- return c.html(renderAlert({
8016
+ return c.html(renderAlert2({
8405
8017
  type: "success",
8406
8018
  message: "Profile updated successfully!",
8407
8019
  dismissible: true
8408
8020
  }));
8409
8021
  } catch (error) {
8410
8022
  console.error("Profile update error:", error);
8411
- return c.html(renderAlert({
8023
+ return c.html(renderAlert2({
8412
8024
  type: "error",
8413
8025
  message: "Failed to update profile. Please try again.",
8414
8026
  dismissible: true
@@ -8422,7 +8034,7 @@ userRoutes.post("/profile/avatar", async (c) => {
8422
8034
  const formData = await c.req.formData();
8423
8035
  const avatarFile = formData.get("avatar");
8424
8036
  if (!avatarFile || !avatarFile.name) {
8425
- return c.html(renderAlert({
8037
+ return c.html(renderAlert2({
8426
8038
  type: "error",
8427
8039
  message: "Please select an image file.",
8428
8040
  dismissible: true
@@ -8430,7 +8042,7 @@ userRoutes.post("/profile/avatar", async (c) => {
8430
8042
  }
8431
8043
  const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
8432
8044
  if (!allowedTypes.includes(avatarFile.type)) {
8433
- return c.html(renderAlert({
8045
+ return c.html(renderAlert2({
8434
8046
  type: "error",
8435
8047
  message: "Please upload a valid image file (JPEG, PNG, GIF, or WebP).",
8436
8048
  dismissible: true
@@ -8438,7 +8050,7 @@ userRoutes.post("/profile/avatar", async (c) => {
8438
8050
  }
8439
8051
  const maxSize = 5 * 1024 * 1024;
8440
8052
  if (avatarFile.size > maxSize) {
8441
- return c.html(renderAlert({
8053
+ return c.html(renderAlert2({
8442
8054
  type: "error",
8443
8055
  message: "Image file must be smaller than 5MB.",
8444
8056
  dismissible: true
@@ -8460,14 +8072,14 @@ userRoutes.post("/profile/avatar", async (c) => {
8460
8072
  c.req.header("x-forwarded-for") || c.req.header("cf-connecting-ip"),
8461
8073
  c.req.header("user-agent")
8462
8074
  );
8463
- return c.html(renderAlert({
8075
+ return c.html(renderAlert2({
8464
8076
  type: "success",
8465
8077
  message: "Profile picture updated successfully!",
8466
8078
  dismissible: true
8467
8079
  }));
8468
8080
  } catch (error) {
8469
8081
  console.error("Avatar upload error:", error);
8470
- return c.html(renderAlert({
8082
+ return c.html(renderAlert2({
8471
8083
  type: "error",
8472
8084
  message: "Failed to upload profile picture. Please try again.",
8473
8085
  dismissible: true
@@ -8483,21 +8095,21 @@ userRoutes.post("/profile/password", async (c) => {
8483
8095
  const newPassword = formData.get("new_password")?.toString() || "";
8484
8096
  const confirmPassword = formData.get("confirm_password")?.toString() || "";
8485
8097
  if (!currentPassword || !newPassword || !confirmPassword) {
8486
- return c.html(renderAlert({
8098
+ return c.html(renderAlert2({
8487
8099
  type: "error",
8488
8100
  message: "All password fields are required.",
8489
8101
  dismissible: true
8490
8102
  }));
8491
8103
  }
8492
8104
  if (newPassword !== confirmPassword) {
8493
- return c.html(renderAlert({
8105
+ return c.html(renderAlert2({
8494
8106
  type: "error",
8495
8107
  message: "New passwords do not match.",
8496
8108
  dismissible: true
8497
8109
  }));
8498
8110
  }
8499
8111
  if (newPassword.length < 8) {
8500
- return c.html(renderAlert({
8112
+ return c.html(renderAlert2({
8501
8113
  type: "error",
8502
8114
  message: "New password must be at least 8 characters long.",
8503
8115
  dismissible: true
@@ -8508,7 +8120,7 @@ userRoutes.post("/profile/password", async (c) => {
8508
8120
  `);
8509
8121
  const userData = await userStmt.bind(user.userId).first();
8510
8122
  if (!userData) {
8511
- return c.html(renderAlert({
8123
+ return c.html(renderAlert2({
8512
8124
  type: "error",
8513
8125
  message: "User not found.",
8514
8126
  dismissible: true
@@ -8516,7 +8128,7 @@ userRoutes.post("/profile/password", async (c) => {
8516
8128
  }
8517
8129
  const validPassword = await chunkBUKT6HP5_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
8518
8130
  if (!validPassword) {
8519
- return c.html(renderAlert({
8131
+ return c.html(renderAlert2({
8520
8132
  type: "error",
8521
8133
  message: "Current password is incorrect.",
8522
8134
  dismissible: true
@@ -8548,14 +8160,14 @@ userRoutes.post("/profile/password", async (c) => {
8548
8160
  c.req.header("x-forwarded-for") || c.req.header("cf-connecting-ip"),
8549
8161
  c.req.header("user-agent")
8550
8162
  );
8551
- return c.html(renderAlert({
8163
+ return c.html(renderAlert2({
8552
8164
  type: "success",
8553
8165
  message: "Password updated successfully!",
8554
8166
  dismissible: true
8555
8167
  }));
8556
8168
  } catch (error) {
8557
8169
  console.error("Password change error:", error);
8558
- return c.html(renderAlert({
8170
+ return c.html(renderAlert2({
8559
8171
  type: "error",
8560
8172
  message: "Failed to update password. Please try again.",
8561
8173
  dismissible: true
@@ -8674,7 +8286,7 @@ userRoutes.get("/users", chunkBUKT6HP5_cjs.requirePermission("users.read"), asyn
8674
8286
  if (isApiRequest) {
8675
8287
  return c.json({ error: "Failed to load users" }, 500);
8676
8288
  }
8677
- return c.html(renderAlert({
8289
+ return c.html(renderAlert2({
8678
8290
  type: "error",
8679
8291
  message: "Failed to load users. Please try again.",
8680
8292
  dismissible: true
@@ -8695,7 +8307,7 @@ userRoutes.get("/users/new", chunkBUKT6HP5_cjs.requirePermission("users.create")
8695
8307
  return c.html(renderUserNewPage(pageData));
8696
8308
  } catch (error) {
8697
8309
  console.error("User new page error:", error);
8698
- return c.html(renderAlert({
8310
+ return c.html(renderAlert2({
8699
8311
  type: "error",
8700
8312
  message: "Failed to load user creation page. Please try again.",
8701
8313
  dismissible: true
@@ -8719,7 +8331,7 @@ userRoutes.post("/users/new", chunkBUKT6HP5_cjs.requirePermission("users.create"
8719
8331
  const isActive = formData.get("is_active") === "1";
8720
8332
  const emailVerified = formData.get("email_verified") === "1";
8721
8333
  if (!firstName || !lastName || !username || !email || !password) {
8722
- return c.html(renderAlert({
8334
+ return c.html(renderAlert2({
8723
8335
  type: "error",
8724
8336
  message: "First name, last name, username, email, and password are required.",
8725
8337
  dismissible: true
@@ -8727,21 +8339,21 @@ userRoutes.post("/users/new", chunkBUKT6HP5_cjs.requirePermission("users.create"
8727
8339
  }
8728
8340
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
8729
8341
  if (!emailRegex.test(email)) {
8730
- return c.html(renderAlert({
8342
+ return c.html(renderAlert2({
8731
8343
  type: "error",
8732
8344
  message: "Please enter a valid email address.",
8733
8345
  dismissible: true
8734
8346
  }));
8735
8347
  }
8736
8348
  if (password.length < 8) {
8737
- return c.html(renderAlert({
8349
+ return c.html(renderAlert2({
8738
8350
  type: "error",
8739
8351
  message: "Password must be at least 8 characters long.",
8740
8352
  dismissible: true
8741
8353
  }));
8742
8354
  }
8743
8355
  if (password !== confirmPassword) {
8744
- return c.html(renderAlert({
8356
+ return c.html(renderAlert2({
8745
8357
  type: "error",
8746
8358
  message: "Passwords do not match.",
8747
8359
  dismissible: true
@@ -8753,7 +8365,7 @@ userRoutes.post("/users/new", chunkBUKT6HP5_cjs.requirePermission("users.create"
8753
8365
  `);
8754
8366
  const existingUser = await checkStmt.bind(username, email).first();
8755
8367
  if (existingUser) {
8756
- return c.html(renderAlert({
8368
+ return c.html(renderAlert2({
8757
8369
  type: "error",
8758
8370
  message: "Username or email is already taken.",
8759
8371
  dismissible: true
@@ -8785,7 +8397,7 @@ userRoutes.post("/users/new", chunkBUKT6HP5_cjs.requirePermission("users.create"
8785
8397
  await chunkBUKT6HP5_cjs.logActivity(
8786
8398
  db,
8787
8399
  user.userId,
8788
- "user.create",
8400
+ "user!.create",
8789
8401
  "users",
8790
8402
  userId,
8791
8403
  { email, username, role },
@@ -8795,9 +8407,9 @@ userRoutes.post("/users/new", chunkBUKT6HP5_cjs.requirePermission("users.create"
8795
8407
  return c.redirect(`/admin/users/${userId}/edit?success=User created successfully`);
8796
8408
  } catch (error) {
8797
8409
  console.error("User creation error:", error);
8798
- return c.html(renderAlert({
8410
+ return c.html(renderAlert2({
8799
8411
  type: "error",
8800
- message: "Failed to create user. Please try again.",
8412
+ message: "Failed to create user!. Please try again.",
8801
8413
  dismissible: true
8802
8414
  }));
8803
8415
  }
@@ -8823,7 +8435,7 @@ userRoutes.get("/users/:id", chunkBUKT6HP5_cjs.requirePermission("users.read"),
8823
8435
  await chunkBUKT6HP5_cjs.logActivity(
8824
8436
  db,
8825
8437
  user.userId,
8826
- "user.view",
8438
+ "user!.view",
8827
8439
  "users",
8828
8440
  userId,
8829
8441
  null,
@@ -8866,7 +8478,7 @@ userRoutes.get("/users/:id/edit", chunkBUKT6HP5_cjs.requirePermission("users.upd
8866
8478
  `);
8867
8479
  const userToEdit = await userStmt.bind(userId).first();
8868
8480
  if (!userToEdit) {
8869
- return c.html(renderAlert({
8481
+ return c.html(renderAlert2({
8870
8482
  type: "error",
8871
8483
  message: "User not found",
8872
8484
  dismissible: true
@@ -8900,9 +8512,9 @@ userRoutes.get("/users/:id/edit", chunkBUKT6HP5_cjs.requirePermission("users.upd
8900
8512
  return c.html(renderUserEditPage(pageData));
8901
8513
  } catch (error) {
8902
8514
  console.error("User edit page error:", error);
8903
- return c.html(renderAlert({
8515
+ return c.html(renderAlert2({
8904
8516
  type: "error",
8905
- message: "Failed to load user. Please try again.",
8517
+ message: "Failed to load user!. Please try again.",
8906
8518
  dismissible: true
8907
8519
  }), 500);
8908
8520
  }
@@ -8923,7 +8535,7 @@ userRoutes.put("/users/:id", chunkBUKT6HP5_cjs.requirePermission("users.update")
8923
8535
  const isActive = formData.get("is_active") === "1";
8924
8536
  const emailVerified = formData.get("email_verified") === "1";
8925
8537
  if (!firstName || !lastName || !username || !email) {
8926
- return c.html(renderAlert({
8538
+ return c.html(renderAlert2({
8927
8539
  type: "error",
8928
8540
  message: "First name, last name, username, and email are required.",
8929
8541
  dismissible: true
@@ -8931,7 +8543,7 @@ userRoutes.put("/users/:id", chunkBUKT6HP5_cjs.requirePermission("users.update")
8931
8543
  }
8932
8544
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
8933
8545
  if (!emailRegex.test(email)) {
8934
- return c.html(renderAlert({
8546
+ return c.html(renderAlert2({
8935
8547
  type: "error",
8936
8548
  message: "Please enter a valid email address.",
8937
8549
  dismissible: true
@@ -8943,9 +8555,9 @@ userRoutes.put("/users/:id", chunkBUKT6HP5_cjs.requirePermission("users.update")
8943
8555
  `);
8944
8556
  const existingUser = await checkStmt.bind(username, email, userId).first();
8945
8557
  if (existingUser) {
8946
- return c.html(renderAlert({
8558
+ return c.html(renderAlert2({
8947
8559
  type: "error",
8948
- message: "Username or email is already taken by another user.",
8560
+ message: "Username or email is already taken by another user!.",
8949
8561
  dismissible: true
8950
8562
  }));
8951
8563
  }
@@ -8972,23 +8584,23 @@ userRoutes.put("/users/:id", chunkBUKT6HP5_cjs.requirePermission("users.update")
8972
8584
  await chunkBUKT6HP5_cjs.logActivity(
8973
8585
  db,
8974
8586
  user.userId,
8975
- "user.update",
8587
+ "user!.update",
8976
8588
  "users",
8977
8589
  userId,
8978
8590
  { fields: ["first_name", "last_name", "username", "email", "phone", "bio", "role", "is_active", "email_verified"] },
8979
8591
  c.req.header("x-forwarded-for") || c.req.header("cf-connecting-ip"),
8980
8592
  c.req.header("user-agent")
8981
8593
  );
8982
- return c.html(renderAlert({
8594
+ return c.html(renderAlert2({
8983
8595
  type: "success",
8984
8596
  message: "User updated successfully!",
8985
8597
  dismissible: true
8986
8598
  }));
8987
8599
  } catch (error) {
8988
8600
  console.error("User update error:", error);
8989
- return c.html(renderAlert({
8601
+ return c.html(renderAlert2({
8990
8602
  type: "error",
8991
- message: "Failed to update user. Please try again.",
8603
+ message: "Failed to update user!. Please try again.",
8992
8604
  dismissible: true
8993
8605
  }));
8994
8606
  }
@@ -9018,7 +8630,7 @@ userRoutes.delete("/users/:id", chunkBUKT6HP5_cjs.requirePermission("users.delet
9018
8630
  await chunkBUKT6HP5_cjs.logActivity(
9019
8631
  db,
9020
8632
  user.userId,
9021
- "user.hard_delete",
8633
+ "user!.hard_delete",
9022
8634
  "users",
9023
8635
  userId,
9024
8636
  { email: userToDelete.email, permanent: true },
@@ -9037,7 +8649,7 @@ userRoutes.delete("/users/:id", chunkBUKT6HP5_cjs.requirePermission("users.delet
9037
8649
  await chunkBUKT6HP5_cjs.logActivity(
9038
8650
  db,
9039
8651
  user.userId,
9040
- "user.soft_delete",
8652
+ "user!.soft_delete",
9041
8653
  "users",
9042
8654
  userId,
9043
8655
  { email: userToDelete.email },
@@ -9104,7 +8716,7 @@ userRoutes.post("/invite-user", chunkBUKT6HP5_cjs.requirePermission("users.creat
9104
8716
  await chunkBUKT6HP5_cjs.logActivity(
9105
8717
  db,
9106
8718
  user.userId,
9107
- "user.invite_sent",
8719
+ "user!.invite_sent",
9108
8720
  "users",
9109
8721
  userId,
9110
8722
  { email, role, invited_user_id: userId },
@@ -9161,7 +8773,7 @@ userRoutes.post("/resend-invitation/:id", chunkBUKT6HP5_cjs.requirePermission("u
9161
8773
  await chunkBUKT6HP5_cjs.logActivity(
9162
8774
  db,
9163
8775
  user.userId,
9164
- "user.invitation_resent",
8776
+ "user!.invitation_resent",
9165
8777
  "users",
9166
8778
  userId,
9167
8779
  { email: invitedUser.email },
@@ -9197,7 +8809,7 @@ userRoutes.delete("/cancel-invitation/:id", chunkBUKT6HP5_cjs.requirePermission(
9197
8809
  await chunkBUKT6HP5_cjs.logActivity(
9198
8810
  db,
9199
8811
  user.userId,
9200
- "user.invitation_cancelled",
8812
+ "user!.invitation_cancelled",
9201
8813
  "users",
9202
8814
  userId,
9203
8815
  { email: invitedUser.email },
@@ -9602,7 +9214,7 @@ function getFileIcon(mimeType) {
9602
9214
  }
9603
9215
 
9604
9216
  // src/templates/pages/admin-media-library.template.ts
9605
- chunkLEGKJ5DI_cjs.init_admin_layout_catalyst_template();
9217
+ chunkET5I4GBD_cjs.init_admin_layout_catalyst_template();
9606
9218
  function renderMediaLibraryPage(data) {
9607
9219
  const pageContent = `
9608
9220
  <div>
@@ -10505,7 +10117,7 @@ function renderMediaLibraryPage(data) {
10505
10117
  </script>
10506
10118
 
10507
10119
  <!-- Confirmation Dialog for Bulk Delete -->
10508
- ${renderConfirmationDialog({
10120
+ ${renderConfirmationDialog2({
10509
10121
  id: "media-bulk-delete-confirm",
10510
10122
  title: "Delete Selected Files",
10511
10123
  message: `Are you sure you want to delete ${data.files.length > 0 ? "the selected files" : "these files"}? This action cannot be undone and the files will be permanently removed.`,
@@ -10517,7 +10129,7 @@ function renderMediaLibraryPage(data) {
10517
10129
  })}
10518
10130
 
10519
10131
  <!-- Confirmation Dialog Script -->
10520
- ${getConfirmationDialogScript()}
10132
+ ${getConfirmationDialogScript2()}
10521
10133
  `;
10522
10134
  function buildPageUrl(page, folder, type) {
10523
10135
  const params = new URLSearchParams();
@@ -10534,7 +10146,7 @@ function renderMediaLibraryPage(data) {
10534
10146
  version: data.version,
10535
10147
  content: pageContent
10536
10148
  };
10537
- return chunkLEGKJ5DI_cjs.renderAdminLayoutCatalyst(layoutData);
10149
+ return chunkET5I4GBD_cjs.renderAdminLayoutCatalyst(layoutData);
10538
10150
  }
10539
10151
 
10540
10152
  // src/templates/components/media-file-details.template.ts
@@ -11452,7 +11064,7 @@ function formatFileSize(bytes) {
11452
11064
  }
11453
11065
 
11454
11066
  // src/templates/pages/admin-plugins-list.template.ts
11455
- chunkLEGKJ5DI_cjs.init_admin_layout_catalyst_template();
11067
+ chunkET5I4GBD_cjs.init_admin_layout_catalyst_template();
11456
11068
  function renderPluginsListPage(data) {
11457
11069
  const pageContent = `
11458
11070
  <div>
@@ -11805,7 +11417,7 @@ function renderPluginsListPage(data) {
11805
11417
  </script>
11806
11418
 
11807
11419
  <!-- Confirmation Dialogs -->
11808
- ${renderConfirmationDialog({
11420
+ ${renderConfirmationDialog2({
11809
11421
  id: "uninstall-plugin-confirm",
11810
11422
  title: "Uninstall Plugin",
11811
11423
  message: "Are you sure you want to uninstall this plugin? This action cannot be undone.",
@@ -11816,7 +11428,7 @@ function renderPluginsListPage(data) {
11816
11428
  onConfirm: "performUninstallPlugin()"
11817
11429
  })}
11818
11430
 
11819
- ${getConfirmationDialogScript()}
11431
+ ${getConfirmationDialogScript2()}
11820
11432
  `;
11821
11433
  const layoutData = {
11822
11434
  title: "Plugins",
@@ -11826,7 +11438,7 @@ function renderPluginsListPage(data) {
11826
11438
  version: data.version,
11827
11439
  content: pageContent
11828
11440
  };
11829
- return chunkLEGKJ5DI_cjs.renderAdminLayoutCatalyst(layoutData);
11441
+ return chunkET5I4GBD_cjs.renderAdminLayoutCatalyst(layoutData);
11830
11442
  }
11831
11443
  function renderPluginCard(plugin) {
11832
11444
  const statusColors = {
@@ -12476,7 +12088,7 @@ function renderPluginSettingsPage(data) {
12476
12088
  user,
12477
12089
  content: pageContent
12478
12090
  };
12479
- return chunkLEGKJ5DI_cjs.renderAdminLayout(layoutData);
12091
+ return chunkET5I4GBD_cjs.renderAdminLayout(layoutData);
12480
12092
  }
12481
12093
  function renderStatusBadge(status) {
12482
12094
  const statusColors = {
@@ -13073,7 +12685,7 @@ function formatLastUpdated(timestamp) {
13073
12685
  }
13074
12686
 
13075
12687
  // src/templates/pages/admin-logs-list.template.ts
13076
- chunkLEGKJ5DI_cjs.init_admin_layout_catalyst_template();
12688
+ chunkET5I4GBD_cjs.init_admin_layout_catalyst_template();
13077
12689
  function renderLogsListPage(data) {
13078
12690
  const { logs, pagination, filters, user } = data;
13079
12691
  const content = `
@@ -13384,7 +12996,7 @@ function renderLogsListPage(data) {
13384
12996
  user,
13385
12997
  content
13386
12998
  };
13387
- return chunkLEGKJ5DI_cjs.renderAdminLayoutCatalyst(layoutData);
12999
+ return chunkET5I4GBD_cjs.renderAdminLayoutCatalyst(layoutData);
13388
13000
  }
13389
13001
  function renderLogDetailsPage(data) {
13390
13002
  const { log, user } = data;
@@ -13596,7 +13208,7 @@ function renderLogDetailsPage(data) {
13596
13208
  </div>
13597
13209
  </div>
13598
13210
  `;
13599
- return chunkLEGKJ5DI_cjs.adminLayoutV2({
13211
+ return chunkET5I4GBD_cjs.adminLayoutV2({
13600
13212
  title: `Log Details - ${log.id}`,
13601
13213
  user,
13602
13214
  content
@@ -13839,7 +13451,7 @@ function renderLogConfigPage(data) {
13839
13451
 
13840
13452
  <script src="https://unpkg.com/htmx.org@1.9.6"></script>
13841
13453
  `;
13842
- return chunkLEGKJ5DI_cjs.adminLayoutV2({
13454
+ return chunkET5I4GBD_cjs.adminLayoutV2({
13843
13455
  title: "Log Configuration",
13844
13456
  user,
13845
13457
  content
@@ -14209,10 +13821,1943 @@ function getCategoryClass(category) {
14209
13821
  return "bg-gray-100 text-gray-800";
14210
13822
  }
14211
13823
  }
13824
+ var adminDesignRoutes = new hono.Hono();
13825
+ adminDesignRoutes.get("/", (c) => {
13826
+ const user = c.get("user");
13827
+ const pageData = {
13828
+ user: user ? {
13829
+ name: user.email,
13830
+ email: user.email,
13831
+ role: user.role
13832
+ } : void 0
13833
+ };
13834
+ return c.html(chunkET5I4GBD_cjs.renderDesignPage(pageData));
13835
+ });
13836
+ var adminCheckboxRoutes = new hono.Hono();
13837
+ adminCheckboxRoutes.get("/", (c) => {
13838
+ const user = c.get("user");
13839
+ const pageData = {
13840
+ user: user ? {
13841
+ name: user.email,
13842
+ email: user.email,
13843
+ role: user.role
13844
+ } : void 0
13845
+ };
13846
+ return c.html(chunkET5I4GBD_cjs.renderCheckboxPage(pageData));
13847
+ });
13848
+
13849
+ // src/templates/pages/admin-faq-form.template.ts
13850
+ function renderFAQForm(data) {
13851
+ const { faq, isEdit, errors, message, messageType } = data;
13852
+ const pageTitle = isEdit ? "Edit FAQ" : "New FAQ";
13853
+ const pageContent = `
13854
+ <div class="w-full px-4 sm:px-6 lg:px-8 py-6 space-y-6">
13855
+ <!-- Header -->
13856
+ <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6">
13857
+ <div>
13858
+ <h1 class="text-2xl font-semibold text-white">${pageTitle}</h1>
13859
+ <p class="mt-2 text-sm text-gray-300">
13860
+ ${isEdit ? "Update the FAQ details below" : "Create a new frequently asked question"}
13861
+ </p>
13862
+ </div>
13863
+ <div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
13864
+ <a href="/admin/faq"
13865
+ class="inline-flex items-center justify-center rounded-xl backdrop-blur-sm bg-white/10 px-4 py-2 text-sm font-semibold text-white border border-white/20 hover:bg-white/20 transition-all">
13866
+ <svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
13867
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
13868
+ </svg>
13869
+ Back to List
13870
+ </a>
13871
+ </div>
13872
+ </div>
13873
+
13874
+ ${message ? chunkET5I4GBD_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
13875
+
13876
+ <!-- Form -->
13877
+ <div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl">
13878
+ <form ${isEdit ? `hx-put="/admin/faq/${faq?.id}"` : 'hx-post="/admin/faq"'}
13879
+ hx-target="body"
13880
+ hx-swap="outerHTML"
13881
+ class="space-y-6 p-6">
13882
+
13883
+ <!-- Question -->
13884
+ <div>
13885
+ <label for="question" class="block text-sm font-medium text-white">
13886
+ Question <span class="text-red-400">*</span>
13887
+ </label>
13888
+ <div class="mt-1">
13889
+ <textarea name="question"
13890
+ id="question"
13891
+ rows="3"
13892
+ required
13893
+ maxlength="500"
13894
+ class="backdrop-blur-sm bg-white/10 border border-white/20 rounded-xl px-3 py-2 text-white placeholder-gray-300 focus:border-blue-400 focus:outline-none transition-colors w-full"
13895
+ placeholder="Enter the frequently asked question...">${faq?.question || ""}</textarea>
13896
+ <p class="mt-1 text-sm text-gray-300">
13897
+ <span id="question-count">0</span>/500 characters
13898
+ </p>
13899
+ </div>
13900
+ ${errors?.question ? `
13901
+ <div class="mt-1">
13902
+ ${errors.question.map((error) => `
13903
+ <p class="text-sm text-red-400">${escapeHtml4(error)}</p>
13904
+ `).join("")}
13905
+ </div>
13906
+ ` : ""}
13907
+ </div>
13908
+
13909
+ <!-- Answer -->
13910
+ <div>
13911
+ <label for="answer" class="block text-sm font-medium text-white">
13912
+ Answer <span class="text-red-400">*</span>
13913
+ </label>
13914
+ <div class="mt-1">
13915
+ <textarea name="answer"
13916
+ id="answer"
13917
+ rows="6"
13918
+ required
13919
+ maxlength="2000"
13920
+ class="backdrop-blur-sm bg-white/10 border border-white/20 rounded-xl px-3 py-2 text-white placeholder-gray-300 focus:border-blue-400 focus:outline-none transition-colors w-full"
13921
+ placeholder="Enter the detailed answer...">${faq?.answer || ""}</textarea>
13922
+ <p class="mt-1 text-sm text-gray-300">
13923
+ <span id="answer-count">0</span>/2000 characters. You can use basic HTML for formatting.
13924
+ </p>
13925
+ </div>
13926
+ ${errors?.answer ? `
13927
+ <div class="mt-1">
13928
+ ${errors.answer.map((error) => `
13929
+ <p class="text-sm text-red-400">${escapeHtml4(error)}</p>
13930
+ `).join("")}
13931
+ </div>
13932
+ ` : ""}
13933
+ </div>
13934
+
13935
+ <!-- Category and Tags Row -->
13936
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
13937
+ <!-- Category -->
13938
+ <div>
13939
+ <label for="category" class="block text-sm font-medium text-white">Category</label>
13940
+ <div class="mt-1">
13941
+ <select name="category"
13942
+ id="category"
13943
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6">
13944
+ <option value="">Select a category</option>
13945
+ <option value="general" ${faq?.category === "general" ? "selected" : ""}>General</option>
13946
+ <option value="technical" ${faq?.category === "technical" ? "selected" : ""}>Technical</option>
13947
+ <option value="billing" ${faq?.category === "billing" ? "selected" : ""}>Billing</option>
13948
+ <option value="support" ${faq?.category === "support" ? "selected" : ""}>Support</option>
13949
+ <option value="account" ${faq?.category === "account" ? "selected" : ""}>Account</option>
13950
+ <option value="features" ${faq?.category === "features" ? "selected" : ""}>Features</option>
13951
+ </select>
13952
+ </div>
13953
+ ${errors?.category ? `
13954
+ <div class="mt-1">
13955
+ ${errors.category.map((error) => `
13956
+ <p class="text-sm text-red-400">${escapeHtml4(error)}</p>
13957
+ `).join("")}
13958
+ </div>
13959
+ ` : ""}
13960
+ </div>
13961
+
13962
+ <!-- Tags -->
13963
+ <div>
13964
+ <label for="tags" class="block text-sm font-medium text-white">Tags</label>
13965
+ <div class="mt-1">
13966
+ <input type="text"
13967
+ name="tags"
13968
+ id="tags"
13969
+ value="${faq?.tags || ""}"
13970
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
13971
+ placeholder="e.g., payment, setup, troubleshooting">
13972
+ <p class="mt-1 text-sm text-gray-300">Separate multiple tags with commas</p>
13973
+ </div>
13974
+ ${errors?.tags ? `
13975
+ <div class="mt-1">
13976
+ ${errors.tags.map((error) => `
13977
+ <p class="text-sm text-red-400">${escapeHtml4(error)}</p>
13978
+ `).join("")}
13979
+ </div>
13980
+ ` : ""}
13981
+ </div>
13982
+ </div>
13983
+
13984
+ <!-- Status and Sort Order Row -->
13985
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
13986
+ <!-- Published Status -->
13987
+ <div>
13988
+ <label class="block text-sm font-medium text-white">Status</label>
13989
+ <div class="mt-2 space-y-2">
13990
+ <div class="flex items-center">
13991
+ <input id="published"
13992
+ name="isPublished"
13993
+ type="radio"
13994
+ value="true"
13995
+ ${!faq || faq.isPublished ? "checked" : ""}
13996
+ class="h-4 w-4 text-blue-600 focus:ring-blue-600 border-gray-600 bg-gray-700">
13997
+ <label for="published" class="ml-2 block text-sm text-white">
13998
+ Published <span class="text-gray-300">(visible to users)</span>
13999
+ </label>
14000
+ </div>
14001
+ <div class="flex items-center">
14002
+ <input id="draft"
14003
+ name="isPublished"
14004
+ type="radio"
14005
+ value="false"
14006
+ ${faq && !faq.isPublished ? "checked" : ""}
14007
+ class="h-4 w-4 text-blue-600 focus:ring-blue-600 border-gray-600 bg-gray-700">
14008
+ <label for="draft" class="ml-2 block text-sm text-white">
14009
+ Draft <span class="text-gray-300">(not visible to users)</span>
14010
+ </label>
14011
+ </div>
14012
+ </div>
14013
+ </div>
14014
+
14015
+ <!-- Sort Order -->
14016
+ <div>
14017
+ <label for="sortOrder" class="block text-sm font-medium text-white">Sort Order</label>
14018
+ <div class="mt-1">
14019
+ <input type="number"
14020
+ name="sortOrder"
14021
+ id="sortOrder"
14022
+ value="${faq?.sortOrder || 0}"
14023
+ min="0"
14024
+ step="1"
14025
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6">
14026
+ <p class="mt-1 text-sm text-gray-300">Lower numbers appear first (0 = highest priority)</p>
14027
+ </div>
14028
+ ${errors?.sortOrder ? `
14029
+ <div class="mt-1">
14030
+ ${errors.sortOrder.map((error) => `
14031
+ <p class="text-sm text-red-400">${escapeHtml4(error)}</p>
14032
+ `).join("")}
14033
+ </div>
14034
+ ` : ""}
14035
+ </div>
14036
+ </div>
14037
+
14038
+ <!-- Form Actions -->
14039
+ <div class="flex items-center justify-end space-x-3 pt-6 border-t border-white/20">
14040
+ <a href="/admin/faq"
14041
+ class="inline-flex items-center justify-center rounded-xl backdrop-blur-sm bg-white/10 px-4 py-2 text-sm font-semibold text-white border border-white/20 hover:bg-white/20 transition-all">
14042
+ Cancel
14043
+ </a>
14044
+ <button type="submit"
14045
+ class="inline-flex items-center justify-center rounded-xl backdrop-blur-sm bg-blue-500/80 px-4 py-2 text-sm font-semibold text-white border border-white/20 hover:bg-blue-500 transition-all">
14046
+ <svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
14047
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
14048
+ </svg>
14049
+ ${isEdit ? "Update FAQ" : "Create FAQ"}
14050
+ </button>
14051
+ </div>
14052
+ </form>
14053
+ </div>
14054
+ </div>
14055
+
14056
+ <script>
14057
+ // Character count for question
14058
+ const questionTextarea = document.getElementById('question');
14059
+ const questionCount = document.getElementById('question-count');
14060
+
14061
+ function updateQuestionCount() {
14062
+ questionCount.textContent = questionTextarea.value.length;
14063
+ }
14064
+
14065
+ questionTextarea.addEventListener('input', updateQuestionCount);
14066
+ updateQuestionCount(); // Initial count
14067
+
14068
+ // Character count for answer
14069
+ const answerTextarea = document.getElementById('answer');
14070
+ const answerCount = document.getElementById('answer-count');
14071
+
14072
+ function updateAnswerCount() {
14073
+ answerCount.textContent = answerTextarea.value.length;
14074
+ }
14075
+
14076
+ answerTextarea.addEventListener('input', updateAnswerCount);
14077
+ updateAnswerCount(); // Initial count
14078
+ </script>
14079
+ `;
14080
+ const layoutData = {
14081
+ title: `${pageTitle} - Admin`,
14082
+ pageTitle,
14083
+ currentPath: isEdit ? `/admin/faq/${faq?.id}` : "/admin/faq/new",
14084
+ user: data.user,
14085
+ content: pageContent
14086
+ };
14087
+ return chunkET5I4GBD_cjs.renderAdminLayout(layoutData);
14088
+ }
14089
+ function escapeHtml4(unsafe) {
14090
+ return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
14091
+ }
14092
+
14093
+ // src/routes/admin-faq.ts
14094
+ var faqSchema = zod.z.object({
14095
+ question: zod.z.string().min(1, "Question is required").max(500, "Question must be under 500 characters"),
14096
+ answer: zod.z.string().min(1, "Answer is required").max(2e3, "Answer must be under 2000 characters"),
14097
+ category: zod.z.string().optional(),
14098
+ tags: zod.z.string().optional(),
14099
+ isPublished: zod.z.string().transform((val) => val === "true"),
14100
+ sortOrder: zod.z.string().transform((val) => parseInt(val, 10)).pipe(zod.z.number().min(0))
14101
+ });
14102
+ var adminFAQRoutes = new hono.Hono();
14103
+ adminFAQRoutes.get("/", async (c) => {
14104
+ try {
14105
+ const user = c.get("user");
14106
+ const { category, published, search, page = "1" } = c.req.query();
14107
+ const currentPage = parseInt(page, 10) || 1;
14108
+ const limit = 20;
14109
+ const offset = (currentPage - 1) * limit;
14110
+ const db = c.env?.DB;
14111
+ if (!db) {
14112
+ return c.html(chunkET5I4GBD_cjs.renderFAQList({
14113
+ faqs: [],
14114
+ totalCount: 0,
14115
+ currentPage: 1,
14116
+ totalPages: 1,
14117
+ user: user ? {
14118
+ name: user.email,
14119
+ email: user.email,
14120
+ role: user.role
14121
+ } : void 0,
14122
+ message: "Database not available",
14123
+ messageType: "error"
14124
+ }));
14125
+ }
14126
+ let whereClause = "WHERE 1=1";
14127
+ const params = [];
14128
+ if (category) {
14129
+ whereClause += " AND category = ?";
14130
+ params.push(category);
14131
+ }
14132
+ if (published !== void 0) {
14133
+ whereClause += " AND isPublished = ?";
14134
+ params.push(published === "true" ? 1 : 0);
14135
+ }
14136
+ if (search) {
14137
+ whereClause += " AND (question LIKE ? OR answer LIKE ? OR tags LIKE ?)";
14138
+ const searchTerm = `%${search}%`;
14139
+ params.push(searchTerm, searchTerm, searchTerm);
14140
+ }
14141
+ const countQuery = `SELECT COUNT(*) as count FROM faqs ${whereClause}`;
14142
+ const { results: countResults } = await db.prepare(countQuery).bind(...params).all();
14143
+ const totalCount = countResults?.[0]?.count || 0;
14144
+ const dataQuery = `
14145
+ SELECT * FROM faqs
14146
+ ${whereClause}
14147
+ ORDER BY sortOrder ASC, created_at DESC
14148
+ LIMIT ? OFFSET ?
14149
+ `;
14150
+ const { results: faqs } = await db.prepare(dataQuery).bind(...params, limit, offset).all();
14151
+ const totalPages = Math.ceil(totalCount / limit);
14152
+ return c.html(chunkET5I4GBD_cjs.renderFAQList({
14153
+ faqs: faqs || [],
14154
+ totalCount,
14155
+ currentPage,
14156
+ totalPages,
14157
+ user: user ? {
14158
+ name: user.email,
14159
+ email: user.email,
14160
+ role: user.role
14161
+ } : void 0
14162
+ }));
14163
+ } catch (error) {
14164
+ console.error("Error fetching FAQs:", error);
14165
+ const user = c.get("user");
14166
+ return c.html(chunkET5I4GBD_cjs.renderFAQList({
14167
+ faqs: [],
14168
+ totalCount: 0,
14169
+ currentPage: 1,
14170
+ totalPages: 1,
14171
+ user: user ? {
14172
+ name: user.email,
14173
+ email: user.email,
14174
+ role: user.role
14175
+ } : void 0,
14176
+ message: "Failed to load FAQs",
14177
+ messageType: "error"
14178
+ }));
14179
+ }
14180
+ });
14181
+ adminFAQRoutes.get("/new", async (c) => {
14182
+ const user = c.get("user");
14183
+ return c.html(renderFAQForm({
14184
+ isEdit: false,
14185
+ user: user ? {
14186
+ name: user.email,
14187
+ email: user.email,
14188
+ role: user.role
14189
+ } : void 0
14190
+ }));
14191
+ });
14192
+ adminFAQRoutes.post("/", async (c) => {
14193
+ try {
14194
+ const formData = await c.req.formData();
14195
+ const data = Object.fromEntries(formData.entries());
14196
+ const validatedData = faqSchema.parse(data);
14197
+ const user = c.get("user");
14198
+ const db = c.env?.DB;
14199
+ if (!db) {
14200
+ return c.html(renderFAQForm({
14201
+ isEdit: false,
14202
+ user: user ? {
14203
+ name: user.email,
14204
+ email: user.email,
14205
+ role: user.role
14206
+ } : void 0,
14207
+ message: "Database not available",
14208
+ messageType: "error"
14209
+ }));
14210
+ }
14211
+ const { results } = await db.prepare(`
14212
+ INSERT INTO faqs (question, answer, category, tags, isPublished, sortOrder)
14213
+ VALUES (?, ?, ?, ?, ?, ?)
14214
+ RETURNING *
14215
+ `).bind(
14216
+ validatedData.question,
14217
+ validatedData.answer,
14218
+ validatedData.category || null,
14219
+ validatedData.tags || null,
14220
+ validatedData.isPublished ? 1 : 0,
14221
+ validatedData.sortOrder
14222
+ ).all();
14223
+ if (results && results.length > 0) {
14224
+ return c.redirect("/admin/faq?message=FAQ created successfully");
14225
+ } else {
14226
+ return c.html(renderFAQForm({
14227
+ isEdit: false,
14228
+ user: user ? {
14229
+ name: user.email,
14230
+ email: user.email,
14231
+ role: user.role
14232
+ } : void 0,
14233
+ message: "Failed to create FAQ",
14234
+ messageType: "error"
14235
+ }));
14236
+ }
14237
+ } catch (error) {
14238
+ console.error("Error creating FAQ:", error);
14239
+ const user = c.get("user");
14240
+ if (error instanceof zod.z.ZodError) {
14241
+ const errors = {};
14242
+ error.errors.forEach((err) => {
14243
+ const field = err.path[0];
14244
+ if (!errors[field]) errors[field] = [];
14245
+ errors[field].push(err.message);
14246
+ });
14247
+ return c.html(renderFAQForm({
14248
+ isEdit: false,
14249
+ user: user ? {
14250
+ name: user.email,
14251
+ email: user.email,
14252
+ role: user.role
14253
+ } : void 0,
14254
+ errors,
14255
+ message: "Please correct the errors below",
14256
+ messageType: "error"
14257
+ }));
14258
+ }
14259
+ return c.html(renderFAQForm({
14260
+ isEdit: false,
14261
+ user: user ? {
14262
+ name: user.email,
14263
+ email: user.email,
14264
+ role: user.role
14265
+ } : void 0,
14266
+ message: "Failed to create FAQ",
14267
+ messageType: "error"
14268
+ }));
14269
+ }
14270
+ });
14271
+ adminFAQRoutes.get("/:id", async (c) => {
14272
+ try {
14273
+ const id = parseInt(c.req.param("id"));
14274
+ const user = c.get("user");
14275
+ const db = c.env?.DB;
14276
+ if (!db) {
14277
+ return c.html(renderFAQForm({
14278
+ isEdit: true,
14279
+ user: user ? {
14280
+ name: user.email,
14281
+ email: user.email,
14282
+ role: user.role
14283
+ } : void 0,
14284
+ message: "Database not available",
14285
+ messageType: "error"
14286
+ }));
14287
+ }
14288
+ const { results } = await db.prepare("SELECT * FROM faqs WHERE id = ?").bind(id).all();
14289
+ if (!results || results.length === 0) {
14290
+ return c.redirect("/admin/faq?message=FAQ not found&type=error");
14291
+ }
14292
+ const faq = results[0];
14293
+ return c.html(renderFAQForm({
14294
+ faq: {
14295
+ id: faq.id,
14296
+ question: faq.question,
14297
+ answer: faq.answer,
14298
+ category: faq.category,
14299
+ tags: faq.tags,
14300
+ isPublished: Boolean(faq.isPublished),
14301
+ sortOrder: faq.sortOrder
14302
+ },
14303
+ isEdit: true,
14304
+ user: user ? {
14305
+ name: user.email,
14306
+ email: user.email,
14307
+ role: user.role
14308
+ } : void 0
14309
+ }));
14310
+ } catch (error) {
14311
+ console.error("Error fetching FAQ:", error);
14312
+ const user = c.get("user");
14313
+ return c.html(renderFAQForm({
14314
+ isEdit: true,
14315
+ user: user ? {
14316
+ name: user.email,
14317
+ email: user.email,
14318
+ role: user.role
14319
+ } : void 0,
14320
+ message: "Failed to load FAQ",
14321
+ messageType: "error"
14322
+ }));
14323
+ }
14324
+ });
14325
+ adminFAQRoutes.put("/:id", async (c) => {
14326
+ try {
14327
+ const id = parseInt(c.req.param("id"));
14328
+ const formData = await c.req.formData();
14329
+ const data = Object.fromEntries(formData.entries());
14330
+ const validatedData = faqSchema.parse(data);
14331
+ const user = c.get("user");
14332
+ const db = c.env?.DB;
14333
+ if (!db) {
14334
+ return c.html(renderFAQForm({
14335
+ isEdit: true,
14336
+ user: user ? {
14337
+ name: user.email,
14338
+ email: user.email,
14339
+ role: user.role
14340
+ } : void 0,
14341
+ message: "Database not available",
14342
+ messageType: "error"
14343
+ }));
14344
+ }
14345
+ const { results } = await db.prepare(`
14346
+ UPDATE faqs
14347
+ SET question = ?, answer = ?, category = ?, tags = ?, isPublished = ?, sortOrder = ?
14348
+ WHERE id = ?
14349
+ RETURNING *
14350
+ `).bind(
14351
+ validatedData.question,
14352
+ validatedData.answer,
14353
+ validatedData.category || null,
14354
+ validatedData.tags || null,
14355
+ validatedData.isPublished ? 1 : 0,
14356
+ validatedData.sortOrder,
14357
+ id
14358
+ ).all();
14359
+ if (results && results.length > 0) {
14360
+ return c.redirect("/admin/faq?message=FAQ updated successfully");
14361
+ } else {
14362
+ return c.html(renderFAQForm({
14363
+ faq: {
14364
+ id,
14365
+ question: validatedData.question,
14366
+ answer: validatedData.answer,
14367
+ category: validatedData.category,
14368
+ tags: validatedData.tags,
14369
+ isPublished: validatedData.isPublished,
14370
+ sortOrder: validatedData.sortOrder
14371
+ },
14372
+ isEdit: true,
14373
+ user: user ? {
14374
+ name: user.email,
14375
+ email: user.email,
14376
+ role: user.role
14377
+ } : void 0,
14378
+ message: "FAQ not found",
14379
+ messageType: "error"
14380
+ }));
14381
+ }
14382
+ } catch (error) {
14383
+ console.error("Error updating FAQ:", error);
14384
+ const user = c.get("user");
14385
+ const id = parseInt(c.req.param("id"));
14386
+ if (error instanceof zod.z.ZodError) {
14387
+ const errors = {};
14388
+ error.errors.forEach((err) => {
14389
+ const field = err.path[0];
14390
+ if (!errors[field]) errors[field] = [];
14391
+ errors[field].push(err.message);
14392
+ });
14393
+ return c.html(renderFAQForm({
14394
+ faq: {
14395
+ id,
14396
+ question: "",
14397
+ answer: "",
14398
+ category: "",
14399
+ tags: "",
14400
+ isPublished: true,
14401
+ sortOrder: 0
14402
+ },
14403
+ isEdit: true,
14404
+ user: user ? {
14405
+ name: user.email,
14406
+ email: user.email,
14407
+ role: user.role
14408
+ } : void 0,
14409
+ errors,
14410
+ message: "Please correct the errors below",
14411
+ messageType: "error"
14412
+ }));
14413
+ }
14414
+ return c.html(renderFAQForm({
14415
+ faq: {
14416
+ id,
14417
+ question: "",
14418
+ answer: "",
14419
+ category: "",
14420
+ tags: "",
14421
+ isPublished: true,
14422
+ sortOrder: 0
14423
+ },
14424
+ isEdit: true,
14425
+ user: user ? {
14426
+ name: user.email,
14427
+ email: user.email,
14428
+ role: user.role
14429
+ } : void 0,
14430
+ message: "Failed to update FAQ",
14431
+ messageType: "error"
14432
+ }));
14433
+ }
14434
+ });
14435
+ adminFAQRoutes.delete("/:id", async (c) => {
14436
+ try {
14437
+ const id = parseInt(c.req.param("id"));
14438
+ const db = c.env?.DB;
14439
+ if (!db) {
14440
+ return c.json({ error: "Database not available" }, 500);
14441
+ }
14442
+ const { changes } = await db.prepare("DELETE FROM faqs WHERE id = ?").bind(id).run();
14443
+ if (changes === 0) {
14444
+ return c.json({ error: "FAQ not found" }, 404);
14445
+ }
14446
+ return c.redirect("/admin/faq?message=FAQ deleted successfully");
14447
+ } catch (error) {
14448
+ console.error("Error deleting FAQ:", error);
14449
+ return c.json({ error: "Failed to delete FAQ" }, 500);
14450
+ }
14451
+ });
14452
+ var admin_faq_default = adminFAQRoutes;
14453
+
14454
+ // src/templates/pages/admin-testimonials-form.template.ts
14455
+ function renderTestimonialsForm(data) {
14456
+ const { testimonial, isEdit, errors, message, messageType } = data;
14457
+ const pageTitle = isEdit ? "Edit Testimonial" : "New Testimonial";
14458
+ const pageContent = `
14459
+ <div class="w-full px-4 sm:px-6 lg:px-8 py-6 space-y-6">
14460
+ <!-- Header -->
14461
+ <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6">
14462
+ <div>
14463
+ <h1 class="text-2xl font-semibold text-white">${pageTitle}</h1>
14464
+ <p class="mt-2 text-sm text-gray-300">
14465
+ ${isEdit ? "Update the testimonial details below" : "Create a new customer testimonial"}
14466
+ </p>
14467
+ </div>
14468
+ <div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
14469
+ <a href="/admin/testimonials"
14470
+ class="inline-flex items-center justify-center rounded-xl backdrop-blur-sm bg-white/10 px-4 py-2 text-sm font-semibold text-white border border-white/20 hover:bg-white/20 transition-all">
14471
+ <svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
14472
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
14473
+ </svg>
14474
+ Back to List
14475
+ </a>
14476
+ </div>
14477
+ </div>
14478
+
14479
+ ${message ? chunkET5I4GBD_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
14480
+
14481
+ <!-- Form -->
14482
+ <div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl">
14483
+ <form ${isEdit ? `hx-put="/admin/testimonials/${testimonial?.id}"` : 'hx-post="/admin/testimonials"'}
14484
+ hx-target="body"
14485
+ hx-swap="outerHTML"
14486
+ class="space-y-6 p-6">
14487
+
14488
+ <!-- Author Information Section -->
14489
+ <div>
14490
+ <h2 class="text-lg font-medium text-white mb-4">Author Information</h2>
14491
+
14492
+ <!-- Author Name -->
14493
+ <div class="mb-4">
14494
+ <label for="authorName" class="block text-sm font-medium text-white">
14495
+ Author Name <span class="text-red-400">*</span>
14496
+ </label>
14497
+ <div class="mt-1">
14498
+ <input type="text"
14499
+ name="authorName"
14500
+ id="authorName"
14501
+ value="${testimonial?.authorName || ""}"
14502
+ required
14503
+ maxlength="100"
14504
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
14505
+ placeholder="John Doe">
14506
+ </div>
14507
+ ${errors?.authorName ? `
14508
+ <div class="mt-1">
14509
+ ${errors.authorName.map((error) => `
14510
+ <p class="text-sm text-red-400">${escapeHtml5(error)}</p>
14511
+ `).join("")}
14512
+ </div>
14513
+ ` : ""}
14514
+ </div>
14515
+
14516
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
14517
+ <!-- Author Title -->
14518
+ <div>
14519
+ <label for="authorTitle" class="block text-sm font-medium text-white">Title/Position</label>
14520
+ <div class="mt-1">
14521
+ <input type="text"
14522
+ name="authorTitle"
14523
+ id="authorTitle"
14524
+ value="${testimonial?.authorTitle || ""}"
14525
+ maxlength="100"
14526
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
14527
+ placeholder="CEO">
14528
+ </div>
14529
+ ${errors?.authorTitle ? `
14530
+ <div class="mt-1">
14531
+ ${errors.authorTitle.map((error) => `
14532
+ <p class="text-sm text-red-400">${escapeHtml5(error)}</p>
14533
+ `).join("")}
14534
+ </div>
14535
+ ` : ""}
14536
+ </div>
14537
+
14538
+ <!-- Author Company -->
14539
+ <div>
14540
+ <label for="authorCompany" class="block text-sm font-medium text-white">Company</label>
14541
+ <div class="mt-1">
14542
+ <input type="text"
14543
+ name="authorCompany"
14544
+ id="authorCompany"
14545
+ value="${testimonial?.authorCompany || ""}"
14546
+ maxlength="100"
14547
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
14548
+ placeholder="Acme Corp">
14549
+ </div>
14550
+ ${errors?.authorCompany ? `
14551
+ <div class="mt-1">
14552
+ ${errors.authorCompany.map((error) => `
14553
+ <p class="text-sm text-red-400">${escapeHtml5(error)}</p>
14554
+ `).join("")}
14555
+ </div>
14556
+ ` : ""}
14557
+ </div>
14558
+ </div>
14559
+ </div>
14560
+
14561
+ <!-- Testimonial Content Section -->
14562
+ <div>
14563
+ <h2 class="text-lg font-medium text-white mb-4">Testimonial</h2>
14564
+
14565
+ <!-- Testimonial Text -->
14566
+ <div class="mb-4">
14567
+ <label for="testimonialText" class="block text-sm font-medium text-white">
14568
+ Testimonial <span class="text-red-400">*</span>
14569
+ </label>
14570
+ <div class="mt-1">
14571
+ <textarea name="testimonialText"
14572
+ id="testimonialText"
14573
+ rows="6"
14574
+ required
14575
+ maxlength="1000"
14576
+ class="backdrop-blur-sm bg-white/10 border border-white/20 rounded-xl px-3 py-2 text-white placeholder-gray-300 focus:border-blue-400 focus:outline-none transition-colors w-full"
14577
+ placeholder="Enter the customer's testimonial...">${testimonial?.testimonialText || ""}</textarea>
14578
+ <p class="mt-1 text-sm text-gray-300">
14579
+ <span id="testimonial-count">0</span>/1000 characters
14580
+ </p>
14581
+ </div>
14582
+ ${errors?.testimonialText ? `
14583
+ <div class="mt-1">
14584
+ ${errors.testimonialText.map((error) => `
14585
+ <p class="text-sm text-red-400">${escapeHtml5(error)}</p>
14586
+ `).join("")}
14587
+ </div>
14588
+ ` : ""}
14589
+ </div>
14590
+
14591
+ <!-- Rating -->
14592
+ <div>
14593
+ <label for="rating" class="block text-sm font-medium text-white">Rating (Optional)</label>
14594
+ <div class="mt-1">
14595
+ <select name="rating"
14596
+ id="rating"
14597
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6">
14598
+ <option value="">No rating</option>
14599
+ <option value="5" ${testimonial?.rating === 5 ? "selected" : ""}>\u2B50\u2B50\u2B50\u2B50\u2B50 (5 stars)</option>
14600
+ <option value="4" ${testimonial?.rating === 4 ? "selected" : ""}>\u2B50\u2B50\u2B50\u2B50 (4 stars)</option>
14601
+ <option value="3" ${testimonial?.rating === 3 ? "selected" : ""}>\u2B50\u2B50\u2B50 (3 stars)</option>
14602
+ <option value="2" ${testimonial?.rating === 2 ? "selected" : ""}>\u2B50\u2B50 (2 stars)</option>
14603
+ <option value="1" ${testimonial?.rating === 1 ? "selected" : ""}>\u2B50 (1 star)</option>
14604
+ </select>
14605
+ </div>
14606
+ ${errors?.rating ? `
14607
+ <div class="mt-1">
14608
+ ${errors.rating.map((error) => `
14609
+ <p class="text-sm text-red-400">${escapeHtml5(error)}</p>
14610
+ `).join("")}
14611
+ </div>
14612
+ ` : ""}
14613
+ </div>
14614
+ </div>
14615
+
14616
+ <!-- Status and Sort Order Row -->
14617
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
14618
+ <!-- Published Status -->
14619
+ <div>
14620
+ <label class="block text-sm font-medium text-white">Status</label>
14621
+ <div class="mt-2 space-y-2">
14622
+ <div class="flex items-center">
14623
+ <input id="published"
14624
+ name="isPublished"
14625
+ type="radio"
14626
+ value="true"
14627
+ ${!testimonial || testimonial.isPublished ? "checked" : ""}
14628
+ class="h-4 w-4 text-blue-600 focus:ring-blue-600 border-gray-600 bg-gray-700">
14629
+ <label for="published" class="ml-2 block text-sm text-white">
14630
+ Published <span class="text-gray-300">(visible to users)</span>
14631
+ </label>
14632
+ </div>
14633
+ <div class="flex items-center">
14634
+ <input id="draft"
14635
+ name="isPublished"
14636
+ type="radio"
14637
+ value="false"
14638
+ ${testimonial && !testimonial.isPublished ? "checked" : ""}
14639
+ class="h-4 w-4 text-blue-600 focus:ring-blue-600 border-gray-600 bg-gray-700">
14640
+ <label for="draft" class="ml-2 block text-sm text-white">
14641
+ Draft <span class="text-gray-300">(not visible to users)</span>
14642
+ </label>
14643
+ </div>
14644
+ </div>
14645
+ </div>
14646
+
14647
+ <!-- Sort Order -->
14648
+ <div>
14649
+ <label for="sortOrder" class="block text-sm font-medium text-white">Sort Order</label>
14650
+ <div class="mt-1">
14651
+ <input type="number"
14652
+ name="sortOrder"
14653
+ id="sortOrder"
14654
+ value="${testimonial?.sortOrder || 0}"
14655
+ min="0"
14656
+ step="1"
14657
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6">
14658
+ <p class="mt-1 text-sm text-gray-300">Lower numbers appear first (0 = highest priority)</p>
14659
+ </div>
14660
+ ${errors?.sortOrder ? `
14661
+ <div class="mt-1">
14662
+ ${errors.sortOrder.map((error) => `
14663
+ <p class="text-sm text-red-400">${escapeHtml5(error)}</p>
14664
+ `).join("")}
14665
+ </div>
14666
+ ` : ""}
14667
+ </div>
14668
+ </div>
14669
+
14670
+ <!-- Form Actions -->
14671
+ <div class="flex items-center justify-end space-x-3 pt-6 border-t border-white/20">
14672
+ <a href="/admin/testimonials"
14673
+ class="inline-flex items-center justify-center rounded-xl backdrop-blur-sm bg-white/10 px-4 py-2 text-sm font-semibold text-white border border-white/20 hover:bg-white/20 transition-all">
14674
+ Cancel
14675
+ </a>
14676
+ <button type="submit"
14677
+ class="inline-flex items-center justify-center rounded-xl backdrop-blur-sm bg-blue-500/80 px-4 py-2 text-sm font-semibold text-white border border-white/20 hover:bg-blue-500 transition-all">
14678
+ <svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
14679
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
14680
+ </svg>
14681
+ ${isEdit ? "Update Testimonial" : "Create Testimonial"}
14682
+ </button>
14683
+ </div>
14684
+ </form>
14685
+ </div>
14686
+ </div>
14687
+
14688
+ <script>
14689
+ // Character count for testimonial
14690
+ const testimonialTextarea = document.getElementById('testimonialText');
14691
+ const testimonialCount = document.getElementById('testimonial-count');
14692
+
14693
+ function updateTestimonialCount() {
14694
+ testimonialCount.textContent = testimonialTextarea.value.length;
14695
+ }
14696
+
14697
+ testimonialTextarea.addEventListener('input', updateTestimonialCount);
14698
+ updateTestimonialCount(); // Initial count
14699
+ </script>
14700
+ `;
14701
+ const layoutData = {
14702
+ title: `${pageTitle} - Admin`,
14703
+ pageTitle,
14704
+ currentPath: isEdit ? `/admin/testimonials/${testimonial?.id}` : "/admin/testimonials/new",
14705
+ user: data.user,
14706
+ content: pageContent
14707
+ };
14708
+ return chunkET5I4GBD_cjs.renderAdminLayout(layoutData);
14709
+ }
14710
+ function escapeHtml5(unsafe) {
14711
+ return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
14712
+ }
14713
+
14714
+ // src/routes/admin-testimonials.ts
14715
+ var testimonialSchema = zod.z.object({
14716
+ authorName: zod.z.string().min(1, "Author name is required").max(100, "Author name must be under 100 characters"),
14717
+ authorTitle: zod.z.string().optional(),
14718
+ authorCompany: zod.z.string().optional(),
14719
+ testimonialText: zod.z.string().min(1, "Testimonial is required").max(1e3, "Testimonial must be under 1000 characters"),
14720
+ rating: zod.z.string().transform((val) => val ? parseInt(val, 10) : void 0).pipe(zod.z.number().min(1).max(5).optional()),
14721
+ isPublished: zod.z.string().transform((val) => val === "true"),
14722
+ sortOrder: zod.z.string().transform((val) => parseInt(val, 10)).pipe(zod.z.number().min(0))
14723
+ });
14724
+ var adminTestimonialsRoutes = new hono.Hono();
14725
+ adminTestimonialsRoutes.get("/", async (c) => {
14726
+ try {
14727
+ const user = c.get("user");
14728
+ const { published, minRating, search, page = "1" } = c.req.query();
14729
+ const currentPage = parseInt(page, 10) || 1;
14730
+ const limit = 20;
14731
+ const offset = (currentPage - 1) * limit;
14732
+ const db = c.env?.DB;
14733
+ if (!db) {
14734
+ return c.html(chunkET5I4GBD_cjs.renderTestimonialsList({
14735
+ testimonials: [],
14736
+ totalCount: 0,
14737
+ currentPage: 1,
14738
+ totalPages: 1,
14739
+ user: user ? {
14740
+ name: user.email,
14741
+ email: user.email,
14742
+ role: user.role
14743
+ } : void 0,
14744
+ message: "Database not available",
14745
+ messageType: "error"
14746
+ }));
14747
+ }
14748
+ let whereClause = "WHERE 1=1";
14749
+ const params = [];
14750
+ if (published !== void 0) {
14751
+ whereClause += " AND isPublished = ?";
14752
+ params.push(published === "true" ? 1 : 0);
14753
+ }
14754
+ if (minRating) {
14755
+ whereClause += " AND rating >= ?";
14756
+ params.push(parseInt(minRating, 10));
14757
+ }
14758
+ if (search) {
14759
+ whereClause += " AND (author_name LIKE ? OR testimonial_text LIKE ? OR author_company LIKE ?)";
14760
+ const searchTerm = `%${search}%`;
14761
+ params.push(searchTerm, searchTerm, searchTerm);
14762
+ }
14763
+ const countQuery = `SELECT COUNT(*) as count FROM testimonials ${whereClause}`;
14764
+ const { results: countResults } = await db.prepare(countQuery).bind(...params).all();
14765
+ const totalCount = countResults?.[0]?.count || 0;
14766
+ const dataQuery = `
14767
+ SELECT * FROM testimonials
14768
+ ${whereClause}
14769
+ ORDER BY sortOrder ASC, created_at DESC
14770
+ LIMIT ? OFFSET ?
14771
+ `;
14772
+ const { results: testimonials } = await db.prepare(dataQuery).bind(...params, limit, offset).all();
14773
+ const totalPages = Math.ceil(totalCount / limit);
14774
+ return c.html(chunkET5I4GBD_cjs.renderTestimonialsList({
14775
+ testimonials: testimonials || [],
14776
+ totalCount,
14777
+ currentPage,
14778
+ totalPages,
14779
+ user: user ? {
14780
+ name: user.email,
14781
+ email: user.email,
14782
+ role: user.role
14783
+ } : void 0
14784
+ }));
14785
+ } catch (error) {
14786
+ console.error("Error fetching testimonials:", error);
14787
+ const user = c.get("user");
14788
+ return c.html(chunkET5I4GBD_cjs.renderTestimonialsList({
14789
+ testimonials: [],
14790
+ totalCount: 0,
14791
+ currentPage: 1,
14792
+ totalPages: 1,
14793
+ user: user ? {
14794
+ name: user.email,
14795
+ email: user.email,
14796
+ role: user.role
14797
+ } : void 0,
14798
+ message: "Failed to load testimonials",
14799
+ messageType: "error"
14800
+ }));
14801
+ }
14802
+ });
14803
+ adminTestimonialsRoutes.get("/new", async (c) => {
14804
+ const user = c.get("user");
14805
+ return c.html(renderTestimonialsForm({
14806
+ isEdit: false,
14807
+ user: user ? {
14808
+ name: user.email,
14809
+ email: user.email,
14810
+ role: user.role
14811
+ } : void 0
14812
+ }));
14813
+ });
14814
+ adminTestimonialsRoutes.post("/", async (c) => {
14815
+ try {
14816
+ const formData = await c.req.formData();
14817
+ const data = Object.fromEntries(formData.entries());
14818
+ const validatedData = testimonialSchema.parse(data);
14819
+ const user = c.get("user");
14820
+ const db = c.env?.DB;
14821
+ if (!db) {
14822
+ return c.html(renderTestimonialsForm({
14823
+ isEdit: false,
14824
+ user: user ? {
14825
+ name: user.email,
14826
+ email: user.email,
14827
+ role: user.role
14828
+ } : void 0,
14829
+ message: "Database not available",
14830
+ messageType: "error"
14831
+ }));
14832
+ }
14833
+ const { results } = await db.prepare(`
14834
+ INSERT INTO testimonials (author_name, author_title, author_company, testimonial_text, rating, isPublished, sortOrder)
14835
+ VALUES (?, ?, ?, ?, ?, ?, ?)
14836
+ RETURNING *
14837
+ `).bind(
14838
+ validatedData.authorName,
14839
+ validatedData.authorTitle || null,
14840
+ validatedData.authorCompany || null,
14841
+ validatedData.testimonialText,
14842
+ validatedData.rating || null,
14843
+ validatedData.isPublished ? 1 : 0,
14844
+ validatedData.sortOrder
14845
+ ).all();
14846
+ if (results && results.length > 0) {
14847
+ return c.redirect("/admin/testimonials?message=Testimonial created successfully");
14848
+ } else {
14849
+ return c.html(renderTestimonialsForm({
14850
+ isEdit: false,
14851
+ user: user ? {
14852
+ name: user.email,
14853
+ email: user.email,
14854
+ role: user.role
14855
+ } : void 0,
14856
+ message: "Failed to create testimonial",
14857
+ messageType: "error"
14858
+ }));
14859
+ }
14860
+ } catch (error) {
14861
+ console.error("Error creating testimonial:", error);
14862
+ const user = c.get("user");
14863
+ if (error instanceof zod.z.ZodError) {
14864
+ const errors = {};
14865
+ error.errors.forEach((err) => {
14866
+ const field = err.path[0];
14867
+ if (!errors[field]) errors[field] = [];
14868
+ errors[field].push(err.message);
14869
+ });
14870
+ return c.html(renderTestimonialsForm({
14871
+ isEdit: false,
14872
+ user: user ? {
14873
+ name: user.email,
14874
+ email: user.email,
14875
+ role: user.role
14876
+ } : void 0,
14877
+ errors,
14878
+ message: "Please correct the errors below",
14879
+ messageType: "error"
14880
+ }));
14881
+ }
14882
+ return c.html(renderTestimonialsForm({
14883
+ isEdit: false,
14884
+ user: user ? {
14885
+ name: user.email,
14886
+ email: user.email,
14887
+ role: user.role
14888
+ } : void 0,
14889
+ message: "Failed to create testimonial",
14890
+ messageType: "error"
14891
+ }));
14892
+ }
14893
+ });
14894
+ adminTestimonialsRoutes.get("/:id", async (c) => {
14895
+ try {
14896
+ const id = parseInt(c.req.param("id"));
14897
+ const user = c.get("user");
14898
+ const db = c.env?.DB;
14899
+ if (!db) {
14900
+ return c.html(renderTestimonialsForm({
14901
+ isEdit: true,
14902
+ user: user ? {
14903
+ name: user.email,
14904
+ email: user.email,
14905
+ role: user.role
14906
+ } : void 0,
14907
+ message: "Database not available",
14908
+ messageType: "error"
14909
+ }));
14910
+ }
14911
+ const { results } = await db.prepare("SELECT * FROM testimonials WHERE id = ?").bind(id).all();
14912
+ if (!results || results.length === 0) {
14913
+ return c.redirect("/admin/testimonials?message=Testimonial not found&type=error");
14914
+ }
14915
+ const testimonial = results[0];
14916
+ return c.html(renderTestimonialsForm({
14917
+ testimonial: {
14918
+ id: testimonial.id,
14919
+ authorName: testimonial.author_name,
14920
+ authorTitle: testimonial.author_title,
14921
+ authorCompany: testimonial.author_company,
14922
+ testimonialText: testimonial.testimonial_text,
14923
+ rating: testimonial.rating,
14924
+ isPublished: Boolean(testimonial.isPublished),
14925
+ sortOrder: testimonial.sortOrder
14926
+ },
14927
+ isEdit: true,
14928
+ user: user ? {
14929
+ name: user.email,
14930
+ email: user.email,
14931
+ role: user.role
14932
+ } : void 0
14933
+ }));
14934
+ } catch (error) {
14935
+ console.error("Error fetching testimonial:", error);
14936
+ const user = c.get("user");
14937
+ return c.html(renderTestimonialsForm({
14938
+ isEdit: true,
14939
+ user: user ? {
14940
+ name: user.email,
14941
+ email: user.email,
14942
+ role: user.role
14943
+ } : void 0,
14944
+ message: "Failed to load testimonial",
14945
+ messageType: "error"
14946
+ }));
14947
+ }
14948
+ });
14949
+ adminTestimonialsRoutes.put("/:id", async (c) => {
14950
+ try {
14951
+ const id = parseInt(c.req.param("id"));
14952
+ const formData = await c.req.formData();
14953
+ const data = Object.fromEntries(formData.entries());
14954
+ const validatedData = testimonialSchema.parse(data);
14955
+ const user = c.get("user");
14956
+ const db = c.env?.DB;
14957
+ if (!db) {
14958
+ return c.html(renderTestimonialsForm({
14959
+ isEdit: true,
14960
+ user: user ? {
14961
+ name: user.email,
14962
+ email: user.email,
14963
+ role: user.role
14964
+ } : void 0,
14965
+ message: "Database not available",
14966
+ messageType: "error"
14967
+ }));
14968
+ }
14969
+ const { results } = await db.prepare(`
14970
+ UPDATE testimonials
14971
+ SET author_name = ?, author_title = ?, author_company = ?, testimonial_text = ?, rating = ?, isPublished = ?, sortOrder = ?
14972
+ WHERE id = ?
14973
+ RETURNING *
14974
+ `).bind(
14975
+ validatedData.authorName,
14976
+ validatedData.authorTitle || null,
14977
+ validatedData.authorCompany || null,
14978
+ validatedData.testimonialText,
14979
+ validatedData.rating || null,
14980
+ validatedData.isPublished ? 1 : 0,
14981
+ validatedData.sortOrder,
14982
+ id
14983
+ ).all();
14984
+ if (results && results.length > 0) {
14985
+ return c.redirect("/admin/testimonials?message=Testimonial updated successfully");
14986
+ } else {
14987
+ return c.html(renderTestimonialsForm({
14988
+ testimonial: {
14989
+ id,
14990
+ authorName: validatedData.authorName,
14991
+ authorTitle: validatedData.authorTitle,
14992
+ authorCompany: validatedData.authorCompany,
14993
+ testimonialText: validatedData.testimonialText,
14994
+ rating: validatedData.rating,
14995
+ isPublished: validatedData.isPublished,
14996
+ sortOrder: validatedData.sortOrder
14997
+ },
14998
+ isEdit: true,
14999
+ user: user ? {
15000
+ name: user.email,
15001
+ email: user.email,
15002
+ role: user.role
15003
+ } : void 0,
15004
+ message: "Testimonial not found",
15005
+ messageType: "error"
15006
+ }));
15007
+ }
15008
+ } catch (error) {
15009
+ console.error("Error updating testimonial:", error);
15010
+ const user = c.get("user");
15011
+ const id = parseInt(c.req.param("id"));
15012
+ if (error instanceof zod.z.ZodError) {
15013
+ const errors = {};
15014
+ error.errors.forEach((err) => {
15015
+ const field = err.path[0];
15016
+ if (!errors[field]) errors[field] = [];
15017
+ errors[field].push(err.message);
15018
+ });
15019
+ return c.html(renderTestimonialsForm({
15020
+ testimonial: {
15021
+ id,
15022
+ authorName: "",
15023
+ authorTitle: "",
15024
+ authorCompany: "",
15025
+ testimonialText: "",
15026
+ rating: void 0,
15027
+ isPublished: true,
15028
+ sortOrder: 0
15029
+ },
15030
+ isEdit: true,
15031
+ user: user ? {
15032
+ name: user.email,
15033
+ email: user.email,
15034
+ role: user.role
15035
+ } : void 0,
15036
+ errors,
15037
+ message: "Please correct the errors below",
15038
+ messageType: "error"
15039
+ }));
15040
+ }
15041
+ return c.html(renderTestimonialsForm({
15042
+ testimonial: {
15043
+ id,
15044
+ authorName: "",
15045
+ authorTitle: "",
15046
+ authorCompany: "",
15047
+ testimonialText: "",
15048
+ rating: void 0,
15049
+ isPublished: true,
15050
+ sortOrder: 0
15051
+ },
15052
+ isEdit: true,
15053
+ user: user ? {
15054
+ name: user.email,
15055
+ email: user.email,
15056
+ role: user.role
15057
+ } : void 0,
15058
+ message: "Failed to update testimonial",
15059
+ messageType: "error"
15060
+ }));
15061
+ }
15062
+ });
15063
+ adminTestimonialsRoutes.delete("/:id", async (c) => {
15064
+ try {
15065
+ const id = parseInt(c.req.param("id"));
15066
+ const db = c.env?.DB;
15067
+ if (!db) {
15068
+ return c.json({ error: "Database not available" }, 500);
15069
+ }
15070
+ const { changes } = await db.prepare("DELETE FROM testimonials WHERE id = ?").bind(id).run();
15071
+ if (changes === 0) {
15072
+ return c.json({ error: "Testimonial not found" }, 404);
15073
+ }
15074
+ return c.redirect("/admin/testimonials?message=Testimonial deleted successfully");
15075
+ } catch (error) {
15076
+ console.error("Error deleting testimonial:", error);
15077
+ return c.json({ error: "Failed to delete testimonial" }, 500);
15078
+ }
15079
+ });
15080
+ var admin_testimonials_default = adminTestimonialsRoutes;
15081
+
15082
+ // src/templates/pages/admin-code-examples-form.template.ts
15083
+ function renderCodeExamplesForm(data) {
15084
+ const { codeExample, isEdit, errors, message, messageType } = data;
15085
+ const pageTitle = isEdit ? "Edit Code Example" : "New Code Example";
15086
+ const pageContent = `
15087
+ <div class="w-full px-4 sm:px-6 lg:px-8 py-6 space-y-6">
15088
+ <!-- Header -->
15089
+ <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6">
15090
+ <div>
15091
+ <h1 class="text-2xl font-semibold text-white">${pageTitle}</h1>
15092
+ <p class="mt-2 text-sm text-gray-300">
15093
+ ${isEdit ? "Update the code example details below" : "Create a new code snippet or example"}
15094
+ </p>
15095
+ </div>
15096
+ <div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
15097
+ <a href="/admin/code-examples"
15098
+ class="inline-flex items-center justify-center rounded-xl backdrop-blur-sm bg-white/10 px-4 py-2 text-sm font-semibold text-white border border-white/20 hover:bg-white/20 transition-all">
15099
+ <svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
15100
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
15101
+ </svg>
15102
+ Back to List
15103
+ </a>
15104
+ </div>
15105
+ </div>
15106
+
15107
+ ${message ? chunkET5I4GBD_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
15108
+
15109
+ <!-- Form -->
15110
+ <div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl">
15111
+ <form ${isEdit ? `hx-put="/admin/code-examples/${codeExample?.id}"` : 'hx-post="/admin/code-examples"'}
15112
+ hx-target="body"
15113
+ hx-swap="outerHTML"
15114
+ class="space-y-6 p-6">
15115
+
15116
+ <!-- Basic Information Section -->
15117
+ <div>
15118
+ <h2 class="text-lg font-medium text-white mb-4">Basic Information</h2>
15119
+
15120
+ <!-- Title -->
15121
+ <div class="mb-4">
15122
+ <label for="title" class="block text-sm font-medium text-white">
15123
+ Title <span class="text-red-400">*</span>
15124
+ </label>
15125
+ <div class="mt-1">
15126
+ <input type="text"
15127
+ name="title"
15128
+ id="title"
15129
+ value="${codeExample?.title || ""}"
15130
+ required
15131
+ maxlength="200"
15132
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-purple-600 sm:text-sm sm:leading-6"
15133
+ placeholder="e.g., React useState Hook Example">
15134
+ </div>
15135
+ ${errors?.title ? `
15136
+ <div class="mt-1">
15137
+ ${errors.title.map((error) => `
15138
+ <p class="text-sm text-red-400">${escapeHtml6(error)}</p>
15139
+ `).join("")}
15140
+ </div>
15141
+ ` : ""}
15142
+ </div>
15143
+
15144
+ <!-- Description -->
15145
+ <div class="mb-4">
15146
+ <label for="description" class="block text-sm font-medium text-white">Description</label>
15147
+ <div class="mt-1">
15148
+ <textarea name="description"
15149
+ id="description"
15150
+ rows="3"
15151
+ maxlength="500"
15152
+ class="backdrop-blur-sm bg-white/10 border border-white/20 rounded-xl px-3 py-2 text-white placeholder-gray-300 focus:border-purple-400 focus:outline-none transition-colors w-full"
15153
+ placeholder="Briefly describe what this code example demonstrates...">${codeExample?.description || ""}</textarea>
15154
+ <p class="mt-1 text-sm text-gray-300">
15155
+ <span id="description-count">0</span>/500 characters
15156
+ </p>
15157
+ </div>
15158
+ ${errors?.description ? `
15159
+ <div class="mt-1">
15160
+ ${errors.description.map((error) => `
15161
+ <p class="text-sm text-red-400">${escapeHtml6(error)}</p>
15162
+ `).join("")}
15163
+ </div>
15164
+ ` : ""}
15165
+ </div>
15166
+
15167
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
15168
+ <!-- Language -->
15169
+ <div>
15170
+ <label for="language" class="block text-sm font-medium text-white">
15171
+ Language <span class="text-red-400">*</span>
15172
+ </label>
15173
+ <div class="mt-1">
15174
+ <select name="language"
15175
+ id="language"
15176
+ required
15177
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 focus:ring-2 focus:ring-inset focus:ring-purple-600 sm:text-sm sm:leading-6">
15178
+ <option value="">Select language...</option>
15179
+ <option value="javascript" ${codeExample?.language === "javascript" ? "selected" : ""}>JavaScript</option>
15180
+ <option value="typescript" ${codeExample?.language === "typescript" ? "selected" : ""}>TypeScript</option>
15181
+ <option value="python" ${codeExample?.language === "python" ? "selected" : ""}>Python</option>
15182
+ <option value="go" ${codeExample?.language === "go" ? "selected" : ""}>Go</option>
15183
+ <option value="rust" ${codeExample?.language === "rust" ? "selected" : ""}>Rust</option>
15184
+ <option value="java" ${codeExample?.language === "java" ? "selected" : ""}>Java</option>
15185
+ <option value="php" ${codeExample?.language === "php" ? "selected" : ""}>PHP</option>
15186
+ <option value="ruby" ${codeExample?.language === "ruby" ? "selected" : ""}>Ruby</option>
15187
+ <option value="sql" ${codeExample?.language === "sql" ? "selected" : ""}>SQL</option>
15188
+ </select>
15189
+ </div>
15190
+ ${errors?.language ? `
15191
+ <div class="mt-1">
15192
+ ${errors.language.map((error) => `
15193
+ <p class="text-sm text-red-400">${escapeHtml6(error)}</p>
15194
+ `).join("")}
15195
+ </div>
15196
+ ` : ""}
15197
+ </div>
15198
+
15199
+ <!-- Category -->
15200
+ <div>
15201
+ <label for="category" class="block text-sm font-medium text-white">Category</label>
15202
+ <div class="mt-1">
15203
+ <input type="text"
15204
+ name="category"
15205
+ id="category"
15206
+ value="${codeExample?.category || ""}"
15207
+ maxlength="50"
15208
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-purple-600 sm:text-sm sm:leading-6"
15209
+ placeholder="e.g., frontend, backend">
15210
+ </div>
15211
+ ${errors?.category ? `
15212
+ <div class="mt-1">
15213
+ ${errors.category.map((error) => `
15214
+ <p class="text-sm text-red-400">${escapeHtml6(error)}</p>
15215
+ `).join("")}
15216
+ </div>
15217
+ ` : ""}
15218
+ </div>
15219
+
15220
+ <!-- Tags -->
15221
+ <div>
15222
+ <label for="tags" class="block text-sm font-medium text-white">Tags</label>
15223
+ <div class="mt-1">
15224
+ <input type="text"
15225
+ name="tags"
15226
+ id="tags"
15227
+ value="${codeExample?.tags || ""}"
15228
+ maxlength="200"
15229
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-purple-600 sm:text-sm sm:leading-6"
15230
+ placeholder="e.g., react, hooks, state">
15231
+ <p class="mt-1 text-sm text-gray-300">Comma-separated tags</p>
15232
+ </div>
15233
+ ${errors?.tags ? `
15234
+ <div class="mt-1">
15235
+ ${errors.tags.map((error) => `
15236
+ <p class="text-sm text-red-400">${escapeHtml6(error)}</p>
15237
+ `).join("")}
15238
+ </div>
15239
+ ` : ""}
15240
+ </div>
15241
+ </div>
15242
+ </div>
15243
+
15244
+ <!-- Code Section -->
15245
+ <div>
15246
+ <h2 class="text-lg font-medium text-white mb-4">Code</h2>
15247
+
15248
+ <!-- Code Editor -->
15249
+ <div class="mb-4">
15250
+ <label for="code" class="block text-sm font-medium text-white">
15251
+ Code <span class="text-red-400">*</span>
15252
+ </label>
15253
+ <div class="mt-1">
15254
+ <textarea name="code"
15255
+ id="code"
15256
+ rows="20"
15257
+ required
15258
+ class="backdrop-blur-sm bg-gray-800/90 border border-white/20 rounded-xl px-3 py-2 text-white placeholder-gray-300 focus:border-purple-400 focus:outline-none transition-colors w-full font-mono text-sm"
15259
+ placeholder="Paste your code here...">${codeExample?.code || ""}</textarea>
15260
+ <p class="mt-1 text-sm text-gray-300">
15261
+ <span id="code-count">0</span> characters
15262
+ </p>
15263
+ </div>
15264
+ ${errors?.code ? `
15265
+ <div class="mt-1">
15266
+ ${errors.code.map((error) => `
15267
+ <p class="text-sm text-red-400">${escapeHtml6(error)}</p>
15268
+ `).join("")}
15269
+ </div>
15270
+ ` : ""}
15271
+ </div>
15272
+ </div>
15273
+
15274
+ <!-- Status and Sort Order Row -->
15275
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
15276
+ <!-- Published Status -->
15277
+ <div>
15278
+ <label class="block text-sm font-medium text-white">Status</label>
15279
+ <div class="mt-2 space-y-2">
15280
+ <div class="flex items-center">
15281
+ <input id="published"
15282
+ name="isPublished"
15283
+ type="radio"
15284
+ value="true"
15285
+ ${!codeExample || codeExample.isPublished ? "checked" : ""}
15286
+ class="h-4 w-4 text-purple-600 focus:ring-purple-600 border-gray-600 bg-gray-700">
15287
+ <label for="published" class="ml-2 block text-sm text-white">
15288
+ Published <span class="text-gray-300">(visible to users)</span>
15289
+ </label>
15290
+ </div>
15291
+ <div class="flex items-center">
15292
+ <input id="draft"
15293
+ name="isPublished"
15294
+ type="radio"
15295
+ value="false"
15296
+ ${codeExample && !codeExample.isPublished ? "checked" : ""}
15297
+ class="h-4 w-4 text-purple-600 focus:ring-purple-600 border-gray-600 bg-gray-700">
15298
+ <label for="draft" class="ml-2 block text-sm text-white">
15299
+ Draft <span class="text-gray-300">(not visible to users)</span>
15300
+ </label>
15301
+ </div>
15302
+ </div>
15303
+ </div>
15304
+
15305
+ <!-- Sort Order -->
15306
+ <div>
15307
+ <label for="sortOrder" class="block text-sm font-medium text-white">Sort Order</label>
15308
+ <div class="mt-1">
15309
+ <input type="number"
15310
+ name="sortOrder"
15311
+ id="sortOrder"
15312
+ value="${codeExample?.sortOrder || 0}"
15313
+ min="0"
15314
+ step="1"
15315
+ class="block w-full rounded-md border-0 bg-gray-700 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-purple-600 sm:text-sm sm:leading-6">
15316
+ <p class="mt-1 text-sm text-gray-300">Lower numbers appear first (0 = highest priority)</p>
15317
+ </div>
15318
+ ${errors?.sortOrder ? `
15319
+ <div class="mt-1">
15320
+ ${errors.sortOrder.map((error) => `
15321
+ <p class="text-sm text-red-400">${escapeHtml6(error)}</p>
15322
+ `).join("")}
15323
+ </div>
15324
+ ` : ""}
15325
+ </div>
15326
+ </div>
15327
+
15328
+ <!-- Form Actions -->
15329
+ <div class="flex items-center justify-end space-x-3 pt-6 border-t border-white/20">
15330
+ <a href="/admin/code-examples"
15331
+ class="inline-flex items-center justify-center rounded-xl backdrop-blur-sm bg-white/10 px-4 py-2 text-sm font-semibold text-white border border-white/20 hover:bg-white/20 transition-all">
15332
+ Cancel
15333
+ </a>
15334
+ <button type="submit"
15335
+ class="inline-flex items-center justify-center rounded-xl backdrop-blur-sm bg-purple-500/80 px-4 py-2 text-sm font-semibold text-white border border-white/20 hover:bg-purple-500 transition-all">
15336
+ <svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
15337
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
15338
+ </svg>
15339
+ ${isEdit ? "Update Code Example" : "Create Code Example"}
15340
+ </button>
15341
+ </div>
15342
+ </form>
15343
+ </div>
15344
+ </div>
15345
+
15346
+ <script>
15347
+ // Character count for description
15348
+ const descriptionTextarea = document.getElementById('description');
15349
+ const descriptionCount = document.getElementById('description-count');
15350
+
15351
+ function updateDescriptionCount() {
15352
+ descriptionCount.textContent = descriptionTextarea.value.length;
15353
+ }
15354
+
15355
+ descriptionTextarea.addEventListener('input', updateDescriptionCount);
15356
+ updateDescriptionCount(); // Initial count
15357
+
15358
+ // Character count for code
15359
+ const codeTextarea = document.getElementById('code');
15360
+ const codeCount = document.getElementById('code-count');
15361
+
15362
+ function updateCodeCount() {
15363
+ codeCount.textContent = codeTextarea.value.length;
15364
+ }
15365
+
15366
+ codeTextarea.addEventListener('input', updateCodeCount);
15367
+ updateCodeCount(); // Initial count
15368
+ </script>
15369
+ `;
15370
+ const layoutData = {
15371
+ title: `${pageTitle} - Admin`,
15372
+ pageTitle,
15373
+ currentPath: isEdit ? `/admin/code-examples/${codeExample?.id}` : "/admin/code-examples/new",
15374
+ user: data.user,
15375
+ content: pageContent
15376
+ };
15377
+ return chunkET5I4GBD_cjs.renderAdminLayout(layoutData);
15378
+ }
15379
+ function escapeHtml6(unsafe) {
15380
+ return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
15381
+ }
15382
+
15383
+ // src/routes/admin-code-examples.ts
15384
+ var codeExampleSchema = zod.z.object({
15385
+ title: zod.z.string().min(1, "Title is required").max(200, "Title must be under 200 characters"),
15386
+ description: zod.z.string().max(500, "Description must be under 500 characters").optional(),
15387
+ code: zod.z.string().min(1, "Code is required"),
15388
+ language: zod.z.string().min(1, "Language is required"),
15389
+ category: zod.z.string().max(50, "Category must be under 50 characters").optional(),
15390
+ tags: zod.z.string().max(200, "Tags must be under 200 characters").optional(),
15391
+ isPublished: zod.z.string().transform((val) => val === "true"),
15392
+ sortOrder: zod.z.string().transform((val) => parseInt(val, 10)).pipe(zod.z.number().min(0))
15393
+ });
15394
+ var adminCodeExamplesRoutes = new hono.Hono();
15395
+ adminCodeExamplesRoutes.get("/", async (c) => {
15396
+ try {
15397
+ const user = c.get("user");
15398
+ const { published, language, search, page = "1" } = c.req.query();
15399
+ const currentPage = parseInt(page, 10) || 1;
15400
+ const limit = 20;
15401
+ const offset = (currentPage - 1) * limit;
15402
+ const db = c.env?.DB;
15403
+ if (!db) {
15404
+ return c.html(chunkET5I4GBD_cjs.renderCodeExamplesList({
15405
+ codeExamples: [],
15406
+ totalCount: 0,
15407
+ currentPage: 1,
15408
+ totalPages: 1,
15409
+ user: user ? {
15410
+ name: user.email,
15411
+ email: user.email,
15412
+ role: user.role
15413
+ } : void 0,
15414
+ message: "Database not available",
15415
+ messageType: "error"
15416
+ }));
15417
+ }
15418
+ let whereClause = "WHERE 1=1";
15419
+ const params = [];
15420
+ if (published !== void 0) {
15421
+ whereClause += " AND isPublished = ?";
15422
+ params.push(published === "true" ? 1 : 0);
15423
+ }
15424
+ if (language) {
15425
+ whereClause += " AND language = ?";
15426
+ params.push(language);
15427
+ }
15428
+ if (search) {
15429
+ whereClause += " AND (title LIKE ? OR description LIKE ? OR code LIKE ? OR tags LIKE ?)";
15430
+ const searchTerm = `%${search}%`;
15431
+ params.push(searchTerm, searchTerm, searchTerm, searchTerm);
15432
+ }
15433
+ const countQuery = `SELECT COUNT(*) as count FROM code_examples ${whereClause}`;
15434
+ const { results: countResults } = await db.prepare(countQuery).bind(...params).all();
15435
+ const totalCount = countResults?.[0]?.count || 0;
15436
+ const dataQuery = `
15437
+ SELECT * FROM code_examples
15438
+ ${whereClause}
15439
+ ORDER BY sortOrder ASC, created_at DESC
15440
+ LIMIT ? OFFSET ?
15441
+ `;
15442
+ const { results: codeExamples } = await db.prepare(dataQuery).bind(...params, limit, offset).all();
15443
+ const totalPages = Math.ceil(totalCount / limit);
15444
+ return c.html(chunkET5I4GBD_cjs.renderCodeExamplesList({
15445
+ codeExamples: codeExamples || [],
15446
+ totalCount,
15447
+ currentPage,
15448
+ totalPages,
15449
+ user: user ? {
15450
+ name: user.email,
15451
+ email: user.email,
15452
+ role: user.role
15453
+ } : void 0
15454
+ }));
15455
+ } catch (error) {
15456
+ console.error("Error fetching code examples:", error);
15457
+ const user = c.get("user");
15458
+ return c.html(chunkET5I4GBD_cjs.renderCodeExamplesList({
15459
+ codeExamples: [],
15460
+ totalCount: 0,
15461
+ currentPage: 1,
15462
+ totalPages: 1,
15463
+ user: user ? {
15464
+ name: user.email,
15465
+ email: user.email,
15466
+ role: user.role
15467
+ } : void 0,
15468
+ message: "Failed to load code examples",
15469
+ messageType: "error"
15470
+ }));
15471
+ }
15472
+ });
15473
+ adminCodeExamplesRoutes.get("/new", async (c) => {
15474
+ const user = c.get("user");
15475
+ return c.html(renderCodeExamplesForm({
15476
+ isEdit: false,
15477
+ user: user ? {
15478
+ name: user.email,
15479
+ email: user.email,
15480
+ role: user.role
15481
+ } : void 0
15482
+ }));
15483
+ });
15484
+ adminCodeExamplesRoutes.post("/", async (c) => {
15485
+ try {
15486
+ const formData = await c.req.formData();
15487
+ const data = Object.fromEntries(formData.entries());
15488
+ const validatedData = codeExampleSchema.parse(data);
15489
+ const user = c.get("user");
15490
+ const db = c.env?.DB;
15491
+ if (!db) {
15492
+ return c.html(renderCodeExamplesForm({
15493
+ isEdit: false,
15494
+ user: user ? {
15495
+ name: user.email,
15496
+ email: user.email,
15497
+ role: user.role
15498
+ } : void 0,
15499
+ message: "Database not available",
15500
+ messageType: "error"
15501
+ }));
15502
+ }
15503
+ const { results } = await db.prepare(`
15504
+ INSERT INTO code_examples (title, description, code, language, category, tags, isPublished, sortOrder)
15505
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
15506
+ RETURNING *
15507
+ `).bind(
15508
+ validatedData.title,
15509
+ validatedData.description || null,
15510
+ validatedData.code,
15511
+ validatedData.language,
15512
+ validatedData.category || null,
15513
+ validatedData.tags || null,
15514
+ validatedData.isPublished ? 1 : 0,
15515
+ validatedData.sortOrder
15516
+ ).all();
15517
+ if (results && results.length > 0) {
15518
+ return c.redirect("/admin/code-examples?message=Code example created successfully");
15519
+ } else {
15520
+ return c.html(renderCodeExamplesForm({
15521
+ isEdit: false,
15522
+ user: user ? {
15523
+ name: user.email,
15524
+ email: user.email,
15525
+ role: user.role
15526
+ } : void 0,
15527
+ message: "Failed to create code example",
15528
+ messageType: "error"
15529
+ }));
15530
+ }
15531
+ } catch (error) {
15532
+ console.error("Error creating code example:", error);
15533
+ const user = c.get("user");
15534
+ if (error instanceof zod.z.ZodError) {
15535
+ const errors = {};
15536
+ error.errors.forEach((err) => {
15537
+ const field = err.path[0];
15538
+ if (!errors[field]) errors[field] = [];
15539
+ errors[field].push(err.message);
15540
+ });
15541
+ return c.html(renderCodeExamplesForm({
15542
+ isEdit: false,
15543
+ user: user ? {
15544
+ name: user.email,
15545
+ email: user.email,
15546
+ role: user.role
15547
+ } : void 0,
15548
+ errors,
15549
+ message: "Please correct the errors below",
15550
+ messageType: "error"
15551
+ }));
15552
+ }
15553
+ return c.html(renderCodeExamplesForm({
15554
+ isEdit: false,
15555
+ user: user ? {
15556
+ name: user.email,
15557
+ email: user.email,
15558
+ role: user.role
15559
+ } : void 0,
15560
+ message: "Failed to create code example",
15561
+ messageType: "error"
15562
+ }));
15563
+ }
15564
+ });
15565
+ adminCodeExamplesRoutes.get("/:id", async (c) => {
15566
+ try {
15567
+ const id = parseInt(c.req.param("id"));
15568
+ const user = c.get("user");
15569
+ const db = c.env?.DB;
15570
+ if (!db) {
15571
+ return c.html(renderCodeExamplesForm({
15572
+ isEdit: true,
15573
+ user: user ? {
15574
+ name: user.email,
15575
+ email: user.email,
15576
+ role: user.role
15577
+ } : void 0,
15578
+ message: "Database not available",
15579
+ messageType: "error"
15580
+ }));
15581
+ }
15582
+ const { results } = await db.prepare("SELECT * FROM code_examples WHERE id = ?").bind(id).all();
15583
+ if (!results || results.length === 0) {
15584
+ return c.redirect("/admin/code-examples?message=Code example not found&type=error");
15585
+ }
15586
+ const example = results[0];
15587
+ return c.html(renderCodeExamplesForm({
15588
+ codeExample: {
15589
+ id: example.id,
15590
+ title: example.title,
15591
+ description: example.description,
15592
+ code: example.code,
15593
+ language: example.language,
15594
+ category: example.category,
15595
+ tags: example.tags,
15596
+ isPublished: Boolean(example.isPublished),
15597
+ sortOrder: example.sortOrder
15598
+ },
15599
+ isEdit: true,
15600
+ user: user ? {
15601
+ name: user.email,
15602
+ email: user.email,
15603
+ role: user.role
15604
+ } : void 0
15605
+ }));
15606
+ } catch (error) {
15607
+ console.error("Error fetching code example:", error);
15608
+ const user = c.get("user");
15609
+ return c.html(renderCodeExamplesForm({
15610
+ isEdit: true,
15611
+ user: user ? {
15612
+ name: user.email,
15613
+ email: user.email,
15614
+ role: user.role
15615
+ } : void 0,
15616
+ message: "Failed to load code example",
15617
+ messageType: "error"
15618
+ }));
15619
+ }
15620
+ });
15621
+ adminCodeExamplesRoutes.put("/:id", async (c) => {
15622
+ try {
15623
+ const id = parseInt(c.req.param("id"));
15624
+ const formData = await c.req.formData();
15625
+ const data = Object.fromEntries(formData.entries());
15626
+ const validatedData = codeExampleSchema.parse(data);
15627
+ const user = c.get("user");
15628
+ const db = c.env?.DB;
15629
+ if (!db) {
15630
+ return c.html(renderCodeExamplesForm({
15631
+ isEdit: true,
15632
+ user: user ? {
15633
+ name: user.email,
15634
+ email: user.email,
15635
+ role: user.role
15636
+ } : void 0,
15637
+ message: "Database not available",
15638
+ messageType: "error"
15639
+ }));
15640
+ }
15641
+ const { results } = await db.prepare(`
15642
+ UPDATE code_examples
15643
+ SET title = ?, description = ?, code = ?, language = ?, category = ?, tags = ?, isPublished = ?, sortOrder = ?
15644
+ WHERE id = ?
15645
+ RETURNING *
15646
+ `).bind(
15647
+ validatedData.title,
15648
+ validatedData.description || null,
15649
+ validatedData.code,
15650
+ validatedData.language,
15651
+ validatedData.category || null,
15652
+ validatedData.tags || null,
15653
+ validatedData.isPublished ? 1 : 0,
15654
+ validatedData.sortOrder,
15655
+ id
15656
+ ).all();
15657
+ if (results && results.length > 0) {
15658
+ return c.redirect("/admin/code-examples?message=Code example updated successfully");
15659
+ } else {
15660
+ return c.html(renderCodeExamplesForm({
15661
+ codeExample: {
15662
+ id,
15663
+ title: validatedData.title,
15664
+ description: validatedData.description,
15665
+ code: validatedData.code,
15666
+ language: validatedData.language,
15667
+ category: validatedData.category,
15668
+ tags: validatedData.tags,
15669
+ isPublished: validatedData.isPublished,
15670
+ sortOrder: validatedData.sortOrder
15671
+ },
15672
+ isEdit: true,
15673
+ user: user ? {
15674
+ name: user.email,
15675
+ email: user.email,
15676
+ role: user.role
15677
+ } : void 0,
15678
+ message: "Code example not found",
15679
+ messageType: "error"
15680
+ }));
15681
+ }
15682
+ } catch (error) {
15683
+ console.error("Error updating code example:", error);
15684
+ const user = c.get("user");
15685
+ const id = parseInt(c.req.param("id"));
15686
+ if (error instanceof zod.z.ZodError) {
15687
+ const errors = {};
15688
+ error.errors.forEach((err) => {
15689
+ const field = err.path[0];
15690
+ if (!errors[field]) errors[field] = [];
15691
+ errors[field].push(err.message);
15692
+ });
15693
+ return c.html(renderCodeExamplesForm({
15694
+ codeExample: {
15695
+ id,
15696
+ title: "",
15697
+ description: "",
15698
+ code: "",
15699
+ language: "",
15700
+ category: "",
15701
+ tags: "",
15702
+ isPublished: true,
15703
+ sortOrder: 0
15704
+ },
15705
+ isEdit: true,
15706
+ user: user ? {
15707
+ name: user.email,
15708
+ email: user.email,
15709
+ role: user.role
15710
+ } : void 0,
15711
+ errors,
15712
+ message: "Please correct the errors below",
15713
+ messageType: "error"
15714
+ }));
15715
+ }
15716
+ return c.html(renderCodeExamplesForm({
15717
+ codeExample: {
15718
+ id,
15719
+ title: "",
15720
+ description: "",
15721
+ code: "",
15722
+ language: "",
15723
+ category: "",
15724
+ tags: "",
15725
+ isPublished: true,
15726
+ sortOrder: 0
15727
+ },
15728
+ isEdit: true,
15729
+ user: user ? {
15730
+ name: user.email,
15731
+ email: user.email,
15732
+ role: user.role
15733
+ } : void 0,
15734
+ message: "Failed to update code example",
15735
+ messageType: "error"
15736
+ }));
15737
+ }
15738
+ });
15739
+ adminCodeExamplesRoutes.delete("/:id", async (c) => {
15740
+ try {
15741
+ const id = parseInt(c.req.param("id"));
15742
+ const db = c.env?.DB;
15743
+ if (!db) {
15744
+ return c.json({ error: "Database not available" }, 500);
15745
+ }
15746
+ const { changes } = await db.prepare("DELETE FROM code_examples WHERE id = ?").bind(id).run();
15747
+ if (changes === 0) {
15748
+ return c.json({ error: "Code example not found" }, 404);
15749
+ }
15750
+ return c.redirect("/admin/code-examples?message=Code example deleted successfully");
15751
+ } catch (error) {
15752
+ console.error("Error deleting code example:", error);
15753
+ return c.json({ error: "Failed to delete code example" }, 500);
15754
+ }
15755
+ });
15756
+ var admin_code_examples_default = adminCodeExamplesRoutes;
14212
15757
 
14213
15758
  // src/routes/index.ts
14214
15759
  var ROUTES_INFO = {
14215
- message: "Routes migration in progress",
15760
+ message: "Core routes available",
14216
15761
  available: [
14217
15762
  "apiRoutes",
14218
15763
  "apiContentCrudRoutes",
@@ -14224,23 +15769,33 @@ var ROUTES_INFO = {
14224
15769
  "adminUsersRoutes",
14225
15770
  "adminMediaRoutes",
14226
15771
  "adminPluginRoutes",
14227
- "adminLogsRoutes"
15772
+ "adminLogsRoutes",
15773
+ "adminDesignRoutes",
15774
+ "adminCheckboxRoutes",
15775
+ "adminFAQRoutes",
15776
+ "adminTestimonialsRoutes",
15777
+ "adminCodeExamplesRoutes"
14228
15778
  ],
14229
- status: "Routes are being added incrementally",
15779
+ status: "Core package routes ready",
14230
15780
  reference: "https://github.com/sonicjs/sonicjs"
14231
15781
  };
14232
15782
 
14233
15783
  exports.ROUTES_INFO = ROUTES_INFO;
15784
+ exports.adminCheckboxRoutes = adminCheckboxRoutes;
15785
+ exports.adminDesignRoutes = adminDesignRoutes;
14234
15786
  exports.adminLogsRoutes = adminLogsRoutes;
14235
15787
  exports.adminMediaRoutes = adminMediaRoutes;
14236
15788
  exports.adminPluginRoutes = adminPluginRoutes;
14237
15789
  exports.admin_api_default = admin_api_default;
15790
+ exports.admin_code_examples_default = admin_code_examples_default;
14238
15791
  exports.admin_content_default = admin_content_default;
15792
+ exports.admin_faq_default = admin_faq_default;
15793
+ exports.admin_testimonials_default = admin_testimonials_default;
14239
15794
  exports.api_content_crud_default = api_content_crud_default;
14240
15795
  exports.api_default = api_default;
14241
15796
  exports.api_media_default = api_media_default;
14242
15797
  exports.api_system_default = api_system_default;
14243
15798
  exports.auth_default = auth_default;
14244
15799
  exports.userRoutes = userRoutes;
14245
- //# sourceMappingURL=chunk-DG4INX36.cjs.map
14246
- //# sourceMappingURL=chunk-DG4INX36.cjs.map
15800
+ //# sourceMappingURL=chunk-QUMBDPNJ.cjs.map
15801
+ //# sourceMappingURL=chunk-QUMBDPNJ.cjs.map