@cedros/data-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +177 -0
  3. package/dist/admin/api.d.ts +19 -0
  4. package/dist/admin/api.js +108 -0
  5. package/dist/admin/components.d.ts +36 -0
  6. package/dist/admin/components.js +22 -0
  7. package/dist/admin/history.d.ts +17 -0
  8. package/dist/admin/history.js +103 -0
  9. package/dist/admin/icons.d.ts +15 -0
  10. package/dist/admin/icons.js +18 -0
  11. package/dist/admin/index.d.ts +13 -0
  12. package/dist/admin/index.js +12 -0
  13. package/dist/admin/permissions.d.ts +4 -0
  14. package/dist/admin/permissions.js +45 -0
  15. package/dist/admin/plugin.d.ts +4 -0
  16. package/dist/admin/plugin.js +180 -0
  17. package/dist/admin/primitives/ConfirmDialog.d.ts +14 -0
  18. package/dist/admin/primitives/ConfirmDialog.js +7 -0
  19. package/dist/admin/primitives/DataTable.d.ts +14 -0
  20. package/dist/admin/primitives/DataTable.js +7 -0
  21. package/dist/admin/primitives/DiffViewer.d.ts +11 -0
  22. package/dist/admin/primitives/DiffViewer.js +8 -0
  23. package/dist/admin/primitives/FormFieldRow.d.ts +23 -0
  24. package/dist/admin/primitives/FormFieldRow.js +16 -0
  25. package/dist/admin/primitives/JsonCodeEditor.d.ts +10 -0
  26. package/dist/admin/primitives/JsonCodeEditor.js +42 -0
  27. package/dist/admin/primitives/Pagination.d.ts +8 -0
  28. package/dist/admin/primitives/Pagination.js +8 -0
  29. package/dist/admin/primitives/Toolbar.d.ts +23 -0
  30. package/dist/admin/primitives/Toolbar.js +10 -0
  31. package/dist/admin/primitives/alerts.d.ts +21 -0
  32. package/dist/admin/primitives/alerts.js +44 -0
  33. package/dist/admin/sectionIds.d.ts +20 -0
  34. package/dist/admin/sectionIds.js +33 -0
  35. package/dist/admin/sections/CollectionsSection.d.ts +2 -0
  36. package/dist/admin/sections/CollectionsSection.js +125 -0
  37. package/dist/admin/sections/ContractVerifySection.d.ts +11 -0
  38. package/dist/admin/sections/ContractVerifySection.js +98 -0
  39. package/dist/admin/sections/CustomDataSection.d.ts +2 -0
  40. package/dist/admin/sections/CustomDataSection.js +256 -0
  41. package/dist/admin/sections/DataOpsSection.d.ts +26 -0
  42. package/dist/admin/sections/DataOpsSection.js +245 -0
  43. package/dist/admin/sections/HistorySection.d.ts +2 -0
  44. package/dist/admin/sections/HistorySection.js +26 -0
  45. package/dist/admin/sections/MonetizationSection.d.ts +2 -0
  46. package/dist/admin/sections/MonetizationSection.js +140 -0
  47. package/dist/admin/sections/NavigationSection.d.ts +13 -0
  48. package/dist/admin/sections/NavigationSection.js +195 -0
  49. package/dist/admin/sections/PagesSection.d.ts +2 -0
  50. package/dist/admin/sections/PagesSection.js +157 -0
  51. package/dist/admin/sections/SchemaDesignerSection.d.ts +2 -0
  52. package/dist/admin/sections/SchemaDesignerSection.js +167 -0
  53. package/dist/admin/sections/SiteSettingsSection.d.ts +12 -0
  54. package/dist/admin/sections/SiteSettingsSection.js +122 -0
  55. package/dist/admin/sections/TippingSection.d.ts +2 -0
  56. package/dist/admin/sections/TippingSection.js +178 -0
  57. package/dist/admin/sections/media/MediaDetail.d.ts +12 -0
  58. package/dist/admin/sections/media/MediaDetail.js +74 -0
  59. package/dist/admin/sections/media/MediaGrid.d.ts +14 -0
  60. package/dist/admin/sections/media/MediaGrid.js +22 -0
  61. package/dist/admin/sections/media/MediaSection.d.ts +2 -0
  62. package/dist/admin/sections/media/MediaSection.js +97 -0
  63. package/dist/admin/sections/media/MediaUploader.d.ts +7 -0
  64. package/dist/admin/sections/media/MediaUploader.js +72 -0
  65. package/dist/admin/sections/media/types.d.ts +33 -0
  66. package/dist/admin/sections/media/types.js +1 -0
  67. package/dist/admin/styles.css +533 -0
  68. package/dist/admin/types.d.ts +85 -0
  69. package/dist/admin/types.js +1 -0
  70. package/dist/index.d.ts +4 -0
  71. package/dist/index.js +3 -0
  72. package/dist/react/CmsContent.d.ts +20 -0
  73. package/dist/react/CmsContent.js +31 -0
  74. package/dist/react/entries.d.ts +9 -0
  75. package/dist/react/entries.js +25 -0
  76. package/dist/react/fetch.d.ts +11 -0
  77. package/dist/react/fetch.js +32 -0
  78. package/dist/react/index.d.ts +10 -0
  79. package/dist/react/index.js +9 -0
  80. package/dist/react/metadata.d.ts +44 -0
  81. package/dist/react/metadata.js +142 -0
  82. package/dist/react/sanitize.d.ts +17 -0
  83. package/dist/react/sanitize.js +326 -0
  84. package/dist/react/server.d.ts +14 -0
  85. package/dist/react/server.js +13 -0
  86. package/dist/react/sitemap.d.ts +28 -0
  87. package/dist/react/sitemap.js +91 -0
  88. package/dist/react/slugs.d.ts +27 -0
  89. package/dist/react/slugs.js +52 -0
  90. package/dist/react/types.d.ts +85 -0
  91. package/dist/react/types.js +1 -0
  92. package/dist/react/visitor.d.ts +7 -0
  93. package/dist/react/visitor.js +18 -0
  94. package/dist/site-templates/BlogTemplates.d.ts +95 -0
  95. package/dist/site-templates/BlogTemplates.js +64 -0
  96. package/dist/site-templates/ContactPageTemplate.d.ts +14 -0
  97. package/dist/site-templates/ContactPageTemplate.js +5 -0
  98. package/dist/site-templates/DashboardOverviewTemplate.d.ts +29 -0
  99. package/dist/site-templates/DashboardOverviewTemplate.js +17 -0
  100. package/dist/site-templates/DashboardShell.d.ts +28 -0
  101. package/dist/site-templates/DashboardShell.js +10 -0
  102. package/dist/site-templates/DocsSidebar.d.ts +14 -0
  103. package/dist/site-templates/DocsSidebar.js +13 -0
  104. package/dist/site-templates/DocsTemplates.d.ts +60 -0
  105. package/dist/site-templates/DocsTemplates.js +47 -0
  106. package/dist/site-templates/HomePageTemplate.d.ts +15 -0
  107. package/dist/site-templates/HomePageTemplate.js +10 -0
  108. package/dist/site-templates/LegalPageTemplate.d.ts +12 -0
  109. package/dist/site-templates/LegalPageTemplate.js +6 -0
  110. package/dist/site-templates/MarkdownContent.d.ts +7 -0
  111. package/dist/site-templates/MarkdownContent.js +24 -0
  112. package/dist/site-templates/NotFoundTemplate.d.ts +9 -0
  113. package/dist/site-templates/NotFoundTemplate.js +5 -0
  114. package/dist/site-templates/SiteFooter.d.ts +13 -0
  115. package/dist/site-templates/SiteFooter.js +4 -0
  116. package/dist/site-templates/SiteLayout.d.ts +14 -0
  117. package/dist/site-templates/SiteLayout.js +6 -0
  118. package/dist/site-templates/TopNav.d.ts +10 -0
  119. package/dist/site-templates/TopNav.js +8 -0
  120. package/dist/site-templates/blogControls.d.ts +19 -0
  121. package/dist/site-templates/blogControls.js +37 -0
  122. package/dist/site-templates/codeBlock.d.ts +9 -0
  123. package/dist/site-templates/codeBlock.js +31 -0
  124. package/dist/site-templates/content-styles.css +410 -0
  125. package/dist/site-templates/contentIndex.d.ts +65 -0
  126. package/dist/site-templates/contentIndex.js +181 -0
  127. package/dist/site-templates/contentUi.d.ts +14 -0
  128. package/dist/site-templates/contentUi.js +24 -0
  129. package/dist/site-templates/docs-styles.css +259 -0
  130. package/dist/site-templates/docsNavigation.d.ts +18 -0
  131. package/dist/site-templates/docsNavigation.js +50 -0
  132. package/dist/site-templates/index.d.ts +28 -0
  133. package/dist/site-templates/index.js +25 -0
  134. package/dist/site-templates/monetization-styles.css +154 -0
  135. package/dist/site-templates/paywallControls.d.ts +22 -0
  136. package/dist/site-templates/paywallControls.js +9 -0
  137. package/dist/site-templates/routing.d.ts +12 -0
  138. package/dist/site-templates/routing.js +36 -0
  139. package/dist/site-templates/solanaAtaSetup.d.ts +11 -0
  140. package/dist/site-templates/solanaAtaSetup.js +38 -0
  141. package/dist/site-templates/solanaMicropayments.d.ts +65 -0
  142. package/dist/site-templates/solanaMicropayments.js +115 -0
  143. package/dist/site-templates/styles.css +332 -0
  144. package/dist/site-templates/tipControls.d.ts +24 -0
  145. package/dist/site-templates/tipControls.js +43 -0
  146. package/dist/site-templates/tocExtractor.d.ts +16 -0
  147. package/dist/site-templates/tocExtractor.js +58 -0
  148. package/dist/site-templates/tocScrollSpy.d.ts +16 -0
  149. package/dist/site-templates/tocScrollSpy.js +37 -0
  150. package/dist/templates.d.ts +8 -0
  151. package/dist/templates.js +20 -0
  152. package/package.json +58 -0
