@sonicjs-cms/core 2.19.0 → 3.0.0-beta.10
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/README.md +52 -52
- package/dist/admin-documents-form.template-DDSH6ROU.js +6 -0
- package/dist/{admin-layout-catalyst.template-UMTIN66R.js.map → admin-documents-form.template-DDSH6ROU.js.map} +1 -1
- package/dist/admin-documents-form.template-LSZKGA5J.cjs +19 -0
- package/dist/{admin-layout-catalyst.template-HFD37TY5.cjs.map → admin-documents-form.template-LSZKGA5J.cjs.map} +1 -1
- package/dist/{filter-bar.template-DlVYMk-T.d.cts → admin-layout-catalyst.template-DrwDUfsE.d.cts} +25 -1
- package/dist/{filter-bar.template-DlVYMk-T.d.ts → admin-layout-catalyst.template-DrwDUfsE.d.ts} +25 -1
- package/dist/admin-layout-catalyst.template-KDHKVLXR.cjs +21 -0
- package/dist/admin-layout-catalyst.template-KDHKVLXR.cjs.map +1 -0
- package/dist/admin-layout-catalyst.template-YQ4EMF2J.js +7 -0
- package/dist/admin-layout-catalyst.template-YQ4EMF2J.js.map +1 -0
- package/dist/app-Bo0X1OWX.d.ts +1268 -0
- package/dist/app-Do66yCcV.d.cts +1268 -0
- package/dist/cache-DDARE4QE.js +4 -0
- package/dist/cache-DDARE4QE.js.map +1 -0
- package/dist/cache-LVYS4BPL.cjs +33 -0
- package/dist/cache-LVYS4BPL.cjs.map +1 -0
- package/dist/chunk-2CB4KY7I.cjs +771 -0
- package/dist/chunk-2CB4KY7I.cjs.map +1 -0
- package/dist/{chunk-ABB34XUS.cjs → chunk-3KYKEXV7.cjs} +667 -19
- package/dist/chunk-3KYKEXV7.cjs.map +1 -0
- package/dist/chunk-4BTBSXMR.cjs +912 -0
- package/dist/chunk-4BTBSXMR.cjs.map +1 -0
- package/dist/{chunk-55RDMDOP.js → chunk-5V62WT6M.js} +181 -57
- package/dist/chunk-5V62WT6M.js.map +1 -0
- package/dist/{chunk-OCL3HMEG.js → chunk-6OC6MF3C.js} +7004 -9807
- package/dist/chunk-6OC6MF3C.js.map +1 -0
- package/dist/chunk-AI663NBO.js +821 -0
- package/dist/chunk-AI663NBO.js.map +1 -0
- package/dist/chunk-ALDRXTUO.js +273 -0
- package/dist/chunk-ALDRXTUO.js.map +1 -0
- package/dist/{chunk-TFNTM3OA.js → chunk-ATUPB6MN.js} +645 -15
- package/dist/chunk-ATUPB6MN.js.map +1 -0
- package/dist/chunk-BLMTL57B.js +767 -0
- package/dist/chunk-BLMTL57B.js.map +1 -0
- package/dist/{chunk-4ZSNJDLS.cjs → chunk-CRGUD4KC.cjs} +9 -9
- package/dist/chunk-CRGUD4KC.cjs.map +1 -0
- package/dist/chunk-F67UK75A.cjs +158 -0
- package/dist/chunk-F67UK75A.cjs.map +1 -0
- package/dist/chunk-GCDZZNIN.js +192 -0
- package/dist/chunk-GCDZZNIN.js.map +1 -0
- package/dist/chunk-HIKBY7MS.cjs +70 -0
- package/dist/chunk-HIKBY7MS.cjs.map +1 -0
- package/dist/{chunk-4NPCDK6B.js → chunk-IDCZBF35.js} +557 -90
- package/dist/chunk-IDCZBF35.js.map +1 -0
- package/dist/chunk-IESEVHXL.js +66 -0
- package/dist/chunk-IESEVHXL.js.map +1 -0
- package/dist/chunk-IGADDMXH.js +387 -0
- package/dist/chunk-IGADDMXH.js.map +1 -0
- package/dist/chunk-IHTXB7AT.cjs +276 -0
- package/dist/chunk-IHTXB7AT.cjs.map +1 -0
- package/dist/chunk-IVPRUGTY.js +242 -0
- package/dist/chunk-IVPRUGTY.js.map +1 -0
- package/dist/{chunk-JZVHLLSI.cjs → chunk-IXUHXTHW.cjs} +2 -151
- package/dist/chunk-IXUHXTHW.cjs.map +1 -0
- package/dist/chunk-J6JTWD2A.cjs +100 -0
- package/dist/chunk-J6JTWD2A.cjs.map +1 -0
- package/dist/chunk-JEQ7FLOD.cjs +199 -0
- package/dist/chunk-JEQ7FLOD.cjs.map +1 -0
- package/dist/{chunk-ON5ZMSU4.js → chunk-JQISFW6U.js} +3 -3
- package/dist/chunk-JQISFW6U.js.map +1 -0
- package/dist/chunk-K25XHMM3.js +566 -0
- package/dist/chunk-K25XHMM3.js.map +1 -0
- package/dist/{chunk-UYJ6TJHX.cjs → chunk-K623Q6WD.cjs} +181 -56
- package/dist/chunk-K623Q6WD.cjs.map +1 -0
- package/dist/{chunk-7A4CB7T3.cjs → chunk-MUNO67TT.cjs} +561 -91
- package/dist/chunk-MUNO67TT.cjs.map +1 -0
- package/dist/chunk-N32OWET6.cjs +327 -0
- package/dist/chunk-N32OWET6.cjs.map +1 -0
- package/dist/chunk-NUKJ54GA.cjs +245 -0
- package/dist/chunk-NUKJ54GA.cjs.map +1 -0
- package/dist/{chunk-XWIA3HVX.js → chunk-OBA2RYZN.js} +6 -1249
- package/dist/chunk-OBA2RYZN.js.map +1 -0
- package/dist/chunk-PMGOBS6X.cjs +408 -0
- package/dist/chunk-PMGOBS6X.cjs.map +1 -0
- package/dist/{chunk-OHYBNCVL.cjs → chunk-PXNTCCPE.cjs} +10 -1256
- package/dist/chunk-PXNTCCPE.cjs.map +1 -0
- package/dist/chunk-PYVFXCSD.js +1828 -0
- package/dist/chunk-PYVFXCSD.js.map +1 -0
- package/dist/{chunk-BU7SFHGP.js → chunk-QZGABF2M.js} +3 -149
- package/dist/chunk-QZGABF2M.js.map +1 -0
- package/dist/{chunk-E4YFJBM2.cjs → chunk-R4ILO3W6.cjs} +876 -829
- package/dist/chunk-R4ILO3W6.cjs.map +1 -0
- package/dist/chunk-RMRJGMDE.js +323 -0
- package/dist/chunk-RMRJGMDE.js.map +1 -0
- package/dist/chunk-RNZFGN4R.js +88 -0
- package/dist/chunk-RNZFGN4R.js.map +1 -0
- package/dist/chunk-RQ6N3FTV.js +900 -0
- package/dist/chunk-RQ6N3FTV.js.map +1 -0
- package/dist/{chunk-R4FOLLFB.cjs → chunk-TO6EY4P7.cjs} +8730 -11520
- package/dist/chunk-TO6EY4P7.cjs.map +1 -0
- package/dist/chunk-V464XBYS.js +154 -0
- package/dist/chunk-V464XBYS.js.map +1 -0
- package/dist/chunk-YA3TJ65D.cjs +575 -0
- package/dist/chunk-YA3TJ65D.cjs.map +1 -0
- package/dist/chunk-YP7GW2G5.cjs +866 -0
- package/dist/chunk-YP7GW2G5.cjs.map +1 -0
- package/dist/{collection-config-B4PG-AaF.d.cts → collection-config-JgHOpFCG.d.cts} +30 -2
- package/dist/{collection-config-B4PG-AaF.d.ts → collection-config-JgHOpFCG.d.ts} +30 -2
- package/dist/config-HFXANXCC.js +6 -0
- package/dist/config-HFXANXCC.js.map +1 -0
- package/dist/config-ON6FNMYX.cjs +19 -0
- package/dist/config-ON6FNMYX.cjs.map +1 -0
- package/dist/define-plugin-BzNHc1ZI.d.ts +1321 -0
- package/dist/define-plugin-IWDKYaVm.d.cts +1321 -0
- package/dist/document-projection-TDWRJX3Z.cjs +13 -0
- package/dist/document-projection-TDWRJX3Z.cjs.map +1 -0
- package/dist/document-projection-YYMC6I4U.js +4 -0
- package/dist/document-projection-YYMC6I4U.js.map +1 -0
- package/dist/index.cjs +13736 -4326
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +331 -493
- package/dist/index.d.ts +331 -493
- package/dist/index.js +13455 -4067
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +38 -32
- package/dist/middleware.d.cts +50 -7
- package/dist/middleware.d.ts +50 -7
- package/dist/middleware.js +9 -3
- package/dist/migrations-2XHQEGOQ.cjs +13 -0
- package/dist/{migrations-566IIPS2.cjs.map → migrations-2XHQEGOQ.cjs.map} +1 -1
- package/dist/migrations-PE3CDVSM.js +4 -0
- package/dist/{migrations-H5IXZNCO.js.map → migrations-PE3CDVSM.js.map} +1 -1
- package/dist/{plugin-bootstrap-DfVerYV4.d.cts → plugin-bootstrap-B8ThJU21.d.cts} +4315 -1661
- package/dist/{plugin-bootstrap-P_ciLp_C.d.ts → plugin-bootstrap-qu8hJgUt.d.ts} +4315 -1661
- package/dist/plugins.cjs +171 -12
- package/dist/plugins.d.cts +36 -2
- package/dist/plugins.d.ts +36 -2
- package/dist/plugins.js +5 -2
- package/dist/rbac-O73MFKDA.js +5 -0
- package/dist/rbac-O73MFKDA.js.map +1 -0
- package/dist/rbac-VONLJJKB.cjs +14 -0
- package/dist/rbac-VONLJJKB.cjs.map +1 -0
- package/dist/routes.cjs +42 -46
- package/dist/routes.d.cts +56 -146
- package/dist/routes.d.ts +56 -146
- package/dist/routes.js +18 -10
- package/dist/services.cjs +43 -76
- package/dist/services.d.cts +93 -55
- package/dist/services.d.ts +93 -55
- package/dist/services.js +6 -3
- package/dist/{telemetry-B9vIV4wh.d.cts → telemetry-Cku1ax74.d.cts} +1 -1
- package/dist/{telemetry-B9vIV4wh.d.ts → telemetry-Cku1ax74.d.ts} +1 -1
- package/dist/templates.cjs +17 -29
- package/dist/templates.d.cts +2 -89
- package/dist/templates.d.ts +2 -89
- package/dist/templates.js +3 -3
- package/dist/types-Dea1eNxU.d.cts +286 -0
- package/dist/types-Dea1eNxU.d.ts +286 -0
- package/dist/types.d.cts +2 -2
- package/dist/types.d.ts +2 -2
- package/dist/utils.cjs +21 -20
- package/dist/utils.d.cts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +3 -2
- package/migrations/0001_core.sql +184 -0
- package/migrations/0002_documents.sql +163 -0
- package/package.json +12 -7
- package/dist/admin-layout-catalyst.template-HFD37TY5.cjs +0 -17
- package/dist/admin-layout-catalyst.template-UMTIN66R.js +0 -7
- package/dist/app-C9esKLmh.d.cts +0 -112
- package/dist/app-C9esKLmh.d.ts +0 -112
- package/dist/chunk-4NPCDK6B.js.map +0 -1
- package/dist/chunk-4ZSNJDLS.cjs.map +0 -1
- package/dist/chunk-55RDMDOP.js.map +0 -1
- package/dist/chunk-635JAMSE.cjs +0 -653
- package/dist/chunk-635JAMSE.cjs.map +0 -1
- package/dist/chunk-7A4CB7T3.cjs.map +0 -1
- package/dist/chunk-ABB34XUS.cjs.map +0 -1
- package/dist/chunk-BU7SFHGP.js.map +0 -1
- package/dist/chunk-E4YFJBM2.cjs.map +0 -1
- package/dist/chunk-EXNEW5US.js +0 -648
- package/dist/chunk-EXNEW5US.js.map +0 -1
- package/dist/chunk-JZV22DEV.js +0 -1783
- package/dist/chunk-JZV22DEV.js.map +0 -1
- package/dist/chunk-JZVHLLSI.cjs.map +0 -1
- package/dist/chunk-OCL3HMEG.js.map +0 -1
- package/dist/chunk-OHYBNCVL.cjs.map +0 -1
- package/dist/chunk-ON5ZMSU4.js.map +0 -1
- package/dist/chunk-QFWHAFEO.js +0 -1843
- package/dist/chunk-QFWHAFEO.js.map +0 -1
- package/dist/chunk-R4FOLLFB.cjs.map +0 -1
- package/dist/chunk-RLMUFFUD.cjs +0 -2219
- package/dist/chunk-RLMUFFUD.cjs.map +0 -1
- package/dist/chunk-TFNTM3OA.js.map +0 -1
- package/dist/chunk-UYJ6TJHX.cjs.map +0 -1
- package/dist/chunk-WAEQXGCX.cjs +0 -1898
- package/dist/chunk-WAEQXGCX.cjs.map +0 -1
- package/dist/chunk-XWIA3HVX.js.map +0 -1
- package/dist/chunk-ZYAYUIZE.js +0 -2217
- package/dist/chunk-ZYAYUIZE.js.map +0 -1
- package/dist/migrations-566IIPS2.cjs +0 -13
- package/dist/migrations-H5IXZNCO.js +0 -4
- package/dist/plugin-manager-BoM3Q7o7.d.cts +0 -328
- package/dist/plugin-manager-Efx9RyDX.d.ts +0 -328
- package/migrations/001_initial_schema.sql +0 -170
- package/migrations/002_faq_plugin.sql +0 -86
- package/migrations/003_stage5_enhancements.sql +0 -121
- package/migrations/004_stage6_user_management.sql +0 -183
- package/migrations/005_stage7_workflow_automation.sql +0 -294
- package/migrations/006_plugin_system.sql +0 -155
- package/migrations/007_demo_login_plugin.sql +0 -23
- package/migrations/008_fix_slug_validation.sql +0 -22
- package/migrations/009_system_logging.sql +0 -57
- package/migrations/011_config_managed_collections.sql +0 -15
- package/migrations/012_testimonials_plugin.sql +0 -80
- package/migrations/013_code_examples_plugin.sql +0 -177
- package/migrations/014_fix_plugin_registry.sql +0 -88
- package/migrations/015_add_remaining_plugins.sql +0 -89
- package/migrations/016_remove_duplicate_cache_plugin.sql +0 -17
- package/migrations/017_auth_configurable_fields.sql +0 -49
- package/migrations/018_settings_table.sql +0 -23
- package/migrations/019_remove_blog_posts_collection.sql +0 -15
- package/migrations/020_add_email_plugin.sql +0 -22
- package/migrations/021_add_magic_link_auth_plugin.sql +0 -42
- package/migrations/022_add_tinymce_plugin.sql +0 -25
- package/migrations/023_add_easy_mdx_plugin.sql +0 -25
- package/migrations/024_add_quill_editor_plugin.sql +0 -25
- package/migrations/025_add_easymde_plugin.sql +0 -25
- package/migrations/026_add_otp_login.sql +0 -42
- package/migrations/027_fix_slug_field_type.sql +0 -18
- package/migrations/028_fix_slug_field_type_in_schemas.sql +0 -30
- package/migrations/029_add_forms_system.sql +0 -184
- package/migrations/030_add_turnstile_to_forms.sql +0 -14
- package/migrations/031_ai_search_plugin.sql +0 -45
- package/migrations/032_user_profiles.sql +0 -37
- package/migrations/033_form_content_integration.sql +0 -19
- package/migrations/034_security_audit_plugin.sql +0 -27
- package/migrations/035_user_profiles_data_column.sql +0 -16
- package/migrations/036_analytics_events.sql +0 -22
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/documents.ts","../src/services/rbac.ts"],"names":[],"mappings":"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","file":"chunk-BLMTL57B.js","sourcesContent":["import { D1Database } from '@cloudflare/workers-types'\nimport { nanoid } from 'nanoid'\nimport type {\n Document,\n DocumentRow,\n CreateDocumentInput,\n UpdateDocumentInput,\n QueryableField,\n} from '../schemas/document'\nimport { DocumentProjection } from './document-projection'\n\nconst DEFAULT_MAX_VERSIONS = 50\n\n/**\n * D29: documents store `created_at`/`updated_at` in SECONDS (see `create`/`saveDraft`), but the legacy\n * `content` table — and therefore the public/CRUD `/api/content` contract — used MILLISECONDS. Any code\n * that shapes a document row into the content response must convert so `new Date(item.created_at)` keeps\n * working for API consumers. Null/undefined pass through unchanged.\n */\nexport function documentSecondsToMs(ts: number | null | undefined): number | null {\n return ts == null ? null : ts * 1000\n}\n\nfunction rowToDocument(row: DocumentRow): Document {\n return {\n id: row.id,\n rootId: row.root_id,\n typeId: row.type_id,\n typeVersion: row.type_version,\n versionOfId: row.version_of_id,\n versionNumber: row.version_number,\n isCurrentDraft: row.is_current_draft === 1,\n isPublished: row.is_published === 1,\n status: row.status,\n parentRootId: row.parent_root_id,\n slug: row.slug,\n path: row.path,\n title: row.title,\n zone: row.zone,\n sortOrder: row.sort_order,\n visible: row.visible === 1,\n publishedAt: row.published_at,\n scheduledAt: row.scheduled_at,\n expiresAt: row.expires_at,\n deletedAt: row.deleted_at,\n tenantId: row.tenant_id,\n locale: row.locale,\n translationGroupId: row.translation_group_id,\n data: JSON.parse(row.data),\n metadata: JSON.parse(row.metadata),\n ownerId: row.owner_id,\n createdBy: row.created_by,\n updatedBy: row.updated_by,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }\n}\n\nexport interface DocumentsServiceOptions {\n queryableFields?: QueryableField[]\n typeSchemaVersion?: number\n maxVersionsPerRoot?: number\n /** Tenant this service operates within. Every root-keyed lookup is scoped to it (R3). POC default: 'default'. */\n tenantId?: string\n /** Retain version history (new row per saveDraft, supersede-as-history). Default false (in-place). */\n versioning?: boolean\n}\n\nexport class DocumentsService {\n private projection: DocumentProjection\n private tenantId: string\n private versioning: boolean\n\n constructor(\n private db: D1Database,\n private opts: DocumentsServiceOptions = {},\n ) {\n this.projection = new DocumentProjection(db)\n this.tenantId = opts.tenantId ?? 'default'\n this.versioning = opts.versioning ?? false\n }\n\n // ─── Create ───────────────────────────────────────────────────────────────\n\n async create(input: CreateDocumentInput, createdBy?: string): Promise<Document> {\n // D23: document timestamps are stored in SECONDS (legacy `content` rows use milliseconds). Any\n // Date() rendering of a document timestamp must multiply by 1000.\n const now = Math.floor(Date.now() / 1000)\n const id = nanoid()\n const publish = input.publishOnCreate ?? false\n // D34: backfill may carry the source row's original timestamps; normal creates default to now.\n const createdAt = input.createdAt ?? now\n const updatedAt = input.updatedAt ?? now\n\n const doc: Document = {\n id,\n rootId: id,\n typeId: input.typeId,\n typeVersion: this.opts.typeSchemaVersion ?? 1,\n versionOfId: null,\n versionNumber: 1,\n isCurrentDraft: true,\n isPublished: publish,\n status: publish ? 'published' : 'draft',\n parentRootId: input.parentRootId ?? '',\n slug: input.slug ?? null,\n path: null,\n title: input.title ?? null,\n zone: input.zone ?? null,\n sortOrder: input.sortOrder ?? 0,\n visible: input.visible ?? true,\n publishedAt: publish ? createdAt : null,\n scheduledAt: input.scheduledAt ?? null,\n expiresAt: input.expiresAt ?? null,\n deletedAt: null,\n // Tenant comes from the service scope unless the caller passes one explicitly. Never trust a\n // request-body tenant: route handlers must override with the resolved request-context tenant.\n tenantId: input.tenantId ?? this.tenantId,\n locale: input.locale ?? 'default',\n translationGroupId: '',\n data: input.data ?? {},\n metadata: input.metadata ?? {},\n ownerId: input.ownerId ?? null,\n createdBy: createdBy ?? null,\n updatedBy: createdBy ?? null,\n createdAt,\n updatedAt,\n }\n\n const insertDoc = this.db.prepare(\n `INSERT INTO documents (id, root_id, type_id, type_version, version_of_id, version_number,\n is_current_draft, is_published, status, parent_root_id, slug, path, title, zone,\n sort_order, visible, published_at, scheduled_at, expires_at, deleted_at,\n tenant_id, locale, translation_group_id, data, metadata,\n owner_id, created_by, updated_by, created_at, updated_at)\n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n ).bind(\n doc.id, doc.rootId, doc.typeId, doc.typeVersion, null, 1,\n 1, publish ? 1 : 0, doc.status, doc.parentRootId, doc.slug, null, doc.title, doc.zone,\n doc.sortOrder, doc.visible ? 1 : 0, doc.publishedAt, doc.scheduledAt, doc.expiresAt, null,\n doc.tenantId, doc.locale, '', JSON.stringify(doc.data), JSON.stringify(doc.metadata),\n doc.ownerId, doc.createdBy, doc.updatedBy, createdAt, updatedAt,\n )\n\n const derivedInserts = this.projection.buildDerivedInsertStatements(doc, this.opts.queryableFields ?? [], now)\n\n await this.db.batch([insertDoc, ...derivedInserts])\n return doc\n }\n\n // ─── Save new draft ───────────────────────────────────────────────────────\n // Atomically: demote previous draft → delete its derived rows (if not published) →\n // insert new draft → materialize derived rows → prune excess versions.\n\n async saveDraft(rootId: string, input: UpdateDocumentInput, updatedBy?: string): Promise<Document> {\n const now = Math.floor(Date.now() / 1000)\n const newId = nanoid()\n\n // Fetch current state synchronously before starting the batch. Tenant-scoped (R3): a service\n // for tenant B must not find or mutate tenant A's root.\n const prevDraftRow = await this.db\n .prepare('SELECT * FROM documents WHERE root_id = ? AND tenant_id = ? AND is_current_draft = 1')\n .bind(rootId, this.tenantId)\n .first<DocumentRow>()\n\n if (!prevDraftRow) throw new Error(`No current draft found for root ${rootId}`)\n\n const prevDraft = rowToDocument(prevDraftRow)\n\n const mergedData = { ...prevDraft.data, ...(input.data ?? {}) }\n const mergedMeta = { ...prevDraft.metadata, ...(input.metadata ?? {}) }\n\n const newDoc: Document = {\n ...prevDraft,\n id: newId,\n rootId,\n typeVersion: this.opts.typeSchemaVersion ?? prevDraft.typeVersion,\n versionOfId: prevDraft.id,\n versionNumber: 0, // computed by SQL below\n isCurrentDraft: true,\n isPublished: false,\n status: 'draft',\n slug: input.slug !== undefined ? input.slug ?? null : prevDraft.slug,\n title: input.title !== undefined ? input.title ?? null : prevDraft.title,\n zone: input.zone !== undefined ? input.zone ?? null : prevDraft.zone,\n sortOrder: input.sortOrder ?? prevDraft.sortOrder,\n visible: input.visible ?? prevDraft.visible,\n scheduledAt: input.scheduledAt !== undefined ? input.scheduledAt : prevDraft.scheduledAt,\n expiresAt: input.expiresAt !== undefined ? input.expiresAt : prevDraft.expiresAt,\n data: mergedData,\n metadata: mergedMeta,\n updatedBy: updatedBy ?? prevDraft.updatedBy,\n updatedAt: now,\n createdAt: now,\n }\n\n const prevIsPublished = prevDraftRow.is_published === 1\n\n // Versioning off + the working draft is a pure draft (not the live published row):\n // update it in place. No new row, no history accumulation. (R7: rebuild derived rows.)\n if (!this.versioning && !prevIsPublished) {\n return this.updateInPlace(prevDraft, input, now, updatedBy)\n }\n\n const statements: D1PreparedStatement[] = [\n // 1. Demote previous current draft FIRST (unique index: never two current drafts mid-batch).\n this.db.prepare('UPDATE documents SET is_current_draft = 0, updated_at = ? WHERE id = ? AND tenant_id = ?')\n .bind(now, prevDraft.id, this.tenantId),\n\n // 2. If the previous draft was not also the published row, delete its derived rows.\n ...(!prevIsPublished ? this.projection.buildDerivedDeleteStatements(prevDraft.id) : []),\n\n // 3. Insert new draft. version_number derived in SQL (COALESCE(MAX)+1 from existing rows).\n // R5 arithmetic — keep balanced: 30 columns = 5 leading '?' + 1 version_number subquery\n // + 3 literals (1,0,'draft') + 21 trailing '?'. Total placeholders: 5 + 1 (subquery\n // root_id) + 21 = 27, which MUST equal the 27 .bind() args below. Do not change one side\n // without recounting the other.\n this.db.prepare(\n `INSERT INTO documents (id, root_id, type_id, type_version, version_of_id, version_number,\n is_current_draft, is_published, status, parent_root_id, slug, path, title, zone,\n sort_order, visible, published_at, scheduled_at, expires_at, deleted_at,\n tenant_id, locale, translation_group_id, data, metadata,\n owner_id, created_by, updated_by, created_at, updated_at)\n SELECT ?,?,?,?,?,\n (SELECT COALESCE(MAX(version_number), 0) + 1 FROM documents WHERE root_id = ?),\n 1,0,'draft',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?\n WHERE 1=1`,\n ).bind(\n newId, rootId, newDoc.typeId, newDoc.typeVersion, prevDraft.id,\n rootId,\n newDoc.parentRootId, newDoc.slug, null, newDoc.title, newDoc.zone,\n newDoc.sortOrder, newDoc.visible ? 1 : 0, null, newDoc.scheduledAt, newDoc.expiresAt, null,\n newDoc.tenantId, newDoc.locale, newDoc.translationGroupId,\n JSON.stringify(newDoc.data), JSON.stringify(newDoc.metadata),\n newDoc.ownerId, newDoc.createdBy, newDoc.updatedBy, now, now,\n ),\n\n // 4. Materialize derived rows for new draft.\n ...this.projection.buildDerivedInsertStatements(newDoc, this.opts.queryableFields ?? [], now),\n ]\n\n // 5. Prune excess versions (beyond maxVersionsPerRoot), never the published or current-draft row,\n // and never a version still referenced as version_of_id by another row (FK RESTRICT).\n const maxVersions = this.opts.maxVersionsPerRoot ?? DEFAULT_MAX_VERSIONS\n statements.push(\n this.db.prepare(\n `DELETE FROM documents WHERE root_id = ? AND tenant_id = ? AND is_current_draft = 0 AND is_published = 0\n AND id NOT IN (\n SELECT id FROM documents WHERE root_id = ? AND tenant_id = ? AND is_current_draft = 0 AND is_published = 0\n ORDER BY version_number DESC LIMIT ?\n )\n AND id NOT IN (SELECT version_of_id FROM documents WHERE version_of_id IS NOT NULL AND root_id = ? AND tenant_id = ?)`,\n ).bind(rootId, this.tenantId, rootId, this.tenantId, maxVersions, rootId, this.tenantId),\n )\n\n await this.db.batch(statements)\n\n // Fetch the saved row to get the SQL-computed version_number.\n const saved = await this.db\n .prepare('SELECT * FROM documents WHERE id = ?')\n .bind(newId)\n .first<DocumentRow>()\n return rowToDocument(saved!)\n }\n\n // In-place draft update (versioning off). Mutates the existing draft row; preserves id/root_id/\n // version_number/version_of_id and the is_current_draft/is_published flags. Rebuilds derived rows.\n private async updateInPlace(\n prevDraft: Document,\n input: UpdateDocumentInput,\n now: number,\n updatedBy?: string,\n ): Promise<Document> {\n const mergedData = { ...prevDraft.data, ...(input.data ?? {}) }\n const mergedMeta = { ...prevDraft.metadata, ...(input.metadata ?? {}) }\n\n const updated: Document = {\n ...prevDraft,\n slug: input.slug !== undefined ? input.slug ?? null : prevDraft.slug,\n title: input.title !== undefined ? input.title ?? null : prevDraft.title,\n zone: input.zone !== undefined ? input.zone ?? null : prevDraft.zone,\n sortOrder: input.sortOrder ?? prevDraft.sortOrder,\n visible: input.visible ?? prevDraft.visible,\n scheduledAt: input.scheduledAt !== undefined ? input.scheduledAt : prevDraft.scheduledAt,\n expiresAt: input.expiresAt !== undefined ? input.expiresAt : prevDraft.expiresAt,\n data: mergedData,\n metadata: mergedMeta,\n updatedBy: updatedBy ?? prevDraft.updatedBy,\n updatedAt: now,\n }\n\n const statements: D1PreparedStatement[] = [\n // R5: 11 SET '?' + 2 WHERE '?' (id, tenant_id) = 13 binds, matching .bind() below.\n this.db.prepare(\n `UPDATE documents SET\n slug = ?, title = ?, zone = ?, sort_order = ?, visible = ?,\n scheduled_at = ?, expires_at = ?, data = ?, metadata = ?, updated_by = ?, updated_at = ?\n WHERE id = ? AND tenant_id = ?`,\n ).bind(\n updated.slug, updated.title, updated.zone, updated.sortOrder, updated.visible ? 1 : 0,\n updated.scheduledAt, updated.expiresAt, JSON.stringify(updated.data), JSON.stringify(updated.metadata),\n updated.updatedBy, now,\n updated.id, this.tenantId,\n ),\n // R7: derived rows track the new data — delete then reinsert for this row.\n ...this.projection.buildDerivedDeleteStatements(updated.id),\n ...this.projection.buildDerivedInsertStatements(updated, this.opts.queryableFields ?? [], now),\n ]\n\n await this.db.batch(statements)\n\n const saved = await this.db.prepare('SELECT * FROM documents WHERE id = ?').bind(updated.id).first<DocumentRow>()\n return rowToDocument(saved!)\n }\n\n // ─── Publish ──────────────────────────────────────────────────────────────\n\n async publish(documentId: string, publishedBy?: string): Promise<Document> {\n const now = Math.floor(Date.now() / 1000)\n\n const targetRow = await this.db\n .prepare('SELECT * FROM documents WHERE id = ? AND tenant_id = ?')\n .bind(documentId, this.tenantId)\n .first<DocumentRow>()\n\n if (!targetRow) throw new Error(`Document ${documentId} not found`)\n\n const prevPublishedRow = await this.db\n .prepare('SELECT * FROM documents WHERE root_id = ? AND tenant_id = ? AND is_published = 1 AND id != ?')\n .bind(targetRow.root_id, this.tenantId, documentId)\n .first<DocumentRow>()\n\n const statements: D1PreparedStatement[] = []\n\n if (prevPublishedRow) {\n if (!this.versioning && prevPublishedRow.is_current_draft !== 1) {\n // Versioning off: old published row is pure history — remove it + its derived rows.\n // First null any version_of_id pointing at it (the target draft may chain off it) so the\n // delete can't trip the FK RESTRICT on documents.version_of_id (plan risk #1; the chain is\n // unused when versioning is off). Tenant-scoped (R3).\n statements.push(this.db.prepare('UPDATE documents SET version_of_id = NULL WHERE version_of_id = ? AND tenant_id = ?')\n .bind(prevPublishedRow.id, this.tenantId))\n statements.push(...this.projection.buildDerivedDeleteStatements(prevPublishedRow.id))\n statements.push(this.db.prepare('DELETE FROM documents WHERE id = ? AND tenant_id = ?')\n .bind(prevPublishedRow.id, this.tenantId))\n } else {\n // existing behavior: clear is_published, drop derived rows if not the current draft\n statements.push(\n this.db.prepare('UPDATE documents SET is_published = 0, updated_at = ? WHERE id = ?')\n .bind(now, prevPublishedRow.id),\n )\n if (prevPublishedRow.is_current_draft !== 1) {\n statements.push(...this.projection.buildDerivedDeleteStatements(prevPublishedRow.id))\n }\n }\n }\n\n // Set published on target row.\n statements.push(\n this.db.prepare(\n `UPDATE documents SET is_published = 1, status = 'published', published_at = ?, updated_at = ?, updated_by = ? WHERE id = ?`,\n ).bind(now, now, publishedBy ?? null, documentId),\n )\n\n // Ensure derived rows exist for the target (they do if it was current draft; materialize if not).\n if (targetRow.is_current_draft !== 1) {\n const targetDoc = rowToDocument(targetRow)\n statements.push(...this.projection.buildDerivedInsertStatements(targetDoc, this.opts.queryableFields ?? [], now))\n }\n\n await this.db.batch(statements)\n\n const saved = await this.db.prepare('SELECT * FROM documents WHERE id = ?').bind(documentId).first<DocumentRow>()\n return rowToDocument(saved!)\n }\n\n // ─── Unpublish ────────────────────────────────────────────────────────────\n\n async unpublish(documentId: string): Promise<Document> {\n const now = Math.floor(Date.now() / 1000)\n\n const row = await this.db\n .prepare('SELECT * FROM documents WHERE id = ? AND tenant_id = ?')\n .bind(documentId, this.tenantId)\n .first<DocumentRow>()\n\n if (!row) throw new Error(`Document ${documentId} not found`)\n if (!row.is_published) throw new Error(`Document ${documentId} is not published`)\n\n const statements: D1PreparedStatement[] = [\n this.db.prepare(`UPDATE documents SET is_published = 0, status = 'draft', updated_at = ? WHERE id = ?`)\n .bind(now, documentId),\n ]\n\n // If the unpublished row is not the current draft, remove its derived rows.\n if (row.is_current_draft !== 1) {\n statements.push(...this.projection.buildDerivedDeleteStatements(documentId))\n }\n\n await this.db.batch(statements)\n\n const saved = await this.db.prepare('SELECT * FROM documents WHERE id = ?').bind(documentId).first<DocumentRow>()\n return rowToDocument(saved!)\n }\n\n // ─── Soft delete ──────────────────────────────────────────────────────────\n\n async softDelete(documentId: string): Promise<void> {\n const now = Math.floor(Date.now() / 1000)\n await this.db\n .prepare('UPDATE documents SET deleted_at = ?, updated_at = ? WHERE id = ? AND tenant_id = ?')\n .bind(now, now, documentId, this.tenantId)\n .run()\n }\n\n // ─── Hard erase (PII types) ───────────────────────────────────────────────\n // Deletes every version row for a root plus all derived data, in dependency order.\n\n async erase(rootId: string, tenantId: string): Promise<void> {\n // Get all document IDs for this root.\n const result = await this.db\n .prepare('SELECT id FROM documents WHERE root_id = ? AND tenant_id = ?')\n .bind(rootId, tenantId)\n .all<{ id: string }>()\n\n const docIds = (result.results ?? []).map(r => r.id)\n if (docIds.length === 0) return\n\n const statements: D1PreparedStatement[] = []\n\n // Delete derived tables first (explicit; don't rely on FK cascade).\n for (const id of docIds) {\n statements.push(this.db.prepare('DELETE FROM document_facets WHERE document_id = ?').bind(id))\n statements.push(this.db.prepare('DELETE FROM document_references WHERE from_document_id = ?').bind(id))\n }\n\n statements.push(this.db.prepare('DELETE FROM document_permissions WHERE root_id = ? AND tenant_id = ?').bind(rootId, tenantId))\n\n // Delete all version rows.\n for (const id of docIds) {\n statements.push(this.db.prepare('DELETE FROM documents WHERE id = ?').bind(id))\n }\n\n await this.db.batch(statements)\n }\n}\n","/**\n * Dynamic RBAC service — document-backed.\n *\n * Roles, verbs, and user-role assignments are stored as `is_auth` documents\n * (slug-addressed) instead of relational tables:\n * rbac_role slug = roleId, data = { name, displayName, description, isSystem, grants:[{resource,verb,scope}] }\n * rbac_verb slug = verbId, data = { name, description, isSystem, sortOrder }\n * rbac_user_roles slug = userId, data = { roleIds:[] }\n * Grants are embedded in the role document (no separate join type); user-role\n * assignments are embedded in a per-user document. Resources are still computed:\n * a fixed set of system resources plus one `document_type:<name>` per type.\n *\n * Wildcards: resource '*' / 'document_type:*', verb '*' / 'manage' (implies all).\n * Scope: 'any' beats 'own' beats 'none'.\n *\n * The public API is unchanged from the relational implementation, so callers\n * (admin-rbac routes, permission checks) need no changes.\n */\nimport { DocumentsService } from './documents'\n\nexport interface RbacRole {\n id: string\n name: string\n display_name: string\n description: string | null\n is_system: number\n}\nexport interface RbacVerb {\n id: string\n name: string\n description: string | null\n is_system: number\n sort_order: number\n}\nexport interface RbacResource {\n key: string // e.g. 'content' or 'collection:blog_posts'\n label: string\n group: 'system' | 'document_type'\n}\nexport type PermissionScope = 'none' | 'own' | 'any'\nexport interface Grant {\n role_id: string\n resource: string\n verb: string\n scope: Exclude<PermissionScope, 'none'>\n}\n\ninterface GrantData { resource: string; verb: string; scope?: PermissionScope }\ninterface RoleData { name: string; displayName: string; description?: string | null; isSystem?: boolean; grants?: GrantData[] }\ninterface VerbData { name: string; description?: string | null; isSystem?: boolean; sortOrder?: number }\ninterface UserRolesData { roleIds?: string[] }\ninterface DocRow { id: string; root_id: string; slug: string | null; data: string }\n\nconst TENANT = 'default'\nconst T_ROLE = 'rbac_role'\nconst T_VERB = 'rbac_verb'\nconst T_USER_ROLES = 'rbac_user_roles'\n\nconst SYSTEM_RESOURCES: RbacResource[] = [\n { key: '*', label: 'All resources', group: 'system' },\n { key: 'portal', label: 'Admin Portal', group: 'system' },\n { key: 'dashboard', label: 'Dashboard', group: 'system' },\n { key: 'rbac', label: 'Roles & Permissions', group: 'system' },\n { key: 'documents', label: 'Documents', group: 'system' as const },\n { key: 'document_types', label: 'Document Types', group: 'system' as const },\n { key: 'email', label: 'Email Management', group: 'system' },\n { key: 'users', label: 'Users', group: 'system' },\n { key: 'settings', label: 'Settings', group: 'system' },\n { key: 'logs', label: 'Logs', group: 'system' },\n]\n\nexport class RbacService {\n // Precedence for projecting the user's RBAC roles back onto the legacy\n // users.role compat column (highest privilege first). Only `admin` is\n // hardcoded as a seeded role — `editor` is listed here purely so that if an\n // administrator chooses to recreate a role named `editor`, legacy code that\n // still gates on the `editor` label keeps working.\n private static readonly LEGACY_ROLE_PRECEDENCE = ['admin', 'editor']\n\n private _docs?: DocumentsService\n\n constructor(private db: D1Database, private kv?: KVNamespace) {}\n\n // ── Document access helpers ──────────────────────────────────────────────────\n\n private docs(): DocumentsService {\n if (!this._docs) {\n this._docs = new DocumentsService(this.db, { tenantId: TENANT, maxVersionsPerRoot: 1, queryableFields: [] })\n }\n return this._docs\n }\n\n private parse<T>(row: DocRow): { id: string; rootId: string; slug: string; data: T } {\n let data: T\n try { data = JSON.parse(row.data) as T } catch { data = {} as T }\n return { id: row.id, rootId: row.root_id, slug: row.slug ?? '', data }\n }\n\n private async listDocs<T>(typeId: string): Promise<Array<{ id: string; rootId: string; slug: string; data: T }>> {\n const res = await this.db\n .prepare(\n `SELECT id, root_id, slug, data FROM documents\n WHERE type_id = ? AND tenant_id = ? AND is_current_draft = 1 AND deleted_at IS NULL`,\n )\n .bind(typeId, TENANT)\n .all<DocRow>()\n return (res.results ?? []).map((r) => this.parse<T>(r))\n }\n\n private async getDoc<T>(typeId: string, slug: string): Promise<{ id: string; rootId: string; data: T } | null> {\n const row = await this.db\n .prepare(\n `SELECT id, root_id, slug, data FROM documents\n WHERE type_id = ? AND tenant_id = ? AND slug = ? AND is_current_draft = 1 AND deleted_at IS NULL`,\n )\n .bind(typeId, TENANT, slug)\n .first<DocRow>()\n return row ? this.parse<T>(row) : null\n }\n\n private async upsertDoc(typeId: string, slug: string, data: unknown, title: string | null): Promise<void> {\n const existing = await this.db\n .prepare(\n `SELECT root_id FROM documents\n WHERE type_id = ? AND tenant_id = ? AND slug = ? AND is_current_draft = 1 AND deleted_at IS NULL`,\n )\n .bind(typeId, TENANT, slug)\n .first<{ root_id: string }>()\n const payload = data as Record<string, unknown>\n if (existing?.root_id) {\n await this.docs().saveDraft(existing.root_id, { data: payload, title })\n } else {\n await this.docs().create({\n typeId,\n tenantId: TENANT,\n locale: 'default',\n parentRootId: '',\n slug,\n title,\n sortOrder: 0,\n visible: true,\n data: payload,\n metadata: {},\n ownerId: null,\n publishOnCreate: false,\n })\n }\n }\n\n private async deleteDoc(typeId: string, slug: string): Promise<void> {\n const doc = await this.getDoc(typeId, slug)\n if (doc) await this.docs().softDelete(doc.id)\n }\n\n private roleToRow(d: { slug: string; data: RoleData }): RbacRole {\n return {\n id: d.slug,\n name: d.data.name,\n display_name: d.data.displayName,\n description: d.data.description ?? null,\n is_system: d.data.isSystem ? 1 : 0,\n }\n }\n\n // ── Reads ────────────────────────────────────────────────────────────────────\n\n async getRoles(): Promise<RbacRole[]> {\n const docs = await this.listDocs<RoleData>(T_ROLE)\n return docs\n .map((d) => this.roleToRow(d))\n .sort((a, b) => b.is_system - a.is_system || a.name.localeCompare(b.name))\n }\n\n async getVerbs(): Promise<RbacVerb[]> {\n const docs = await this.listDocs<VerbData>(T_VERB)\n return docs\n .map((d) => ({\n id: d.slug,\n name: d.data.name,\n description: d.data.description ?? null,\n is_system: d.data.isSystem ? 1 : 0,\n sort_order: d.data.sortOrder ?? 100,\n }))\n .sort((a, b) => a.sort_order - b.sort_order || a.name.localeCompare(b.name))\n }\n\n /** System resources + one `document_type:<name>` per active document type. */\n async getResources(): Promise<RbacResource[]> {\n const types = (\n await this.db\n .prepare('SELECT name, display_name FROM document_types WHERE is_active = 1 ORDER BY name')\n .all<{ name: string; display_name: string }>()\n ).results as Array<{ name: string; display_name: string }>\n const documentTypeResources: RbacResource[] = [\n { key: 'document_type:*', label: 'All document types', group: 'document_type' },\n ...types.map((t) => ({\n key: `document_type:${t.name}`,\n label: t.display_name || t.name,\n group: 'document_type' as const,\n })),\n ]\n return [...SYSTEM_RESOURCES, ...documentTypeResources]\n }\n\n async getGrants(): Promise<Grant[]> {\n const roles = await this.listDocs<RoleData>(T_ROLE)\n const out: Grant[] = []\n for (const r of roles) {\n for (const g of r.data.grants ?? []) {\n out.push({ role_id: r.slug, resource: g.resource, verb: g.verb, scope: g.scope === 'own' ? 'own' : 'any' })\n }\n }\n return out\n }\n\n async getRolesForUser(userId: string): Promise<RbacRole[]> {\n const ur = await this.getDoc<UserRolesData>(T_USER_ROLES, userId)\n const roleIds = new Set(ur?.data.roleIds ?? [])\n if (roleIds.size === 0) return []\n const roles = await this.listDocs<RoleData>(T_ROLE)\n return roles.filter((r) => roleIds.has(r.slug)).map((d) => this.roleToRow(d))\n }\n\n /** Grants attached to a set of role ids (from the embedded role grants). */\n private async grantsForRoleIds(roleIds: string[]): Promise<Array<{ resource: string; verb: string; scope: PermissionScope }>> {\n if (roleIds.length === 0) return []\n const want = new Set(roleIds)\n const roles = await this.listDocs<RoleData>(T_ROLE)\n const out: Array<{ resource: string; verb: string; scope: PermissionScope }> = []\n for (const r of roles) {\n if (!want.has(r.slug)) continue\n for (const g of r.data.grants ?? []) {\n out.push({ resource: g.resource, verb: g.verb, scope: g.scope === 'own' ? 'own' : 'any' })\n }\n }\n return out\n }\n\n /** Does a single grant row satisfy the requested (resource, verb)? */\n private grantMatches(g: { resource: string; verb: string }, resource: string, verb: string): boolean {\n const resourceOk =\n g.resource === '*' ||\n g.resource === resource ||\n (g.resource === 'document_type:*' && resource.startsWith('document_type:'))\n if (!resourceOk) return false\n return g.verb === '*' || g.verb === verb || g.verb === 'manage'\n }\n\n private strongestScope(scopes: PermissionScope[]): PermissionScope {\n if (scopes.includes('any')) return 'any'\n if (scopes.includes('own')) return 'own'\n return 'none'\n }\n\n /** Can the user perform `verb` on `resource`? Reads the live grant matrix. */\n async can(userId: string, resource: string, verb: string): Promise<boolean> {\n return (await this.getPermissionScope(userId, resource, verb)) !== 'none'\n }\n\n /** Highest scope granted to the user for `resource:verb`. */\n async getPermissionScope(userId: string, resource: string, verb: string): Promise<PermissionScope> {\n const ur = await this.getDoc<UserRolesData>(T_USER_ROLES, userId)\n const grants = await this.grantsForRoleIds(ur?.data.roleIds ?? [])\n return this.strongestScope(\n grants.filter((g) => this.grantMatches(g, resource, verb)).map((g) => (g.scope === 'own' ? 'own' : 'any')),\n )\n }\n\n /** Flattened, human-readable permission list for a user. Cached in KV for 60 s. */\n async permissionsForUser(userId: string): Promise<string[]> {\n if (this.kv) {\n const cached = await this.kv.get(`rbac:perms:${userId}`)\n if (cached !== null) return JSON.parse(cached) as string[]\n }\n const ur = await this.getDoc<UserRolesData>(T_USER_ROLES, userId)\n const roleIds = ur?.data.roleIds ?? []\n if (roleIds.length === 0) return []\n const grants = await this.grantsForRoleIds(roleIds)\n const resources = await this.getResources()\n const verbs = await this.getVerbs()\n const out = new Set<string>()\n for (const r of resources) {\n for (const v of verbs) {\n if (grants.some((g) => this.grantMatches(g, r.key, v.name))) out.add(`${r.key}:${v.name}`)\n }\n }\n const result = [...out].sort()\n if (this.kv) {\n await this.kv.put(`rbac:perms:${userId}`, JSON.stringify(result), { expirationTtl: 60 })\n }\n return result\n }\n\n // ── Mutations ──────────────────────────────────────────────────────────────\n\n async createRole(name: string, displayName: string, description = ''): Promise<void> {\n const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-')\n const id = `role-${slug}`\n await this.upsertDoc(\n T_ROLE,\n id,\n { name: name.toLowerCase(), displayName, description, isSystem: false, grants: [] } satisfies RoleData,\n displayName,\n )\n }\n\n async deleteRole(roleId: string): Promise<void> {\n const role = await this.getDoc<RoleData>(T_ROLE, roleId)\n if (!role || role.data.isSystem) return // System roles cannot be deleted.\n await this.deleteDoc(T_ROLE, roleId)\n }\n\n /**\n * Update a role's display name and description. The `name` (slug) can only be\n * changed for custom roles — system role names are referenced by the legacy\n * mapping, so they stay fixed.\n */\n async updateRole(roleId: string, displayName: string, description = '', name?: string): Promise<void> {\n const role = await this.getDoc<RoleData>(T_ROLE, roleId)\n if (!role) return\n const next: RoleData = { ...role.data, displayName, description }\n if (!role.data.isSystem && name) {\n next.name = name.toLowerCase().replace(/[^a-z0-9]+/g, '-')\n }\n await this.upsertDoc(T_ROLE, roleId, next, displayName)\n }\n\n /** Update displayName + portal access in a single write to avoid double-saveDraft FK issues. */\n async updateRoleAndPortalAccess(\n roleId: string,\n displayName: string,\n name: string | undefined,\n portalEnabled: boolean,\n description?: string,\n ): Promise<void> {\n const role = await this.getDoc<RoleData>(T_ROLE, roleId)\n if (!role) return\n const next: RoleData = { ...role.data, displayName, description: description ?? role.data.description ?? '' }\n if (!role.data.isSystem && name) {\n next.name = name.toLowerCase().replace(/[^a-z0-9]+/g, '-')\n }\n const grants = (next.grants ?? []).filter((g) => !(g.resource === 'portal' && g.verb === 'access'))\n if (portalEnabled) grants.push({ resource: 'portal', verb: 'access', scope: 'any' })\n next.grants = grants\n await this.upsertDoc(T_ROLE, roleId, next, displayName)\n }\n\n async createVerb(name: string, description = ''): Promise<void> {\n const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-')\n const id = `verb-${slug}`\n await this.upsertDoc(\n T_VERB,\n id,\n { name: name.toLowerCase(), description, isSystem: false, sortOrder: 100 } satisfies VerbData,\n name,\n )\n }\n\n async deleteVerb(verbId: string): Promise<void> {\n const verb = await this.getDoc<VerbData>(T_VERB, verbId)\n if (!verb || verb.data.isSystem) return\n await this.deleteDoc(T_VERB, verbId)\n }\n\n /** Replace all grants for one role with the supplied (resource, verb, scope) rows. */\n async setRoleGrants(\n roleId: string,\n pairs: Array<{ resource: string; verb: string; scope?: Exclude<PermissionScope, 'none'> }>,\n ): Promise<void> {\n const role = await this.getDoc<RoleData>(T_ROLE, roleId)\n if (!role) return\n const grants: GrantData[] = pairs.map((p) => ({ resource: p.resource, verb: p.verb, scope: p.scope === 'own' ? 'own' : 'any' }))\n await this.upsertDoc(T_ROLE, roleId, { ...role.data, grants } satisfies RoleData, role.data.displayName)\n }\n\n /**\n * Count active users (optionally excluding one) who hold BOTH an effective\n * portal:access grant and an effective rbac:manage grant — the users who could\n * recover from a permission lockout. Powers the self-lockout guard.\n */\n async countPortalAdmins(excludeUserId?: string): Promise<number> {\n const active = (\n await this.db.prepare('SELECT id FROM auth_user WHERE is_active = 1').all<{ id: string }>()\n ).results as Array<{ id: string }>\n const activeIds = new Set(active.map((u) => u.id))\n\n // role id -> its grants\n const roles = await this.listDocs<RoleData>(T_ROLE)\n const grantsByRole = new Map<string, GrantData[]>(roles.map((r) => [r.slug, r.data.grants ?? []]))\n\n const userRoles = await this.listDocs<UserRolesData>(T_USER_ROLES)\n let count = 0\n for (const ur of userRoles) {\n const userId = ur.slug\n if (!activeIds.has(userId)) continue\n if (excludeUserId && userId === excludeUserId) continue\n let portal = false\n let rbac = false\n for (const rid of ur.data.roleIds ?? []) {\n for (const g of grantsByRole.get(rid) ?? []) {\n if (this.grantMatches(g, 'portal', 'access')) portal = true\n if (this.grantMatches(g, 'rbac', 'manage')) rbac = true\n }\n }\n if (portal && rbac) count++\n }\n return count\n }\n\n /**\n * Replace a user's RBAC role assignments. The `rbac_user_roles` document is the\n * single source of truth for authorization; the legacy `auth_user.role` column\n * is kept as a derived projection (highest-precedence system role, else\n * 'viewer') so the two never diverge.\n */\n async setUserRoles(userId: string, roleIds: string[]): Promise<void> {\n const allRoles = await this.listDocs<RoleData>(T_ROLE)\n const byId = new Map(allRoles.map((r) => [r.slug, r.data]))\n const names = roleIds.map((id) => byId.get(id)?.name).filter((n): n is string => !!n)\n const primaryRole = RbacService.LEGACY_ROLE_PRECEDENCE.find((r) => names.includes(r)) || 'viewer'\n\n // Self-lockout guard: never leave zero active users who can BOTH enter the\n // portal and manage RBAC (the minimum needed to recover).\n const newGrants: GrantData[] = []\n for (const id of roleIds) for (const g of byId.get(id)?.grants ?? []) newGrants.push(g)\n const userWillBeAdmin =\n newGrants.some((g) => this.grantMatches(g, 'portal', 'access')) &&\n newGrants.some((g) => this.grantMatches(g, 'rbac', 'manage'))\n if (!userWillBeAdmin && (await this.countPortalAdmins(userId)) === 0) {\n throw new Error(\n 'Refusing to update roles: this would leave no user able to manage Roles & Permissions and access the portal. Grant another user portal access + Roles & Permissions first.',\n )\n }\n\n await this.upsertDoc(T_USER_ROLES, userId, { roleIds } satisfies UserRolesData, null)\n // Keep the legacy auth_user.role column in lockstep as a projection of RBAC.\n await this.db.prepare('UPDATE auth_user SET role = ?, updated_at = ? WHERE id = ?').bind(primaryRole, Date.now(), userId).run()\n if (this.kv) await this.kv.delete(`rbac:perms:${userId}`)\n }\n\n async setRolePortalAccess(roleId: string, enabled: boolean): Promise<void> {\n const role = await this.getDoc<RoleData>(T_ROLE, roleId)\n if (!role) return\n const grants = (role.data.grants ?? []).filter((g) => !(g.resource === 'portal' && g.verb === 'access'))\n if (enabled) grants.push({ resource: 'portal', verb: 'access', scope: 'any' })\n await this.upsertDoc(T_ROLE, roleId, { ...role.data, grants } satisfies RoleData, role.data.displayName)\n }\n\n // ── Bootstrap helpers ────────────────────────────────────────────────────────\n\n /**\n * Seed the system roles, verbs, and their grants as documents. Idempotent —\n * existing roles/verbs (by slug) are left untouched. Replaces the INSERT OR\n * IGNORE seeds that lived in migration 0001. Call at bootstrap, after the rbac\n * document types are registered.\n */\n async ensureSystemRbacSeed(): Promise<void> {\n // `admin` is the only hardcoded SYSTEM role (locked, undeletable). `editor`\n // is seeded as a non-system example so a fresh install has a usable second\n // role out of the box, but an administrator can edit, rename, or delete it.\n const roles: Array<RoleData & { id: string }> = [\n { id: 'role-admin', name: 'admin', displayName: 'Administrator', description: 'Full access to everything', isSystem: true,\n grants: [\n { resource: '*', verb: 'manage' }, { resource: 'portal', verb: 'access' }, { resource: 'rbac', verb: 'manage' },\n { resource: 'document_types', verb: 'manage' }, { resource: 'email', verb: 'manage' }, { resource: 'users', verb: 'manage' },\n ] },\n { id: 'role-editor', name: 'editor', displayName: 'Editor', description: 'Manage documents across all types', isSystem: false,\n grants: [\n { resource: 'portal', verb: 'access' },\n { resource: 'documents', verb: 'manage' },\n { resource: 'document_type:*', verb: 'read' },\n { resource: 'document_type:*', verb: 'create' },\n { resource: 'document_type:*', verb: 'update' },\n { resource: 'document_type:*', verb: 'delete' },\n { resource: 'settings', verb: 'read' },\n ] },\n ]\n const verbs: Array<VerbData & { id: string }> = [\n { id: 'verb-access', name: 'access', description: 'Enter or use a portal/resource', isSystem: true, sortOrder: 5 },\n { id: 'verb-read', name: 'read', description: 'View a resource', isSystem: true, sortOrder: 10 },\n { id: 'verb-create', name: 'create', description: 'Create a resource', isSystem: true, sortOrder: 20 },\n { id: 'verb-update', name: 'update', description: 'Edit a resource', isSystem: true, sortOrder: 30 },\n { id: 'verb-delete', name: 'delete', description: 'Remove a resource', isSystem: true, sortOrder: 40 },\n { id: 'verb-manage', name: 'manage', description: 'Full control (implies all verbs)', isSystem: true, sortOrder: 50 },\n ]\n\n for (const r of roles) {\n if (await this.getDoc(T_ROLE, r.id)) continue\n const { id, ...data } = r\n await this.upsertDoc(T_ROLE, id, data, r.displayName)\n }\n for (const v of verbs) {\n if (await this.getDoc(T_VERB, v.id)) continue\n const { id, ...data } = v\n await this.upsertDoc(T_VERB, id, data, v.name)\n }\n }\n\n /** Assign a role to a user by role name (e.g. 'admin'), preserving existing roles. */\n async addUserRoleByName(userId: string, roleName: string): Promise<void> {\n const roles = await this.listDocs<RoleData>(T_ROLE)\n const role = roles.find((r) => r.data.name === roleName.toLowerCase())\n if (!role) return\n const ur = await this.getDoc<UserRolesData>(T_USER_ROLES, userId)\n const roleIds = new Set(ur?.data.roleIds ?? [])\n if (roleIds.has(role.slug)) return\n roleIds.add(role.slug)\n await this.setUserRoles(userId, [...roleIds])\n }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkK623Q6WD_cjs = require('./chunk-K623Q6WD.cjs');
|
|
4
4
|
|
|
5
5
|
// src/templates/filter-bar.template.ts
|
|
6
6
|
function renderFilterBar(data) {
|
|
@@ -68,11 +68,11 @@ function renderFilterBar(data) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// src/templates/index.ts
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
chunkK623Q6WD_cjs.init_admin_layout_catalyst_template();
|
|
72
|
+
chunkK623Q6WD_cjs.init_logo_template();
|
|
73
73
|
|
|
74
74
|
// src/templates/pages/admin-forms-docs.template.ts
|
|
75
|
-
|
|
75
|
+
chunkK623Q6WD_cjs.init_admin_layout_catalyst_template();
|
|
76
76
|
function renderFormsDocsPage(data) {
|
|
77
77
|
const pageContent = `
|
|
78
78
|
<style>
|
|
@@ -1353,11 +1353,11 @@ function MyForm() {
|
|
|
1353
1353
|
user: data.user,
|
|
1354
1354
|
version: data.version
|
|
1355
1355
|
};
|
|
1356
|
-
return
|
|
1356
|
+
return chunkK623Q6WD_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
1357
1357
|
}
|
|
1358
1358
|
|
|
1359
1359
|
// src/templates/pages/admin-forms-examples.template.ts
|
|
1360
|
-
|
|
1360
|
+
chunkK623Q6WD_cjs.init_admin_layout_catalyst_template();
|
|
1361
1361
|
function renderFormsExamplesPage(data) {
|
|
1362
1362
|
const pageContent = `
|
|
1363
1363
|
<style>
|
|
@@ -2460,11 +2460,11 @@ function renderFormsExamplesPage(data) {
|
|
|
2460
2460
|
user: data.user,
|
|
2461
2461
|
version: data.version
|
|
2462
2462
|
};
|
|
2463
|
-
return
|
|
2463
|
+
return chunkK623Q6WD_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
2464
2464
|
}
|
|
2465
2465
|
|
|
2466
2466
|
exports.renderFilterBar = renderFilterBar;
|
|
2467
2467
|
exports.renderFormsDocsPage = renderFormsDocsPage;
|
|
2468
2468
|
exports.renderFormsExamplesPage = renderFormsExamplesPage;
|
|
2469
|
-
//# sourceMappingURL=chunk-
|
|
2470
|
-
//# sourceMappingURL=chunk-
|
|
2469
|
+
//# sourceMappingURL=chunk-CRGUD4KC.cjs.map
|
|
2470
|
+
//# sourceMappingURL=chunk-CRGUD4KC.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/templates/filter-bar.template.ts","../src/templates/index.ts","../src/templates/pages/admin-forms-docs.template.ts","../src/templates/pages/admin-forms-examples.template.ts"],"names":["init_admin_layout_catalyst_template","init_logo_template","renderAdminLayoutCatalyst"],"mappings":";;;;;AA8BO,SAAS,gBAAgB,IAAA,EAA6B;AAC3D,EAAA,OAAO;AAAA;AAAA;AAAA,QAAA,EAGC,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,CAAA,MAAA,KAAU;AAAA;AAAA,gFAAA,EAE6C,OAAO,KAAK,CAAA;AAAA;AAAA,oBAAA,EAExE,OAAO,IAAI,CAAA;AAAA;AAAA;AAAA;AAAA,cAAA,EAIjB,MAAA,CAAO,OAAA,CAAQ,GAAA,CAAI,CAAA,MAAA,KAAU;AAAA,+BAAA,EACZ,OAAO,KAAK,CAAA,EAAA,EAAK,MAAA,CAAO,QAAA,GAAW,aAAa,EAAE,CAAA;AAAA,kBAAA,EAC/D,OAAO,KAAK;AAAA;AAAA,cAAA,CAEjB,CAAA,CAAE,IAAA,CAAK,EAAE,CAAC;AAAA;AAAA;AAAA,QAAA,CAGhB,CAAA,CAAE,IAAA,CAAK,EAAE,CAAC;;AAAA,QAAA,EAET,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA,GAAI;AAAA;AAAA,YAAA,EAEtC,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,CAAA,MAAA,KAAU;AAAA;AAAA;AAAA;AAAA,gBAAA,EAIvB,OAAO,OAAA,GAAU,CAAA,SAAA,EAAY,MAAA,CAAO,OAAO,MAAM,EAAE;AAAA,gBAAA,EACnD,OAAO,KAAA,GAAQ,CAAA,QAAA,EAAW,MAAA,CAAO,KAAK,MAAM,EAAE;AAAA,gBAAA,EAC9C,OAAO,QAAA,GAAW,CAAA,WAAA,EAAc,MAAA,CAAO,QAAQ,MAAM,EAAE;AAAA;AAAA,gBAAA,EAEvD,OAAO,KAAK;AAAA;AAAA,YAAA,CAEjB,CAAA,CAAE,IAAA,CAAK,EAAE,CAAC;AAAA;AAAA,QAAA,CAAA,GAEX,EAAE;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA,CAAA;AA2Bd;;;AC5DAA,qDAAA,EAAA;AAKAC,oCAAA,EAAA;;;ACrCAD,qDAAA,EAAA;AAWO,SAAS,oBAAoB,IAAA,EAAiC;AACnE,EAAA,MAAM,WAAA,GAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA,CAAA;AAyvCpB,EAAA,MAAM,UAAA,GAAsC;AAAA,IAC1C,KAAA,EAAO,uBAAA;AAAA,IACP,SAAA,EAAW,uBAAA;AAAA,IACX,OAAA,EAAS,WAAA;AAAA,IACT,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,SAAS,IAAA,CAAK;AAAA,GAChB;AAEA,EAAA,OAAOE,4CAA0B,UAAU,CAAA;AAC7C;;;AC9wCAF,qDAAA,EAAA;AAWO,SAAS,wBAAwB,IAAA,EAAqC;AAC3E,EAAA,MAAM,WAAA,GAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA,CAAA;AAukCpB,EAAA,MAAM,UAAA,GAAsC;AAAA,IAC1C,KAAA,EAAO,gBAAA;AAAA,IACP,SAAA,EAAW,gBAAA;AAAA,IACX,OAAA,EAAS,WAAA;AAAA,IACT,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,SAAS,IAAA,CAAK;AAAA,GAChB;AAEA,EAAA,OAAOE,4CAA0B,UAAU,CAAA;AAC7C","file":"chunk-CRGUD4KC.cjs","sourcesContent":["export interface FilterOption {\n value: string\n label: string\n selected?: boolean\n color?: string\n}\n\nexport interface Filter {\n name: string\n label: string\n options: FilterOption[]\n}\n\nexport interface FilterBarData {\n filters: Filter[]\n actions?: Array<{\n label: string\n className?: string\n onclick?: string\n hxGet?: string\n hxTarget?: string\n }>\n bulkActions?: Array<{\n label: string\n value: string\n icon?: string\n className?: string\n }>\n}\n\nexport function renderFilterBar(data: FilterBarData): string {\n return `\n <div class=\"rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6 mb-6\">\n <form id=\"filter-form\" class=\"flex flex-wrap gap-4 items-center\">\n ${data.filters.map(filter => `\n <div class=\"flex items-center space-x-2\">\n <label class=\"text-sm font-medium text-zinc-500 dark:text-zinc-400\">${filter.label}:</label>\n <select\n name=\"${filter.name}\"\n class=\"rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 focus:ring-2 focus:ring-blue-600 dark:focus:ring-blue-500 focus:outline-none transition-colors\"\n onchange=\"updateFilters()\"\n >\n ${filter.options.map(option => `\n <option value=\"${option.value}\" ${option.selected ? 'selected' : ''}>\n ${option.label}\n </option>\n `).join('')}\n </select>\n </div>\n `).join('')}\n\n ${data.actions && data.actions.length > 0 ? `\n <div class=\"flex items-center space-x-2 ml-auto\">\n ${data.actions.map(action => `\n <button\n type=\"button\"\n 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 ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors\"\n ${action.onclick ? `onclick=\"${action.onclick}\"` : ''}\n ${action.hxGet ? `hx-get=\"${action.hxGet}\"` : ''}\n ${action.hxTarget ? `hx-target=\"${action.hxTarget}\"` : ''}\n >\n ${action.label}\n </button>\n `).join('')}\n </div>\n ` : ''}\n </form>\n\n <script>\n function updateFilters() {\n const form = document.getElementById('filter-form');\n const formData = new FormData(form);\n const params = new URLSearchParams(window.location.search);\n\n // Update params with form values\n for (const [key, value] of formData.entries()) {\n if (value) {\n params.set(key, value);\n } else {\n params.delete(key);\n }\n }\n\n // Reset to page 1 when filters change\n params.set('page', '1');\n\n // Update URL and reload\n window.location.href = window.location.pathname + '?' + params.toString();\n }\n </script>\n </div>\n `\n}","/**\n * Templates Module Exports\n *\n * Reusable HTML template components for SonicJS\n */\n\n// Form templates\nexport { renderForm, renderFormField } from './form.template'\nexport type { FormField, FormData } from './form.template'\n\n// Table templates\nexport { renderTable } from './table.template'\nexport type { TableColumn, TableData } from './table.template'\n\n// Pagination templates\nexport { renderPagination } from './pagination.template'\nexport type { PaginationData } from './pagination.template'\n\n// Alert templates\nexport { renderAlert } from './alert.template'\nexport type { AlertData } from './alert.template'\n\n// Confirmation dialog templates\nexport { renderConfirmationDialog, getConfirmationDialogScript } from './confirmation-dialog.template'\nexport type { ConfirmationDialogOptions } from './confirmation-dialog.template'\n\n// Filter bar templates\nexport { renderFilterBar } from './filter-bar.template'\nexport type { FilterBarData, Filter, FilterOption } from './filter-bar.template'\n\n// Layout templates\nexport { renderAdminLayout } from './layouts/admin-layout-v2.template'\nexport { renderAdminLayoutCatalyst } from './layouts/admin-layout-catalyst.template'\nexport type { AdminLayoutData } from './layouts/admin-layout-v2.template'\nexport type { AdminLayoutCatalystData } from './layouts/admin-layout-catalyst.template'\n\n// Component templates\nexport { renderLogo } from './components/logo.template'\n\n// Page templates - Admin\nexport { renderCheckboxPage } from './pages/admin-checkboxes.template'\nexport type { CheckboxPageData } from './pages/admin-checkboxes.template'\nexport { renderFormsDocsPage } from './pages/admin-forms-docs.template'\nexport type { FormsDocsPageData } from './pages/admin-forms-docs.template'\nexport { renderFormsExamplesPage } from './pages/admin-forms-examples.template'\nexport type { FormsExamplesPageData } from './pages/admin-forms-examples.template'","import { renderAdminLayoutCatalyst, AdminLayoutCatalystData } from '../layouts/admin-layout-catalyst.template'\n\nexport interface FormsDocsPageData {\n user?: {\n name: string\n email: string\n role: string\n }\n version?: string\n}\n\nexport function renderFormsDocsPage(data: FormsDocsPageData): string {\n const pageContent = `\n <style>\n /* Light theme matching examples page */\n .docs-container {\n display: flex;\n gap: 0;\n min-height: calc(100vh - 200px);\n background: #ffffff;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n }\n \n .docs-sidebar {\n width: 280px;\n background: #f8f9fa;\n border-right: 1px solid #e0e0e0;\n padding: 1.5rem 0;\n overflow-y: auto;\n }\n \n .docs-sidebar h3 {\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: #6b7280;\n padding: 0 1.5rem;\n margin-bottom: 0.75rem;\n }\n \n .docs-nav {\n list-style: none;\n padding: 0;\n margin: 0 0 2rem 0;\n }\n \n .docs-nav li {\n margin: 0;\n }\n \n .docs-nav a {\n display: block;\n padding: 0.75rem 1.5rem;\n color: #374151;\n text-decoration: none;\n font-size: 0.875rem;\n transition: all 0.2s;\n border-left: 3px solid transparent;\n }\n \n .docs-nav a:hover {\n background: #e9ecef;\n color: #1f2937;\n }\n \n .docs-nav a.active {\n background: #e3f2fd;\n color: #1976d2;\n border-left-color: #1976d2;\n font-weight: 500;\n }\n \n .docs-content {\n flex: 1;\n padding: 2rem;\n background: #ffffff;\n overflow-y: auto;\n }\n \n .doc-section {\n display: none;\n }\n \n .doc-section.active {\n display: block;\n }\n \n .doc-header {\n margin-bottom: 2rem;\n padding-bottom: 1rem;\n border-bottom: 2px solid #e0e0e0;\n }\n \n .doc-header h2 {\n font-size: 1.875rem;\n font-weight: 700;\n color: #1f2937;\n margin-bottom: 0.5rem;\n }\n \n .doc-header p {\n color: #6b7280;\n font-size: 1rem;\n }\n \n .field-example {\n background: #f8f9fa;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n padding: 1.5rem;\n margin-bottom: 1.5rem;\n }\n \n .field-example h3 {\n font-size: 1.125rem;\n font-weight: 600;\n color: #1f2937;\n margin-bottom: 0.75rem;\n }\n \n .field-example p {\n color: #6b7280;\n font-size: 0.875rem;\n margin-bottom: 1rem;\n }\n \n .code-block {\n background: #1e1e1e;\n color: #d4d4d4;\n padding: 1rem;\n border-radius: 6px;\n overflow-x: auto;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 0.875rem;\n line-height: 1.6;\n }\n \n .info-box {\n background: #e3f2fd;\n border: 1px solid #90caf9;\n border-radius: 8px;\n padding: 1rem;\n margin-bottom: 1.5rem;\n color: #1565c0;\n font-size: 0.875rem;\n }\n \n .info-box strong {\n font-weight: 600;\n }\n </style>\n\n <div class=\"mb-6\">\n <div class=\"flex items-center gap-3 mb-4\">\n <a href=\"/admin/forms\" class=\"inline-flex items-center text-sm text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-white transition-colors\">\n <svg class=\"w-4 h-4 mr-1\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"/>\n </svg>\n Back to Forms\n </a>\n </div>\n <h1 class=\"text-3xl font-bold text-zinc-950 dark:text-white\">Forms Quick Reference</h1>\n <p class=\"mt-2 text-zinc-600 dark:text-zinc-400\">Comprehensive guide to all Form.io field types and features</p>\n </div>\n\n <div class=\"docs-container\">\n <!-- Sidebar Navigation -->\n <aside class=\"docs-sidebar\">\n <h3>Quick Start</h3>\n <ul class=\"docs-nav\">\n <li><a href=\"#overview\" class=\"doc-link active\">Overview</a></li>\n <li><a href=\"#getting-started\" class=\"doc-link\">Getting Started</a></li>\n </ul>\n \n <h3>Basic Fields</h3>\n <ul class=\"docs-nav\">\n <li><a href=\"#textfield\" class=\"doc-link\">Text Field</a></li>\n <li><a href=\"#textarea\" class=\"doc-link\">Text Area</a></li>\n <li><a href=\"#number\" class=\"doc-link\">Number</a></li>\n <li><a href=\"#password\" class=\"doc-link\">Password</a></li>\n <li><a href=\"#email\" class=\"doc-link\">Email</a></li>\n <li><a href=\"#url\" class=\"doc-link\">URL</a></li>\n <li><a href=\"#phonenumber\" class=\"doc-link\">Phone Number</a></li>\n </ul>\n \n <h3>Date & Time</h3>\n <ul class=\"docs-nav\">\n <li><a href=\"#datetime\" class=\"doc-link\">Date/Time</a></li>\n <li><a href=\"#day\" class=\"doc-link\">Day</a></li>\n <li><a href=\"#time\" class=\"doc-link\">Time</a></li>\n </ul>\n \n <h3>Selection Fields</h3>\n <ul class=\"docs-nav\">\n <li><a href=\"#select\" class=\"doc-link\">Select Dropdown</a></li>\n <li><a href=\"#selectboxes\" class=\"doc-link\">Select Boxes</a></li>\n <li><a href=\"#radio\" class=\"doc-link\">Radio</a></li>\n <li><a href=\"#checkbox\" class=\"doc-link\">Checkbox</a></li>\n </ul>\n \n <h3>Advanced Fields</h3>\n <ul class=\"docs-nav\">\n <li><a href=\"#currency\" class=\"doc-link\">Currency</a></li>\n <li><a href=\"#tags\" class=\"doc-link\">Tags</a></li>\n <li><a href=\"#survey\" class=\"doc-link\">Survey</a></li>\n <li><a href=\"#signature\" class=\"doc-link\">Signature</a></li>\n <li><a href=\"#file\" class=\"doc-link\">File Upload</a></li>\n <li><a href=\"#address\" class=\"doc-link\">Address</a></li>\n <li><a href=\"#turnstile\" class=\"doc-link\">🛡️ Turnstile</a></li>\n </ul>\n \n <h3>Layout Components</h3>\n <ul class=\"docs-nav\">\n <li><a href=\"#panel\" class=\"doc-link\">Panel</a></li>\n <li><a href=\"#columns\" class=\"doc-link\">Columns</a></li>\n <li><a href=\"#tabs\" class=\"doc-link\">Tabs</a></li>\n <li><a href=\"#table\" class=\"doc-link\">Table</a></li>\n <li><a href=\"#fieldset\" class=\"doc-link\">Fieldset</a></li>\n </ul>\n \n <h3>Data Components</h3>\n <ul class=\"docs-nav\">\n <li><a href=\"#datagrid\" class=\"doc-link\">Data Grid</a></li>\n <li><a href=\"#editgrid\" class=\"doc-link\">Edit Grid</a></li>\n </ul>\n \n <h3>Guides</h3>\n <ul class=\"docs-nav\">\n <li><a href=\"#wizard\" class=\"doc-link\">Multi-Page Wizards</a></li>\n <li><a href=\"#embedding\" class=\"doc-link\">Embedding Forms</a></li>\n <li><a href=\"#validation\" class=\"doc-link\">Validation</a></li>\n <li><a href=\"#conditional\" class=\"doc-link\">Conditional Logic</a></li>\n </ul>\n </aside>\n\n <!-- Main Content Area -->\n <main class=\"docs-content\">\n \n <!-- Overview Section -->\n <section id=\"overview\" class=\"doc-section active\">\n <div class=\"doc-header\">\n <h2>📚 Overview</h2>\n <p>Complete reference for SonicJS Forms powered by Form.io</p>\n </div>\n \n <div class=\"info-box\">\n <strong>💡 New to SonicJS Forms?</strong> Start with \"Getting Started\" in the sidebar, then explore the field types you need.\n </div>\n \n <h3 style=\"font-size: 1.25rem; font-weight: 600; margin-bottom: 1rem; color: #1f2937;\">Key Features</h3>\n <div style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem;\">\n <div style=\"padding: 1rem; background: #f0fdf4; border-radius: 8px;\">\n <strong style=\"color: #16a34a;\">✓ Visual Builder</strong>\n <p style=\"font-size: 0.875rem; color: #15803d; margin-top: 0.25rem;\">Drag-and-drop interface</p>\n </div>\n <div style=\"padding: 1rem; background: #f0fdf4; border-radius: 8px;\">\n <strong style=\"color: #16a34a;\">✓ 40+ Field Types</strong>\n <p style=\"font-size: 0.875rem; color: #15803d; margin-top: 0.25rem;\">Text, date, file, signature, etc.</p>\n </div>\n <div style=\"padding: 1rem; background: #f0fdf4; border-radius: 8px;\">\n <strong style=\"color: #16a34a;\">✓ Multi-Page Wizards</strong>\n <p style=\"font-size: 0.875rem; color: #15803d; margin-top: 0.25rem;\">Step-by-step forms</p>\n </div>\n <div style=\"padding: 1rem; background: #f0fdf4; border-radius: 8px;\">\n <strong style=\"color: #16a34a;\">✓ Headless API</strong>\n <p style=\"font-size: 0.875rem; color: #15803d; margin-top: 0.25rem;\">JSON schema & REST API</p>\n </div>\n <div style=\"padding: 1rem; background: #f0fdf4; border-radius: 8px;\">\n <strong style=\"color: #16a34a;\">✓ File Uploads</strong>\n <p style=\"font-size: 0.875rem; color: #15803d; margin-top: 0.25rem;\">Cloudflare R2 storage</p>\n </div>\n <div style=\"padding: 1rem; background: #f0fdf4; border-radius: 8px;\">\n <strong style=\"color: #16a34a;\">✓ 100% Open Source</strong>\n <p style=\"font-size: 0.875rem; color: #15803d; margin-top: 0.25rem;\">No vendor lock-in</p>\n </div>\n </div>\n \n <h3 style=\"font-size: 1.25rem; font-weight: 600; margin-bottom: 1rem; color: #1f2937;\">Quick Links</h3>\n <div style=\"display: flex; gap: 1rem; flex-wrap: wrap;\">\n <a href=\"/admin/forms/examples\" style=\"display: inline-flex; align-items: center; padding: 0.625rem 1.25rem; background: #3b82f6; color: white; border-radius: 6px; text-decoration: none; font-size: 0.875rem; font-weight: 500;\">\n <svg style=\"width: 1rem; height: 1rem; margin-right: 0.5rem;\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\"/>\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z\"/>\n </svg>\n View Examples\n </a>\n <a href=\"/admin/forms/new\" style=\"display: inline-flex; align-items: center; padding: 0.625rem 1.25rem; background: #10b981; color: white; border-radius: 6px; text-decoration: none; font-size: 0.875rem; font-weight: 500;\">\n <svg style=\"width: 1rem; height: 1rem; margin-right: 0.5rem;\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fill-rule=\"evenodd\" d=\"M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z\" clip-rule=\"evenodd\"/>\n </svg>\n Create New Form\n </a>\n <a href=\"/admin/forms\" style=\"display: inline-flex; align-items: center; padding: 0.625rem 1.25rem; background: #6b7280; color: white; border-radius: 6px; text-decoration: none; font-size: 0.875rem; font-weight: 500;\">\n <svg style=\"width: 1rem; height: 1rem; margin-right: 0.5rem;\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"/>\n </svg>\n View All Forms\n </a>\n </div>\n </section>\n\n <!-- Getting Started Section -->\n <section id=\"getting-started\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🚀 Getting Started</h2>\n <p>Create your first form in 3 easy steps</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Step 1: Create a Form</h3>\n <p>Navigate to <code>/admin/forms</code> and click \"Create Form\"</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Step 2: Build Your Form</h3>\n <p>Drag and drop field types from the sidebar to build your form visually</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Step 3: Publish & Embed</h3>\n <p>Save your form and access it via:</p>\n <ul style=\"margin-left: 1.5rem; margin-top: 0.5rem; list-style-type: disc;\">\n <li><code>/forms/your-form-name</code> - Public form page</li>\n <li><code>/forms/your-form-name/schema</code> - JSON schema API</li>\n <li><code>/api/forms/:id/submit</code> - Submission endpoint</li>\n </ul>\n </div>\n </section>\n\n <!-- Text Field -->\n <section id=\"textfield\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📝 Text Field</h2>\n <p>Single-line text input for short text values</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Most common field type for names, titles, and short text</p>\n <pre class=\"code-block\">{\n \"type\": \"textfield\",\n \"key\": \"firstName\",\n \"label\": \"First Name\",\n \"placeholder\": \"Enter your first name\",\n \"validate\": {\n \"required\": true,\n \"minLength\": 2,\n \"maxLength\": 50\n }\n}</pre>\n </div>\n \n <div class=\"info-box\">\n <strong>💡 Pro Tip:</strong> Use <code>inputMask</code> for formatted input like SSN or custom patterns.\n </div>\n </section>\n\n <!-- Text Area -->\n <section id=\"textarea\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📄 Text Area</h2>\n <p>Multi-line text input for longer text content</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Perfect for comments, descriptions, and multi-line text</p>\n <pre class=\"code-block\">{\n \"type\": \"textarea\",\n \"key\": \"comments\",\n \"label\": \"Additional Comments\",\n \"placeholder\": \"Enter your comments here...\",\n \"rows\": 5,\n \"validate\": {\n \"required\": false,\n \"maxLength\": 1000\n }\n}</pre>\n </div>\n </section>\n\n <!-- Number Field -->\n <section id=\"number\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🔢 Number</h2>\n <p>Numeric input with validation</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>For ages, quantities, scores, and any numeric value</p>\n <pre class=\"code-block\">{\n \"type\": \"number\",\n \"key\": \"age\",\n \"label\": \"Age\",\n \"placeholder\": \"18\",\n \"validate\": {\n \"required\": true,\n \"min\": 18,\n \"max\": 120\n }\n}</pre>\n </div>\n </section>\n\n <!-- Password Field -->\n <section id=\"password\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🔒 Password</h2>\n <p>Masked text input for sensitive data</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Automatically masks input for security</p>\n <pre class=\"code-block\">{\n \"type\": \"password\",\n \"key\": \"password\",\n \"label\": \"Password\",\n \"placeholder\": \"Enter password\",\n \"validate\": {\n \"required\": true,\n \"minLength\": 8,\n \"pattern\": \"^(?=.*[A-Za-z])(?=.*\\\\d)[A-Za-z\\\\d]{8,}$\"\n }\n}</pre>\n </div>\n </section>\n\n <!-- Email Field -->\n <section id=\"email\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📧 Email</h2>\n <p>Email input with automatic validation</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Validates email format automatically</p>\n <pre class=\"code-block\">{\n \"type\": \"email\",\n \"key\": \"email\",\n \"label\": \"Email Address\",\n \"placeholder\": \"you@example.com\",\n \"validate\": {\n \"required\": true\n }\n}</pre>\n </div>\n </section>\n\n <!-- URL Field -->\n <section id=\"url\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🌐 URL</h2>\n <p>URL input with validation</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Validates URL format (http/https)</p>\n <pre class=\"code-block\">{\n \"type\": \"url\",\n \"key\": \"website\",\n \"label\": \"Website\",\n \"placeholder\": \"https://example.com\",\n \"validate\": {\n \"required\": false\n }\n}</pre>\n </div>\n </section>\n\n <!-- Phone Number Field -->\n <section id=\"phonenumber\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📞 Phone Number</h2>\n <p>Phone number input with formatting</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Automatically formats phone numbers</p>\n <pre class=\"code-block\">{\n \"type\": \"phoneNumber\",\n \"key\": \"phone\",\n \"label\": \"Phone Number\",\n \"placeholder\": \"(555) 555-5555\",\n \"validate\": {\n \"required\": true\n }\n}</pre>\n </div>\n </section>\n\n <!-- DateTime Field -->\n <section id=\"datetime\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📅 Date/Time</h2>\n <p>Date and time picker</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Interactive date/time picker with format control</p>\n <pre class=\"code-block\">{\n \"type\": \"datetime\",\n \"key\": \"appointmentDateTime\",\n \"label\": \"Appointment Date & Time\",\n \"format\": \"yyyy-MM-dd hh:mm a\",\n \"enableTime\": true,\n \"validate\": {\n \"required\": true\n }\n}</pre>\n </div>\n </section>\n\n <!-- Day Field -->\n <section id=\"day\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📆 Day</h2>\n <p>Day/Month/Year selector</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Separate dropdowns for day, month, and year</p>\n <pre class=\"code-block\">{\n \"type\": \"day\",\n \"key\": \"birthDate\",\n \"label\": \"Date of Birth\",\n \"fields\": {\n \"day\": { \"placeholder\": \"Day\" },\n \"month\": { \"placeholder\": \"Month\" },\n \"year\": { \"placeholder\": \"Year\" }\n },\n \"validate\": {\n \"required\": true\n }\n}</pre>\n </div>\n </section>\n\n <!-- Time Field -->\n <section id=\"time\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🕐 Time</h2>\n <p>Time picker (hours and minutes)</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Select time in HH:MM format</p>\n <pre class=\"code-block\">{\n \"type\": \"time\",\n \"key\": \"preferredTime\",\n \"label\": \"Preferred Contact Time\",\n \"validate\": {\n \"required\": false\n }\n}</pre>\n </div>\n </section>\n\n <!-- Select Field -->\n <section id=\"select\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🔽 Select Dropdown</h2>\n <p>Single selection dropdown</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Choose one option from a list</p>\n <pre class=\"code-block\">{\n \"type\": \"select\",\n \"key\": \"country\",\n \"label\": \"Country\",\n \"placeholder\": \"Select your country\",\n \"data\": {\n \"values\": [\n { \"label\": \"United States\", \"value\": \"us\" },\n { \"label\": \"Canada\", \"value\": \"ca\" },\n { \"label\": \"United Kingdom\", \"value\": \"uk\" },\n { \"label\": \"Australia\", \"value\": \"au\" }\n ]\n },\n \"validate\": {\n \"required\": true\n }\n}</pre>\n </div>\n </section>\n\n <!-- Select Boxes Field -->\n <section id=\"selectboxes\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>☑️ Select Boxes</h2>\n <p>Multiple checkbox selections</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Select multiple options with checkboxes</p>\n <pre class=\"code-block\">{\n \"type\": \"selectboxes\",\n \"key\": \"interests\",\n \"label\": \"Areas of Interest\",\n \"values\": [\n { \"label\": \"Sports\", \"value\": \"sports\" },\n { \"label\": \"Music\", \"value\": \"music\" },\n { \"label\": \"Technology\", \"value\": \"tech\" },\n { \"label\": \"Travel\", \"value\": \"travel\" }\n ]\n}</pre>\n </div>\n </section>\n\n <!-- Radio Field -->\n <section id=\"radio\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🔘 Radio</h2>\n <p>Single selection with radio buttons</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Choose one option from radio buttons</p>\n <pre class=\"code-block\">{\n \"type\": \"radio\",\n \"key\": \"gender\",\n \"label\": \"Gender\",\n \"values\": [\n { \"label\": \"Male\", \"value\": \"male\" },\n { \"label\": \"Female\", \"value\": \"female\" },\n { \"label\": \"Non-binary\", \"value\": \"nonbinary\" },\n { \"label\": \"Prefer not to say\", \"value\": \"other\" }\n ],\n \"validate\": {\n \"required\": true\n }\n}</pre>\n </div>\n </section>\n\n <!-- Checkbox Field -->\n <section id=\"checkbox\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>✅ Checkbox</h2>\n <p>Single checkbox for boolean values</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>For agreements, subscriptions, and yes/no options</p>\n <pre class=\"code-block\">{\n \"type\": \"checkbox\",\n \"key\": \"newsletter\",\n \"label\": \"Subscribe to newsletter\",\n \"validate\": {\n \"required\": false\n }\n}\n\n// Required checkbox (terms agreement)\n{\n \"type\": \"checkbox\",\n \"key\": \"terms\",\n \"label\": \"I agree to the terms and conditions\",\n \"validate\": {\n \"required\": true\n }\n}</pre>\n </div>\n </section>\n\n <!-- Currency Field -->\n <section id=\"currency\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>💰 Currency</h2>\n <p>Formatted currency input</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Automatically formats with currency symbol</p>\n <pre class=\"code-block\">{\n \"type\": \"currency\",\n \"key\": \"salary\",\n \"label\": \"Expected Salary\",\n \"currency\": \"USD\",\n \"placeholder\": \"$50,000\",\n \"validate\": {\n \"required\": true,\n \"min\": 0\n }\n}</pre>\n </div>\n </section>\n\n <!-- Tags Field -->\n <section id=\"tags\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🏷️ Tags</h2>\n <p>Multi-value tag input</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Type and press Enter to add tags</p>\n <pre class=\"code-block\">{\n \"type\": \"tags\",\n \"key\": \"skills\",\n \"label\": \"Skills\",\n \"placeholder\": \"Type and press Enter (e.g. JavaScript, Python)\",\n \"validate\": {\n \"required\": false\n }\n}</pre>\n </div>\n </section>\n\n <!-- Survey Field -->\n <section id=\"survey\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📊 Survey</h2>\n <p>Matrix-style rating questions</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Multiple questions with rating scale</p>\n <pre class=\"code-block\">{\n \"type\": \"survey\",\n \"key\": \"satisfaction\",\n \"label\": \"Customer Satisfaction Survey\",\n \"questions\": [\n { \"label\": \"Product Quality\", \"value\": \"quality\" },\n { \"label\": \"Customer Service\", \"value\": \"service\" },\n { \"label\": \"Value for Money\", \"value\": \"value\" }\n ],\n \"values\": [\n { \"label\": \"Poor\", \"value\": \"1\" },\n { \"label\": \"Fair\", \"value\": \"2\" },\n { \"label\": \"Good\", \"value\": \"3\" },\n { \"label\": \"Excellent\", \"value\": \"4\" }\n ]\n}</pre>\n </div>\n </section>\n\n <!-- Signature Field -->\n <section id=\"signature\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>✍️ Signature</h2>\n <p>Digital signature pad</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Capture signatures with mouse or touch</p>\n <pre class=\"code-block\">{\n \"type\": \"signature\",\n \"key\": \"signature\",\n \"label\": \"Signature\",\n \"footer\": \"Sign above\",\n \"width\": \"100%\",\n \"height\": \"150px\",\n \"validate\": {\n \"required\": true\n }\n}</pre>\n </div>\n </section>\n\n <!-- File Upload Field -->\n <section id=\"file\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📁 File Upload</h2>\n <p>File upload with storage options</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Upload files to Cloudflare R2 or base64 encode</p>\n <pre class=\"code-block\">{\n \"type\": \"file\",\n \"key\": \"resume\",\n \"label\": \"Upload Resume\",\n \"storage\": \"r2\",\n \"filePattern\": \".pdf,.doc,.docx\",\n \"fileMaxSize\": \"5MB\",\n \"multiple\": false,\n \"validate\": {\n \"required\": true\n }\n}\n\n// Multiple files\n{\n \"type\": \"file\",\n \"key\": \"attachments\",\n \"label\": \"Attachments\",\n \"storage\": \"base64\",\n \"multiple\": true,\n \"fileMaxSize\": \"10MB\"\n}</pre>\n </div>\n \n <div class=\"info-box\">\n <strong>💡 Storage Options:</strong> Use <code>storage: \"r2\"</code> for Cloudflare R2 (recommended) or <code>storage: \"base64\"</code> to encode in submission data.\n </div>\n </section>\n\n <!-- Address Field -->\n <section id=\"address\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📍 Address</h2>\n <p>Address autocomplete with Google Maps</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Google Maps API-powered address autocomplete</p>\n <pre class=\"code-block\">{\n \"type\": \"address\",\n \"key\": \"address\",\n \"label\": \"Address\",\n \"provider\": \"google\",\n \"map\": {\n \"key\": \"YOUR_GOOGLE_MAPS_API_KEY\"\n },\n \"validate\": {\n \"required\": true\n }\n}</pre>\n </div>\n \n <div class=\"info-box\">\n <strong>⚠️ API Key Required:</strong> Enable Google Maps Places API and Maps JavaScript API in Google Cloud Console.\n </div>\n </section>\n\n <!-- Turnstile Component -->\n <section id=\"turnstile\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🛡️ Turnstile</h2>\n <p>CAPTCHA-free bot protection by Cloudflare</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Add invisible bot protection to your forms</p>\n <pre class=\"code-block\">{\n \"type\": \"turnstile\",\n \"key\": \"turnstile\",\n \"label\": \"Turnstile Verification\",\n \"theme\": \"auto\",\n \"size\": \"normal\",\n \"appearance\": \"always\",\n \"persistent\": false,\n \"protected\": true\n}</pre>\n </div>\n \n <div class=\"field-example\">\n <h3>Configuration Options</h3>\n <pre class=\"code-block\">{\n \"type\": \"turnstile\",\n \"key\": \"turnstile\",\n \"label\": \"Security Check\",\n \"theme\": \"light\", // \"light\", \"dark\", \"auto\"\n \"size\": \"compact\", // \"normal\", \"compact\"\n \"appearance\": \"always\", // \"always\", \"execute\", \"interaction-only\"\n \"action\": \"submit-form\", // Optional: action name for analytics\n \"errorMessage\": \"Please complete the security verification\"\n}</pre>\n </div>\n \n <div class=\"info-box\">\n <strong>🔧 Setup Required:</strong> Enable the Turnstile plugin in Settings → Plugins and configure your site key and secret key from Cloudflare Dashboard.\n </div>\n \n <div class=\"info-box\">\n <strong>💡 Usage Tips:</strong>\n <ul style=\"margin: 10px 0 0 20px; padding: 0;\">\n <li><strong>Invisible Mode:</strong> Use <code>\"appearance\": \"interaction-only\"</code> for seamless UX</li>\n <li><strong>Dark Mode:</strong> Use <code>\"theme\": \"auto\"</code> to match user preferences</li>\n <li><strong>Compact Size:</strong> Use <code>\"size\": \"compact\"</code> for mobile forms</li>\n <li><strong>Backend Validation:</strong> Tokens are automatically validated server-side</li>\n </ul>\n </div>\n </section>\n\n <!-- Panel Component -->\n <section id=\"panel\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📦 Panel</h2>\n <p>Group fields in collapsible panels</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Used for wizards and grouping related fields</p>\n <pre class=\"code-block\">{\n \"type\": \"panel\",\n \"key\": \"personalInfo\",\n \"title\": \"Personal Information\",\n \"collapsible\": true,\n \"collapsed\": false,\n \"components\": [\n { \"type\": \"textfield\", \"key\": \"firstName\", \"label\": \"First Name\" },\n { \"type\": \"textfield\", \"key\": \"lastName\", \"label\": \"Last Name\" },\n { \"type\": \"email\", \"key\": \"email\", \"label\": \"Email\" }\n ]\n}</pre>\n </div>\n </section>\n\n <!-- Columns Component -->\n <section id=\"columns\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📊 Columns</h2>\n <p>Multi-column layout</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Create side-by-side fields (responsive)</p>\n <pre class=\"code-block\">{\n \"type\": \"columns\",\n \"columns\": [\n {\n \"components\": [\n { \"type\": \"textfield\", \"key\": \"firstName\", \"label\": \"First Name\" }\n ],\n \"width\": 6\n },\n {\n \"components\": [\n { \"type\": \"textfield\", \"key\": \"lastName\", \"label\": \"Last Name\" }\n ],\n \"width\": 6\n }\n ]\n}</pre>\n </div>\n \n <div class=\"info-box\">\n <strong>💡 Width System:</strong> Width is based on 12-column grid. Use 6 for 50%, 4 for 33%, 3 for 25%, etc.\n </div>\n </section>\n\n <!-- Tabs Component -->\n <section id=\"tabs\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📑 Tabs</h2>\n <p>Organize fields in tabs</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Create tabbed interface for complex forms</p>\n <pre class=\"code-block\">{\n \"type\": \"tabs\",\n \"components\": [\n {\n \"label\": \"Personal Info\",\n \"key\": \"tab1\",\n \"components\": [...]\n },\n {\n \"label\": \"Contact Info\",\n \"key\": \"tab2\",\n \"components\": [...]\n }\n ]\n}</pre>\n </div>\n </section>\n\n <!-- Table Component -->\n <section id=\"table\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📋 Table</h2>\n <p>Table layout for forms</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Create table-based layouts</p>\n <pre class=\"code-block\">{\n \"type\": \"table\",\n \"numRows\": 3,\n \"numCols\": 2,\n \"rows\": [\n [\n { \"components\": [{ \"type\": \"textfield\", \"key\": \"cell1\" }] },\n { \"components\": [{ \"type\": \"textfield\", \"key\": \"cell2\" }] }\n ]\n ]\n}</pre>\n </div>\n </section>\n\n <!-- Fieldset Component -->\n <section id=\"fieldset\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>📦 Fieldset</h2>\n <p>Group fields with border and legend</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>HTML fieldset with legend label</p>\n <pre class=\"code-block\">{\n \"type\": \"fieldset\",\n \"legend\": \"Contact Information\",\n \"components\": [\n { \"type\": \"email\", \"key\": \"email\", \"label\": \"Email\" },\n { \"type\": \"phoneNumber\", \"key\": \"phone\", \"label\": \"Phone\" }\n ]\n}</pre>\n </div>\n </section>\n\n <!-- Data Grid Component -->\n <section id=\"datagrid\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🗃️ Data Grid</h2>\n <p>Repeatable row data entry</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Add/remove rows of structured data</p>\n <pre class=\"code-block\">{\n \"type\": \"datagrid\",\n \"key\": \"items\",\n \"label\": \"Items\",\n \"addAnother\": \"Add Item\",\n \"components\": [\n { \"type\": \"textfield\", \"key\": \"name\", \"label\": \"Item Name\" },\n { \"type\": \"number\", \"key\": \"quantity\", \"label\": \"Quantity\" },\n { \"type\": \"currency\", \"key\": \"price\", \"label\": \"Price\" }\n ]\n}</pre>\n </div>\n </section>\n\n <!-- Edit Grid Component -->\n <section id=\"editgrid\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>✏️ Edit Grid</h2>\n <p>Editable table with modal editing</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Basic Usage</h3>\n <p>Similar to Data Grid but with modal editing</p>\n <pre class=\"code-block\">{\n \"type\": \"editgrid\",\n \"key\": \"contacts\",\n \"label\": \"Contacts\",\n \"components\": [\n { \"type\": \"textfield\", \"key\": \"name\", \"label\": \"Name\" },\n { \"type\": \"email\", \"key\": \"email\", \"label\": \"Email\" },\n { \"type\": \"phoneNumber\", \"key\": \"phone\", \"label\": \"Phone\" }\n ]\n}</pre>\n </div>\n </section>\n\n <!-- Multi-Page Wizards Guide -->\n <section id=\"wizard\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🧙 Multi-Page Wizards</h2>\n <p>Create step-by-step forms with progress tracking</p>\n </div>\n \n <div class=\"info-box\">\n <strong>💡 How It Works:</strong> Set <code>display: \"wizard\"</code> and use Panel components for each step. Form.io automatically adds navigation buttons.\n </div>\n \n <div class=\"field-example\">\n <h3>Complete Wizard Example</h3>\n <pre class=\"code-block\">{\n \"display\": \"wizard\",\n \"components\": [\n {\n \"type\": \"panel\",\n \"key\": \"step1\",\n \"title\": \"Step 1: Personal Info\",\n \"components\": [\n { \"type\": \"textfield\", \"key\": \"firstName\", \"label\": \"First Name\" },\n { \"type\": \"textfield\", \"key\": \"lastName\", \"label\": \"Last Name\" }\n ]\n },\n {\n \"type\": \"panel\",\n \"key\": \"step2\",\n \"title\": \"Step 2: Contact Info\",\n \"components\": [\n { \"type\": \"email\", \"key\": \"email\", \"label\": \"Email\" },\n { \"type\": \"phoneNumber\", \"key\": \"phone\", \"label\": \"Phone\" }\n ]\n },\n {\n \"type\": \"panel\",\n \"key\": \"step3\",\n \"title\": \"Step 3: Review\",\n \"components\": [\n { \"type\": \"checkbox\", \"key\": \"terms\", \"label\": \"I agree\" }\n ]\n }\n ]\n}</pre>\n </div>\n </section>\n\n <!-- Embedding Forms Guide -->\n <section id=\"embedding\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🌐 Embedding Forms</h2>\n <p>Multiple ways to embed forms on your website</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Method 1: JavaScript (Recommended)</h3>\n <p>Load form schema via API and render with Form.io</p>\n <pre class=\"code-block\"><div id=\"form\"></div>\n<script src=\"https://cdn.form.io/formiojs/formio.full.min.js\"></script>\n<script>\n fetch('/forms/contact_form/schema')\n .then(r => r.json())\n .then(data => {\n Formio.createForm(\n document.getElementById('form'),\n data.schema\n ).then(form => {\n // Handle submission\n form.on('submitDone', (submission) => {\n console.log('Submitted:', submission);\n });\n });\n });\n</script></pre>\n </div>\n \n <div class=\"field-example\">\n <h3>Method 2: iFrame</h3>\n <p>Simple iframe embed (less flexible)</p>\n <pre class=\"code-block\"><iframe \n src=\"/forms/contact_form\"\n width=\"100%\"\n height=\"600\"\n frameborder=\"0\"\n></iframe></pre>\n </div>\n \n <div class=\"field-example\">\n <h3>Method 3: React Component</h3>\n <p>Use Form.io React library</p>\n <pre class=\"code-block\">import { Form } from '@formio/react';\n\nfunction MyForm() {\n const [schema, setSchema] = useState(null);\n \n useEffect(() => {\n fetch('/forms/contact_form/schema')\n .then(r => r.json())\n .then(data => setSchema(data.schema));\n }, []);\n \n const handleSubmit = (submission) => {\n fetch('/api/forms/123/submit', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(submission.data)\n });\n };\n \n return schema ? (\n <Form form={schema} onSubmit={handleSubmit} />\n ) : null;\n}</pre>\n </div>\n </section>\n\n <!-- Validation Guide -->\n <section id=\"validation\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>✅ Validation</h2>\n <p>Built-in validation rules</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Common Validation Rules</h3>\n <pre class=\"code-block\">{\n \"validate\": {\n \"required\": true, // Field must have value\n \"minLength\": 2, // Minimum characters\n \"maxLength\": 50, // Maximum characters\n \"min\": 0, // Minimum number value\n \"max\": 100, // Maximum number value\n \"pattern\": \"^[A-Za-z]+$\", // Regular expression\n \"custom\": \"valid = (input === 'yes');\" // Custom validation\n }\n}</pre>\n </div>\n \n <div class=\"field-example\">\n <h3>Custom Error Messages</h3>\n <pre class=\"code-block\">{\n \"validate\": {\n \"required\": true,\n \"customMessage\": \"Please provide your full name\"\n }\n}</pre>\n </div>\n </section>\n\n <!-- Conditional Logic Guide -->\n <section id=\"conditional\" class=\"doc-section\">\n <div class=\"doc-header\">\n <h2>🔀 Conditional Logic</h2>\n <p>Show/hide fields based on conditions</p>\n </div>\n \n <div class=\"field-example\">\n <h3>Simple Conditional</h3>\n <p>Show field only when condition is met</p>\n <pre class=\"code-block\">{\n \"type\": \"textfield\",\n \"key\": \"companyName\",\n \"label\": \"Company Name\",\n \"conditional\": {\n \"show\": true,\n \"when\": \"hasCompany\",\n \"eq\": true\n }\n}</pre>\n </div>\n \n <div class=\"field-example\">\n <h3>Advanced Conditional (JavaScript)</h3>\n <p>Complex conditions using custom JavaScript</p>\n <pre class=\"code-block\">{\n \"type\": \"textfield\",\n \"key\": \"discount\",\n \"label\": \"Discount Code\",\n \"customConditional\": \"show = (data.total > 100 && data.memberType === 'premium');\"\n}</pre>\n </div>\n </section>\n\n </main>\n </div>\n\n <script>\n // Navigation\n document.querySelectorAll('.doc-link').forEach(link => {\n link.addEventListener('click', function(e) {\n e.preventDefault();\n const targetId = this.getAttribute('href').substring(1);\n \n // Update active link\n document.querySelectorAll('.doc-link').forEach(l => l.classList.remove('active'));\n this.classList.add('active');\n \n // Update active section\n document.querySelectorAll('.doc-section').forEach(s => s.classList.remove('active'));\n document.getElementById(targetId).classList.add('active');\n \n // Scroll to top\n document.querySelector('.docs-content').scrollTop = 0;\n });\n });\n </script>\n `\n\n const layoutData: AdminLayoutCatalystData = {\n title: 'Forms Quick Reference',\n pageTitle: 'Forms Quick Reference',\n content: pageContent,\n user: data.user,\n version: data.version\n }\n\n return renderAdminLayoutCatalyst(layoutData)\n}\n","import { renderAdminLayoutCatalyst, AdminLayoutCatalystData } from '../layouts/admin-layout-catalyst.template'\n\nexport interface FormsExamplesPageData {\n user?: {\n name: string\n email: string\n role: string\n }\n version?: string\n}\n\nexport function renderFormsExamplesPage(data: FormsExamplesPageData): string {\n const pageContent = `\n <style>\n /* Light theme for examples page */\n .examples-container {\n display: flex;\n gap: 0;\n min-height: calc(100vh - 200px);\n background: #ffffff;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n }\n \n .examples-sidebar {\n width: 280px;\n background: #f8f9fa;\n border-right: 1px solid #e0e0e0;\n padding: 1.5rem 0;\n overflow-y: auto;\n }\n \n .examples-sidebar h3 {\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: #6b7280;\n padding: 0 1.5rem;\n margin-bottom: 0.75rem;\n }\n \n .examples-nav {\n list-style: none;\n padding: 0;\n margin: 0 0 2rem 0;\n }\n \n .examples-nav li {\n margin: 0;\n }\n \n .examples-nav a {\n display: block;\n padding: 0.75rem 1.5rem;\n color: #374151;\n text-decoration: none;\n font-size: 0.875rem;\n transition: all 0.2s;\n border-left: 3px solid transparent;\n }\n \n .examples-nav a:hover {\n background: #e9ecef;\n color: #1f2937;\n }\n \n .examples-nav a.active {\n background: #e3f2fd;\n color: #1976d2;\n border-left-color: #1976d2;\n font-weight: 500;\n }\n \n .examples-content {\n flex: 1;\n padding: 2rem;\n background: #ffffff;\n overflow-y: auto;\n }\n \n .example-section {\n display: none;\n }\n \n .example-section.active {\n display: block;\n }\n \n .example-header {\n margin-bottom: 2rem;\n padding-bottom: 1rem;\n border-bottom: 2px solid #e0e0e0;\n }\n \n .example-header h2 {\n font-size: 1.875rem;\n font-weight: 700;\n color: #1f2937;\n margin-bottom: 0.5rem;\n }\n \n .example-header p {\n color: #6b7280;\n font-size: 1rem;\n }\n \n .example-demo {\n background: #f8f9fa;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n padding: 2rem;\n margin-bottom: 2rem;\n }\n \n .example-code {\n background: #1e1e1e;\n color: #d4d4d4;\n padding: 1.5rem;\n border-radius: 8px;\n overflow-x: auto;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 0.875rem;\n line-height: 1.6;\n }\n \n .code-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 1rem;\n }\n \n .code-header h3 {\n font-size: 0.875rem;\n font-weight: 600;\n color: #374151;\n }\n \n .copy-btn {\n padding: 0.375rem 0.75rem;\n font-size: 0.75rem;\n background: #f3f4f6;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.2s;\n }\n \n .copy-btn:hover {\n background: #e5e7eb;\n }\n \n /* Form.io overrides for lighter theme */\n .formio-component {\n margin-bottom: 1.25rem;\n }\n \n .formio-component label {\n color: #374151;\n font-weight: 500;\n margin-bottom: 0.5rem;\n }\n \n .formio-component input,\n .formio-component textarea,\n .formio-component select {\n border: 1px solid #d1d5db;\n border-radius: 6px;\n padding: 0.625rem 0.875rem;\n }\n \n .formio-component input:focus,\n .formio-component textarea:focus,\n .formio-component select:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n }\n \n .btn-primary {\n background: #3b82f6 !important;\n border-color: #3b82f6 !important;\n padding: 0.625rem 1.25rem !important;\n border-radius: 6px !important;\n }\n \n .btn-primary:hover {\n background: #2563eb !important;\n border-color: #2563eb !important;\n }\n \n .alert-success {\n background: #10b981 !important;\n color: white !important;\n border: none !important;\n border-radius: 6px !important;\n }\n </style>\n\n <div class=\"mb-6\">\n <div class=\"flex items-center gap-3 mb-4\">\n <a href=\"/admin/forms\" class=\"inline-flex items-center text-sm text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-white transition-colors\">\n <svg class=\"w-4 h-4 mr-1\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"/>\n </svg>\n Back to Forms\n </a>\n </div>\n <h1 class=\"text-3xl font-bold text-zinc-950 dark:text-white\">Form Examples</h1>\n <p class=\"mt-2 text-zinc-600 dark:text-zinc-400\">Interactive examples showcasing Form.io capabilities</p>\n </div>\n\n <div class=\"examples-container\">\n <!-- Sidebar Navigation -->\n <aside class=\"examples-sidebar\">\n <h3>Getting Started</h3>\n <ul class=\"examples-nav\">\n <li><a href=\"#kitchen-sink\" class=\"example-link active\">Kitchen Sink</a></li>\n <li><a href=\"#simple-contact\" class=\"example-link\">Simple Contact Form</a></li>\n <li><a href=\"#thank-you\" class=\"example-link\">Thank You Page</a></li>\n </ul>\n \n <h3>Advanced Forms</h3>\n <ul class=\"examples-nav\">\n <li><a href=\"#wizard-form\" class=\"example-link\">Multi-Page Wizard</a></li>\n <li><a href=\"#conditional-logic\" class=\"example-link\">Conditional Logic</a></li>\n <li><a href=\"#file-upload\" class=\"example-link\">File Upload</a></li>\n </ul>\n \n <h3>Components</h3>\n <ul class=\"examples-nav\">\n <li><a href=\"#address-maps\" class=\"example-link\">Address with Maps</a></li>\n <li><a href=\"#signature\" class=\"example-link\">Signature Pad</a></li>\n <li><a href=\"#data-grid\" class=\"example-link\">Data Grid</a></li>\n <li><a href=\"#turnstile-protection\" class=\"example-link\">🛡️ Turnstile Protection</a></li>\n </ul>\n </aside>\n\n <!-- Main Content Area -->\n <main class=\"examples-content\">\n \n <!-- Kitchen Sink Example -->\n <section id=\"kitchen-sink\" class=\"example-section active\">\n <div class=\"example-header\">\n <h2>🍳 Kitchen Sink</h2>\n <p>A comprehensive form showcasing all major field types and configurations.</p>\n </div>\n \n <div class=\"example-demo\">\n <div id=\"form-kitchen-sink\"></div>\n </div>\n \n <div class=\"code-header\">\n <h3>Form Schema (JSON)</h3>\n <button class=\"copy-btn\" onclick=\"copyCode('kitchen-sink-code')\">Copy Code</button>\n </div>\n <pre class=\"example-code\" id=\"kitchen-sink-code\">{\n \"display\": \"form\",\n \"components\": [\n // Basic Text Fields\n { \"type\": \"textfield\", \"key\": \"firstName\", \"label\": \"First Name\" },\n { \"type\": \"email\", \"key\": \"email\", \"label\": \"Email\" },\n { \"type\": \"phoneNumber\", \"key\": \"phone\", \"label\": \"Phone\" },\n { \"type\": \"password\", \"key\": \"password\", \"label\": \"Password\" },\n { \"type\": \"url\", \"key\": \"website\", \"label\": \"Website\" },\n { \"type\": \"textarea\", \"key\": \"bio\", \"label\": \"Biography\" },\n \n // Date & Time\n { \"type\": \"datetime\", \"key\": \"appointmentDateTime\", \"label\": \"Appointment\" },\n { \"type\": \"day\", \"key\": \"birthDate\", \"label\": \"Birth Date\" },\n { \"type\": \"time\", \"key\": \"preferredTime\", \"label\": \"Time\" },\n \n // Selections\n { \"type\": \"select\", \"key\": \"country\", \"label\": \"Country\", \n \"data\": { \"values\": [{ \"label\": \"USA\", \"value\": \"us\" }] }},\n { \"type\": \"selectboxes\", \"key\": \"interests\", \"label\": \"Interests\",\n \"values\": [{ \"label\": \"Sports\", \"value\": \"sports\" }] },\n { \"type\": \"radio\", \"key\": \"gender\", \"label\": \"Gender\",\n \"values\": [{ \"label\": \"Male\", \"value\": \"male\" }] },\n { \"type\": \"checkbox\", \"key\": \"newsletter\", \"label\": \"Newsletter\" },\n \n // Advanced\n { \"type\": \"currency\", \"key\": \"salary\", \"label\": \"Salary\" },\n { \"type\": \"tags\", \"key\": \"skills\", \"label\": \"Skills\" },\n { \"type\": \"survey\", \"key\": \"satisfaction\", \"label\": \"Satisfaction\" },\n { \"type\": \"signature\", \"key\": \"signature\", \"label\": \"Signature\" },\n { \"type\": \"file\", \"key\": \"resume\", \"label\": \"Resume\", \"storage\": \"base64\" }\n ]\n}</pre>\n </section>\n\n <!-- Simple Contact Form Example -->\n <section id=\"simple-contact\" class=\"example-section\">\n <div class=\"example-header\">\n <h2>📧 Simple Contact Form</h2>\n <p>A minimal contact form with validation.</p>\n </div>\n \n <div class=\"example-demo\">\n <div id=\"form-simple-contact\"></div>\n </div>\n \n <div class=\"code-header\">\n <h3>Form Schema (JSON)</h3>\n <button class=\"copy-btn\" onclick=\"copyCode('contact-code')\">Copy Code</button>\n </div>\n <pre class=\"example-code\" id=\"contact-code\">{\n \"display\": \"form\",\n \"components\": [\n {\n \"type\": \"textfield\",\n \"key\": \"name\",\n \"label\": \"Full Name\",\n \"validate\": { \"required\": true }\n },\n {\n \"type\": \"email\",\n \"key\": \"email\",\n \"label\": \"Email Address\",\n \"validate\": { \"required\": true }\n },\n {\n \"type\": \"textarea\",\n \"key\": \"message\",\n \"label\": \"Message\",\n \"rows\": 5,\n \"validate\": { \"required\": true }\n }\n ]\n}</pre>\n </section>\n\n <!-- Thank You Page Example -->\n <section id=\"thank-you\" class=\"example-section\">\n <div class=\"example-header\">\n <h2>🎉 Thank You Page</h2>\n <p>Handle form submission and redirect to a thank you message.</p>\n </div>\n \n <div class=\"example-demo\">\n <div id=\"form-thank-you\"></div>\n <div id=\"thank-you-message\" style=\"display: none; padding: 2rem; background: #10b981; color: white; border-radius: 8px; text-align: center;\">\n <h3 style=\"font-size: 1.5rem; margin-bottom: 0.5rem;\">✅ Thank You!</h3>\n <p>Your form has been submitted successfully.</p>\n </div>\n </div>\n \n <div class=\"code-header\">\n <h3>Form Schema (JSON)</h3>\n <button class=\"copy-btn\" onclick=\"copyCode('thankyou-schema-code')\">Copy Code</button>\n </div>\n <pre class=\"example-code\" id=\"thankyou-schema-code\">{\n \"display\": \"form\",\n \"components\": [\n { \"type\": \"textfield\", \"key\": \"firstName\", \"label\": \"First Name\", \n \"validate\": { \"required\": true }},\n { \"type\": \"textfield\", \"key\": \"lastName\", \"label\": \"Last Name\", \n \"validate\": { \"required\": true }},\n { \"type\": \"email\", \"key\": \"email\", \"label\": \"Email Address\", \n \"validate\": { \"required\": true }},\n { \"type\": \"phoneNumber\", \"key\": \"phone\", \"label\": \"Phone Number\" },\n { \"type\": \"textarea\", \"key\": \"message\", \"label\": \"Message\", \n \"validate\": { \"required\": true }},\n { \"type\": \"button\", \"action\": \"submit\", \"label\": \"Submit Form\" }\n ]\n}</pre>\n \n <div class=\"code-header\">\n <h3>JavaScript Code</h3>\n <button class=\"copy-btn\" onclick=\"copyCode('thankyou-code')\">Copy Code</button>\n </div>\n <pre class=\"example-code\" id=\"thankyou-code\">Formio.createForm(document.getElementById('formio'), formSchema)\n .then(function(form) {\n // Handle successful submission\n form.on('submitDone', function(submission) {\n console.log('Form submitted:', submission);\n \n // Hide form and show thank you message\n form.element.style.display = 'none';\n document.getElementById('thank-you-message').style.display = 'block';\n \n // Or redirect to another page:\n // window.location = '/thank-you';\n });\n });</pre>\n </section>\n\n <!-- Wizard Form Example -->\n <section id=\"wizard-form\" class=\"example-section\">\n <div class=\"example-header\">\n <h2>🧙 Multi-Page Wizard</h2>\n <p>Step-by-step form with multiple pages and progress indicator.</p>\n </div>\n \n <div class=\"example-demo\">\n <div id=\"form-wizard\"></div>\n </div>\n \n <div class=\"code-header\">\n <h3>Form Schema (JSON)</h3>\n <button class=\"copy-btn\" onclick=\"copyCode('wizard-code')\">Copy Code</button>\n </div>\n <pre class=\"example-code\" id=\"wizard-code\">{\n \"display\": \"wizard\",\n \"components\": [\n {\n \"type\": \"panel\",\n \"key\": \"step1PersonalInfo\",\n \"title\": \"Step 1: Personal Information\",\n \"components\": [\n { \"type\": \"textfield\", \"key\": \"firstName\", \"label\": \"First Name\" },\n { \"type\": \"textfield\", \"key\": \"lastName\", \"label\": \"Last Name\" },\n { \"type\": \"datetime\", \"key\": \"birthDate\", \"label\": \"Date of Birth\" },\n { \"type\": \"select\", \"key\": \"gender\", \"label\": \"Gender\" }\n ]\n },\n {\n \"type\": \"panel\",\n \"key\": \"step2ContactInfo\",\n \"title\": \"Step 2: Contact Information\",\n \"components\": [\n { \"type\": \"email\", \"key\": \"email\", \"label\": \"Email\" },\n { \"type\": \"phoneNumber\", \"key\": \"phone\", \"label\": \"Phone\" },\n { \"type\": \"textfield\", \"key\": \"address\", \"label\": \"Address\" },\n { \"type\": \"select\", \"key\": \"country\", \"label\": \"Country\" }\n ]\n },\n {\n \"type\": \"panel\",\n \"key\": \"step3Preferences\",\n \"title\": \"Step 3: Preferences & Review\",\n \"components\": [\n { \"type\": \"selectboxes\", \"key\": \"interests\", \"label\": \"Interests\" },\n { \"type\": \"radio\", \"key\": \"contactMethod\", \"label\": \"Contact Method\" },\n { \"type\": \"textarea\", \"key\": \"comments\", \"label\": \"Comments\" },\n { \"type\": \"checkbox\", \"key\": \"terms\", \"label\": \"I agree to terms\" }\n ]\n }\n ]\n}</pre>\n </section>\n\n <!-- More examples... -->\n <section id=\"conditional-logic\" class=\"example-section\">\n <div class=\"example-header\">\n <h2>🔀 Conditional Logic</h2>\n <p>Show/hide fields based on user input.</p>\n </div>\n <div class=\"example-demo\">\n <div id=\"form-conditional\"></div>\n </div>\n </section>\n\n <section id=\"file-upload\" class=\"example-section\">\n <div class=\"example-header\">\n <h2>📁 File Upload</h2>\n <p>Upload files to Cloudflare R2 storage.</p>\n </div>\n <div class=\"example-demo\">\n <div id=\"form-file-upload\"></div>\n </div>\n </section>\n\n <section id=\"address-maps\" class=\"example-section\">\n <div class=\"example-header\">\n <h2>📍 Address with Maps</h2>\n <p>Google Maps autocomplete for address input.</p>\n </div>\n <div class=\"example-demo\">\n <div id=\"form-address\"></div>\n </div>\n </section>\n\n <section id=\"signature\" class=\"example-section\">\n <div class=\"example-header\">\n <h2>✍️ Signature Pad</h2>\n <p>Capture digital signatures.</p>\n </div>\n <div class=\"example-demo\">\n <div id=\"form-signature\"></div>\n </div>\n </section>\n\n <section id=\"data-grid\" class=\"example-section\">\n <div class=\"example-header\">\n <h2>📊 Data Grid</h2>\n <p>Repeatable data entry with add/remove rows.</p>\n </div>\n <div class=\"example-demo\">\n <div id=\"form-data-grid\"></div>\n </div>\n </section>\n\n <section id=\"turnstile-protection\" class=\"example-section\">\n <div class=\"example-header\">\n <h2>🛡️ Turnstile Protection</h2>\n <p>CAPTCHA-free bot protection by Cloudflare - drag and drop from the Premium section in the form builder.</p>\n </div>\n \n <div class=\"info-box\" style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px;\">\n <h3 style=\"margin: 0 0 10px 0; font-size: 18px;\">✨ Key Features</h3>\n <ul style=\"margin: 0; padding-left: 20px;\">\n <li><strong>No CAPTCHA puzzles</strong> - Seamless user experience</li>\n <li><strong>Invisible protection</strong> - Works in the background</li>\n <li><strong>Auto-validated</strong> - Server-side token verification</li>\n <li><strong>Privacy-first</strong> - Cloudflare's secure infrastructure</li>\n </ul>\n </div>\n \n <div class=\"example-demo\">\n <div id=\"form-turnstile\"></div>\n </div>\n \n <div class=\"info-box\" style=\"margin-top: 20px;\">\n <strong>🔧 Setup Instructions:</strong>\n <ol style=\"margin: 10px 0 0 20px; padding: 0;\">\n <li>Go to <strong>Settings → Plugins</strong> and enable Turnstile plugin</li>\n <li>Get free API keys from <a href=\"https://dash.cloudflare.com/?to=/:account/turnstile\" target=\"_blank\" style=\"color: #3b82f6;\">Cloudflare Dashboard</a></li>\n <li>Configure site key and secret key in plugin settings</li>\n <li>Drag Turnstile component from <strong>Premium</strong> section in form builder</li>\n </ol>\n </div>\n \n <div class=\"info-box\" style=\"margin-top: 15px; background: #fef3c7; border: 1px solid #fbbf24;\">\n <strong>💡 Pro Tip:</strong> Use <code>\"appearance\": \"interaction-only\"</code> for invisible mode - the widget only appears when suspicious activity is detected!\n </div>\n </section>\n\n </main>\n </div>\n\n <!-- Load Form.io -->\n <script src=\"https://cdn.form.io/formiojs/formio.full.min.js\"></script>\n \n <!-- Register Turnstile Component -->\n <script>\n // Register custom Turnstile component (same as public forms)\n (function() {\n // Will register when Form.io loads\n function registerTurnstile() {\n if (!window.Formio || !window.Formio.Components) {\n return false;\n }\n\n const FieldComponent = Formio.Components.components.field;\n\n class TurnstileComponent extends FieldComponent {\n static schema(...extend) {\n return FieldComponent.schema({\n type: 'turnstile',\n label: 'Turnstile Verification',\n key: 'turnstile',\n input: true,\n persistent: false,\n protected: true\n }, ...extend);\n }\n\n render() {\n return super.render(\\`\n <div ref=\"turnstileContainer\" class=\"formio-component-turnstile\">\n <div ref=\"turnstileWidget\" style=\"margin: 15px 0; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px; text-align: center; color: white;\">\n <div style=\"font-size: 32px; margin-bottom: 10px;\">🛡️</div>\n <div style=\"font-weight: 600; font-size: 16px; margin-bottom: 5px;\">Turnstile Verification</div>\n <div style=\"font-size: 13px; opacity: 0.9;\">CAPTCHA-free bot protection by Cloudflare</div>\n <div style=\"font-size: 12px; margin-top: 10px; opacity: 0.8;\">Enable Turnstile plugin in Settings → Plugins to activate</div>\n </div>\n </div>\n \\`);\n }\n\n attach(element) {\n this.loadRefs(element, { turnstileContainer: 'single', turnstileWidget: 'single' });\n return super.attach(element);\n }\n }\n\n Formio.Components.addComponent('turnstile', TurnstileComponent);\n console.log('✅ Turnstile component registered on examples page');\n return true;\n }\n \n // Try to register immediately\n if (!registerTurnstile()) {\n // If Form.io not loaded yet, try again after a delay\n setTimeout(registerTurnstile, 100);\n }\n })();\n </script>\n \n <script>\n // Debug: Check if elements exist\n console.log('Script loaded');\n \n // Navigation function\n function setupNavigation() {\n console.log('Setting up navigation...');\n const links = document.querySelectorAll('.example-link');\n console.log('Found', links.length, 'navigation links');\n \n // Navigation\n links.forEach(link => {\n link.addEventListener('click', function(e) {\n e.preventDefault();\n const targetId = this.getAttribute('href').substring(1);\n console.log('Navigating to:', targetId);\n \n // Update active link\n document.querySelectorAll('.example-link').forEach(l => l.classList.remove('active'));\n this.classList.add('active');\n \n // Update active section\n document.querySelectorAll('.example-section').forEach(s => s.classList.remove('active'));\n const targetSection = document.getElementById(targetId);\n if (targetSection) {\n targetSection.classList.add('active');\n console.log('Activated section:', targetId);\n } else {\n console.error('Section not found:', targetId);\n }\n \n // Scroll to top\n const content = document.querySelector('.examples-content');\n if (content) {\n content.scrollTop = 0;\n }\n \n // Update URL hash\n window.location.hash = targetId;\n });\n });\n \n // Handle initial hash on page load\n function handleHash() {\n const hash = window.location.hash.substring(1);\n console.log('Handling hash:', hash);\n if (hash) {\n const link = document.querySelector('.example-link[href=\"#' + hash + '\"]');\n if (link) {\n link.click();\n }\n }\n }\n \n // Call on load and hash change\n handleHash();\n window.addEventListener('hashchange', handleHash);\n }\n\n // Copy code function\n window.copyCode = function(elementId) {\n const code = document.getElementById(elementId).textContent;\n navigator.clipboard.writeText(code).then(() => {\n const btn = event.target;\n const originalText = btn.textContent;\n btn.textContent = 'Copied!';\n setTimeout(() => btn.textContent = originalText, 2000);\n });\n };\n\n // Initialize forms\n function initForms() {\n const kitchenSinkSchema = {\n display: 'form',\n components: [\n { \n type: 'htmlelement', \n tag: 'h3', \n content: 'Basic Fields',\n className: 'mb-3 text-lg font-semibold'\n },\n { type: 'textfield', key: 'firstName', label: 'First Name', placeholder: 'Enter your first name', validate: { required: true } },\n { type: 'textfield', key: 'lastName', label: 'Last Name', placeholder: 'Enter your last name', validate: { required: true } },\n { type: 'email', key: 'email', label: 'Email Address', placeholder: 'you@example.com', validate: { required: true } },\n { type: 'phoneNumber', key: 'phone', label: 'Phone Number', placeholder: '(555) 555-5555' },\n { type: 'number', key: 'age', label: 'Age', placeholder: '18', validate: { min: 18, max: 120 } },\n { type: 'password', key: 'password', label: 'Password', placeholder: 'Enter password', validate: { required: true } },\n { type: 'url', key: 'website', label: 'Website', placeholder: 'https://example.com' },\n { type: 'textarea', key: 'bio', label: 'Biography', rows: 4, placeholder: 'Tell us about yourself' },\n \n { \n type: 'htmlelement', \n tag: 'h3', \n content: 'Date & Time Fields',\n className: 'mt-4 mb-3 text-lg font-semibold'\n },\n { type: 'datetime', key: 'appointmentDateTime', label: 'Appointment Date & Time', format: 'yyyy-MM-dd hh:mm a', enableTime: true },\n { type: 'day', key: 'birthDate', label: 'Birth Date (Day/Month/Year)' },\n { type: 'time', key: 'preferredTime', label: 'Preferred Contact Time' },\n \n { \n type: 'htmlelement', \n tag: 'h3', \n content: 'Selection Fields',\n className: 'mt-4 mb-3 text-lg font-semibold'\n },\n { \n type: 'select', \n key: 'country', \n label: 'Country', \n placeholder: 'Select your country',\n data: { \n values: [\n { label: 'United States', value: 'us' },\n { label: 'Canada', value: 'ca' },\n { label: 'United Kingdom', value: 'uk' },\n { label: 'Australia', value: 'au' },\n { label: 'Germany', value: 'de' },\n { label: 'France', value: 'fr' }\n ] \n }\n },\n { \n type: 'selectboxes', \n key: 'interests', \n label: 'Interests (Multiple Selection)', \n values: [\n { label: 'Sports', value: 'sports' },\n { label: 'Music', value: 'music' },\n { label: 'Technology', value: 'tech' },\n { label: 'Travel', value: 'travel' },\n { label: 'Reading', value: 'reading' }\n ]\n },\n { \n type: 'radio', \n key: 'gender', \n label: 'Gender', \n values: [\n { label: 'Male', value: 'male' },\n { label: 'Female', value: 'female' },\n { label: 'Non-binary', value: 'nonbinary' },\n { label: 'Prefer not to say', value: 'prefer_not_to_say' }\n ]\n },\n { type: 'checkbox', key: 'newsletter', label: 'Subscribe to newsletter' },\n { type: 'checkbox', key: 'terms', label: 'I agree to the terms and conditions', validate: { required: true } },\n \n { \n type: 'htmlelement', \n tag: 'h3', \n content: 'Advanced Fields',\n className: 'mt-4 mb-3 text-lg font-semibold'\n },\n { \n type: 'currency', \n key: 'salary', \n label: 'Expected Salary', \n currency: 'USD',\n placeholder: '$50,000'\n },\n { \n type: 'tags', \n key: 'skills', \n label: 'Skills (Type and press Enter)', \n placeholder: 'e.g. JavaScript, Python, React'\n },\n { \n type: 'survey', \n key: 'satisfaction', \n label: 'Satisfaction Survey',\n questions: [\n { label: 'Product Quality', value: 'quality' },\n { label: 'Customer Service', value: 'service' },\n { label: 'Value for Money', value: 'value' }\n ],\n values: [\n { label: 'Poor', value: '1' },\n { label: 'Fair', value: '2' },\n { label: 'Good', value: '3' },\n { label: 'Excellent', value: '4' }\n ]\n },\n { \n type: 'signature', \n key: 'signature', \n label: 'Signature', \n footer: 'Sign above',\n width: '100%',\n height: '150px'\n },\n { \n type: 'file', \n key: 'resume', \n label: 'Upload Resume (PDF, DOC)', \n storage: 'base64',\n filePattern: '.pdf,.doc,.docx',\n fileMaxSize: '5MB'\n },\n \n { type: 'button', action: 'submit', label: 'Submit Kitchen Sink Form', theme: 'primary', className: 'mt-4' }\n ]\n };\n Formio.createForm(document.getElementById('form-kitchen-sink'), kitchenSinkSchema);\n\n // Simple Contact\n const contactSchema = {\n display: 'form',\n components: [\n { type: 'textfield', key: 'name', label: 'Full Name', validate: { required: true } },\n { type: 'email', key: 'email', label: 'Email Address', validate: { required: true } },\n { type: 'textarea', key: 'message', label: 'Message', rows: 5, validate: { required: true } },\n { type: 'button', action: 'submit', label: 'Send Message', theme: 'primary' }\n ]\n };\n Formio.createForm(document.getElementById('form-simple-contact'), contactSchema);\n\n // Thank You Page - Match Form.io's official example\n const thankYouSchema = {\n display: 'form',\n components: [\n { \n type: 'htmlelement', \n tag: 'p', \n content: 'Fill out this form and watch it redirect to a thank you page after submission.',\n className: 'mb-4 text-gray-600'\n },\n { type: 'textfield', key: 'firstName', label: 'First Name', placeholder: 'Enter your first name', validate: { required: true } },\n { type: 'textfield', key: 'lastName', label: 'Last Name', placeholder: 'Enter your last name', validate: { required: true } },\n { type: 'email', key: 'email', label: 'Email Address', placeholder: 'you@example.com', validate: { required: true } },\n { type: 'phoneNumber', key: 'phone', label: 'Phone Number', placeholder: '(555) 555-5555' },\n { type: 'textarea', key: 'message', label: 'Message', rows: 4, placeholder: 'Your message here...', validate: { required: true } },\n { type: 'button', action: 'submit', label: 'Submit Form', theme: 'primary' }\n ]\n };\n Formio.createForm(document.getElementById('form-thank-you'), thankYouSchema)\n .then(function(form) {\n form.on('submitDone', function(submission) {\n console.log('Form submitted:', submission);\n form.element.style.display = 'none';\n const thankYouMsg = document.getElementById('thank-you-message');\n thankYouMsg.style.display = 'block';\n // In a real application, you would redirect:\n // window.location = '/thank-you-page';\n });\n });\n\n // Wizard - Proper 3-step multi-page wizard\n const wizardSchema = {\n display: 'wizard',\n components: [\n {\n type: 'panel',\n key: 'step1PersonalInfo',\n title: 'Step 1: Personal Information',\n components: [\n { \n type: 'htmlelement', \n tag: 'p', \n content: 'Please provide your personal information.',\n className: 'mb-3 text-gray-600'\n },\n { type: 'textfield', key: 'firstName', label: 'First Name', placeholder: 'John', validate: { required: true } },\n { type: 'textfield', key: 'lastName', label: 'Last Name', placeholder: 'Doe', validate: { required: true } },\n { type: 'datetime', key: 'birthDate', label: 'Date of Birth', format: 'yyyy-MM-dd', validate: { required: true } },\n { \n type: 'select', \n key: 'gender', \n label: 'Gender',\n data: { \n values: [\n { label: 'Male', value: 'male' },\n { label: 'Female', value: 'female' },\n { label: 'Non-binary', value: 'nonbinary' },\n { label: 'Prefer not to say', value: 'other' }\n ]\n }\n }\n ]\n },\n {\n type: 'panel',\n key: 'step2ContactInfo',\n title: 'Step 2: Contact Information',\n components: [\n { \n type: 'htmlelement', \n tag: 'p', \n content: 'How can we reach you?',\n className: 'mb-3 text-gray-600'\n },\n { type: 'email', key: 'email', label: 'Email Address', placeholder: 'john.doe@example.com', validate: { required: true } },\n { type: 'phoneNumber', key: 'phone', label: 'Phone Number', placeholder: '(555) 555-5555', validate: { required: true } },\n { type: 'textfield', key: 'address', label: 'Street Address', placeholder: '123 Main St' },\n { type: 'textfield', key: 'city', label: 'City', placeholder: 'New York' },\n { \n type: 'select', \n key: 'country', \n label: 'Country',\n data: { \n values: [\n { label: 'United States', value: 'us' },\n { label: 'Canada', value: 'ca' },\n { label: 'United Kingdom', value: 'uk' },\n { label: 'Australia', value: 'au' }\n ]\n },\n validate: { required: true }\n }\n ]\n },\n {\n type: 'panel',\n key: 'step3Preferences',\n title: 'Step 3: Preferences & Review',\n components: [\n { \n type: 'htmlelement', \n tag: 'p', \n content: 'Almost done! Tell us your preferences.',\n className: 'mb-3 text-gray-600'\n },\n { \n type: 'selectboxes', \n key: 'interests', \n label: 'Areas of Interest', \n values: [\n { label: 'Product Updates', value: 'products' },\n { label: 'Newsletter', value: 'newsletter' },\n { label: 'Special Offers', value: 'offers' },\n { label: 'Events & Webinars', value: 'events' }\n ]\n },\n { \n type: 'radio', \n key: 'contactMethod', \n label: 'Preferred Contact Method',\n values: [\n { label: 'Email', value: 'email' },\n { label: 'Phone', value: 'phone' },\n { label: 'SMS', value: 'sms' }\n ],\n validate: { required: true }\n },\n { type: 'textarea', key: 'comments', label: 'Additional Comments', rows: 3, placeholder: 'Any other information you would like to share...' },\n { type: 'checkbox', key: 'terms', label: 'I agree to the terms and conditions', validate: { required: true } }\n ]\n }\n ]\n };\n Formio.createForm(document.getElementById('form-wizard'), wizardSchema);\n\n // Conditional Logic\n const conditionalSchema = {\n display: 'form',\n components: [\n { type: 'checkbox', key: 'hasCompany', label: 'I am registering on behalf of a company' },\n { type: 'textfield', key: 'companyName', label: 'Company Name', \n conditional: { show: true, when: 'hasCompany', eq: true }\n },\n { type: 'button', action: 'submit', label: 'Submit', theme: 'primary' }\n ]\n };\n Formio.createForm(document.getElementById('form-conditional'), conditionalSchema);\n\n // File Upload - Proper example with actual file field\n const fileSchema = {\n display: 'form',\n components: [\n { \n type: 'htmlelement', \n tag: 'p', \n content: 'Upload files to Cloudflare R2 storage (or base64 encoding for demo).',\n className: 'mb-4 text-gray-600'\n },\n { type: 'textfield', key: 'name', label: 'Your Name', placeholder: 'John Doe', validate: { required: true } },\n { type: 'email', key: 'email', label: 'Email Address', placeholder: 'john@example.com', validate: { required: true } },\n { \n type: 'file', \n key: 'resume', \n label: 'Upload Resume (PDF, DOC, DOCX)', \n storage: 'base64',\n filePattern: '.pdf,.doc,.docx',\n fileMaxSize: '5MB',\n validate: { required: true }\n },\n { \n type: 'file', \n key: 'portfolio', \n label: 'Portfolio/Work Samples (Optional)', \n storage: 'base64',\n filePattern: '.pdf,.zip,.jpg,.png',\n fileMaxSize: '10MB',\n multiple: false\n },\n { \n type: 'file', \n key: 'attachments', \n label: 'Additional Attachments (Multiple files allowed)', \n storage: 'base64',\n multiple: true,\n fileMaxSize: '5MB'\n },\n { type: 'textarea', key: 'coverLetter', label: 'Cover Letter', rows: 5, placeholder: 'Tell us why you are a great fit...' },\n { type: 'button', action: 'submit', label: 'Upload & Submit', theme: 'primary' }\n ]\n };\n Formio.createForm(document.getElementById('form-file-upload'), fileSchema);\n\n // Address (without API key for demo)\n const addressSchema = {\n display: 'form',\n components: [\n { type: 'textfield', key: 'street', label: 'Street Address' },\n { type: 'textfield', key: 'city', label: 'City' },\n { type: 'textfield', key: 'state', label: 'State' },\n { type: 'textfield', key: 'zip', label: 'ZIP Code' },\n { type: 'button', action: 'submit', label: 'Submit', theme: 'primary' }\n ]\n };\n Formio.createForm(document.getElementById('form-address'), addressSchema);\n\n // Signature\n const signatureSchema = {\n display: 'form',\n components: [\n { type: 'textfield', key: 'name', label: 'Your Name' },\n { type: 'signature', key: 'signature', label: 'Sign Here', width: '100%', height: '150px' },\n { type: 'button', action: 'submit', label: 'Submit', theme: 'primary' }\n ]\n };\n Formio.createForm(document.getElementById('form-signature'), signatureSchema);\n\n // Data Grid\n const dataGridSchema = {\n display: 'form',\n components: [\n { \n type: 'datagrid', \n key: 'items', \n label: 'Items',\n components: [\n { type: 'textfield', key: 'item', label: 'Item' },\n { type: 'number', key: 'quantity', label: 'Quantity' }\n ]\n },\n { type: 'button', action: 'submit', label: 'Submit', theme: 'primary' }\n ]\n };\n Formio.createForm(document.getElementById('form-data-grid'), dataGridSchema);\n \n // Turnstile Protection Form\n const turnstileSchema = {\n components: [\n { \n type: 'textfield', \n key: 'fullName', \n label: 'Full Name',\n placeholder: 'Enter your full name',\n validate: { required: true }\n },\n { \n type: 'email', \n key: 'email', \n label: 'Email Address',\n placeholder: 'you@example.com',\n validate: { required: true }\n },\n { \n type: 'textarea', \n key: 'message', \n label: 'Message',\n placeholder: 'Tell us what you are thinking...',\n rows: 4,\n validate: { required: true }\n },\n {\n type: 'turnstile',\n key: 'turnstile',\n label: 'Security Verification',\n theme: 'auto',\n size: 'normal',\n appearance: 'always',\n persistent: false,\n protected: true\n },\n { \n type: 'button', \n action: 'submit', \n label: 'Send Secure Message', \n theme: 'primary',\n block: true\n }\n ]\n };\n Formio.createForm(document.getElementById('form-turnstile'), turnstileSchema);\n }\n\n // Wait for Form.io to load\n if (typeof Formio !== 'undefined') {\n initForms();\n setupNavigation();\n } else {\n setTimeout(function checkFormio() {\n if (typeof Formio !== 'undefined') {\n initForms();\n setupNavigation();\n } else {\n setTimeout(checkFormio, 100);\n }\n }, 100);\n }\n </script>\n `\n\n const layoutData: AdminLayoutCatalystData = {\n title: 'Forms Examples',\n pageTitle: 'Forms Examples',\n content: pageContent,\n user: data.user,\n version: data.version\n }\n\n return renderAdminLayoutCatalyst(layoutData)\n}\n"]}
|