@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.
- package/dist/chunk-4XI3YBKU.cjs +266 -0
- package/dist/chunk-4XI3YBKU.cjs.map +1 -0
- package/dist/chunk-ET5I4GBD.cjs +3515 -0
- package/dist/chunk-ET5I4GBD.cjs.map +1 -0
- package/dist/{chunk-FDUDHGI6.js → chunk-JETM2U2D.js} +2345 -795
- package/dist/chunk-JETM2U2D.js.map +1 -0
- package/dist/chunk-LU6J53IX.js +262 -0
- package/dist/chunk-LU6J53IX.js.map +1 -0
- package/dist/chunk-P3VS4DV3.js +3498 -0
- package/dist/chunk-P3VS4DV3.js.map +1 -0
- package/dist/{chunk-DG4INX36.cjs → chunk-QUMBDPNJ.cjs} +2392 -837
- package/dist/chunk-QUMBDPNJ.cjs.map +1 -0
- package/dist/index.cjs +56 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/routes.cjs +34 -14
- package/dist/routes.js +2 -2
- package/dist/templates.cjs +42 -22
- package/dist/templates.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-6BHDKYLU.cjs +0 -798
- package/dist/chunk-6BHDKYLU.cjs.map +0 -1
- package/dist/chunk-DG4INX36.cjs.map +0 -1
- package/dist/chunk-FDUDHGI6.js.map +0 -1
- package/dist/chunk-FZYF43TN.js +0 -789
- package/dist/chunk-FZYF43TN.js.map +0 -1
- package/dist/chunk-G4JGVZAF.js +0 -1337
- package/dist/chunk-G4JGVZAF.js.map +0 -1
- package/dist/chunk-LEGKJ5DI.cjs +0 -1344
- package/dist/chunk-LEGKJ5DI.cjs.map +0 -1
|
@@ -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
|
|
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
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
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
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
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
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
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
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
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
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
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
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
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
|
-
|
|
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/
|
|
3568
|
-
function
|
|
3569
|
-
const
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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
|
|
7154
|
+
return chunkET5I4GBD_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
7543
7155
|
}
|
|
7544
7156
|
|
|
7545
7157
|
// src/templates/pages/admin-user-new.template.ts
|
|
7546
|
-
|
|
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
|
|
7442
|
+
return chunkET5I4GBD_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
7831
7443
|
}
|
|
7832
7444
|
|
|
7833
7445
|
// src/templates/pages/admin-users-list.template.ts
|
|
7834
|
-
|
|
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
|
|
7472
|
+
const escapeHtml7 = (text) => text.replace(/[&<>"']/g, (char) => ({
|
|
7861
7473
|
"&": "&",
|
|
7862
7474
|
"<": "<",
|
|
7863
7475
|
">": ">",
|
|
@@ -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 =
|
|
7481
|
+
const fullName = escapeHtml7(`${truncatedFirstName} ${truncatedLastName}`);
|
|
7870
7482
|
const truncatedUsername = row.username.length > 100 ? row.username.substring(0, 100) + "..." : row.username;
|
|
7871
|
-
const username =
|
|
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
|
|
7499
|
+
const escapeHtml7 = (text) => text.replace(/[&<>"']/g, (char) => ({
|
|
7888
7500
|
"&": "&",
|
|
7889
7501
|
"<": "<",
|
|
7890
7502
|
">": ">",
|
|
7891
7503
|
'"': """,
|
|
7892
7504
|
"'": "'"
|
|
7893
7505
|
})[char] || char);
|
|
7894
|
-
const escapedEmail =
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
8410
|
+
return c.html(renderAlert2({
|
|
8799
8411
|
type: "error",
|
|
8800
|
-
message: "Failed to create user
|
|
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
|
|
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(
|
|
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(
|
|
8515
|
+
return c.html(renderAlert2({
|
|
8904
8516
|
type: "error",
|
|
8905
|
-
message: "Failed to load user
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
8601
|
+
return c.html(renderAlert2({
|
|
8990
8602
|
type: "error",
|
|
8991
|
-
message: "Failed to update user
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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: "
|
|
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: "
|
|
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-
|
|
14246
|
-
//# sourceMappingURL=chunk-
|
|
15800
|
+
//# sourceMappingURL=chunk-QUMBDPNJ.cjs.map
|
|
15801
|
+
//# sourceMappingURL=chunk-QUMBDPNJ.cjs.map
|