@@ -0,0 +1,157 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { requestJson } from "../api.js";
4
+ import { AdminButton, Card, JsonEditor, StatusNotice } from "../components.js";
5
+ export default function PagesSection({ pluginContext }) {
6
+ const canWritePages = pluginContext.hasPermission("data:pages:write");
7
+ const canBootstrap = pluginContext.hasPermission("data:ops:write");
8
+ const [templates, setTemplates] = useState([]);
9
+ const [entries, setEntries] = useState([]);
10
+ const [activePageKey, setActivePageKey] = useState("");
11
+ const [payloadEditor, setPayloadEditor] = useState("{}");
12
+ const [status, setStatus] = useState("");
13
+ const [statusTone, setStatusTone] = useState("neutral");
14
+ const [loading, setLoading] = useState(false);
15
+ const load = useCallback(async () => {
16
+ setLoading(true);
17
+ setStatus("");
18
+ setStatusTone("neutral");
19
+ try {
20
+ const [templateData, pageData] = await Promise.all([
21
+ requestJson(pluginContext, "/admin/default-pages"),
22
+ requestJson(pluginContext, "/admin/pages")
23
+ ]);
24
+ setTemplates(templateData);
25
+ setEntries(pageData);
26
+ const firstKey = pageData[0]?.entry_key ?? templateData[0]?.key ?? "";
27
+ const knownKeys = new Set([
28
+ ...pageData.map((entry) => entry.entry_key),
29
+ ...templateData.map((template) => template.key)
30
+ ]);
31
+ setActivePageKey((current) => (current && knownKeys.has(current) ? current : firstKey));
32
+ }
33
+ catch (error) {
34
+ setStatus(error.message);
35
+ setStatusTone("error");
36
+ }
37
+ finally {
38
+ setLoading(false);
39
+ }
40
+ }, [pluginContext]);
41
+ useEffect(() => {
42
+ void load();
43
+ }, [load]);
44
+ const activePayload = useMemo(() => {
45
+ const existing = entries.find((entry) => entry.entry_key === activePageKey);
46
+ if (existing) {
47
+ return existing.payload;
48
+ }
49
+ const fallback = templates.find((template) => template.key === activePageKey);
50
+ if (!fallback) {
51
+ return null;
52
+ }
53
+ return buildDefaultPagePayload(fallback);
54
+ }, [activePageKey, entries, templates]);
55
+ const activeTemplate = useMemo(() => templates.find((template) => template.key === activePageKey) ?? null, [activePageKey, templates]);
56
+ const activeEntry = useMemo(() => entries.find((entry) => entry.entry_key === activePageKey) ?? null, [activePageKey, entries]);
57
+ useEffect(() => {
58
+ if (!activePayload) {
59
+ return;
60
+ }
61
+ setPayloadEditor(JSON.stringify(activePayload, null, 2));
62
+ }, [activePayload]);
63
+ const save = useCallback(async () => {
64
+ if (!canWritePages) {
65
+ setStatus("You do not have permission to edit pages.");
66
+ setStatusTone("error");
67
+ return;
68
+ }
69
+ if (!activePageKey) {
70
+ setStatus("Select a page first.");
71
+ setStatusTone("error");
72
+ return;
73
+ }
74
+ let payload;
75
+ try {
76
+ const parsed = JSON.parse(payloadEditor);
77
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
78
+ throw new Error("Page payload must be a JSON object");
79
+ }
80
+ payload = parsed;
81
+ }
82
+ catch (error) {
83
+ setStatus(`Invalid JSON: ${error.message}`);
84
+ setStatusTone("error");
85
+ return;
86
+ }
87
+ setLoading(true);
88
+ setStatus("");
89
+ setStatusTone("neutral");
90
+ try {
91
+ await requestJson(pluginContext, `/admin/pages/${encodeURIComponent(activePageKey)}`, {
92
+ method: "PUT",
93
+ body: { payload }
94
+ });
95
+ setStatus("Page saved.");
96
+ setStatusTone("success");
97
+ await load();
98
+ }
99
+ catch (error) {
100
+ setStatus(error.message);
101
+ setStatusTone("error");
102
+ }
103
+ finally {
104
+ setLoading(false);
105
+ }
106
+ }, [activePageKey, canWritePages, load, payloadEditor, pluginContext]);
107
+ const bootstrap = useCallback(async () => {
108
+ if (!canBootstrap) {
109
+ setStatus("You do not have permission to initialize defaults.");
110
+ setStatusTone("error");
111
+ return;
112
+ }
113
+ setLoading(true);
114
+ setStatus("");
115
+ setStatusTone("neutral");
116
+ try {
117
+ await requestJson(pluginContext, "/admin/bootstrap", {
118
+ method: "POST"
119
+ });
120
+ setStatus("Defaults initialized.");
121
+ setStatusTone("success");
122
+ await load();
123
+ }
124
+ catch (error) {
125
+ setStatus(error.message);
126
+ setStatusTone("error");
127
+ }
128
+ finally {
129
+ setLoading(false);
130
+ }
131
+ }, [canBootstrap, load, pluginContext]);
132
+ const resetActivePage = useCallback(() => {
133
+ if (!activeTemplate) {
134
+ return;
135
+ }
136
+ setPayloadEditor(JSON.stringify(buildDefaultPagePayload(activeTemplate), null, 2));
137
+ setStatus("Editor reset to template default payload.");
138
+ setStatusTone("neutral");
139
+ }, [activeTemplate]);
140
+ return (_jsxs("div", { className: "cedros-data", children: [_jsxs("header", { className: "cedros-data__header", children: [_jsxs("div", { children: [_jsx("h2", { className: "cedros-data__title", children: "Pages" }), _jsx("p", { className: "cedros-data__subtitle", children: "Edit the JSON payloads for the default page set." }), !canWritePages && (_jsx("p", { className: "cedros-data__subtitle", children: "Read-only mode. Missing `data:pages:write` permission." }))] }), _jsxs("div", { className: "cedros-data-actions", children: [_jsx(AdminButton, { variant: "secondary", onClick: () => void bootstrap(), disabled: loading || !canBootstrap, title: !canBootstrap ? "Requires data:ops:write" : undefined, children: "Initialize Defaults" }), _jsx(AdminButton, { variant: "secondary", onClick: resetActivePage, disabled: !activeTemplate, children: "Reset Payload" }), _jsx(AdminButton, { variant: "primary", onClick: () => void save(), disabled: loading || !activePageKey || !canWritePages, title: !canWritePages ? "Requires data:pages:write" : undefined, children: "Save Page" })] })] }), _jsxs("div", { className: "cedros-data-grid cedros-data-grid--page", children: [_jsx(Card, { title: `Default Pages (${templates.length})`, subtitle: "Core pages available for this site.", children: _jsxs("div", { className: "cedros-data-list", children: [templates.length === 0 && _jsx("div", { className: "cedros-data-empty", children: "No templates registered." }), templates.map((template) => {
141
+ const selected = template.key === activePageKey;
142
+ return (_jsx("button", { className: `cedros-data-list-item ${selected ? "cedros-data-list-item--active" : ""}`, onClick: () => setActivePageKey(template.key), children: template.title }, template.key));
143
+ })] }) }), _jsx(Card, { title: activeTemplate?.title ?? "Select a page", subtitle: activeTemplate
144
+ ? `${activeTemplate.route} • ${activeTemplate.section}`
145
+ : "Choose a page to edit its payload.", actions: activeEntry ? (_jsxs("span", { className: "cedros-data-key", children: ["updated: ", new Date(activeEntry.updated_at).toLocaleString()] })) : undefined, children: _jsx(JsonEditor, { label: "Page Payload", value: payloadEditor, onChange: (event) => setPayloadEditor(event.target.value), disabled: !activePageKey || !canWritePages }) })] }), status && _jsx(StatusNotice, { tone: statusTone, message: status })] }));
146
+ }
147
+ function buildDefaultPagePayload(template) {
148
+ return {
149
+ key: template.key,
150
+ title: template.title,
151
+ route: template.route,
152
+ section: template.section,
153
+ status: "draft",
154
+ meta: {},
155
+ blocks: []
156
+ };
157
+ }
@@ -0,0 +1,2 @@
1
+ import type { AdminSectionProps } from "../types.js";
2
+ export default function SchemaDesignerSection({ pluginContext }: AdminSectionProps): React.JSX.Element;
@@ -0,0 +1,167 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useMemo, useState } from "react";
3
+ import { requestJson } from "../api.js";
4
+ import { AdminButton, Card, JsonEditor, StatusNotice } from "../components.js";
5
+ const EMPTY_SCHEMA = {
6
+ types: [],
7
+ tables: []
8
+ };
9
+ export default function SchemaDesignerSection({ pluginContext }) {
10
+ const canLoadCurrentSchema = pluginContext.hasPermission("data:ops:write");
11
+ const canWriteSchema = pluginContext.hasPermission("data:schema:write");
12
+ const [currentSchema, setCurrentSchema] = useState(EMPTY_SCHEMA);
13
+ const [proposalEditor, setProposalEditor] = useState(JSON.stringify(EMPTY_SCHEMA, null, 2));
14
+ const [latestReport, setLatestReport] = useState(null);
15
+ const [status, setStatus] = useState("");
16
+ const [tone, setTone] = useState("neutral");
17
+ const [loading, setLoading] = useState(false);
18
+ const loadCurrent = useCallback(async () => {
19
+ if (!canLoadCurrentSchema) {
20
+ setStatus("You do not have permission to load the current schema.");
21
+ setTone("error");
22
+ return;
23
+ }
24
+ setLoading(true);
25
+ setStatus("");
26
+ setTone("neutral");
27
+ try {
28
+ const exportPayload = await requestJson(pluginContext, "/site/export");
29
+ const parsed = normalizeSchemaDefinition(exportPayload.custom_schema);
30
+ setCurrentSchema(parsed);
31
+ setProposalEditor(JSON.stringify(parsed, null, 2));
32
+ setLatestReport(null);
33
+ setStatus("Current schema loaded.");
34
+ setTone("success");
35
+ }
36
+ catch (error) {
37
+ setStatus(error.message);
38
+ setTone("error");
39
+ }
40
+ finally {
41
+ setLoading(false);
42
+ }
43
+ }, [canLoadCurrentSchema, pluginContext]);
44
+ const proposalSchema = useMemo(() => {
45
+ try {
46
+ return normalizeSchemaDefinition(JSON.parse(proposalEditor));
47
+ }
48
+ catch (_error) {
49
+ return null;
50
+ }
51
+ }, [proposalEditor]);
52
+ const diff = useMemo(() => {
53
+ if (!proposalSchema) {
54
+ return {
55
+ additive: [],
56
+ breaking: [],
57
+ warnings: ["Proposed schema JSON is invalid."]
58
+ };
59
+ }
60
+ return summarizeDiff(currentSchema, proposalSchema);
61
+ }, [currentSchema, proposalSchema]);
62
+ const applySchema = useCallback(async () => {
63
+ if (!canWriteSchema) {
64
+ setStatus("You do not have permission to apply schema changes.");
65
+ setTone("error");
66
+ return;
67
+ }
68
+ if (!proposalSchema) {
69
+ setStatus("Cannot apply invalid schema JSON.");
70
+ setTone("error");
71
+ return;
72
+ }
73
+ setLoading(true);
74
+ setStatus("");
75
+ setTone("neutral");
76
+ try {
77
+ const report = await requestJson(pluginContext, "/custom-schema", {
78
+ method: "POST",
79
+ body: {
80
+ definition: proposalSchema
81
+ }
82
+ });
83
+ setLatestReport(report);
84
+ if (!report.applied) {
85
+ setStatus(`Schema blocked due to breaking changes: ${report.breaking_changes.join(", ") || "unknown"}`);
86
+ setTone("error");
87
+ return;
88
+ }
89
+ setCurrentSchema(proposalSchema);
90
+ setStatus(`Custom schema applied (version ${report.version}).`);
91
+ setTone("success");
92
+ }
93
+ catch (error) {
94
+ setStatus(error.message);
95
+ setTone("error");
96
+ }
97
+ finally {
98
+ setLoading(false);
99
+ }
100
+ }, [canWriteSchema, pluginContext, proposalSchema]);
101
+ const resetProposal = useCallback(() => {
102
+ setProposalEditor(JSON.stringify(currentSchema, null, 2));
103
+ setStatus("Proposal reset to current schema.");
104
+ setTone("neutral");
105
+ }, [currentSchema]);
106
+ return (_jsxs("div", { className: "cedros-data", children: [_jsxs("header", { className: "cedros-data__header", children: [_jsxs("div", { children: [_jsx("h2", { className: "cedros-data__title", children: "Schema Designer" }), _jsx("p", { className: "cedros-data__subtitle", children: "Design additive custom schema migrations for this deployment." }), !canLoadCurrentSchema && (_jsx("p", { className: "cedros-data__subtitle", children: "Loading the current schema requires `data:ops:write`." })), !canWriteSchema && (_jsx("p", { className: "cedros-data__subtitle", children: "Applying schema changes requires `data:schema:write`." }))] }), _jsxs("div", { className: "cedros-data-actions", children: [_jsx(AdminButton, { variant: "secondary", onClick: () => void loadCurrent(), disabled: loading || !canLoadCurrentSchema, title: !canLoadCurrentSchema ? "Requires data:ops:write" : undefined, children: "Load Current" }), _jsx(AdminButton, { variant: "secondary", onClick: resetProposal, disabled: loading, children: "Reset Proposal" }), _jsx(AdminButton, { variant: "primary", onClick: () => void applySchema(), disabled: loading || !canWriteSchema, title: !canWriteSchema ? "Requires data:schema:write" : undefined, children: "Apply Schema" })] })] }), _jsxs("div", { className: "cedros-data-grid cedros-data-grid--two", children: [_jsx(Card, { title: "Current Schema", subtitle: "Loaded from latest site export.", children: _jsx(JsonEditor, { label: "Current", value: JSON.stringify(currentSchema, null, 2), readOnly: true }) }), _jsx(Card, { title: "Proposed Schema", subtitle: "Full schema JSON (types, tables, constraints, indexes).", children: _jsx(JsonEditor, { label: "Proposal", value: proposalEditor, onChange: (event) => setProposalEditor(event.target.value), disabled: !canWriteSchema }) })] }), _jsxs(Card, { title: "Diff Preview", subtitle: "Server blocks breaking changes automatically.", children: [_jsx(DiffColumn, { title: "Additive Changes", items: diff.additive }), _jsx(DiffColumn, { title: "Breaking Changes", items: diff.breaking, isBreaking: true }), _jsx(DiffColumn, { title: "Validation Warnings", items: diff.warnings, isBreaking: true })] }), latestReport && (_jsx(Card, { title: "Last Apply Report", subtitle: `Version ${latestReport.version} • ${latestReport.applied ? "applied" : "blocked"}`, children: _jsx(JsonEditor, { label: "Report", value: JSON.stringify(latestReport, null, 2), readOnly: true }) })), status && _jsx(StatusNotice, { tone: tone, message: status })] }));
107
+ }
108
+ function DiffColumn({ title, items, isBreaking = false }) {
109
+ return (_jsxs("div", { style: { display: "grid", gap: "0.4rem" }, children: [_jsx("strong", { children: title }), items.length === 0 && _jsx("span", { className: "cedros-data-empty", children: "None." }), items.map((item) => (_jsxs("span", { className: "cedros-data-key", style: { color: isBreaking ? "var(--cd-error)" : undefined }, children: ["\u2022 ", item] }, `${title}-${item}`)))] }));
110
+ }
111
+ function normalizeSchemaDefinition(raw) {
112
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
113
+ return EMPTY_SCHEMA;
114
+ }
115
+ const value = raw;
116
+ const types = Array.isArray(value.types)
117
+ ? value.types.filter(isRecord).map((entry) => ({ ...entry }))
118
+ : [];
119
+ const tables = Array.isArray(value.tables)
120
+ ? value.tables.filter(isRecord).map((entry) => ({ ...entry }))
121
+ : [];
122
+ return { types, tables };
123
+ }
124
+ function summarizeDiff(currentSchema, proposalSchema) {
125
+ const currentTypes = new Set(currentSchema.types.map((item) => typeKey(item)));
126
+ const proposalTypes = new Set(proposalSchema.types.map((item) => typeKey(item)));
127
+ const currentTables = new Set(currentSchema.tables.map((item) => tableName(item)));
128
+ const proposalTables = new Set(proposalSchema.tables.map((item) => tableName(item)));
129
+ const additive = [];
130
+ const breaking = [];
131
+ const warnings = [];
132
+ for (const value of proposalTypes) {
133
+ if (!currentTypes.has(value)) {
134
+ additive.push(`Add type ${value}`);
135
+ }
136
+ }
137
+ for (const value of currentTypes) {
138
+ if (!proposalTypes.has(value)) {
139
+ breaking.push(`Remove type ${value}`);
140
+ }
141
+ }
142
+ for (const value of proposalTables) {
143
+ if (!currentTables.has(value)) {
144
+ additive.push(`Add table ${value}`);
145
+ }
146
+ }
147
+ for (const value of currentTables) {
148
+ if (!proposalTables.has(value)) {
149
+ breaking.push(`Remove table ${value}`);
150
+ }
151
+ }
152
+ if (proposalSchema.types.length === 0 && proposalSchema.tables.length === 0) {
153
+ warnings.push("Proposal contains no types or tables.");
154
+ }
155
+ return { additive, breaking, warnings };
156
+ }
157
+ function typeKey(type) {
158
+ const kind = typeof type.kind === "string" ? type.kind : "type";
159
+ const name = typeof type.name === "string" ? type.name : "unnamed";
160
+ return `${kind}:${name}`;
161
+ }
162
+ function tableName(table) {
163
+ return typeof table.name === "string" ? table.name : "unnamed";
164
+ }
165
+ function isRecord(value) {
166
+ return !!value && typeof value === "object" && !Array.isArray(value);
167
+ }
@@ -0,0 +1,12 @@
1
+ import type { AdminSectionProps } from "../types.js";
2
+ interface SiteSettingsRecord {
3
+ siteTitle: string;
4
+ siteDescription: string;
5
+ locale: string;
6
+ timezone: string;
7
+ }
8
+ type SiteSettingsPayload = Record<string, unknown>;
9
+ export default function SiteSettingsSection({ pluginContext }: AdminSectionProps): React.JSX.Element;
10
+ export declare function normalizeSettingsPayload(value: unknown): SiteSettingsPayload;
11
+ export declare function mergeSettingsRecordIntoPayload(payload: SiteSettingsPayload, settings: SiteSettingsRecord): SiteSettingsPayload;
12
+ export {};
@@ -0,0 +1,122 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from "react";
3
+ import { requestJson } from "../api.js";
4
+ import { AdminButton, Card, JsonEditor, StatusNotice, TextInput } from "../components.js";
5
+ const DEFAULT_SETTINGS = {
6
+ siteTitle: "New Site",
7
+ siteDescription: "",
8
+ locale: "en-US",
9
+ timezone: "UTC"
10
+ };
11
+ export default function SiteSettingsSection({ pluginContext }) {
12
+ const canWriteSettings = pluginContext.hasPermission("data:settings:write");
13
+ const [settingsPayload, setSettingsPayload] = useState(normalizeSettingsPayload(DEFAULT_SETTINGS));
14
+ const [settings, setSettings] = useState(DEFAULT_SETTINGS);
15
+ const [rawEditor, setRawEditor] = useState(JSON.stringify(DEFAULT_SETTINGS, null, 2));
16
+ const [status, setStatus] = useState("");
17
+ const [tone, setTone] = useState("neutral");
18
+ const [loading, setLoading] = useState(false);
19
+ const load = useCallback(async () => {
20
+ setLoading(true);
21
+ setStatus("");
22
+ setTone("neutral");
23
+ try {
24
+ const entries = await requestJson(pluginContext, "/entries/query", {
25
+ method: "POST",
26
+ body: {
27
+ collection_name: "site_settings",
28
+ entry_keys: ["global"],
29
+ contains: null,
30
+ limit: 1,
31
+ offset: 0
32
+ }
33
+ });
34
+ const payload = normalizeSettingsPayload(entries[0]?.payload ?? DEFAULT_SETTINGS);
35
+ const typed = toSettingsRecord(payload);
36
+ setSettingsPayload(payload);
37
+ setSettings(typed);
38
+ setRawEditor(JSON.stringify(payload, null, 2));
39
+ }
40
+ catch (error) {
41
+ setStatus(error.message);
42
+ setTone("error");
43
+ }
44
+ finally {
45
+ setLoading(false);
46
+ }
47
+ }, [pluginContext]);
48
+ useEffect(() => {
49
+ void load();
50
+ }, [load]);
51
+ const save = useCallback(async (nextPayload) => {
52
+ if (!canWriteSettings) {
53
+ setStatus("You do not have permission to edit site settings.");
54
+ setTone("error");
55
+ return;
56
+ }
57
+ setLoading(true);
58
+ setStatus("");
59
+ setTone("neutral");
60
+ try {
61
+ await requestJson(pluginContext, "/entries/upsert", {
62
+ method: "POST",
63
+ body: {
64
+ collection_name: "site_settings",
65
+ entry_key: "global",
66
+ payload: nextPayload
67
+ }
68
+ });
69
+ setSettingsPayload(nextPayload);
70
+ setSettings(toSettingsRecord(nextPayload));
71
+ setRawEditor(JSON.stringify(nextPayload, null, 2));
72
+ setStatus("Site settings saved.");
73
+ setTone("success");
74
+ }
75
+ catch (error) {
76
+ setStatus(error.message);
77
+ setTone("error");
78
+ }
79
+ finally {
80
+ setLoading(false);
81
+ }
82
+ }, [canWriteSettings, pluginContext]);
83
+ const saveForm = useCallback(async () => {
84
+ await save(mergeSettingsRecordIntoPayload(settingsPayload, settings));
85
+ }, [save, settings, settingsPayload]);
86
+ const applyRaw = useCallback(async () => {
87
+ let parsed;
88
+ try {
89
+ parsed = JSON.parse(rawEditor);
90
+ }
91
+ catch (error) {
92
+ setStatus(`Invalid JSON: ${error.message}`);
93
+ setTone("error");
94
+ return;
95
+ }
96
+ await save(normalizeSettingsPayload(parsed));
97
+ }, [rawEditor, save]);
98
+ return (_jsxs("div", { className: "cedros-data", children: [_jsxs("header", { className: "cedros-data__header", children: [_jsxs("div", { children: [_jsx("h2", { className: "cedros-data__title", children: "Site Settings" }), _jsx("p", { className: "cedros-data__subtitle", children: "Configure the global metadata for this site." }), !canWriteSettings && (_jsx("p", { className: "cedros-data__subtitle", children: "Read-only mode. Missing `data:settings:write` permission." }))] }), _jsxs("div", { className: "cedros-data-actions", children: [_jsx(AdminButton, { variant: "secondary", onClick: () => void load(), disabled: loading, children: "Refresh" }), _jsx(AdminButton, { variant: "primary", onClick: () => void saveForm(), disabled: loading || !canWriteSettings, title: !canWriteSettings ? "Requires data:settings:write" : undefined, children: "Save Form" }), _jsx(AdminButton, { variant: "secondary", onClick: () => void applyRaw(), disabled: loading || !canWriteSettings, title: !canWriteSettings ? "Requires data:settings:write" : undefined, children: "Save JSON" })] })] }), _jsxs("div", { className: "cedros-data-grid cedros-data-grid--two", children: [_jsxs(Card, { title: "Global Site Fields", subtitle: "These values back site_settings/global.", children: [_jsx(TextInput, { label: "Site Title", value: settings.siteTitle, onChange: (event) => setSettings((previous) => ({ ...previous, siteTitle: event.target.value })), placeholder: "Cedros Docs", disabled: !canWriteSettings }), _jsx(TextInput, { label: "Site Description", value: settings.siteDescription, onChange: (event) => setSettings((previous) => ({ ...previous, siteDescription: event.target.value })), placeholder: "Developer docs and guides", disabled: !canWriteSettings }), _jsx(TextInput, { label: "Locale", value: settings.locale, onChange: (event) => setSettings((previous) => ({ ...previous, locale: event.target.value })), placeholder: "en-US", disabled: !canWriteSettings }), _jsx(TextInput, { label: "Timezone", value: settings.timezone, onChange: (event) => setSettings((previous) => ({ ...previous, timezone: event.target.value })), placeholder: "America/Los_Angeles", disabled: !canWriteSettings })] }), _jsx(Card, { title: "Settings JSON", subtitle: "Advanced editor for all site settings fields.", children: _jsx(JsonEditor, { label: "site_settings/global", value: rawEditor, onChange: (event) => setRawEditor(event.target.value), disabled: !canWriteSettings }) })] }), status && _jsx(StatusNotice, { tone: tone, message: status })] }));
99
+ }
100
+ export function normalizeSettingsPayload(value) {
101
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
102
+ return { ...DEFAULT_SETTINGS };
103
+ }
104
+ return { ...value };
105
+ }
106
+ export function mergeSettingsRecordIntoPayload(payload, settings) {
107
+ return {
108
+ ...normalizeSettingsPayload(payload),
109
+ ...settings
110
+ };
111
+ }
112
+ function toSettingsRecord(value) {
113
+ const source = normalizeSettingsPayload(value);
114
+ return {
115
+ siteTitle: typeof source.siteTitle === "string" && source.siteTitle.trim().length > 0
116
+ ? source.siteTitle
117
+ : DEFAULT_SETTINGS.siteTitle,
118
+ siteDescription: typeof source.siteDescription === "string" ? source.siteDescription : DEFAULT_SETTINGS.siteDescription,
119
+ locale: typeof source.locale === "string" && source.locale ? source.locale : DEFAULT_SETTINGS.locale,
120
+ timezone: typeof source.timezone === "string" && source.timezone ? source.timezone : DEFAULT_SETTINGS.timezone
121
+ };
122
+ }
@@ -0,0 +1,2 @@
1
+ import type { AdminSectionProps } from "../types.js";
2
+ export default function TippingSection({ pluginContext }: AdminSectionProps): React.JSX.Element;
@@ -0,0 +1,178 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from "react";
3
+ import { requestJson } from "../api.js";
4
+ import { AdminButton, Card, JsonEditor, StatusNotice, TextInput } from "../components.js";
5
+ const KNOWN_CURRENCIES = [
6
+ { symbol: "SOL", enabled: true },
7
+ { symbol: "USDC", mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", enabled: false },
8
+ ];
9
+ const DEFAULT_CONFIG = {
10
+ enabled: false,
11
+ recipient: "",
12
+ label: "Leave a tip",
13
+ description: "",
14
+ allowPerPostRecipient: false,
15
+ currencies: KNOWN_CURRENCIES,
16
+ };
17
+ export default function TippingSection({ pluginContext }) {
18
+ const canWrite = pluginContext.hasPermission("data:settings:write");
19
+ const [payload, setPayload] = useState({ ...DEFAULT_CONFIG });
20
+ const [config, setConfig] = useState(DEFAULT_CONFIG);
21
+ const [rawEditor, setRawEditor] = useState(JSON.stringify(DEFAULT_CONFIG, null, 2));
22
+ const [status, setStatus] = useState("");
23
+ const [tone, setTone] = useState("neutral");
24
+ const [loading, setLoading] = useState(false);
25
+ const load = useCallback(async () => {
26
+ setLoading(true);
27
+ setStatus("");
28
+ setTone("neutral");
29
+ try {
30
+ const entries = await requestJson(pluginContext, "/entries/query", {
31
+ method: "POST",
32
+ body: {
33
+ collection_name: "site_settings",
34
+ entry_keys: ["tipping"],
35
+ contains: null,
36
+ limit: 1,
37
+ offset: 0
38
+ }
39
+ });
40
+ const raw = normalizePayload(entries[0]?.payload ?? DEFAULT_CONFIG);
41
+ const typed = toConfig(raw);
42
+ setPayload(raw);
43
+ setConfig(typed);
44
+ setRawEditor(JSON.stringify(raw, null, 2));
45
+ }
46
+ catch (error) {
47
+ setStatus(error.message);
48
+ setTone("error");
49
+ }
50
+ finally {
51
+ setLoading(false);
52
+ }
53
+ }, [pluginContext]);
54
+ useEffect(() => {
55
+ void load();
56
+ }, [load]);
57
+ const save = useCallback(async (nextPayload) => {
58
+ if (!canWrite) {
59
+ setStatus("You do not have permission to edit tipping settings.");
60
+ setTone("error");
61
+ return;
62
+ }
63
+ setLoading(true);
64
+ setStatus("");
65
+ setTone("neutral");
66
+ try {
67
+ await requestJson(pluginContext, "/entries/upsert", {
68
+ method: "POST",
69
+ body: {
70
+ collection_name: "site_settings",
71
+ entry_key: "tipping",
72
+ payload: nextPayload
73
+ }
74
+ });
75
+ setPayload(nextPayload);
76
+ setConfig(toConfig(nextPayload));
77
+ setRawEditor(JSON.stringify(nextPayload, null, 2));
78
+ setStatus("Tipping settings saved.");
79
+ setTone("success");
80
+ }
81
+ catch (error) {
82
+ setStatus(error.message);
83
+ setTone("error");
84
+ }
85
+ finally {
86
+ setLoading(false);
87
+ }
88
+ }, [canWrite, pluginContext]);
89
+ const saveForm = useCallback(async () => {
90
+ await save(mergeConfigIntoPayload(payload, config));
91
+ }, [save, config, payload]);
92
+ const applyRaw = useCallback(async () => {
93
+ let parsed;
94
+ try {
95
+ parsed = JSON.parse(rawEditor);
96
+ }
97
+ catch (error) {
98
+ setStatus(`Invalid JSON: ${error.message}`);
99
+ setTone("error");
100
+ return;
101
+ }
102
+ await save(normalizePayload(parsed));
103
+ }, [rawEditor, save]);
104
+ const handleEnsureAta = useCallback(async () => {
105
+ const splCurrencies = config.currencies.filter((c) => c.mint && c.enabled);
106
+ if (!config.recipient || splCurrencies.length === 0)
107
+ return;
108
+ setLoading(true);
109
+ setStatus("");
110
+ setTone("neutral");
111
+ try {
112
+ const { ensureRecipientAta } = await import("../../site-templates/solanaAtaSetup.js");
113
+ const results = [];
114
+ for (const cur of splCurrencies) {
115
+ const ata = await ensureRecipientAta(config.recipient, cur.mint);
116
+ results.push(`${cur.symbol}: ${ata}`);
117
+ }
118
+ setStatus(`ATAs ready: ${results.join(", ")}`);
119
+ setTone("success");
120
+ }
121
+ catch (error) {
122
+ setStatus(`ATA creation failed: ${error.message}`);
123
+ setTone("error");
124
+ }
125
+ finally {
126
+ setLoading(false);
127
+ }
128
+ }, [config.recipient, config.currencies]);
129
+ return (_jsxs("div", { className: "cedros-data", children: [_jsxs("header", { className: "cedros-data__header", children: [_jsxs("div", { children: [_jsx("h2", { className: "cedros-data__title", children: "Tipping" }), _jsx("p", { className: "cedros-data__subtitle", children: "Configure tipping widget for your site." }), !canWrite && (_jsx("p", { className: "cedros-data__subtitle", children: "Read-only mode. Missing `data:settings:write` permission." }))] }), _jsxs("div", { className: "cedros-data-actions", children: [_jsx(AdminButton, { variant: "secondary", onClick: () => void load(), disabled: loading, children: "Refresh" }), _jsx(AdminButton, { variant: "primary", onClick: () => void saveForm(), disabled: loading || !canWrite, title: !canWrite ? "Requires data:settings:write" : undefined, children: "Save Form" }), _jsx(AdminButton, { variant: "secondary", onClick: () => void applyRaw(), disabled: loading || !canWrite, title: !canWrite ? "Requires data:settings:write" : undefined, children: "Save JSON" })] })] }), _jsxs("div", { className: "cedros-data-grid cedros-data-grid--two", children: [_jsxs(Card, { title: "Tipping Settings", subtitle: "Basic tipping configuration.", children: [_jsxs("label", { className: "cedros-data-checkbox-row", children: [_jsx("input", { type: "checkbox", checked: config.enabled, onChange: (e) => setConfig((prev) => ({ ...prev, enabled: e.target.checked })), disabled: !canWrite }), "Enabled"] }), _jsx(TextInput, { label: "Recipient", value: config.recipient, onChange: (e) => setConfig((prev) => ({ ...prev, recipient: e.target.value })), placeholder: "Wallet address or identifier", disabled: !canWrite }), _jsx(TextInput, { label: "Label", value: config.label, onChange: (e) => setConfig((prev) => ({ ...prev, label: e.target.value })), placeholder: "Leave a tip", disabled: !canWrite }), _jsx(TextInput, { label: "Description", value: config.description, onChange: (e) => setConfig((prev) => ({ ...prev, description: e.target.value })), placeholder: "Optional description", disabled: !canWrite }), _jsxs("label", { className: "cedros-data-checkbox-row", children: [_jsx("input", { type: "checkbox", checked: config.allowPerPostRecipient, onChange: (e) => setConfig((prev) => ({ ...prev, allowPerPostRecipient: e.target.checked })), disabled: !canWrite }), "Allow per-post recipient"] }), _jsx("p", { className: "cedros-data__subtitle", style: { margin: 0 }, children: "When enabled, individual blog posts can override the site-wide tip recipient." }), _jsx("h4", { style: { margin: "1rem 0 0.5rem" }, children: "Currencies" }), config.currencies.map((cur) => (_jsxs("label", { className: "cedros-data-checkbox-row", children: [_jsx("input", { type: "checkbox", checked: cur.enabled, disabled: !canWrite || cur.symbol === "SOL", onChange: (e) => {
130
+ setConfig((prev) => ({
131
+ ...prev,
132
+ currencies: prev.currencies.map((c) => c.symbol === cur.symbol ? { ...c, enabled: e.target.checked } : c),
133
+ }));
134
+ } }), cur.symbol, cur.symbol === "SOL" ? " (always enabled)" : ""] }, cur.symbol))), config.currencies.some((c) => c.mint && c.enabled) && (_jsx(AdminButton, { variant: "secondary", disabled: !config.recipient || loading, onClick: () => void handleEnsureAta(), title: !config.recipient ? "Set recipient address first" : undefined, children: "Ensure Recipient ATA" }))] }), _jsx(Card, { title: "Tipping JSON", subtitle: "Advanced editor for currencies, presets, and more.", children: _jsx(JsonEditor, { label: "site_settings/tipping", value: rawEditor, onChange: (e) => setRawEditor(e.target.value), disabled: !canWrite }) })] }), status && _jsx(StatusNotice, { tone: tone, message: status })] }));
135
+ }
136
+ function normalizePayload(value) {
137
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
138
+ return { ...DEFAULT_CONFIG };
139
+ }
140
+ return { ...value };
141
+ }
142
+ function mergeConfigIntoPayload(payload, config) {
143
+ return { ...normalizePayload(payload), ...config };
144
+ }
145
+ function parseCurrencies(raw) {
146
+ if (!Array.isArray(raw))
147
+ return KNOWN_CURRENCIES;
148
+ const parsed = raw
149
+ .filter((c) => !!c && typeof c === "object")
150
+ .map((c) => ({
151
+ symbol: typeof c.symbol === "string" ? c.symbol : "?",
152
+ mint: typeof c.mint === "string" ? c.mint : undefined,
153
+ enabled: typeof c.enabled === "boolean" ? c.enabled : false,
154
+ }));
155
+ // Ensure SOL is always present and enabled
156
+ if (!parsed.some((c) => c.symbol === "SOL")) {
157
+ parsed.unshift({ symbol: "SOL", enabled: true });
158
+ }
159
+ else {
160
+ const sol = parsed.find((c) => c.symbol === "SOL");
161
+ if (sol)
162
+ sol.enabled = true;
163
+ }
164
+ return parsed;
165
+ }
166
+ function toConfig(value) {
167
+ const source = normalizePayload(value);
168
+ return {
169
+ enabled: typeof source.enabled === "boolean" ? source.enabled : DEFAULT_CONFIG.enabled,
170
+ recipient: typeof source.recipient === "string" ? source.recipient : DEFAULT_CONFIG.recipient,
171
+ label: typeof source.label === "string" && source.label ? source.label : DEFAULT_CONFIG.label,
172
+ description: typeof source.description === "string" ? source.description : DEFAULT_CONFIG.description,
173
+ allowPerPostRecipient: typeof source.allowPerPostRecipient === "boolean"
174
+ ? source.allowPerPostRecipient
175
+ : DEFAULT_CONFIG.allowPerPostRecipient,
176
+ currencies: parseCurrencies(source.currencies),
177
+ };
178
+ }