convex-cms 0.0.5-alpha.0 → 0.0.5-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -144
- package/admin/README.md +99 -0
- package/admin/src/components/AdminLayout.tsx +22 -0
- package/admin/src/components/BreakingChangesWarningDialog.tsx +81 -0
- package/admin/src/components/BulkActionBar.tsx +190 -0
- package/admin/src/components/BulkOperationModal.tsx +177 -0
- package/admin/src/components/ContentEntryEditor.tsx +1104 -0
- package/admin/src/components/ContentTypeFormModal.tsx +1012 -0
- package/admin/src/components/ErrorBoundary.tsx +83 -0
- package/admin/src/components/ErrorState.tsx +147 -0
- package/admin/src/components/Header.tsx +294 -0
- package/admin/src/components/RouteGuard.tsx +264 -0
- package/admin/src/components/Sidebar.tsx +90 -0
- package/admin/src/components/TaxonomyEditor.tsx +348 -0
- package/admin/src/components/TermTree.tsx +533 -0
- package/admin/src/components/UploadDropzone.tsx +383 -0
- package/admin/src/components/VersionCompare.tsx +250 -0
- package/admin/src/components/VersionHistory.tsx +279 -0
- package/admin/src/components/VersionRollbackModal.tsx +79 -0
- package/admin/src/components/cmsds/CmsButton.tsx +101 -0
- package/admin/src/components/cmsds/CmsDialog.tsx +139 -0
- package/admin/src/components/cmsds/CmsDropdown.tsx +62 -0
- package/admin/src/components/cmsds/CmsEmptyState.tsx +54 -0
- package/admin/src/components/cmsds/CmsField.tsx +47 -0
- package/admin/src/components/cmsds/CmsPageHeader.tsx +35 -0
- package/admin/src/components/cmsds/CmsStatusBadge.tsx +153 -0
- package/admin/src/components/cmsds/CmsSurface.tsx +52 -0
- package/admin/src/components/cmsds/CmsTable.tsx +164 -0
- package/admin/src/components/cmsds/CmsToolbar.tsx +58 -0
- package/admin/src/components/cmsds/index.ts +10 -0
- package/admin/src/components/fields/BooleanField.tsx +74 -0
- package/admin/src/components/fields/CategoryField.tsx +394 -0
- package/admin/src/components/fields/DateField.tsx +173 -0
- package/admin/src/components/fields/DefaultFieldRenderer.tsx +74 -0
- package/admin/src/components/fields/FieldRenderer.tsx +180 -0
- package/admin/src/components/fields/FieldWrapper.tsx +57 -0
- package/admin/src/components/fields/JsonField.tsx +172 -0
- package/admin/src/components/fields/MediaField.tsx +367 -0
- package/admin/src/components/fields/MultiSelectField.tsx +118 -0
- package/admin/src/components/fields/NumberField.tsx +77 -0
- package/admin/src/components/fields/ReferenceField.tsx +386 -0
- package/admin/src/components/fields/RichTextField.tsx +171 -0
- package/admin/src/components/fields/SelectField.tsx +62 -0
- package/admin/src/components/fields/TagField.tsx +325 -0
- package/admin/src/components/fields/TextAreaField.tsx +68 -0
- package/admin/src/components/fields/TextField.tsx +56 -0
- package/admin/src/components/fields/index.ts +54 -0
- package/admin/src/components/fields/registry.ts +64 -0
- package/admin/src/components/fields/types.ts +217 -0
- package/admin/src/components/filters/TaxonomyFilter.tsx +254 -0
- package/admin/src/components/filters/index.ts +1 -0
- package/admin/src/components/index.ts +8 -0
- package/admin/src/components/media/MediaAssetActions.tsx +115 -0
- package/admin/src/components/media/MediaAssetEditDialog.tsx +217 -0
- package/admin/src/components/media/MediaBulkActionBar.tsx +51 -0
- package/admin/src/components/media/MediaFolderActions.tsx +69 -0
- package/admin/src/components/media/MediaFolderEditDialog.tsx +126 -0
- package/admin/src/components/media/MediaMoveModal.tsx +179 -0
- package/admin/src/components/media/MediaPreviewModal.tsx +370 -0
- package/admin/src/components/media/MediaTaxonomyPicker.tsx +304 -0
- package/admin/src/components/media/MediaTrashBulkActionBar.tsx +59 -0
- package/admin/src/components/ui/accordion.tsx +64 -0
- package/admin/src/components/ui/alert-dialog.tsx +155 -0
- package/admin/src/components/ui/alert.tsx +66 -0
- package/admin/src/components/ui/avatar.tsx +53 -0
- package/admin/src/components/ui/badge.tsx +46 -0
- package/admin/src/components/ui/breadcrumb.tsx +109 -0
- package/admin/src/components/ui/button.tsx +62 -0
- package/admin/src/components/ui/calendar.tsx +220 -0
- package/admin/src/components/ui/card.tsx +92 -0
- package/admin/src/components/ui/checkbox.tsx +30 -0
- package/admin/src/components/ui/command.tsx +182 -0
- package/admin/src/components/ui/dialog.tsx +143 -0
- package/admin/src/components/ui/dropdown-menu.tsx +257 -0
- package/admin/src/components/ui/form.tsx +167 -0
- package/admin/src/components/ui/input.tsx +21 -0
- package/admin/src/components/ui/label.tsx +24 -0
- package/admin/src/components/ui/popover.tsx +46 -0
- package/admin/src/components/ui/scroll-area.tsx +56 -0
- package/admin/src/components/ui/select.tsx +190 -0
- package/admin/src/components/ui/separator.tsx +26 -0
- package/admin/src/components/ui/sheet.tsx +137 -0
- package/admin/src/components/ui/sidebar.tsx +724 -0
- package/admin/src/components/ui/skeleton.tsx +13 -0
- package/admin/src/components/ui/sonner.tsx +38 -0
- package/admin/src/components/ui/switch.tsx +31 -0
- package/admin/src/components/ui/table.tsx +114 -0
- package/admin/src/components/ui/tabs.tsx +66 -0
- package/admin/src/components/ui/textarea.tsx +18 -0
- package/admin/src/components/ui/tooltip.tsx +61 -0
- package/admin/src/contexts/AdminConfigContext.tsx +30 -0
- package/admin/src/contexts/AuthContext.tsx +330 -0
- package/admin/src/contexts/BreadcrumbContext.tsx +49 -0
- package/admin/src/contexts/SettingsConfigContext.tsx +57 -0
- package/admin/src/contexts/ThemeContext.tsx +91 -0
- package/admin/src/contexts/index.ts +20 -0
- package/admin/src/embed/components/EmbedHeader.tsx +103 -0
- package/admin/src/embed/components/EmbedLayout.tsx +29 -0
- package/admin/src/embed/components/EmbedSidebar.tsx +119 -0
- package/admin/src/embed/components/index.ts +3 -0
- package/admin/src/embed/contexts/ApiContext.tsx +32 -0
- package/admin/src/embed/index.tsx +184 -0
- package/admin/src/embed/navigation.tsx +202 -0
- package/admin/src/embed/pages/Content.tsx +19 -0
- package/admin/src/embed/pages/ContentTypes.tsx +19 -0
- package/admin/src/embed/pages/Dashboard.tsx +19 -0
- package/admin/src/embed/pages/Media.tsx +19 -0
- package/admin/src/embed/pages/Settings.tsx +22 -0
- package/admin/src/embed/pages/Taxonomies.tsx +22 -0
- package/admin/src/embed/pages/Trash.tsx +22 -0
- package/admin/src/embed/pages/index.ts +7 -0
- package/admin/src/embed/types.ts +24 -0
- package/admin/src/hooks/index.ts +2 -0
- package/admin/src/hooks/use-mobile.ts +19 -0
- package/admin/src/hooks/useBreadcrumbLabel.ts +15 -0
- package/admin/src/hooks/usePermissions.ts +211 -0
- package/admin/src/lib/admin-config.ts +111 -0
- package/admin/src/lib/cn.ts +6 -0
- package/admin/src/lib/config.server.ts +56 -0
- package/admin/src/lib/convex.ts +26 -0
- package/admin/src/lib/embed-adapter.ts +80 -0
- package/admin/src/lib/icons.tsx +96 -0
- package/admin/src/lib/loadAdminConfig.ts +92 -0
- package/admin/src/lib/motion.ts +29 -0
- package/admin/src/lib/navigation.ts +43 -0
- package/admin/src/lib/tanstack-adapter.ts +82 -0
- package/admin/src/pages/ContentPage.tsx +337 -0
- package/admin/src/pages/ContentTypesPage.tsx +457 -0
- package/admin/src/pages/DashboardPage.tsx +163 -0
- package/admin/src/pages/MediaPage.tsx +34 -0
- package/admin/src/pages/SettingsPage.tsx +486 -0
- package/admin/src/pages/TaxonomiesPage.tsx +289 -0
- package/admin/src/pages/TrashPage.tsx +421 -0
- package/admin/src/pages/index.ts +14 -0
- package/admin/src/routeTree.gen.ts +262 -0
- package/admin/src/router.tsx +22 -0
- package/admin/src/routes/__root.tsx +250 -0
- package/admin/src/routes/content-types.tsx +20 -0
- package/admin/src/routes/content.tsx +20 -0
- package/admin/src/routes/entries/$entryId.tsx +107 -0
- package/admin/src/routes/entries/new.$contentTypeId.tsx +69 -0
- package/admin/src/routes/entries/type/$contentTypeId.tsx +503 -0
- package/admin/src/routes/index.tsx +20 -0
- package/admin/src/routes/media.tsx +1095 -0
- package/admin/src/routes/settings.tsx +20 -0
- package/admin/src/routes/taxonomies.tsx +20 -0
- package/admin/src/routes/trash.tsx +20 -0
- package/admin/src/styles/globals.css +69 -0
- package/admin/src/styles/tailwind-config.css +74 -0
- package/admin/src/styles/theme.css +73 -0
- package/admin/src/types/index.ts +221 -0
- package/admin/src/utils/errorParsing.ts +163 -0
- package/admin/src/utils/index.ts +5 -0
- package/admin/src/vite-env.d.ts +14 -0
- package/admin/tailwind.preset.cjs +102 -0
- package/admin-dist/nitro.json +1 -1
- package/admin-dist/public/assets/{CmsEmptyState-CiMQwSQV.js → CmsEmptyState-CkqBIab3.js} +1 -1
- package/admin-dist/public/assets/{CmsPageHeader-ohOq0luT.js → CmsPageHeader-CUtl5MMG.js} +1 -1
- package/admin-dist/public/assets/{CmsStatusBadge-BdNf0V9v.js → CmsStatusBadge-CUYFgEe-.js} +1 -1
- package/admin-dist/public/assets/{CmsSurface-CWup6Jh7.js → CmsSurface-CsJfAVa3.js} +1 -1
- package/admin-dist/public/assets/{CmsToolbar-cEBlCHa3.js → CmsToolbar-CnfbcxeP.js} +1 -1
- package/admin-dist/public/assets/{ContentEntryEditor-BY5ypfUs.js → ContentEntryEditor-BU220CCy.js} +1 -1
- package/admin-dist/public/assets/TaxonomyFilter-CWCxC5HZ.js +1 -0
- package/admin-dist/public/assets/_contentTypeId-DK8cskRt.js +1 -0
- package/admin-dist/public/assets/{_entryId-BpSmrfAm.js → _entryId-CuVMExbb.js} +1 -1
- package/admin-dist/public/assets/{alert-Bf2l8kxw.js → alert-CF1BSzGR.js} +1 -1
- package/admin-dist/public/assets/{badge-qPrc4AUM.js → badge-CmuOIVKp.js} +1 -1
- package/admin-dist/public/assets/{circle-check-big-Dgozy3vV.js → circle-check-big-BKDVG6DU.js} +1 -1
- package/admin-dist/public/assets/{command-QOmNhlb0.js → command-XJxnF2Sd.js} +1 -1
- package/admin-dist/public/assets/content-QBUxdxbS.js +1 -0
- package/admin-dist/public/assets/content-types-CrNEm8Hf.js +2 -0
- package/admin-dist/public/assets/globals-B7Wsfh_v.css +1 -0
- package/admin-dist/public/assets/index-C7xOwudI.js +1 -0
- package/admin-dist/public/assets/{label-DCsUdvFh.js → label-CHCnXeBk.js} +1 -1
- package/admin-dist/public/assets/{link-2-Czw1N61H.js → link-2-Bb34judH.js} +1 -1
- package/admin-dist/public/assets/{list-DtCsXj8-.js → list-9Pzt48ld.js} +1 -1
- package/admin-dist/public/assets/{main-CXgkZMhe.js → main-CjQ2VI9L.js} +3 -3
- package/admin-dist/public/assets/media-Dc5PWt2Q.js +1 -0
- package/admin-dist/public/assets/{new._contentTypeId-CoTDxKzf.js → new._contentTypeId-C_I4YxIa.js} +1 -1
- package/admin-dist/public/assets/{plus-xCFJK0RC.js → plus-Ceef7DHk.js} +1 -1
- package/admin-dist/public/assets/{rotate-ccw-DIqK63wY.js → rotate-ccw-7k7-4VUq.js} +1 -1
- package/admin-dist/public/assets/{scroll-area-B-yrE66a.js → scroll-area-CC6wujnp.js} +1 -1
- package/admin-dist/public/assets/{search-CbCbboeU.js → search-DwoUV2pv.js} +1 -1
- package/admin-dist/public/assets/{select-Co3TZFJb.js → select-hOZTp8aC.js} +1 -1
- package/admin-dist/public/assets/{settings-BspTTv_o.js → settings-t2PbCZh4.js} +1 -1
- package/admin-dist/public/assets/{switch-CfavASmR.js → switch-jX2pDaNU.js} +1 -1
- package/admin-dist/public/assets/{tabs-CN5s5u2W.js → tabs-q4EbZk7c.js} +1 -1
- package/admin-dist/public/assets/{tanstack-adapter-npeE3RdY.js → tanstack-adapter-B-Glm4kH.js} +1 -1
- package/admin-dist/public/assets/taxonomies-kyk5P4ZW.js +1 -0
- package/admin-dist/public/assets/{textarea-BJ0XFZpT.js → textarea-B6SfBmr0.js} +1 -1
- package/admin-dist/public/assets/trash-BOCnIznD.js +1 -0
- package/admin-dist/public/assets/{triangle-alert-BZRcqsUg.js → triangle-alert-CXFIO_Gu.js} +1 -1
- package/admin-dist/public/assets/{useBreadcrumbLabel-DwZlwvFF.js → useBreadcrumbLabel-_6qBagc3.js} +1 -1
- package/admin-dist/public/assets/{usePermissions-C1JQhfqb.js → usePermissions-M1ijZ7a6.js} +1 -1
- package/admin-dist/server/_ssr/{CmsButton-B45JAKR1.mjs → CmsButton-DOiTVKQq.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsEmptyState-D_BQFAVR.mjs → CmsEmptyState-fbnGt3LD.mjs} +2 -2
- package/admin-dist/server/_ssr/{CmsPageHeader-CrUZA59A.mjs → CmsPageHeader-DHRrdOZa.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsStatusBadge-B-sj6yaj.mjs → CmsStatusBadge-s7obWbKZ.mjs} +2 -2
- package/admin-dist/server/_ssr/{CmsSurface-DKJZhpjk.mjs → CmsSurface-rFoYjb62.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsToolbar-ByaW5iXf.mjs → CmsToolbar-zTE45z2q.mjs} +2 -2
- package/admin-dist/server/_ssr/{ContentEntryEditor-D3_Jb1dq.mjs → ContentEntryEditor-BLoEjT_m.mjs} +12 -12
- package/admin-dist/server/_ssr/{TaxonomyFilter-BRJkuCtA.mjs → TaxonomyFilter-XAtaJC2z.mjs} +5 -5
- package/admin-dist/server/_ssr/{_contentTypeId-B9kA6CaM.mjs → _contentTypeId-Csl4822C.mjs} +13 -13
- package/admin-dist/server/_ssr/{_entryId-BddcMkZN.mjs → _entryId-D8alLFBx.mjs} +15 -15
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BffZedId.mjs +4 -0
- package/admin-dist/server/_ssr/{command-CGtVr8Gb.mjs → command-C0Di14--.mjs} +1 -1
- package/admin-dist/server/_ssr/{content-D1tbeOd0.mjs → content-CT-FPsmV.mjs} +12 -55
- package/admin-dist/server/_ssr/{content-types-BZqY_BER.mjs → content-types-C8cBFdzE.mjs} +15 -46
- package/admin-dist/server/_ssr/{index-BIdq4xaC.mjs → index-BJtcrEc-.mjs} +5 -5
- package/admin-dist/server/_ssr/index.mjs +2 -2
- package/admin-dist/server/_ssr/{label-T-QNKAr6.mjs → label-qn2Afwl4.mjs} +1 -1
- package/admin-dist/server/_ssr/{media-C-xqjBrl.mjs → media-qv5IAsMZ.mjs} +14 -14
- package/admin-dist/server/_ssr/{new._contentTypeId-DWic9cRq.mjs → new._contentTypeId-DdGyrhqs.mjs} +13 -13
- package/admin-dist/server/_ssr/{router-D1BMAMJT.mjs → router-nSVkxb6Y.mjs} +11 -11
- package/admin-dist/server/_ssr/{scroll-area-C0pic_WA.mjs → scroll-area-BCinP455.mjs} +1 -1
- package/admin-dist/server/_ssr/{select-CqmuN2F6.mjs → select-BKQlQScw.mjs} +1 -1
- package/admin-dist/server/_ssr/{settings-CAkncGGV.mjs → settings-BCr2KQlk.mjs} +55 -40
- package/admin-dist/server/_ssr/{switch-CgmuJkT9.mjs → switch-BaOi42fE.mjs} +1 -1
- package/admin-dist/server/_ssr/{tabs-CnMj0aRy.mjs → tabs-DYXEi9kq.mjs} +2 -2
- package/admin-dist/server/_ssr/{tanstack-adapter-BXZrMauE.mjs → tanstack-adapter-Bsz8kha-.mjs} +1 -1
- package/admin-dist/server/_ssr/{taxonomies-thl3BfVm.mjs → taxonomies-CueMHTbE.mjs} +30 -19
- package/admin-dist/server/_ssr/{textarea-4K5OJgeh.mjs → textarea-CI0Jqx2x.mjs} +1 -1
- package/admin-dist/server/_ssr/{trash-B40Gx5zP.mjs → trash-DE6W8GoX.mjs} +20 -17
- package/admin-dist/server/_ssr/{useBreadcrumbLabel-rn-fL4zV.mjs → useBreadcrumbLabel-B5Yi72lM.mjs} +1 -1
- package/admin-dist/server/_ssr/{usePermissions-CKeM6_Vw.mjs → usePermissions-C3nZ-Izm.mjs} +1 -1
- package/admin-dist/server/index.mjs +187 -194
- package/dist/client/admin/bulk.d.ts +79 -0
- package/dist/client/admin/bulk.d.ts.map +1 -0
- package/dist/client/admin/bulk.js +72 -0
- package/dist/client/admin/bulk.js.map +1 -0
- package/dist/client/admin/contentLock.d.ts +118 -0
- package/dist/client/admin/contentLock.d.ts.map +1 -0
- package/dist/client/admin/contentLock.js +81 -0
- package/dist/client/admin/contentLock.js.map +1 -0
- package/dist/client/{adminApi.d.ts → admin/contentTypes.d.ts} +39 -1134
- package/dist/client/admin/contentTypes.d.ts.map +1 -0
- package/dist/client/admin/contentTypes.js +122 -0
- package/dist/client/admin/contentTypes.js.map +1 -0
- package/dist/client/admin/dashboard.d.ts +16 -0
- package/dist/client/admin/dashboard.d.ts.map +1 -0
- package/dist/client/admin/dashboard.js +33 -0
- package/dist/client/admin/dashboard.js.map +1 -0
- package/dist/client/admin/entries.d.ts +358 -0
- package/dist/client/admin/entries.d.ts.map +1 -0
- package/dist/client/admin/entries.js +220 -0
- package/dist/client/admin/entries.js.map +1 -0
- package/dist/client/admin/index.d.ts +6568 -0
- package/dist/client/admin/index.d.ts.map +1 -0
- package/dist/client/admin/index.js +305 -0
- package/dist/client/admin/index.js.map +1 -0
- package/dist/client/admin/media.d.ts +1038 -0
- package/dist/client/admin/media.d.ts.map +1 -0
- package/dist/client/admin/media.js +489 -0
- package/dist/client/admin/media.js.map +1 -0
- package/dist/client/admin/taxonomies.d.ts +339 -0
- package/dist/client/admin/taxonomies.d.ts.map +1 -0
- package/dist/client/admin/taxonomies.js +364 -0
- package/dist/client/admin/taxonomies.js.map +1 -0
- package/dist/client/admin/trash.d.ts +91 -0
- package/dist/client/admin/trash.d.ts.map +1 -0
- package/dist/client/admin/trash.js +71 -0
- package/dist/client/admin/trash.js.map +1 -0
- package/dist/client/admin/types.d.ts +320 -0
- package/dist/client/admin/types.d.ts.map +1 -0
- package/dist/client/admin/types.js +7 -0
- package/dist/client/admin/types.js.map +1 -0
- package/dist/client/admin/validators.d.ts +3886 -0
- package/dist/client/admin/validators.d.ts.map +1 -0
- package/dist/client/admin/validators.js +322 -0
- package/dist/client/admin/validators.js.map +1 -0
- package/dist/client/admin/versions.d.ts +106 -0
- package/dist/client/admin/versions.d.ts.map +1 -0
- package/dist/client/admin/versions.js +57 -0
- package/dist/client/admin/versions.js.map +1 -0
- package/dist/client/adminApiTypes.d.ts +27 -0
- package/dist/client/adminApiTypes.d.ts.map +1 -0
- package/dist/client/adminApiTypes.js +12 -0
- package/dist/client/adminApiTypes.js.map +1 -0
- package/dist/client/{admin-config.d.ts → adminConfig.d.ts} +2 -2
- package/dist/client/adminConfig.d.ts.map +1 -0
- package/dist/client/{admin-config.js → adminConfig.js} +1 -1
- package/dist/client/adminConfig.js.map +1 -0
- package/dist/client/agentTools.d.ts +4 -4
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +15 -2
- package/dist/client/index.js.map +1 -1
- package/dist/component/contentEntries.d.ts +4 -4
- package/dist/component/contentEntryMutations.d.ts +46 -0
- package/dist/component/contentEntryMutations.d.ts.map +1 -1
- package/dist/component/contentEntryMutations.js +1 -1
- package/dist/component/contentEntryMutations.js.map +1 -1
- package/dist/component/contentTypeMigration.d.ts +1 -1
- package/dist/component/contentTypeMutations.d.ts +22 -0
- package/dist/component/contentTypeMutations.d.ts.map +1 -1
- package/dist/component/contentTypeMutations.js +1 -1
- package/dist/component/contentTypeMutations.js.map +1 -1
- package/dist/component/mediaAssetMutations.d.ts +47 -0
- package/dist/component/mediaAssetMutations.d.ts.map +1 -1
- package/dist/component/mediaAssetMutations.js +1 -1
- package/dist/component/mediaAssetMutations.js.map +1 -1
- package/dist/component/schema.d.ts +9 -0
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +1 -1
- package/dist/component/schema.js.map +1 -1
- package/package.json +85 -3
- package/admin-dist/public/assets/ErrorState-C4nJ-ml4.js +0 -1
- package/admin-dist/public/assets/TaxonomyFilter-BgE_SR_O.js +0 -1
- package/admin-dist/public/assets/_contentTypeId-DtZectcC.js +0 -1
- package/admin-dist/public/assets/content-OEBGlxg1.js +0 -1
- package/admin-dist/public/assets/content-types-CjQliqVV.js +0 -2
- package/admin-dist/public/assets/globals-hAmgC66w.css +0 -1
- package/admin-dist/public/assets/index-BH_ECMhv.js +0 -1
- package/admin-dist/public/assets/media-DTJ3-ViE.js +0 -1
- package/admin-dist/public/assets/taxonomies-CgG46fIF.js +0 -1
- package/admin-dist/public/assets/trash-B3daldm5.js +0 -1
- package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +0 -89
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-Dd7AmelK.mjs +0 -4
- package/dist/client/admin-config.d.ts.map +0 -1
- package/dist/client/admin-config.js.map +0 -1
- package/dist/client/adminApi.d.ts.map +0 -1
- package/dist/client/adminApi.js +0 -736
- package/dist/client/adminApi.js.map +0 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { cn } from "~/lib/cn"
|
|
2
|
+
|
|
3
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
data-slot="skeleton"
|
|
7
|
+
className={cn("bg-accent animate-pulse rounded-md", className)}
|
|
8
|
+
{...props}
|
|
9
|
+
/>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { Skeleton }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CircleCheckIcon,
|
|
3
|
+
InfoIcon,
|
|
4
|
+
Loader2Icon,
|
|
5
|
+
OctagonXIcon,
|
|
6
|
+
TriangleAlertIcon,
|
|
7
|
+
} from "lucide-react"
|
|
8
|
+
import { useTheme } from "next-themes"
|
|
9
|
+
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
|
10
|
+
|
|
11
|
+
const Toaster = ({ ...props }: ToasterProps) => {
|
|
12
|
+
const { theme = "system" } = useTheme()
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Sonner
|
|
16
|
+
theme={theme as ToasterProps["theme"]}
|
|
17
|
+
className="toaster group"
|
|
18
|
+
icons={{
|
|
19
|
+
success: <CircleCheckIcon className="size-4" />,
|
|
20
|
+
info: <InfoIcon className="size-4" />,
|
|
21
|
+
warning: <TriangleAlertIcon className="size-4" />,
|
|
22
|
+
error: <OctagonXIcon className="size-4" />,
|
|
23
|
+
loading: <Loader2Icon className="size-4 animate-spin" />,
|
|
24
|
+
}}
|
|
25
|
+
style={
|
|
26
|
+
{
|
|
27
|
+
"--normal-bg": "var(--popover)",
|
|
28
|
+
"--normal-text": "var(--popover-foreground)",
|
|
29
|
+
"--normal-border": "var(--border)",
|
|
30
|
+
"--border-radius": "var(--radius)",
|
|
31
|
+
} as React.CSSProperties
|
|
32
|
+
}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { Toaster }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
|
5
|
+
|
|
6
|
+
import { cn } from "~/lib/cn"
|
|
7
|
+
|
|
8
|
+
function Switch({
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
|
|
12
|
+
return (
|
|
13
|
+
<SwitchPrimitive.Root
|
|
14
|
+
data-slot="switch"
|
|
15
|
+
className={cn(
|
|
16
|
+
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
>
|
|
21
|
+
<SwitchPrimitive.Thumb
|
|
22
|
+
data-slot="switch-thumb"
|
|
23
|
+
className={cn(
|
|
24
|
+
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
|
25
|
+
)}
|
|
26
|
+
/>
|
|
27
|
+
</SwitchPrimitive.Root>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { Switch }
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "~/lib/cn"
|
|
4
|
+
|
|
5
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="table-container"
|
|
9
|
+
className="relative w-full overflow-x-auto"
|
|
10
|
+
>
|
|
11
|
+
<table
|
|
12
|
+
data-slot="table"
|
|
13
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
21
|
+
return (
|
|
22
|
+
<thead
|
|
23
|
+
data-slot="table-header"
|
|
24
|
+
className={cn("[&_tr]:border-b", className)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
31
|
+
return (
|
|
32
|
+
<tbody
|
|
33
|
+
data-slot="table-body"
|
|
34
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
41
|
+
return (
|
|
42
|
+
<tfoot
|
|
43
|
+
data-slot="table-footer"
|
|
44
|
+
className={cn(
|
|
45
|
+
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
54
|
+
return (
|
|
55
|
+
<tr
|
|
56
|
+
data-slot="table-row"
|
|
57
|
+
className={cn(
|
|
58
|
+
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
67
|
+
return (
|
|
68
|
+
<th
|
|
69
|
+
data-slot="table-head"
|
|
70
|
+
className={cn(
|
|
71
|
+
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
72
|
+
className
|
|
73
|
+
)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
80
|
+
return (
|
|
81
|
+
<td
|
|
82
|
+
data-slot="table-cell"
|
|
83
|
+
className={cn(
|
|
84
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
85
|
+
className
|
|
86
|
+
)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function TableCaption({
|
|
93
|
+
className,
|
|
94
|
+
...props
|
|
95
|
+
}: React.ComponentProps<"caption">) {
|
|
96
|
+
return (
|
|
97
|
+
<caption
|
|
98
|
+
data-slot="table-caption"
|
|
99
|
+
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
Table,
|
|
107
|
+
TableHeader,
|
|
108
|
+
TableBody,
|
|
109
|
+
TableFooter,
|
|
110
|
+
TableHead,
|
|
111
|
+
TableRow,
|
|
112
|
+
TableCell,
|
|
113
|
+
TableCaption,
|
|
114
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
|
5
|
+
|
|
6
|
+
import { cn } from "~/lib/cn"
|
|
7
|
+
|
|
8
|
+
function Tabs({
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
|
12
|
+
return (
|
|
13
|
+
<TabsPrimitive.Root
|
|
14
|
+
data-slot="tabs"
|
|
15
|
+
className={cn("flex flex-col gap-2", className)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function TabsList({
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
|
25
|
+
return (
|
|
26
|
+
<TabsPrimitive.List
|
|
27
|
+
data-slot="tabs-list"
|
|
28
|
+
className={cn(
|
|
29
|
+
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
|
30
|
+
className
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function TabsTrigger({
|
|
38
|
+
className,
|
|
39
|
+
...props
|
|
40
|
+
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
41
|
+
return (
|
|
42
|
+
<TabsPrimitive.Trigger
|
|
43
|
+
data-slot="tabs-trigger"
|
|
44
|
+
className={cn(
|
|
45
|
+
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function TabsContent({
|
|
54
|
+
className,
|
|
55
|
+
...props
|
|
56
|
+
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
57
|
+
return (
|
|
58
|
+
<TabsPrimitive.Content
|
|
59
|
+
data-slot="tabs-content"
|
|
60
|
+
className={cn("flex-1 outline-none", className)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "~/lib/cn"
|
|
4
|
+
|
|
5
|
+
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
6
|
+
return (
|
|
7
|
+
<textarea
|
|
8
|
+
data-slot="textarea"
|
|
9
|
+
className={cn(
|
|
10
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Textarea }
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|
5
|
+
|
|
6
|
+
import { cn } from "~/lib/cn"
|
|
7
|
+
|
|
8
|
+
function TooltipProvider({
|
|
9
|
+
delayDuration = 0,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
12
|
+
return (
|
|
13
|
+
<TooltipPrimitive.Provider
|
|
14
|
+
data-slot="tooltip-provider"
|
|
15
|
+
delayDuration={delayDuration}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function Tooltip({
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
24
|
+
return (
|
|
25
|
+
<TooltipProvider>
|
|
26
|
+
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
27
|
+
</TooltipProvider>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function TooltipTrigger({
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
34
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function TooltipContent({
|
|
38
|
+
className,
|
|
39
|
+
sideOffset = 0,
|
|
40
|
+
children,
|
|
41
|
+
...props
|
|
42
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
43
|
+
return (
|
|
44
|
+
<TooltipPrimitive.Portal>
|
|
45
|
+
<TooltipPrimitive.Content
|
|
46
|
+
data-slot="tooltip-content"
|
|
47
|
+
sideOffset={sideOffset}
|
|
48
|
+
className={cn(
|
|
49
|
+
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
|
56
|
+
</TooltipPrimitive.Content>
|
|
57
|
+
</TooltipPrimitive.Portal>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
+
import type { AdminConfig, NavItem } from "../lib/admin-config";
|
|
3
|
+
import { getVisibleNavItems } from "../lib/admin-config";
|
|
4
|
+
|
|
5
|
+
interface AdminConfigContextValue extends AdminConfig {
|
|
6
|
+
navItems: { main: NavItem[]; config: NavItem[] };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const AdminConfigContext = createContext<AdminConfigContextValue | null>(null);
|
|
10
|
+
|
|
11
|
+
export function AdminConfigProvider({
|
|
12
|
+
config,
|
|
13
|
+
children,
|
|
14
|
+
}: {
|
|
15
|
+
config: AdminConfig;
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
}) {
|
|
18
|
+
const navItems = getVisibleNavItems(config);
|
|
19
|
+
const value: AdminConfigContextValue = { ...config, navItems };
|
|
20
|
+
|
|
21
|
+
return <AdminConfigContext.Provider value={value}>{children}</AdminConfigContext.Provider>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useAdminConfig(): AdminConfigContextValue {
|
|
25
|
+
const config = useContext(AdminConfigContext);
|
|
26
|
+
if (!config) {
|
|
27
|
+
throw new Error("useAdminConfig must be used within AdminConfigProvider");
|
|
28
|
+
}
|
|
29
|
+
return config;
|
|
30
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Context Provider
|
|
3
|
+
*
|
|
4
|
+
* This module provides authentication state management for the admin UI.
|
|
5
|
+
* It integrates with the parent app's auth system via configurable hooks,
|
|
6
|
+
* allowing the CMS to work with any authentication provider.
|
|
7
|
+
*
|
|
8
|
+
* The context provides:
|
|
9
|
+
* - Current user information (id, name, email, role)
|
|
10
|
+
* - Authentication state (loading, authenticated, error)
|
|
11
|
+
* - Permission checking utilities
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* // In app setup, provide auth hooks
|
|
16
|
+
* <AuthProvider
|
|
17
|
+
* getUser={async () => ({ id: 'user_123', name: 'John', email: 'john@example.com' })}
|
|
18
|
+
* getUserRole={async ({ userId }) => 'editor'}
|
|
19
|
+
* >
|
|
20
|
+
* <App />
|
|
21
|
+
* </AuthProvider>
|
|
22
|
+
*
|
|
23
|
+
* // In components, use the auth context
|
|
24
|
+
* const { user, role, isAuthenticated, isLoading } = useAuth();
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
createContext,
|
|
30
|
+
useContext,
|
|
31
|
+
useState,
|
|
32
|
+
useEffect,
|
|
33
|
+
useCallback,
|
|
34
|
+
type ReactNode,
|
|
35
|
+
} from 'react';
|
|
36
|
+
import {
|
|
37
|
+
hasPermission,
|
|
38
|
+
type RoleName,
|
|
39
|
+
type Resource,
|
|
40
|
+
type Action,
|
|
41
|
+
type OwnershipScope,
|
|
42
|
+
} from '../../../src/component/roles';
|
|
43
|
+
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// Types
|
|
46
|
+
// =============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* User information from the parent app's auth system.
|
|
50
|
+
*/
|
|
51
|
+
export interface AuthUser {
|
|
52
|
+
/** Unique user identifier */
|
|
53
|
+
id: string;
|
|
54
|
+
/** Display name */
|
|
55
|
+
name?: string;
|
|
56
|
+
/** Email address */
|
|
57
|
+
email?: string;
|
|
58
|
+
/** Avatar URL */
|
|
59
|
+
avatarUrl?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Context for getting a user's role.
|
|
64
|
+
*/
|
|
65
|
+
export interface GetUserRoleContext {
|
|
66
|
+
userId: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Hook to get the current authenticated user.
|
|
71
|
+
* Returns null if not authenticated.
|
|
72
|
+
*/
|
|
73
|
+
export type GetUserHook = () => Promise<AuthUser | null> | AuthUser | null;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Hook to get a user's role from the parent app.
|
|
77
|
+
* Should return the role name (e.g., 'admin', 'editor') or null if no role.
|
|
78
|
+
*/
|
|
79
|
+
export type GetUserRoleHook = (
|
|
80
|
+
context: GetUserRoleContext
|
|
81
|
+
) => Promise<string | null> | string | null;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Hook to handle logout.
|
|
85
|
+
*/
|
|
86
|
+
export type LogoutHook = () => Promise<void> | void;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Authentication state.
|
|
90
|
+
*/
|
|
91
|
+
export type AuthState = 'loading' | 'authenticated' | 'unauthenticated' | 'error';
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Permission check parameters.
|
|
95
|
+
*/
|
|
96
|
+
export interface PermissionCheck {
|
|
97
|
+
resource: Resource;
|
|
98
|
+
action: Action;
|
|
99
|
+
scope?: OwnershipScope;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Auth context value provided to consumers.
|
|
104
|
+
*/
|
|
105
|
+
export interface AuthContextValue {
|
|
106
|
+
/** Current authenticated user, or null if not authenticated */
|
|
107
|
+
user: AuthUser | null;
|
|
108
|
+
/** Current user's role in the CMS, or null if no role assigned */
|
|
109
|
+
role: RoleName | string | null;
|
|
110
|
+
/** Current authentication state */
|
|
111
|
+
authState: AuthState;
|
|
112
|
+
/** Whether authentication is still loading */
|
|
113
|
+
isLoading: boolean;
|
|
114
|
+
/** Whether the user is authenticated */
|
|
115
|
+
isAuthenticated: boolean;
|
|
116
|
+
/** Error message if authentication failed */
|
|
117
|
+
error: string | null;
|
|
118
|
+
/** Check if the current user has a specific permission */
|
|
119
|
+
checkPermission: (permission: PermissionCheck) => boolean;
|
|
120
|
+
/** Logout the current user */
|
|
121
|
+
logout: () => Promise<void>;
|
|
122
|
+
/** Refresh authentication state */
|
|
123
|
+
refresh: () => Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Props for the AuthProvider component.
|
|
128
|
+
*/
|
|
129
|
+
export interface AuthProviderProps {
|
|
130
|
+
children: ReactNode;
|
|
131
|
+
/**
|
|
132
|
+
* Hook to get the current authenticated user.
|
|
133
|
+
* This should integrate with your auth provider (Clerk, Auth0, etc.).
|
|
134
|
+
*/
|
|
135
|
+
getUser: GetUserHook;
|
|
136
|
+
/**
|
|
137
|
+
* Hook to get a user's CMS role.
|
|
138
|
+
* This maps the authenticated user to a CMS role.
|
|
139
|
+
*/
|
|
140
|
+
getUserRole: GetUserRoleHook;
|
|
141
|
+
/**
|
|
142
|
+
* Hook to handle logout.
|
|
143
|
+
* Optional - if not provided, logout will just clear local state.
|
|
144
|
+
*/
|
|
145
|
+
onLogout?: LogoutHook;
|
|
146
|
+
/**
|
|
147
|
+
* Whether to automatically redirect to login when unauthenticated.
|
|
148
|
+
* Defaults to false.
|
|
149
|
+
*/
|
|
150
|
+
autoRedirectToLogin?: boolean;
|
|
151
|
+
/**
|
|
152
|
+
* URL to redirect to for login.
|
|
153
|
+
* Only used if autoRedirectToLogin is true.
|
|
154
|
+
*/
|
|
155
|
+
loginUrl?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// =============================================================================
|
|
159
|
+
// Context
|
|
160
|
+
// =============================================================================
|
|
161
|
+
|
|
162
|
+
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
163
|
+
|
|
164
|
+
// =============================================================================
|
|
165
|
+
// Provider Component
|
|
166
|
+
// =============================================================================
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Authentication Provider Component
|
|
170
|
+
*
|
|
171
|
+
* Wraps the application and provides authentication state to all children.
|
|
172
|
+
* Integrates with the parent app's auth system via hooks.
|
|
173
|
+
*/
|
|
174
|
+
export function AuthProvider({
|
|
175
|
+
children,
|
|
176
|
+
getUser,
|
|
177
|
+
getUserRole,
|
|
178
|
+
onLogout,
|
|
179
|
+
autoRedirectToLogin = false,
|
|
180
|
+
loginUrl = '/login',
|
|
181
|
+
}: AuthProviderProps) {
|
|
182
|
+
const [user, setUser] = useState<AuthUser | null>(null);
|
|
183
|
+
const [role, setRole] = useState<RoleName | string | null>(null);
|
|
184
|
+
const [authState, setAuthState] = useState<AuthState>('loading');
|
|
185
|
+
const [error, setError] = useState<string | null>(null);
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Load authentication state.
|
|
189
|
+
*/
|
|
190
|
+
const loadAuth = useCallback(async () => {
|
|
191
|
+
setAuthState('loading');
|
|
192
|
+
setError(null);
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// Get the current user from the parent app's auth system
|
|
196
|
+
const currentUser = await getUser();
|
|
197
|
+
|
|
198
|
+
if (!currentUser) {
|
|
199
|
+
setUser(null);
|
|
200
|
+
setRole(null);
|
|
201
|
+
setAuthState('unauthenticated');
|
|
202
|
+
|
|
203
|
+
// Optionally redirect to login
|
|
204
|
+
if (autoRedirectToLogin && typeof window !== 'undefined') {
|
|
205
|
+
window.location.href = loginUrl;
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Get the user's CMS role
|
|
211
|
+
const userRole = await getUserRole({ userId: currentUser.id });
|
|
212
|
+
|
|
213
|
+
setUser(currentUser);
|
|
214
|
+
setRole(userRole);
|
|
215
|
+
setAuthState('authenticated');
|
|
216
|
+
} catch (err) {
|
|
217
|
+
console.error('Authentication error:', err);
|
|
218
|
+
setUser(null);
|
|
219
|
+
setRole(null);
|
|
220
|
+
setError(err instanceof Error ? err.message : 'Authentication failed');
|
|
221
|
+
setAuthState('error');
|
|
222
|
+
}
|
|
223
|
+
}, [getUser, getUserRole, autoRedirectToLogin, loginUrl]);
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Load auth on mount.
|
|
227
|
+
*/
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
loadAuth();
|
|
230
|
+
}, [loadAuth]);
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Check if the current user has a specific permission.
|
|
234
|
+
*/
|
|
235
|
+
const checkPermission = useCallback(
|
|
236
|
+
(permission: PermissionCheck): boolean => {
|
|
237
|
+
if (!role) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
return hasPermission(role, permission);
|
|
241
|
+
},
|
|
242
|
+
[role]
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Logout the current user.
|
|
247
|
+
*/
|
|
248
|
+
const logout = useCallback(async () => {
|
|
249
|
+
try {
|
|
250
|
+
if (onLogout) {
|
|
251
|
+
await onLogout();
|
|
252
|
+
}
|
|
253
|
+
setUser(null);
|
|
254
|
+
setRole(null);
|
|
255
|
+
setAuthState('unauthenticated');
|
|
256
|
+
|
|
257
|
+
if (autoRedirectToLogin && typeof window !== 'undefined') {
|
|
258
|
+
window.location.href = loginUrl;
|
|
259
|
+
}
|
|
260
|
+
} catch (err) {
|
|
261
|
+
console.error('Logout error:', err);
|
|
262
|
+
setError(err instanceof Error ? err.message : 'Logout failed');
|
|
263
|
+
}
|
|
264
|
+
}, [onLogout, autoRedirectToLogin, loginUrl]);
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Refresh authentication state.
|
|
268
|
+
*/
|
|
269
|
+
const refresh = useCallback(async () => {
|
|
270
|
+
await loadAuth();
|
|
271
|
+
}, [loadAuth]);
|
|
272
|
+
|
|
273
|
+
const value: AuthContextValue = {
|
|
274
|
+
user,
|
|
275
|
+
role,
|
|
276
|
+
authState,
|
|
277
|
+
isLoading: authState === 'loading',
|
|
278
|
+
isAuthenticated: authState === 'authenticated',
|
|
279
|
+
error,
|
|
280
|
+
checkPermission,
|
|
281
|
+
logout,
|
|
282
|
+
refresh,
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// =============================================================================
|
|
289
|
+
// Hook
|
|
290
|
+
// =============================================================================
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Hook to access authentication state.
|
|
294
|
+
*
|
|
295
|
+
* @throws Error if used outside of AuthProvider
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* ```tsx
|
|
299
|
+
* function UserProfile() {
|
|
300
|
+
* const { user, role, isAuthenticated, logout } = useAuth();
|
|
301
|
+
*
|
|
302
|
+
* if (!isAuthenticated) {
|
|
303
|
+
* return <LoginPrompt />;
|
|
304
|
+
* }
|
|
305
|
+
*
|
|
306
|
+
* return (
|
|
307
|
+
* <div>
|
|
308
|
+
* <p>Welcome, {user.name}!</p>
|
|
309
|
+
* <p>Role: {role}</p>
|
|
310
|
+
* <button onClick={logout}>Logout</button>
|
|
311
|
+
* </div>
|
|
312
|
+
* );
|
|
313
|
+
* }
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
export function useAuth(): AuthContextValue {
|
|
317
|
+
const context = useContext(AuthContext);
|
|
318
|
+
|
|
319
|
+
if (!context) {
|
|
320
|
+
throw new Error('useAuth must be used within an AuthProvider');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return context;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// =============================================================================
|
|
327
|
+
// Default Export
|
|
328
|
+
// =============================================================================
|
|
329
|
+
|
|
330
|
+
export default AuthProvider;
|