@carlonicora/nextjs-jsonapi 5.3.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BlockNoteEditor-WTSQANKI.mjs → BlockNoteEditor-GVQCKRMU.mjs} +9 -8
- package/dist/BlockNoteEditor-GVQCKRMU.mjs.map +1 -0
- package/dist/{BlockNoteEditor-UJ6363LC.js → BlockNoteEditor-THBJH2UR.js} +24 -23
- package/dist/BlockNoteEditor-THBJH2UR.js.map +1 -0
- package/dist/{BlockNoteViewer-JVKWJZ6J.mjs → BlockNoteViewer-U6E3DS4F.mjs} +3 -3
- package/dist/{BlockNoteViewer-EL4JFB6P.js → BlockNoteViewer-VZOVKTFO.js} +4 -4
- package/dist/{BlockNoteViewer-EL4JFB6P.js.map → BlockNoteViewer-VZOVKTFO.js.map} +1 -1
- package/dist/BlogCategoryFields-DO48vOTE.d.mts +14 -0
- package/dist/BlogCategoryFields-DO48vOTE.d.ts +14 -0
- package/dist/BlogPostInterface-BB_4wcDR.d.mts +124 -0
- package/dist/BlogPostInterface-bWAT1rfR.d.ts +124 -0
- package/dist/billing/index.js +442 -442
- package/dist/billing/index.mjs +4 -4
- package/dist/blog-public.types-BbrzsC1c.d.mts +30 -0
- package/dist/blog-public.types-BbrzsC1c.d.ts +30 -0
- package/dist/chunk-3CGROPF3.mjs +107 -0
- package/dist/chunk-3CGROPF3.mjs.map +1 -0
- package/dist/{chunk-QKZUE5OF.mjs → chunk-3YW35YHQ.mjs} +1 -101
- package/dist/chunk-3YW35YHQ.mjs.map +1 -0
- package/dist/{chunk-6WU74VFU.mjs → chunk-6SQYXUVP.mjs} +2 -2
- package/dist/{chunk-CRI4T47Y.js → chunk-G6UE6MEP.js} +814 -59
- package/dist/chunk-G6UE6MEP.js.map +1 -0
- package/dist/{chunk-XGTQWBDJ.js → chunk-KSYQEKJX.js} +11 -111
- package/dist/chunk-KSYQEKJX.js.map +1 -0
- package/dist/{chunk-EO75CV76.js → chunk-S4YJ5WCW.js} +8 -8
- package/dist/{chunk-EO75CV76.js.map → chunk-S4YJ5WCW.js.map} +1 -1
- package/dist/{chunk-3BAM23VA.js → chunk-TY5FYP3G.js} +930 -883
- package/dist/chunk-TY5FYP3G.js.map +1 -0
- package/dist/{chunk-JQWI62NV.mjs → chunk-VC5A3AUZ.mjs} +776 -21
- package/dist/chunk-VC5A3AUZ.mjs.map +1 -0
- package/dist/{chunk-FNTQQFLH.mjs → chunk-W7F5BDSV.mjs} +2 -2
- package/dist/{chunk-QQQE3N2X.js → chunk-WPYT4UKJ.js} +3 -3
- package/dist/{chunk-QQQE3N2X.js.map → chunk-WPYT4UKJ.js.map} +1 -1
- package/dist/{chunk-MIZTWBPX.mjs → chunk-X5WIUUNG.mjs} +1485 -1438
- package/dist/chunk-X5WIUUNG.mjs.map +1 -0
- package/dist/chunk-YDYVJM4I.js +107 -0
- package/dist/chunk-YDYVJM4I.js.map +1 -0
- package/dist/client/index.js +5 -5
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +4 -4
- package/dist/components/editors/server.d.mts +15 -0
- package/dist/components/editors/server.d.ts +15 -0
- package/dist/components/editors/server.js +38 -0
- package/dist/components/editors/server.js.map +1 -0
- package/dist/components/editors/server.mjs +38 -0
- package/dist/components/editors/server.mjs.map +1 -0
- package/dist/components/index.d.mts +46 -12
- package/dist/components/index.d.ts +46 -12
- package/dist/components/index.js +7 -5
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +8 -6
- package/dist/contexts/index.d.mts +9 -1
- package/dist/contexts/index.d.ts +9 -1
- package/dist/contexts/index.js +5 -5
- package/dist/contexts/index.mjs +4 -4
- package/dist/core/index.d.mts +214 -3
- package/dist/core/index.d.ts +214 -3
- package/dist/core/index.js +38 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +37 -3
- package/dist/email-automations/index.d.mts +3 -0
- package/dist/email-automations/index.d.ts +3 -0
- package/dist/email-automations/index.js +99 -95
- package/dist/email-automations/index.js.map +1 -1
- package/dist/email-automations/index.mjs +30 -26
- package/dist/email-automations/index.mjs.map +1 -1
- package/dist/features/analytics/index.d.mts +11 -4
- package/dist/features/analytics/index.d.ts +11 -4
- package/dist/features/analytics/index.js +127 -135
- package/dist/features/analytics/index.js.map +1 -1
- package/dist/features/analytics/index.mjs +16 -24
- package/dist/features/analytics/index.mjs.map +1 -1
- package/dist/features/analytics/server.d.mts +5 -2
- package/dist/features/analytics/server.d.ts +5 -2
- package/dist/features/analytics/server.js +9 -8
- package/dist/features/analytics/server.js.map +1 -1
- package/dist/features/analytics/server.mjs +7 -6
- package/dist/features/analytics/server.mjs.map +1 -1
- package/dist/features/blog/index.css +30 -0
- package/dist/features/blog/index.css.map +1 -0
- package/dist/features/blog/index.d.mts +276 -0
- package/dist/features/blog/index.d.ts +276 -0
- package/dist/features/blog/index.js +1810 -0
- package/dist/features/blog/index.js.map +1 -0
- package/dist/features/blog/index.mjs +1810 -0
- package/dist/features/blog/index.mjs.map +1 -0
- package/dist/features/blog/server.d.mts +36 -0
- package/dist/features/blog/server.d.ts +36 -0
- package/dist/features/blog/server.js +167 -0
- package/dist/features/blog/server.js.map +1 -0
- package/dist/features/blog/server.mjs +167 -0
- package/dist/features/blog/server.mjs.map +1 -0
- package/dist/features/help/index.js +38 -38
- package/dist/features/help/index.mjs +4 -4
- package/dist/features/tokenusage/index.d.mts +1 -3
- package/dist/features/tokenusage/index.d.ts +1 -3
- package/dist/features/tokenusage/index.js +127 -135
- package/dist/features/tokenusage/index.js.map +1 -1
- package/dist/features/tokenusage/index.mjs +10 -18
- package/dist/features/tokenusage/index.mjs.map +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +39 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +38 -4
- package/dist/{s3.service-CY-jgOAM.d.ts → s3.service-Bz_K-tjz.d.ts} +11 -0
- package/dist/{s3.service-Sri0QP7Z.d.mts → s3.service-D5zNCEW3.d.mts} +11 -0
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +9 -7
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +13 -11
- package/messages/en.json +93 -0
- package/messages/it.json +93 -0
- package/package.json +19 -1
- package/src/components/editors/BlockNoteEditor.tsx +13 -3
- package/src/components/editors/server/BlockNoteServerContent.tsx +28 -0
- package/src/components/editors/server/__tests__/renderBlockNoteHTML.spec.ts +44 -0
- package/src/components/editors/server/renderBlockNoteHTML.ts +26 -0
- package/src/components/editors/server-entry.ts +7 -0
- package/src/components/forms/FormBlockNote.tsx +4 -0
- package/src/components/index.ts +1 -0
- package/src/components/navigations/ContentTitle.tsx +14 -3
- package/src/components/viewers/BlockNoteStaticViewer.tsx +44 -0
- package/src/core/index.ts +4 -0
- package/src/core/registry/ModuleRegistry.ts +6 -0
- package/src/features/administration/components/AdminIndexContainer.tsx +61 -33
- package/src/features/administration/components/__tests__/AdminIndexContainer.spec.tsx +61 -1
- package/src/features/administration/components/index.ts +2 -1
- package/src/features/administration/contexts/AdministrationContext.tsx +11 -3
- package/src/features/administration/contexts/__tests__/AdministrationContext.spec.tsx +40 -0
- package/src/features/ai-connection/ai-connection.module.ts +2 -0
- package/src/features/ai-connection/contexts/AiConnectionsContext.tsx +3 -16
- package/src/features/ai-connection/contexts/__tests__/AiConnectionsContext.spec.tsx +33 -0
- package/src/features/analytics/__tests__/server-entry.spec.tsx +2 -1
- package/src/features/analytics/analytics.modules.ts +22 -5
- package/src/features/analytics/components/AnalyticsAdminContainer.tsx +4 -3
- package/src/features/analytics/components/AnalyticsAdminPageContainer.tsx +6 -3
- package/src/features/analytics/components/AnalyticsJourneyContainer.tsx +4 -3
- package/src/features/analytics/components/__tests__/AnalyticsJourneyContainer.spec.tsx +11 -0
- package/src/features/analytics/components/__tests__/AnalyticsPageContainers.spec.tsx +12 -1
- package/src/features/analytics/components/server/AnalyticsAdminPage.tsx +6 -3
- package/src/features/analytics/contexts/AnalyticsAdminContext.tsx +8 -17
- package/src/features/analytics/contexts/__tests__/AnalyticsAdminContext.spec.tsx +17 -0
- package/src/features/billing/stripe-price/stripe-price.module.ts +2 -0
- package/src/features/billing/stripe-product/stripe-product.module.ts +2 -0
- package/src/features/blog/BlogModules.ts +49 -0
- package/src/features/blog/components/admin/BlogAdminContainer.tsx +39 -0
- package/src/features/blog/components/admin/BlogCategoriesList.tsx +56 -0
- package/src/features/blog/components/admin/BlogCategoryEditor.tsx +230 -0
- package/src/features/blog/components/admin/BlogCoverImageField.tsx +123 -0
- package/src/features/blog/components/admin/BlogLayoutSetting.tsx +86 -0
- package/src/features/blog/components/admin/BlogPostComparison.tsx +54 -0
- package/src/features/blog/components/admin/BlogPostContainer.tsx +101 -0
- package/src/features/blog/components/admin/BlogPostDeleter.tsx +35 -0
- package/src/features/blog/components/admin/BlogPostEditor.tsx +216 -0
- package/src/features/blog/components/admin/BlogPostPublishButton.tsx +50 -0
- package/src/features/blog/components/admin/BlogPostTranslationEditor.tsx +118 -0
- package/src/features/blog/components/admin/BlogPostTranslationPanel.tsx +136 -0
- package/src/features/blog/components/admin/BlogPostsList.tsx +58 -0
- package/src/features/blog/components/admin/BlogTranslationStatus.tsx +104 -0
- package/src/features/blog/components/admin/__tests__/BlogPostTranslationPanel.spec.tsx +100 -0
- package/src/features/blog/components/admin/__tests__/BlogPostsList.spec.tsx +30 -0
- package/src/features/blog/components/admin/index.ts +14 -0
- package/src/features/blog/components/public/BlogCategoryChips.tsx +59 -0
- package/src/features/blog/components/public/BlogList.tsx +21 -0
- package/src/features/blog/components/public/BlogListColumn.tsx +53 -0
- package/src/features/blog/components/public/BlogListFeatured.tsx +57 -0
- package/src/features/blog/components/public/BlogListParts.tsx +74 -0
- package/src/features/blog/components/public/BlogListRail.tsx +62 -0
- package/src/features/blog/components/public/BlogPostCard.tsx +125 -0
- package/src/features/blog/components/public/BlogPostMeta.tsx +49 -0
- package/src/features/blog/components/public/BlogPostView.tsx +80 -0
- package/src/features/blog/components/public/BlogSearchBox.tsx +52 -0
- package/src/features/blog/components/public/__tests__/BlogList.spec.tsx +175 -0
- package/src/features/blog/components/public/__tests__/BlogPostView.spec.tsx +134 -0
- package/src/features/blog/components/public/blogPublicHelpers.ts +30 -0
- package/src/features/blog/components/public/index.ts +3 -0
- package/src/features/blog/contexts/BlogPostContext.tsx +66 -0
- package/src/features/blog/contexts/index.ts +1 -0
- package/src/features/blog/data/BlogCategory.ts +93 -0
- package/src/features/blog/data/BlogCategoryFields.ts +4 -0
- package/src/features/blog/data/BlogCategoryInterface.ts +24 -0
- package/src/features/blog/data/BlogCategoryService.ts +54 -0
- package/src/features/blog/data/BlogCategoryTranslation.ts +90 -0
- package/src/features/blog/data/BlogCategoryTranslationInterface.ts +22 -0
- package/src/features/blog/data/BlogCategoryTranslationService.ts +30 -0
- package/src/features/blog/data/BlogPost.spec.ts +123 -0
- package/src/features/blog/data/BlogPost.ts +159 -0
- package/src/features/blog/data/BlogPostFields.ts +7 -0
- package/src/features/blog/data/BlogPostInterface.ts +39 -0
- package/src/features/blog/data/BlogPostService.ts +82 -0
- package/src/features/blog/data/BlogPostTranslation.spec.ts +73 -0
- package/src/features/blog/data/BlogPostTranslation.ts +110 -0
- package/src/features/blog/data/BlogPostTranslationInterface.ts +27 -0
- package/src/features/blog/data/BlogPostTranslationService.ts +30 -0
- package/src/features/blog/data/BlogSettings.ts +37 -0
- package/src/features/blog/data/BlogSettingsInterface.ts +11 -0
- package/src/features/blog/data/BlogSettingsService.ts +24 -0
- package/src/features/blog/data/index.ts +17 -0
- package/src/features/blog/hooks/index.ts +3 -0
- package/src/features/blog/hooks/useBlogCategoryNames.ts +34 -0
- package/src/features/blog/hooks/useBlogCategoryTableStructure.tsx +84 -0
- package/src/features/blog/hooks/useBlogPostTableStructure.tsx +126 -0
- package/src/features/blog/index.ts +15 -0
- package/src/features/blog/server/fetchers.ts +169 -0
- package/src/features/blog/server/toPublic.spec.ts +91 -0
- package/src/features/blog/server/toPublic.ts +36 -0
- package/src/features/blog/server-entry.ts +17 -0
- package/src/features/blog/types/blog-public.types.ts +34 -0
- package/src/features/blog/types/blog.constants.ts +40 -0
- package/src/features/email-automation/components/containers/EmailAutomationsListContainer.tsx +12 -3
- package/src/features/rbac/contexts/RbacContext.tsx +4 -7
- package/src/features/rbac/contexts/__tests__/RbacContext.spec.tsx +42 -0
- package/src/features/s3/data/s3.service.ts +17 -0
- package/src/features/tokenusage/components/TokenUsageAdminContainer.tsx +4 -3
- package/src/features/tokenusage/contexts/TokenUsageAdminContext.tsx +3 -18
- package/src/features/tokenusage/contexts/__tests__/TokenUsageAdminContext.spec.tsx +17 -0
- package/src/features/tokenusage/tokenusage-admin.module.ts +4 -3
- package/src/features/tokenusage/tokenusage.modules.ts +12 -2
- package/src/features/user/contexts/UserContext.tsx +18 -3
- package/src/features/user/contexts/__tests__/UserContext.spec.tsx +95 -0
- package/dist/BlockNoteEditor-UJ6363LC.js.map +0 -1
- package/dist/BlockNoteEditor-WTSQANKI.mjs.map +0 -1
- package/dist/chunk-3BAM23VA.js.map +0 -1
- package/dist/chunk-CRI4T47Y.js.map +0 -1
- package/dist/chunk-JQWI62NV.mjs.map +0 -1
- package/dist/chunk-MIZTWBPX.mjs.map +0 -1
- package/dist/chunk-QKZUE5OF.mjs.map +0 -1
- package/dist/chunk-XGTQWBDJ.js.map +0 -1
- /package/dist/{BlockNoteViewer-JVKWJZ6J.mjs.map → BlockNoteViewer-U6E3DS4F.mjs.map} +0 -0
- /package/dist/{chunk-6WU74VFU.mjs.map → chunk-6SQYXUVP.mjs.map} +0 -0
- /package/dist/{chunk-FNTQQFLH.mjs.map → chunk-W7F5BDSV.mjs.map} +0 -0
package/messages/it.json
CHANGED
|
@@ -736,6 +736,99 @@
|
|
|
736
736
|
}
|
|
737
737
|
}
|
|
738
738
|
},
|
|
739
|
+
"blog": {
|
|
740
|
+
"admin": {
|
|
741
|
+
"title": "Blog",
|
|
742
|
+
"newPost": "Nuovo articolo",
|
|
743
|
+
"editPost": "Modifica articolo",
|
|
744
|
+
"newCategory": "Nuova categoria",
|
|
745
|
+
"editCategory": "Modifica categoria",
|
|
746
|
+
"empty": "Nessun articolo. Scrivi il primo.",
|
|
747
|
+
"emptyCategories": "Nessuna categoria."
|
|
748
|
+
},
|
|
749
|
+
"tabs": {
|
|
750
|
+
"posts": "Articoli",
|
|
751
|
+
"categories": "Categorie",
|
|
752
|
+
"layout": "Impaginazione"
|
|
753
|
+
},
|
|
754
|
+
"fields": {
|
|
755
|
+
"title": "Titolo",
|
|
756
|
+
"slug": "Slug",
|
|
757
|
+
"slugHelp": "L'ultima parte dell'indirizzo dell'articolo. Cambiarlo rompe i link che puntano già all'articolo.",
|
|
758
|
+
"summary": "Sommario",
|
|
759
|
+
"summaryHelp": "Lascialo vuoto e lo scriverà l'AI a partire dal testo.",
|
|
760
|
+
"body": "Testo",
|
|
761
|
+
"coverImage": "Immagine di copertina",
|
|
762
|
+
"coverImageAlt": "Descrizione dell'immagine di copertina",
|
|
763
|
+
"categories": "Categorie",
|
|
764
|
+
"originalLanguage": "Lingua originale",
|
|
765
|
+
"name": "Nome",
|
|
766
|
+
"description": "Descrizione",
|
|
767
|
+
"image": "Immagine",
|
|
768
|
+
"status": "Stato",
|
|
769
|
+
"publishedAt": "Pubblicato",
|
|
770
|
+
"languages": "Lingue pronte"
|
|
771
|
+
},
|
|
772
|
+
"status": {
|
|
773
|
+
"draft": "Bozza",
|
|
774
|
+
"published": "Pubblicato",
|
|
775
|
+
"unpublished": "Ritirato"
|
|
776
|
+
},
|
|
777
|
+
"actions": {
|
|
778
|
+
"publish": "Pubblica",
|
|
779
|
+
"unpublish": "Ritira",
|
|
780
|
+
"edit": "Modifica",
|
|
781
|
+
"translateAgain": "Traduci di nuovo",
|
|
782
|
+
"compareWithOriginal": "Confronta con l'originale",
|
|
783
|
+
"hideComparison": "Nascondi confronto"
|
|
784
|
+
},
|
|
785
|
+
"translation": {
|
|
786
|
+
"original": "Originale",
|
|
787
|
+
"translatedFrom": "Lingua originale: {language}, tradotto il {date}",
|
|
788
|
+
"editedByHand": "Modificato a mano",
|
|
789
|
+
"overwriteWarning": "Modificare l'originale lo farà tradurre di nuovo e sostituirà queste modifiche.",
|
|
790
|
+
"translating": "Traduzione in corso…",
|
|
791
|
+
"failed": "Traduzione non riuscita",
|
|
792
|
+
"pending": "In attesa di traduzione"
|
|
793
|
+
},
|
|
794
|
+
"layout": {
|
|
795
|
+
"title": "Impaginazione dell'elenco pubblico",
|
|
796
|
+
"featured": "In evidenza",
|
|
797
|
+
"featuredDescription": "L'articolo più recente in grande, poi una griglia di schede su tre colonne.",
|
|
798
|
+
"column": "Colonna",
|
|
799
|
+
"columnDescription": "Una colonna di lettura stretta, una riga per articolo con l'immagine accanto al testo.",
|
|
800
|
+
"rail": "Barra laterale",
|
|
801
|
+
"railDescription": "Ricerca e categorie in una barra laterale, l'articolo più recente in grande, gli altri su due colonne.",
|
|
802
|
+
"saved": "Impaginazione salvata"
|
|
803
|
+
},
|
|
804
|
+
"public": {
|
|
805
|
+
"eyebrow": "Blog",
|
|
806
|
+
"title": "Note e guide",
|
|
807
|
+
"subtitle": "Idee, guide e novità dal team.",
|
|
808
|
+
"all": "Tutti",
|
|
809
|
+
"search": "Cerca",
|
|
810
|
+
"searchPlaceholder": "Cerca negli articoli",
|
|
811
|
+
"loadMore": "Carica altri",
|
|
812
|
+
"readingTime": "{minutes} min di lettura",
|
|
813
|
+
"updated": "Aggiornato il {date}",
|
|
814
|
+
"by": "Di {author}",
|
|
815
|
+
"back": "Tutti gli articoli",
|
|
816
|
+
"moreIn": "Altro in {category}",
|
|
817
|
+
"empty": "Ancora nessun articolo.",
|
|
818
|
+
"noResults": "Nessun articolo corrisponde alla ricerca."
|
|
819
|
+
},
|
|
820
|
+
"entities": {
|
|
821
|
+
"post": "Articolo",
|
|
822
|
+
"posts": "Articoli",
|
|
823
|
+
"category": "Categoria",
|
|
824
|
+
"categories": "Categorie"
|
|
825
|
+
},
|
|
826
|
+
"errors": {
|
|
827
|
+
"title": "Il titolo è obbligatorio",
|
|
828
|
+
"name": "Il nome è obbligatorio",
|
|
829
|
+
"language": "Scegli la lingua originale"
|
|
830
|
+
}
|
|
831
|
+
},
|
|
739
832
|
"canvas": {
|
|
740
833
|
"toolbar": {
|
|
741
834
|
"fitView": "Adatta alla vista",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carlonicora/nextjs-jsonapi",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"description": "Next.js JSON:API client with server/client support and caching",
|
|
5
5
|
"author": "Carlo Nicora",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -49,6 +49,11 @@
|
|
|
49
49
|
"require": "./dist/contexts/index.js"
|
|
50
50
|
},
|
|
51
51
|
"./contexts/styles.css": "./dist/contexts/index.css",
|
|
52
|
+
"./blocknote/server": {
|
|
53
|
+
"types": "./dist/components/editors/server.d.ts",
|
|
54
|
+
"import": "./dist/components/editors/server.mjs",
|
|
55
|
+
"require": "./dist/components/editors/server.js"
|
|
56
|
+
},
|
|
52
57
|
"./testing": {
|
|
53
58
|
"types": "./dist/testing/index.d.ts",
|
|
54
59
|
"import": "./dist/testing/index.mjs",
|
|
@@ -74,6 +79,16 @@
|
|
|
74
79
|
"import": "./dist/features/help/server.mjs",
|
|
75
80
|
"require": "./dist/features/help/server.js"
|
|
76
81
|
},
|
|
82
|
+
"./blog": {
|
|
83
|
+
"types": "./dist/features/blog/index.d.ts",
|
|
84
|
+
"import": "./dist/features/blog/index.mjs",
|
|
85
|
+
"require": "./dist/features/blog/index.js"
|
|
86
|
+
},
|
|
87
|
+
"./blog/server": {
|
|
88
|
+
"types": "./dist/features/blog/server.d.ts",
|
|
89
|
+
"import": "./dist/features/blog/server.mjs",
|
|
90
|
+
"require": "./dist/features/blog/server.js"
|
|
91
|
+
},
|
|
77
92
|
"./tokenusage": {
|
|
78
93
|
"types": "./dist/features/tokenusage/index.d.ts",
|
|
79
94
|
"import": "./dist/features/tokenusage/index.mjs",
|
|
@@ -154,6 +169,7 @@
|
|
|
154
169
|
"@base-ui/react": "1.8.0",
|
|
155
170
|
"@blocknote/core": "^0.55.0",
|
|
156
171
|
"@blocknote/react": "^0.55.0",
|
|
172
|
+
"@blocknote/server-util": "^0.55.0",
|
|
157
173
|
"@blocknote/shadcn": "^0.55.0",
|
|
158
174
|
"@blocknote/xl-ai": "^0.55.0",
|
|
159
175
|
"@dnd-kit/core": "^6.3.1",
|
|
@@ -203,6 +219,8 @@
|
|
|
203
219
|
"tw-animate-css": "^1.4.0",
|
|
204
220
|
"uuid": "^14.0.2",
|
|
205
221
|
"vaul": "^1.1.2",
|
|
222
|
+
"y-prosemirror": "^1.3.7",
|
|
223
|
+
"y-protocols": "^1.0.6",
|
|
206
224
|
"zod": "^4.6.5"
|
|
207
225
|
},
|
|
208
226
|
"devDependencies": {
|
|
@@ -78,6 +78,13 @@ export type BlockNoteEditorProps = {
|
|
|
78
78
|
diffContent?: PartialBlock[];
|
|
79
79
|
placeholder?: string;
|
|
80
80
|
bordered?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Storage folder for pasted images, used instead of the current company's
|
|
83
|
+
* `companies/<companyId>/<type>` folder: the image key becomes
|
|
84
|
+
* `<uploadKeyPrefix>/<id>/<file name>`. For platform content edited by users
|
|
85
|
+
* who belong to no company (the blog). Omitted: the company folder, as before.
|
|
86
|
+
*/
|
|
87
|
+
uploadKeyPrefix?: string;
|
|
81
88
|
inlineContentSpecs?: Record<string, any>;
|
|
82
89
|
renderOverlays?: (editor: any) => React.ReactNode;
|
|
83
90
|
enableMentions?: boolean;
|
|
@@ -405,6 +412,7 @@ export default function BlockNoteEditor({
|
|
|
405
412
|
diffContent,
|
|
406
413
|
placeholder,
|
|
407
414
|
bordered,
|
|
415
|
+
uploadKeyPrefix,
|
|
408
416
|
inlineContentSpecs,
|
|
409
417
|
renderOverlays,
|
|
410
418
|
enableMentions,
|
|
@@ -547,7 +555,7 @@ export default function BlockNoteEditor({
|
|
|
547
555
|
|
|
548
556
|
const uploadImage = useCallback(
|
|
549
557
|
async (file: File): Promise<string> => {
|
|
550
|
-
if (!company) {
|
|
558
|
+
if (!company && !uploadKeyPrefix) {
|
|
551
559
|
errorToast({
|
|
552
560
|
title: t(`common.errors.upload`),
|
|
553
561
|
error: t(`common.errors.upload_description`),
|
|
@@ -556,7 +564,9 @@ export default function BlockNoteEditor({
|
|
|
556
564
|
}
|
|
557
565
|
|
|
558
566
|
const fileType = file.type;
|
|
559
|
-
const key =
|
|
567
|
+
const key = uploadKeyPrefix
|
|
568
|
+
? `${uploadKeyPrefix}/${id}/${file.name}`
|
|
569
|
+
: `companies/${company!.id}/${type}/${id}/${file.name}`;
|
|
560
570
|
|
|
561
571
|
const s3: S3Interface = await S3Service.getPreSignedUrl({
|
|
562
572
|
key: key,
|
|
@@ -577,7 +587,7 @@ export default function BlockNoteEditor({
|
|
|
577
587
|
|
|
578
588
|
return signedImage.url;
|
|
579
589
|
},
|
|
580
|
-
[company, id, t],
|
|
590
|
+
[company, id, t, type, uploadKeyPrefix],
|
|
581
591
|
);
|
|
582
592
|
|
|
583
593
|
// Utility: Remove trailing empty blocks for read-only display
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PartialBlock } from "@blocknote/core";
|
|
2
|
+
import "@blocknote/shadcn/style.css";
|
|
3
|
+
// Imported through the package's own client subpath, like the analytics server
|
|
4
|
+
// pages: the built server entry must reach the viewer through the "use client"
|
|
5
|
+
// bundle, not inline it.
|
|
6
|
+
import { BlockNoteStaticViewer } from "@carlonicora/nextjs-jsonapi/components";
|
|
7
|
+
import { renderBlockNoteHTML } from "./renderBlockNoteHTML";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Read-only rich text rendered on the server, so the text is in the HTML that
|
|
11
|
+
* crawlers read. For public read-only pages only: everywhere else, display goes
|
|
12
|
+
* through BlockNoteEditorContainer.
|
|
13
|
+
*/
|
|
14
|
+
export async function BlockNoteServerContent({
|
|
15
|
+
content,
|
|
16
|
+
size,
|
|
17
|
+
className,
|
|
18
|
+
}: {
|
|
19
|
+
content?: PartialBlock[];
|
|
20
|
+
size?: "default" | "sm";
|
|
21
|
+
className?: string;
|
|
22
|
+
}) {
|
|
23
|
+
if (!Array.isArray(content) || content.length === 0) return null;
|
|
24
|
+
|
|
25
|
+
const html = await renderBlockNoteHTML(content);
|
|
26
|
+
|
|
27
|
+
return <BlockNoteStaticViewer html={html} size={size} className={className} />;
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
// The server render runs in Node, where there is no window or document of our own:
|
|
3
|
+
// BlockNote's ServerBlockNoteEditor brings its JSDOM.
|
|
4
|
+
import type { PartialBlock } from "@blocknote/core";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { renderBlockNoteHTML } from "../renderBlockNoteHTML";
|
|
7
|
+
|
|
8
|
+
const blocks: PartialBlock[] = [
|
|
9
|
+
{ type: "heading", props: { level: 2 }, content: "Prep situations" },
|
|
10
|
+
{
|
|
11
|
+
type: "paragraph",
|
|
12
|
+
content: [
|
|
13
|
+
{ type: "text", text: "A scene that can go ", styles: {} },
|
|
14
|
+
{ type: "text", text: "three ways", styles: { bold: true } },
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
{ type: "bulletListItem", content: "First clue" },
|
|
18
|
+
{ type: "bulletListItem", content: "Second clue" },
|
|
19
|
+
{
|
|
20
|
+
type: "paragraph",
|
|
21
|
+
content: [{ type: "link", href: "https://example.com/guide", content: "Read the guide" }],
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
describe("renderBlockNoteHTML", () => {
|
|
26
|
+
it("renders the blocks' text into HTML on the server", async () => {
|
|
27
|
+
const html = await renderBlockNoteHTML(blocks);
|
|
28
|
+
|
|
29
|
+
expect(html).toContain("Prep situations");
|
|
30
|
+
expect(html).toMatch(/<h2[^>]*>.*Prep situations.*<\/h2>/s);
|
|
31
|
+
expect(html).toMatch(/<strong[^>]*>three ways<\/strong>/);
|
|
32
|
+
expect(html).toContain("First clue");
|
|
33
|
+
expect(html).toContain("Second clue");
|
|
34
|
+
expect(html).toContain('href="https://example.com/guide"');
|
|
35
|
+
expect(html).toContain("Read the guide");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("leaves the server globals as they were", async () => {
|
|
39
|
+
await Promise.all([renderBlockNoteHTML(blocks), renderBlockNoteHTML(blocks)]);
|
|
40
|
+
|
|
41
|
+
expect(typeof (globalThis as { window?: unknown }).window).toBe("undefined");
|
|
42
|
+
expect(typeof (globalThis as { document?: unknown }).document).toBe("undefined");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PartialBlock } from "@blocknote/core";
|
|
2
|
+
import { ServerBlockNoteEditor } from "@blocknote/server-util";
|
|
3
|
+
|
|
4
|
+
// Default schema only, the same one BlockNoteViewer uses: the editor's mention
|
|
5
|
+
// and diff inline specs are React specs from "use client" modules (next/link,
|
|
6
|
+
// hover cards), so they cannot run here.
|
|
7
|
+
let editor: ServerBlockNoteEditor | undefined;
|
|
8
|
+
|
|
9
|
+
// ServerBlockNoteEditor swaps globalThis.window/document for its JSDOM around
|
|
10
|
+
// each call and restores them after an await. Two overlapping calls would
|
|
11
|
+
// restore each other's values and could leave the JSDOM window on the server's
|
|
12
|
+
// globals, so calls run one at a time.
|
|
13
|
+
let queue: Promise<unknown> = Promise.resolve();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Renders a BlockNote blocks array to the HTML the editor itself would render,
|
|
17
|
+
* through BlockNote's own server editor (`blocksToFullHTML`). Server only.
|
|
18
|
+
*/
|
|
19
|
+
export function renderBlockNoteHTML(blocks: PartialBlock[]): Promise<string> {
|
|
20
|
+
const run = queue.then(() => {
|
|
21
|
+
editor ??= ServerBlockNoteEditor.create();
|
|
22
|
+
return editor.blocksToFullHTML(blocks);
|
|
23
|
+
});
|
|
24
|
+
queue = run.catch(() => undefined);
|
|
25
|
+
return run;
|
|
26
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Server-only barrel for BlockNote rendering.
|
|
2
|
+
// Consumed via `@carlonicora/nextjs-jsonapi/blocknote/server`. Must NOT receive a
|
|
3
|
+
// "use client" directive: it loads @blocknote/server-util (and its JSDOM), which
|
|
4
|
+
// must never reach a client bundle. The app lists @blocknote/server-util in
|
|
5
|
+
// `serverExternalPackages` so Node loads it instead of the bundler.
|
|
6
|
+
|
|
7
|
+
export { BlockNoteServerContent } from "./server/BlockNoteServerContent";
|
|
@@ -41,6 +41,7 @@ export function FormBlockNote({
|
|
|
41
41
|
aiConfig,
|
|
42
42
|
aiHandleRef,
|
|
43
43
|
headerAction,
|
|
44
|
+
uploadKeyPrefix,
|
|
44
45
|
}: {
|
|
45
46
|
form: any;
|
|
46
47
|
id: string;
|
|
@@ -74,6 +75,8 @@ export function FormBlockNote({
|
|
|
74
75
|
aiHandleRef?: React.MutableRefObject<import("../editors/BlockNoteEditor").BlockNoteAiHandle | null>;
|
|
75
76
|
/** Rendered on the label row, right-aligned — e.g. a "Suggest impression" button. */
|
|
76
77
|
headerAction?: React.ReactNode;
|
|
78
|
+
/** See `BlockNoteEditorProps.uploadKeyPrefix`. */
|
|
79
|
+
uploadKeyPrefix?: string;
|
|
77
80
|
}) {
|
|
78
81
|
const initialContentRef = useRef<any>(null);
|
|
79
82
|
const lastEditorContentRef = useRef<any>(undefined);
|
|
@@ -133,6 +136,7 @@ export function FormBlockNote({
|
|
|
133
136
|
bordered
|
|
134
137
|
inlineContentSpecs={inlineContentSpecs}
|
|
135
138
|
renderOverlays={renderOverlays}
|
|
139
|
+
uploadKeyPrefix={uploadKeyPrefix}
|
|
136
140
|
enableMentions={enableMentions}
|
|
137
141
|
mentionSearchFn={mentionSearchFn}
|
|
138
142
|
mentionSearchParams={mentionSearchParams}
|
package/src/components/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./contents";
|
|
|
7
7
|
export * from "./details";
|
|
8
8
|
export * from "./editors";
|
|
9
9
|
export { BlockNoteViewerContainer } from "./viewers/BlockNoteViewerContainer";
|
|
10
|
+
export { BlockNoteStaticViewer } from "./viewers/BlockNoteStaticViewer";
|
|
10
11
|
export * from "./errors";
|
|
11
12
|
export * from "./forms";
|
|
12
13
|
export * from "./navigations";
|
|
@@ -11,9 +11,20 @@ type TitleProps = {
|
|
|
11
11
|
className?: string;
|
|
12
12
|
module?: ModuleWithPermissions;
|
|
13
13
|
prioritizeFunctions?: boolean;
|
|
14
|
+
/** Renders the title line as a real heading (public SEO pages). Omitted: a plain div, as before. */
|
|
15
|
+
headingLevel?: 1 | 2;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
|
-
export function ContentTitle({
|
|
18
|
+
export function ContentTitle({
|
|
19
|
+
module,
|
|
20
|
+
type,
|
|
21
|
+
element,
|
|
22
|
+
functions,
|
|
23
|
+
className,
|
|
24
|
+
prioritizeFunctions,
|
|
25
|
+
headingLevel,
|
|
26
|
+
}: TitleProps) {
|
|
27
|
+
const ElementTag = headingLevel === 1 ? "h1" : headingLevel === 2 ? "h2" : "div";
|
|
17
28
|
const [clientFunctions, setClientFunctions] = useState<ReactNode>(null);
|
|
18
29
|
const [isClient, setIsClient] = useState(false);
|
|
19
30
|
|
|
@@ -43,9 +54,9 @@ export function ContentTitle({ module, type, element, functions, className, prio
|
|
|
43
54
|
</div>
|
|
44
55
|
)}
|
|
45
56
|
{/* Element (name) - truncates with ellipsis when space is limited */}
|
|
46
|
-
<
|
|
57
|
+
<ElementTag className={cn("text-primary text-3xl font-semibold", prioritizeFunctions ? "truncate" : "w-full")}>
|
|
47
58
|
{element}
|
|
48
|
-
</
|
|
59
|
+
</ElementTag>
|
|
49
60
|
</div>
|
|
50
61
|
{/* Functions section - doesn't shrink when prioritizeFunctions is true */}
|
|
51
62
|
{isClient && clientFunctions && (
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTheme } from "next-themes";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { cn } from "../../utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shows HTML produced by BlockNoteServerContent inside the same container
|
|
9
|
+
* markup BlockNoteView renders, so BlockNote's stylesheet and the app's
|
|
10
|
+
* `.BlockNoteView` rules apply as they do to BlockNoteViewer.
|
|
11
|
+
*/
|
|
12
|
+
export function BlockNoteStaticViewer({
|
|
13
|
+
html,
|
|
14
|
+
size,
|
|
15
|
+
className,
|
|
16
|
+
}: {
|
|
17
|
+
html: string;
|
|
18
|
+
size?: "default" | "sm";
|
|
19
|
+
className?: string;
|
|
20
|
+
}) {
|
|
21
|
+
// The server cannot know the theme: render light until mounted, then follow
|
|
22
|
+
// the app theme like BlockNoteViewer, so hydration never mismatches.
|
|
23
|
+
const [mounted, setMounted] = useState(false);
|
|
24
|
+
const { resolvedTheme } = useTheme();
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
setMounted(true);
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
const theme = mounted && resolvedTheme === "dark" ? "dark" : "light";
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={cn("bn-root bn-container", theme, "bn-shadcn BlockNoteView", size === "sm" && "small", className)}
|
|
35
|
+
data-color-scheme={theme}
|
|
36
|
+
>
|
|
37
|
+
<div
|
|
38
|
+
className="bn-editor bn-default-styles ProseMirror"
|
|
39
|
+
contentEditable={false}
|
|
40
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
package/src/core/index.ts
CHANGED
|
@@ -75,6 +75,10 @@ export * from "../features/email-automation/EmailAutomationTriggerModule";
|
|
|
75
75
|
// server page can wrap EmailAutomationService.findOne in cache(). The client-marked
|
|
76
76
|
// ./email-automations entry carries only components, contexts and hooks.
|
|
77
77
|
export * from "../features/email-automation/data";
|
|
78
|
+
// Blog modules and data: server-safe, same placement as email automation above.
|
|
79
|
+
// The client-marked ./blog entry carries only components, contexts and hooks.
|
|
80
|
+
export * from "../features/blog/BlogModules";
|
|
81
|
+
export * from "../features/blog/data";
|
|
78
82
|
export * from "../features/handbook/HandbookPageModule";
|
|
79
83
|
export * from "../features/handbook/HandbookSectionModule";
|
|
80
84
|
export * from "../features/handbook/HandbookThreadMessageModule";
|
|
@@ -46,6 +46,12 @@ export interface FoundationModuleDefinitions {
|
|
|
46
46
|
EmailAutomationStep: ModuleWithPermissions;
|
|
47
47
|
EmailAutomationEnrollment: ModuleWithPermissions;
|
|
48
48
|
EmailAutomationTrigger: ModuleWithPermissions;
|
|
49
|
+
// Blog modules
|
|
50
|
+
BlogPost: ModuleWithPermissions;
|
|
51
|
+
BlogPostTranslation: ModuleWithPermissions;
|
|
52
|
+
BlogCategory: ModuleWithPermissions;
|
|
53
|
+
BlogCategoryTranslation: ModuleWithPermissions;
|
|
54
|
+
BlogSettings: ModuleWithPermissions;
|
|
49
55
|
// Referral modules
|
|
50
56
|
Referral: ModuleWithPermissions;
|
|
51
57
|
ReferralStats: ModuleWithPermissions;
|
|
@@ -8,14 +8,21 @@ import { Link } from "../../../shadcnui";
|
|
|
8
8
|
import { AdministrationProvider } from "../contexts/AdministrationContext";
|
|
9
9
|
import type { AdminGroup, AdminSection } from "../data/admin-section.types";
|
|
10
10
|
|
|
11
|
+
/** Keys of the built-in platform sections, for `hiddenSections`. */
|
|
12
|
+
export type PlatformSectionKey = "companies" | "users" | "token-usage" | "ai-connections" | "rbac" | "billing";
|
|
13
|
+
|
|
11
14
|
/**
|
|
12
|
-
* The sections every NJA project has. Hrefs are constants rather than
|
|
13
|
-
* the path convention IS the contract that makes this component
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
* The sections every NJA project has, by key. Hrefs are constants rather than
|
|
16
|
+
* props: the path convention IS the contract that makes this component
|
|
17
|
+
* reusable. Exported so an app that passes its own `groups` places these
|
|
18
|
+
* entries without retyping their keys, icons and copy.
|
|
19
|
+
*
|
|
20
|
+
* `billing` is the only conditional entry: the default platform group shows it
|
|
21
|
+
* only when Stripe is configured (see AdminIndexGrid). An app that places it in
|
|
22
|
+
* its own `groups` applies the same check itself.
|
|
16
23
|
*/
|
|
17
|
-
const
|
|
18
|
-
{
|
|
24
|
+
export const ADMIN_PLATFORM_SECTIONS: Record<PlatformSectionKey, AdminSection> = {
|
|
25
|
+
companies: {
|
|
19
26
|
key: "companies",
|
|
20
27
|
icon: Building2Icon,
|
|
21
28
|
labelKey: "entities.companies",
|
|
@@ -23,7 +30,7 @@ const PLATFORM_SECTIONS: AdminSection[] = [
|
|
|
23
30
|
descriptionKey: "administration.companies.description",
|
|
24
31
|
href: "/administration/companies",
|
|
25
32
|
},
|
|
26
|
-
{
|
|
33
|
+
users: {
|
|
27
34
|
key: "users",
|
|
28
35
|
icon: UsersIcon,
|
|
29
36
|
labelKey: "entities.users",
|
|
@@ -31,21 +38,21 @@ const PLATFORM_SECTIONS: AdminSection[] = [
|
|
|
31
38
|
descriptionKey: "administration.users.description",
|
|
32
39
|
href: "/administration/users",
|
|
33
40
|
},
|
|
34
|
-
{
|
|
41
|
+
"token-usage": {
|
|
35
42
|
key: "token-usage",
|
|
36
43
|
icon: CoinsIcon,
|
|
37
44
|
labelKey: "token_usage.admin.title",
|
|
38
45
|
descriptionKey: "administration.token_usage.description",
|
|
39
46
|
href: "/administration/token-usage",
|
|
40
47
|
},
|
|
41
|
-
{
|
|
48
|
+
"ai-connections": {
|
|
42
49
|
key: "ai-connections",
|
|
43
50
|
icon: PlugZapIcon,
|
|
44
51
|
labelKey: "ai_connections.admin.title",
|
|
45
52
|
descriptionKey: "administration.ai_connections.description",
|
|
46
53
|
href: "/administration/ai-connections",
|
|
47
54
|
},
|
|
48
|
-
{
|
|
55
|
+
rbac: {
|
|
49
56
|
key: "rbac",
|
|
50
57
|
icon: ShieldCheckIcon,
|
|
51
58
|
labelKey: "entities.rbac",
|
|
@@ -53,27 +60,33 @@ const PLATFORM_SECTIONS: AdminSection[] = [
|
|
|
53
60
|
descriptionKey: "administration.rbac.description",
|
|
54
61
|
href: "/administration/rbac",
|
|
55
62
|
},
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* are the same check — isStripeConfigured is a one-line wrapper over this call.
|
|
64
|
-
*/
|
|
65
|
-
const BILLING_SECTION: AdminSection = {
|
|
66
|
-
key: "billing",
|
|
67
|
-
icon: CreditCardIcon,
|
|
68
|
-
labelKey: "billing.admin.products.title",
|
|
69
|
-
descriptionKey: "administration.billing.description",
|
|
70
|
-
href: "/administration/products",
|
|
63
|
+
billing: {
|
|
64
|
+
key: "billing",
|
|
65
|
+
icon: CreditCardIcon,
|
|
66
|
+
labelKey: "billing.admin.products.title",
|
|
67
|
+
descriptionKey: "administration.billing.description",
|
|
68
|
+
href: "/administration/products",
|
|
69
|
+
},
|
|
71
70
|
};
|
|
72
71
|
|
|
73
|
-
/**
|
|
74
|
-
|
|
72
|
+
/** The unconditional platform entries, in the default group's order. */
|
|
73
|
+
const PLATFORM_SECTIONS: AdminSection[] = [
|
|
74
|
+
ADMIN_PLATFORM_SECTIONS.companies,
|
|
75
|
+
ADMIN_PLATFORM_SECTIONS.users,
|
|
76
|
+
ADMIN_PLATFORM_SECTIONS["token-usage"],
|
|
77
|
+
ADMIN_PLATFORM_SECTIONS["ai-connections"],
|
|
78
|
+
ADMIN_PLATFORM_SECTIONS.rbac,
|
|
79
|
+
];
|
|
75
80
|
|
|
76
81
|
type AdminIndexProps = {
|
|
82
|
+
/**
|
|
83
|
+
* Replaces the default groups (the built-in platform group plus
|
|
84
|
+
* `additionalGroups`) with exactly these, in this order. For an app that
|
|
85
|
+
* arranges its administration by its own headings; it places the platform
|
|
86
|
+
* entries it wants from ADMIN_PLATFORM_SECTIONS. When set, `additionalGroups`
|
|
87
|
+
* is ignored and the Stripe check on `billing` is the app's to apply.
|
|
88
|
+
*/
|
|
89
|
+
groups?: AdminGroup[];
|
|
77
90
|
/** Project-specific groups, appended after the built-in platform group. */
|
|
78
91
|
additionalGroups?: AdminGroup[];
|
|
79
92
|
/**
|
|
@@ -90,10 +103,12 @@ type AdminIndexProps = {
|
|
|
90
103
|
* The grid itself, with no page shell — this is what the specs render, because
|
|
91
104
|
* RoundPageContainer pulls in the app Header and its four header contexts.
|
|
92
105
|
*/
|
|
93
|
-
export function AdminIndexGrid({ additionalGroups = [], hiddenSections = [] }: AdminIndexProps) {
|
|
106
|
+
export function AdminIndexGrid({ groups: customGroups, additionalGroups = [], hiddenSections = [] }: AdminIndexProps) {
|
|
94
107
|
const t = useTranslations();
|
|
95
108
|
|
|
96
|
-
const available = getStripePublishableKey()
|
|
109
|
+
const available = getStripePublishableKey()
|
|
110
|
+
? [...PLATFORM_SECTIONS, ADMIN_PLATFORM_SECTIONS.billing]
|
|
111
|
+
: PLATFORM_SECTIONS;
|
|
97
112
|
|
|
98
113
|
const platformGroup: AdminGroup = {
|
|
99
114
|
key: "platform",
|
|
@@ -101,7 +116,16 @@ export function AdminIndexGrid({ additionalGroups = [], hiddenSections = [] }: A
|
|
|
101
116
|
sections: available.filter((section) => !hiddenSections.includes(section.key as PlatformSectionKey)),
|
|
102
117
|
};
|
|
103
118
|
|
|
104
|
-
|
|
119
|
+
// Custom groups still honour hiddenSections, so a project that cannot serve
|
|
120
|
+
// a platform route drops it the same way in either mode.
|
|
121
|
+
const groups = (
|
|
122
|
+
customGroups
|
|
123
|
+
? customGroups.map((group) => ({
|
|
124
|
+
...group,
|
|
125
|
+
sections: group.sections.filter((section) => !hiddenSections.includes(section.key as PlatformSectionKey)),
|
|
126
|
+
}))
|
|
127
|
+
: [platformGroup, ...additionalGroups]
|
|
128
|
+
).filter((group) => group.sections.length > 0);
|
|
105
129
|
|
|
106
130
|
return (
|
|
107
131
|
<div className="mx-auto flex w-full max-w-5xl flex-1 flex-col gap-8 py-8">
|
|
@@ -146,11 +170,15 @@ export function AdminIndexGrid({ additionalGroups = [], hiddenSections = [] }: A
|
|
|
146
170
|
* and its own page chrome (AdministrationProvider), so it does not inherit the
|
|
147
171
|
* title and breadcrumb of whatever page rendered before it.
|
|
148
172
|
*/
|
|
149
|
-
export function AdminIndexContainer({ additionalGroups, hiddenSections }: AdminIndexProps) {
|
|
173
|
+
export function AdminIndexContainer({ groups, additionalGroups, hiddenSections }: AdminIndexProps) {
|
|
174
|
+
const t = useTranslations();
|
|
175
|
+
|
|
176
|
+
// The index is the "Administration" page itself, so it names itself in the
|
|
177
|
+
// breadcrumb like every administration page does ("Home > Companies").
|
|
150
178
|
return (
|
|
151
|
-
<AdministrationProvider>
|
|
179
|
+
<AdministrationProvider section={t("administration.title")}>
|
|
152
180
|
<RoundPageContainer>
|
|
153
|
-
<AdminIndexGrid additionalGroups={additionalGroups} hiddenSections={hiddenSections} />
|
|
181
|
+
<AdminIndexGrid groups={groups} additionalGroups={additionalGroups} hiddenSections={hiddenSections} />
|
|
154
182
|
</RoundPageContainer>
|
|
155
183
|
</AdministrationProvider>
|
|
156
184
|
);
|