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,83 @@
|
|
|
1
|
+
import { Component, type ReactNode, type ErrorInfo } from 'react';
|
|
2
|
+
|
|
3
|
+
interface ErrorBoundaryProps {
|
|
4
|
+
/** Child components to render */
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
/** Optional fallback UI to render on error */
|
|
7
|
+
fallback?: ReactNode;
|
|
8
|
+
/** Optional callback when an error is caught */
|
|
9
|
+
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
10
|
+
/** Optional title for the error state */
|
|
11
|
+
title?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ErrorBoundaryState {
|
|
15
|
+
hasError: boolean;
|
|
16
|
+
error: Error | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ErrorBoundary catches JavaScript errors in child component trees
|
|
21
|
+
* and displays a fallback UI instead of crashing the whole app.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <ErrorBoundary fallback={<div>Something went wrong</div>}>
|
|
26
|
+
* <MyComponent />
|
|
27
|
+
* </ErrorBoundary>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
31
|
+
constructor(props: ErrorBoundaryProps) {
|
|
32
|
+
super(props);
|
|
33
|
+
this.state = { hasError: false, error: null };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
37
|
+
return { hasError: true, error };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
|
41
|
+
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
|
42
|
+
this.props.onError?.(error, errorInfo);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
handleRetry = (): void => {
|
|
46
|
+
this.setState({ hasError: false, error: null });
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
render(): ReactNode {
|
|
50
|
+
if (this.state.hasError) {
|
|
51
|
+
if (this.props.fallback) {
|
|
52
|
+
return this.props.fallback;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="error-boundary" role="alert">
|
|
57
|
+
<div className="error-boundary-icon">
|
|
58
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
59
|
+
<circle cx="12" cy="12" r="10" />
|
|
60
|
+
<line x1="12" y1="8" x2="12" y2="12" />
|
|
61
|
+
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
62
|
+
</svg>
|
|
63
|
+
</div>
|
|
64
|
+
<h3 className="error-boundary-title">
|
|
65
|
+
{this.props.title || 'Something went wrong'}
|
|
66
|
+
</h3>
|
|
67
|
+
<p className="error-boundary-message">
|
|
68
|
+
{this.state.error?.message || 'An unexpected error occurred'}
|
|
69
|
+
</p>
|
|
70
|
+
<button
|
|
71
|
+
className="btn btn-primary"
|
|
72
|
+
onClick={this.handleRetry}
|
|
73
|
+
type="button"
|
|
74
|
+
>
|
|
75
|
+
Try Again
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return this.props.children;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
interface ErrorStateProps {
|
|
4
|
+
/** The error to display */
|
|
5
|
+
error: Error | string | null | undefined;
|
|
6
|
+
/** Optional title for the error (defaults to "Something went wrong") */
|
|
7
|
+
title?: string;
|
|
8
|
+
/** Optional retry handler - shows a retry button when provided */
|
|
9
|
+
onRetry?: () => void;
|
|
10
|
+
/** Whether a retry is currently in progress */
|
|
11
|
+
isRetrying?: boolean;
|
|
12
|
+
/** Optional custom icon */
|
|
13
|
+
icon?: ReactNode;
|
|
14
|
+
/** Optional additional CSS class */
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* ErrorState displays a full-page or section error state with
|
|
20
|
+
* an icon, message, and optional retry button.
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* ```tsx
|
|
24
|
+
* if (error) {
|
|
25
|
+
* return <ErrorState error={error} onRetry={() => refetch()} />
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function ErrorState({
|
|
30
|
+
error,
|
|
31
|
+
title = 'Something went wrong',
|
|
32
|
+
onRetry,
|
|
33
|
+
isRetrying = false,
|
|
34
|
+
icon,
|
|
35
|
+
className = '',
|
|
36
|
+
}: ErrorStateProps) {
|
|
37
|
+
const errorMessage = error instanceof Error ? error.message : error ?? 'An unexpected error occurred';
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className={`error-state ${className}`} role="alert" data-testid="error-state">
|
|
41
|
+
<div className="error-state-icon">
|
|
42
|
+
{icon ?? (
|
|
43
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
|
44
|
+
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
|
45
|
+
<line x1="12" y1="9" x2="12" y2="13" />
|
|
46
|
+
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
47
|
+
</svg>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
<h3 className="error-state-title">{title}</h3>
|
|
51
|
+
<p className="error-state-message">{errorMessage}</p>
|
|
52
|
+
{onRetry && (
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
className="btn btn-primary"
|
|
56
|
+
onClick={onRetry}
|
|
57
|
+
disabled={isRetrying}
|
|
58
|
+
data-testid="error-retry-button"
|
|
59
|
+
>
|
|
60
|
+
{isRetrying ? 'Retrying...' : 'Try Again'}
|
|
61
|
+
</button>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface ErrorAlertProps {
|
|
68
|
+
/** The error to display */
|
|
69
|
+
error: Error | string | null | undefined;
|
|
70
|
+
/** Optional dismiss handler - shows a dismiss button when provided */
|
|
71
|
+
onDismiss?: () => void;
|
|
72
|
+
/** Optional retry handler - shows a retry button when provided */
|
|
73
|
+
onRetry?: () => void;
|
|
74
|
+
/** Whether a retry is currently in progress */
|
|
75
|
+
isRetrying?: boolean;
|
|
76
|
+
/** Variant of the alert (default: 'error') */
|
|
77
|
+
variant?: 'error' | 'warning';
|
|
78
|
+
/** Optional additional CSS class */
|
|
79
|
+
className?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* ErrorAlert displays an inline error alert with optional dismiss/retry actions.
|
|
84
|
+
*
|
|
85
|
+
* Usage:
|
|
86
|
+
* ```tsx
|
|
87
|
+
* {error && <ErrorAlert error={error} onDismiss={() => setError(null)} />}
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function ErrorAlert({
|
|
91
|
+
error,
|
|
92
|
+
onDismiss,
|
|
93
|
+
onRetry,
|
|
94
|
+
isRetrying = false,
|
|
95
|
+
variant = 'error',
|
|
96
|
+
className = '',
|
|
97
|
+
}: ErrorAlertProps) {
|
|
98
|
+
if (!error) return null;
|
|
99
|
+
|
|
100
|
+
const errorMessage = error instanceof Error ? error.message : error;
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
className={`error-alert error-alert--${variant} ${className}`}
|
|
105
|
+
role="alert"
|
|
106
|
+
data-testid="error-alert"
|
|
107
|
+
>
|
|
108
|
+
<div className="error-alert-icon">
|
|
109
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
110
|
+
<circle cx="12" cy="12" r="10" />
|
|
111
|
+
<line x1="12" y1="8" x2="12" y2="12" />
|
|
112
|
+
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
113
|
+
</svg>
|
|
114
|
+
</div>
|
|
115
|
+
<div className="error-alert-content">
|
|
116
|
+
<p className="error-alert-message">{errorMessage}</p>
|
|
117
|
+
</div>
|
|
118
|
+
<div className="error-alert-actions">
|
|
119
|
+
{onRetry && (
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
className="error-alert-btn error-alert-btn--retry"
|
|
123
|
+
onClick={onRetry}
|
|
124
|
+
disabled={isRetrying}
|
|
125
|
+
data-testid="error-alert-retry"
|
|
126
|
+
>
|
|
127
|
+
{isRetrying ? 'Retrying...' : 'Retry'}
|
|
128
|
+
</button>
|
|
129
|
+
)}
|
|
130
|
+
{onDismiss && (
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
className="error-alert-btn error-alert-btn--dismiss"
|
|
134
|
+
onClick={onDismiss}
|
|
135
|
+
aria-label="Dismiss error"
|
|
136
|
+
data-testid="error-alert-dismiss"
|
|
137
|
+
>
|
|
138
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
139
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
140
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
141
|
+
</svg>
|
|
142
|
+
</button>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { useRouterState, useNavigate, Link } from '@tanstack/react-router'
|
|
2
|
+
import { useAuth, useAdminConfig, useBreadcrumbContext } from '~/contexts'
|
|
3
|
+
import { getRole } from '../../../src/component/roles'
|
|
4
|
+
import {
|
|
5
|
+
Breadcrumb,
|
|
6
|
+
BreadcrumbItem,
|
|
7
|
+
BreadcrumbLink,
|
|
8
|
+
BreadcrumbList,
|
|
9
|
+
BreadcrumbPage,
|
|
10
|
+
BreadcrumbSeparator,
|
|
11
|
+
} from '~/components/ui/breadcrumb'
|
|
12
|
+
import {
|
|
13
|
+
DropdownMenu,
|
|
14
|
+
DropdownMenuContent,
|
|
15
|
+
DropdownMenuItem,
|
|
16
|
+
DropdownMenuLabel,
|
|
17
|
+
DropdownMenuSeparator,
|
|
18
|
+
DropdownMenuTrigger,
|
|
19
|
+
} from '~/components/ui/dropdown-menu'
|
|
20
|
+
import { Avatar, AvatarFallback, AvatarImage } from '~/components/ui/avatar'
|
|
21
|
+
import { Button } from '~/components/ui/button'
|
|
22
|
+
import {
|
|
23
|
+
Popover,
|
|
24
|
+
PopoverContent,
|
|
25
|
+
PopoverTrigger,
|
|
26
|
+
} from '~/components/ui/popover'
|
|
27
|
+
import {
|
|
28
|
+
Bell,
|
|
29
|
+
HelpCircle,
|
|
30
|
+
Home,
|
|
31
|
+
LogOut,
|
|
32
|
+
Settings as _Settings,
|
|
33
|
+
User,
|
|
34
|
+
ChevronDown,
|
|
35
|
+
ExternalLink,
|
|
36
|
+
Book,
|
|
37
|
+
Code,
|
|
38
|
+
MessageSquare,
|
|
39
|
+
} from 'lucide-react'
|
|
40
|
+
import { cn as _cn } from '~/lib/cn'
|
|
41
|
+
import { Fragment } from 'react'
|
|
42
|
+
|
|
43
|
+
interface BreadcrumbData {
|
|
44
|
+
label: string
|
|
45
|
+
to?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const KEYBOARD_SHORTCUTS = [
|
|
49
|
+
{ keys: ['⌘', 'S'], description: 'Save entry' },
|
|
50
|
+
{ keys: ['⌘', '⇧', 'P'], description: 'Publish entry' },
|
|
51
|
+
{ keys: ['⌘', 'K'], description: 'Quick search' },
|
|
52
|
+
{ keys: ['Esc'], description: 'Close modal/panel' },
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
const HELP_RESOURCES = [
|
|
56
|
+
{ label: 'Documentation', url: 'https://docs.convex.dev', icon: Book },
|
|
57
|
+
{ label: 'API Reference', url: 'https://docs.convex.dev/api', icon: Code },
|
|
58
|
+
{ label: 'Community Discord', url: 'https://discord.gg/convex', icon: MessageSquare },
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
const routeLabels: Record<string, string> = {
|
|
62
|
+
'/': 'Dashboard',
|
|
63
|
+
'/content': 'Content',
|
|
64
|
+
'/media': 'Media Library',
|
|
65
|
+
'/content-types': 'Content Types',
|
|
66
|
+
'/settings': 'Settings',
|
|
67
|
+
'/taxonomies': 'Taxonomies',
|
|
68
|
+
'/trash': 'Trash',
|
|
69
|
+
'/entries': 'Entries',
|
|
70
|
+
'/entries/type': 'Content Types',
|
|
71
|
+
'/entries/new': 'New Entry',
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getBreadcrumbs(
|
|
75
|
+
pathname: string,
|
|
76
|
+
appName: string,
|
|
77
|
+
overrides: Map<string, string>
|
|
78
|
+
): BreadcrumbData[] {
|
|
79
|
+
const breadcrumbs: BreadcrumbData[] = [{ label: appName, to: '/' }]
|
|
80
|
+
|
|
81
|
+
if (pathname === '/') {
|
|
82
|
+
return breadcrumbs
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const segments = pathname.split('/').filter(Boolean)
|
|
86
|
+
let currentPath = ''
|
|
87
|
+
|
|
88
|
+
segments.forEach((segment, index) => {
|
|
89
|
+
currentPath += `/${segment}`
|
|
90
|
+
const isLast = index === segments.length - 1
|
|
91
|
+
|
|
92
|
+
// Check for dynamic overrides first, then static labels, then fallback
|
|
93
|
+
let label = overrides.get(currentPath) ?? routeLabels[currentPath]
|
|
94
|
+
if (!label) {
|
|
95
|
+
label = segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (isLast) {
|
|
99
|
+
breadcrumbs.push({ label })
|
|
100
|
+
} else {
|
|
101
|
+
breadcrumbs.push({ label, to: currentPath })
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
return breadcrumbs
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function Header() {
|
|
109
|
+
const routerState = useRouterState()
|
|
110
|
+
const navigate = useNavigate()
|
|
111
|
+
const { branding } = useAdminConfig()
|
|
112
|
+
|
|
113
|
+
let overrides = new Map<string, string>()
|
|
114
|
+
try {
|
|
115
|
+
const breadcrumbContext = useBreadcrumbContext()
|
|
116
|
+
overrides = breadcrumbContext.overrides
|
|
117
|
+
} catch {
|
|
118
|
+
// BreadcrumbProvider not available, use empty overrides
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const breadcrumbs = getBreadcrumbs(routerState.location.pathname, branding.appName, overrides)
|
|
122
|
+
|
|
123
|
+
let user = null
|
|
124
|
+
let role = null
|
|
125
|
+
let logout = async () => {}
|
|
126
|
+
let isAuthenticated = false
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const auth = useAuth()
|
|
130
|
+
user = auth.user
|
|
131
|
+
role = auth.role
|
|
132
|
+
logout = auth.logout
|
|
133
|
+
isAuthenticated = auth.isAuthenticated
|
|
134
|
+
} catch {
|
|
135
|
+
// AuthProvider not available
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const roleDefinition = role ? getRole(role) : null
|
|
139
|
+
const roleDisplayName = roleDefinition?.displayName ?? role ?? 'No Role'
|
|
140
|
+
const userDisplayName = user?.name ?? user?.email ?? 'User'
|
|
141
|
+
|
|
142
|
+
const getInitials = (name: string) => {
|
|
143
|
+
const parts = name.split(' ')
|
|
144
|
+
if (parts.length >= 2) {
|
|
145
|
+
return `${parts[0][0]}${parts[1][0]}`.toUpperCase()
|
|
146
|
+
}
|
|
147
|
+
return name.slice(0, 2).toUpperCase()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const userInitials = user?.name ? getInitials(user.name) : 'U'
|
|
151
|
+
|
|
152
|
+
const handleLogout = async () => {
|
|
153
|
+
await logout()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<header className="sticky top-0 z-40 flex h-14 items-center justify-between border-b border-border bg-background/95 px-6 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
158
|
+
<Breadcrumb>
|
|
159
|
+
<BreadcrumbList>
|
|
160
|
+
{breadcrumbs.map((crumb, index) => (
|
|
161
|
+
<Fragment key={index}>
|
|
162
|
+
{index > 0 && <BreadcrumbSeparator />}
|
|
163
|
+
<BreadcrumbItem>
|
|
164
|
+
{crumb.to ? (
|
|
165
|
+
<BreadcrumbLink asChild>
|
|
166
|
+
<Link to={crumb.to} className="flex items-center gap-1.5">
|
|
167
|
+
{index === 0 && <Home className="size-3.5" />}
|
|
168
|
+
<span>{crumb.label}</span>
|
|
169
|
+
</Link>
|
|
170
|
+
</BreadcrumbLink>
|
|
171
|
+
) : (
|
|
172
|
+
<BreadcrumbPage>{crumb.label}</BreadcrumbPage>
|
|
173
|
+
)}
|
|
174
|
+
</BreadcrumbItem>
|
|
175
|
+
</Fragment>
|
|
176
|
+
))}
|
|
177
|
+
</BreadcrumbList>
|
|
178
|
+
</Breadcrumb>
|
|
179
|
+
|
|
180
|
+
<div className="flex items-center gap-1">
|
|
181
|
+
<Popover>
|
|
182
|
+
<PopoverTrigger asChild>
|
|
183
|
+
<Button variant="ghost" size="icon" className="size-9">
|
|
184
|
+
<Bell className="size-4" />
|
|
185
|
+
<span className="sr-only">Notifications</span>
|
|
186
|
+
</Button>
|
|
187
|
+
</PopoverTrigger>
|
|
188
|
+
<PopoverContent align="end" className="w-80">
|
|
189
|
+
<div className="flex items-center justify-between pb-2">
|
|
190
|
+
<h4 className="font-medium">Notifications</h4>
|
|
191
|
+
</div>
|
|
192
|
+
<div className="flex flex-col items-center justify-center py-8 text-center">
|
|
193
|
+
<Bell className="size-8 text-muted-foreground/50" />
|
|
194
|
+
<p className="mt-2 text-sm font-medium">No notifications yet</p>
|
|
195
|
+
<p className="text-xs text-muted-foreground">You're all caught up!</p>
|
|
196
|
+
</div>
|
|
197
|
+
</PopoverContent>
|
|
198
|
+
</Popover>
|
|
199
|
+
|
|
200
|
+
<Popover>
|
|
201
|
+
<PopoverTrigger asChild>
|
|
202
|
+
<Button variant="ghost" size="icon" className="size-9">
|
|
203
|
+
<HelpCircle className="size-4" />
|
|
204
|
+
<span className="sr-only">Help</span>
|
|
205
|
+
</Button>
|
|
206
|
+
</PopoverTrigger>
|
|
207
|
+
<PopoverContent align="end" className="w-72">
|
|
208
|
+
<div className="pb-2">
|
|
209
|
+
<h4 className="font-medium">Help & Resources</h4>
|
|
210
|
+
</div>
|
|
211
|
+
<div className="space-y-4">
|
|
212
|
+
<div>
|
|
213
|
+
<h5 className="mb-2 text-xs font-medium text-muted-foreground">Keyboard Shortcuts</h5>
|
|
214
|
+
<div className="space-y-1">
|
|
215
|
+
{KEYBOARD_SHORTCUTS.map((shortcut, index) => (
|
|
216
|
+
<div key={index} className="flex items-center justify-between text-sm">
|
|
217
|
+
<span className="text-muted-foreground">{shortcut.description}</span>
|
|
218
|
+
<div className="flex gap-0.5">
|
|
219
|
+
{shortcut.keys.map((key, keyIndex) => (
|
|
220
|
+
<kbd
|
|
221
|
+
key={keyIndex}
|
|
222
|
+
className="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-xs"
|
|
223
|
+
>
|
|
224
|
+
{key}
|
|
225
|
+
</kbd>
|
|
226
|
+
))}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
))}
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
<div>
|
|
233
|
+
<h5 className="mb-2 text-xs font-medium text-muted-foreground">Resources</h5>
|
|
234
|
+
<div className="space-y-1">
|
|
235
|
+
{HELP_RESOURCES.map((resource) => (
|
|
236
|
+
<a
|
|
237
|
+
key={resource.label}
|
|
238
|
+
href={resource.url}
|
|
239
|
+
target="_blank"
|
|
240
|
+
rel="noopener noreferrer"
|
|
241
|
+
className="flex items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent"
|
|
242
|
+
>
|
|
243
|
+
<resource.icon className="size-4 text-muted-foreground" />
|
|
244
|
+
<span className="flex-1">{resource.label}</span>
|
|
245
|
+
<ExternalLink className="size-3 text-muted-foreground" />
|
|
246
|
+
</a>
|
|
247
|
+
))}
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</PopoverContent>
|
|
252
|
+
</Popover>
|
|
253
|
+
|
|
254
|
+
<DropdownMenu>
|
|
255
|
+
<DropdownMenuTrigger asChild>
|
|
256
|
+
<Button variant="ghost" className="h-9 gap-2 pl-2 pr-3">
|
|
257
|
+
<Avatar className="size-6">
|
|
258
|
+
<AvatarImage src={user?.avatarUrl} alt={userDisplayName} />
|
|
259
|
+
<AvatarFallback className="text-xs">{userInitials}</AvatarFallback>
|
|
260
|
+
</Avatar>
|
|
261
|
+
<span className="text-sm font-medium">{userDisplayName}</span>
|
|
262
|
+
<ChevronDown className="size-3.5 text-muted-foreground" />
|
|
263
|
+
</Button>
|
|
264
|
+
</DropdownMenuTrigger>
|
|
265
|
+
<DropdownMenuContent align="end" className="w-56">
|
|
266
|
+
<DropdownMenuLabel className="font-normal">
|
|
267
|
+
<div className="flex flex-col space-y-1">
|
|
268
|
+
<p className="text-sm font-medium">{userDisplayName}</p>
|
|
269
|
+
{user?.email && (
|
|
270
|
+
<p className="text-xs text-muted-foreground">{user.email}</p>
|
|
271
|
+
)}
|
|
272
|
+
<p className="text-xs text-muted-foreground">{roleDisplayName}</p>
|
|
273
|
+
</div>
|
|
274
|
+
</DropdownMenuLabel>
|
|
275
|
+
<DropdownMenuSeparator />
|
|
276
|
+
<DropdownMenuItem onClick={() => navigate({ to: '/settings' })}>
|
|
277
|
+
<User className="mr-2 size-4" />
|
|
278
|
+
<span>Profile & Settings</span>
|
|
279
|
+
</DropdownMenuItem>
|
|
280
|
+
{isAuthenticated && (
|
|
281
|
+
<>
|
|
282
|
+
<DropdownMenuSeparator />
|
|
283
|
+
<DropdownMenuItem onClick={handleLogout} className="text-destructive focus:text-destructive">
|
|
284
|
+
<LogOut className="mr-2 size-4" />
|
|
285
|
+
<span>Logout</span>
|
|
286
|
+
</DropdownMenuItem>
|
|
287
|
+
</>
|
|
288
|
+
)}
|
|
289
|
+
</DropdownMenuContent>
|
|
290
|
+
</DropdownMenu>
|
|
291
|
+
</div>
|
|
292
|
+
</header>
|
|
293
|
+
)
|
|
294
|
+
}
|