@carlonicora/nextjs-jsonapi 1.109.0 → 1.111.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/dist/{AssistantMessageInterface-Cs1yb-gF.d.ts → AssistantMessageInterface-BNzf1etU.d.ts} +14 -1
  2. package/dist/{AssistantMessageInterface-DQ3mH5L8.d.mts → AssistantMessageInterface-DrgHDjZ5.d.mts} +14 -1
  3. package/dist/{BlockNoteEditor-3XYBZLWO.js → BlockNoteEditor-7OKW6HHN.js} +19 -20
  4. package/dist/BlockNoteEditor-7OKW6HHN.js.map +1 -0
  5. package/dist/{BlockNoteEditor-EBFZG7AL.mjs → BlockNoteEditor-K7IIWM3S.mjs} +4 -5
  6. package/dist/{BlockNoteEditor-EBFZG7AL.mjs.map → BlockNoteEditor-K7IIWM3S.mjs.map} +1 -1
  7. package/dist/BlockNoteViewer-OPSCTHKJ.mjs +31 -0
  8. package/dist/BlockNoteViewer-OPSCTHKJ.mjs.map +1 -0
  9. package/dist/BlockNoteViewer-UW5VZ5UF.js +31 -0
  10. package/dist/BlockNoteViewer-UW5VZ5UF.js.map +1 -0
  11. package/dist/billing/index.d.mts +41 -41
  12. package/dist/billing/index.d.ts +41 -41
  13. package/dist/billing/index.js +346 -347
  14. package/dist/billing/index.js.map +1 -1
  15. package/dist/billing/index.mjs +3 -4
  16. package/dist/billing/index.mjs.map +1 -1
  17. package/dist/{chunk-7E3O52U5.js → chunk-6XWW67F2.js} +25 -12
  18. package/dist/chunk-6XWW67F2.js.map +1 -0
  19. package/dist/{chunk-UHO3KUUH.js → chunk-AKXUNE3A.js} +977 -850
  20. package/dist/chunk-AKXUNE3A.js.map +1 -0
  21. package/dist/{chunk-UOYIWJEJ.mjs → chunk-CWVYU4YZ.mjs} +18 -5
  22. package/dist/{chunk-UOYIWJEJ.mjs.map → chunk-CWVYU4YZ.mjs.map} +1 -1
  23. package/dist/{chunk-CRTVAQEK.mjs → chunk-TXVVZLUQ.mjs} +1388 -1261
  24. package/dist/chunk-TXVVZLUQ.mjs.map +1 -0
  25. package/dist/{chunk-CFI4WZ5R.js → chunk-U2HR57MY.js} +111 -5
  26. package/dist/chunk-U2HR57MY.js.map +1 -0
  27. package/dist/chunk-WPAQRB5Q.mjs +7 -0
  28. package/dist/chunk-WPAQRB5Q.mjs.map +1 -0
  29. package/dist/chunk-YBZVWLPH.js +7 -0
  30. package/dist/chunk-YBZVWLPH.js.map +1 -0
  31. package/dist/{chunk-MSNNAHDB.mjs → chunk-ZYPNVEHB.mjs} +112 -6
  32. package/dist/{chunk-MSNNAHDB.mjs.map → chunk-ZYPNVEHB.mjs.map} +1 -1
  33. package/dist/client/index.d.mts +3 -4
  34. package/dist/client/index.d.ts +3 -4
  35. package/dist/client/index.js +4 -5
  36. package/dist/client/index.js.map +1 -1
  37. package/dist/client/index.mjs +3 -4
  38. package/dist/components/index.d.mts +418 -415
  39. package/dist/components/index.d.ts +418 -415
  40. package/dist/components/index.js +6 -5
  41. package/dist/components/index.js.map +1 -1
  42. package/dist/components/index.mjs +5 -4
  43. package/dist/{config-CN23v3eJ.d.ts → config-BrkU_Ynz.d.ts} +2 -2
  44. package/dist/{config-gh88Qn4h.d.mts → config-snd2ToEB.d.mts} +2 -2
  45. package/dist/contexts/index.d.mts +15 -15
  46. package/dist/contexts/index.d.ts +15 -15
  47. package/dist/contexts/index.js +4 -5
  48. package/dist/contexts/index.js.map +1 -1
  49. package/dist/contexts/index.mjs +3 -4
  50. package/dist/core/index.d.mts +35 -4
  51. package/dist/core/index.d.ts +35 -4
  52. package/dist/core/index.js +2 -2
  53. package/dist/core/index.mjs +1 -1
  54. package/dist/features/help/index.d.mts +25 -66
  55. package/dist/features/help/index.d.ts +25 -66
  56. package/dist/features/help/index.js +114 -124
  57. package/dist/features/help/index.js.map +1 -1
  58. package/dist/features/help/index.mjs +95 -105
  59. package/dist/features/help/index.mjs.map +1 -1
  60. package/dist/features/help/server.d.mts +27 -65
  61. package/dist/features/help/server.d.ts +27 -65
  62. package/dist/features/help/server.js +4 -119
  63. package/dist/features/help/server.js.map +1 -1
  64. package/dist/features/help/server.mjs +4 -119
  65. package/dist/features/help/server.mjs.map +1 -1
  66. package/dist/help-content-config.interface-C5ESIod9.d.mts +13 -0
  67. package/dist/help-content-config.interface-C5ESIod9.d.ts +13 -0
  68. package/dist/index.d.mts +3 -3
  69. package/dist/index.d.ts +3 -3
  70. package/dist/index.js +3 -4
  71. package/dist/index.js.map +1 -1
  72. package/dist/index.mjs +2 -3
  73. package/dist/scripts/generate-web-module/__tests__/container.spec.d.ts +2 -0
  74. package/dist/scripts/generate-web-module/__tests__/container.spec.d.ts.map +1 -0
  75. package/dist/scripts/generate-web-module/__tests__/container.spec.js +54 -0
  76. package/dist/scripts/generate-web-module/__tests__/container.spec.js.map +1 -0
  77. package/dist/scripts/generate-web-module/__tests__/data-service.spec.d.ts +2 -0
  78. package/dist/scripts/generate-web-module/__tests__/data-service.spec.d.ts.map +1 -0
  79. package/dist/scripts/generate-web-module/__tests__/data-service.spec.js +19 -0
  80. package/dist/scripts/generate-web-module/__tests__/data-service.spec.js.map +1 -0
  81. package/dist/scripts/generate-web-module/__tests__/editor.spec.d.ts +2 -0
  82. package/dist/scripts/generate-web-module/__tests__/editor.spec.d.ts.map +1 -0
  83. package/dist/scripts/generate-web-module/__tests__/editor.spec.js +35 -0
  84. package/dist/scripts/generate-web-module/__tests__/editor.spec.js.map +1 -0
  85. package/dist/scripts/generate-web-module/__tests__/fixtures.d.ts +4 -0
  86. package/dist/scripts/generate-web-module/__tests__/fixtures.d.ts.map +1 -0
  87. package/dist/scripts/generate-web-module/__tests__/fixtures.js +45 -0
  88. package/dist/scripts/generate-web-module/__tests__/fixtures.js.map +1 -0
  89. package/dist/scripts/generate-web-module/__tests__/model-interface.spec.d.ts +2 -0
  90. package/dist/scripts/generate-web-module/__tests__/model-interface.spec.d.ts.map +1 -0
  91. package/dist/scripts/generate-web-module/__tests__/model-interface.spec.js +74 -0
  92. package/dist/scripts/generate-web-module/__tests__/model-interface.spec.js.map +1 -0
  93. package/dist/scripts/generate-web-module/__tests__/plumbing.spec.d.ts +2 -0
  94. package/dist/scripts/generate-web-module/__tests__/plumbing.spec.d.ts.map +1 -0
  95. package/dist/scripts/generate-web-module/__tests__/plumbing.spec.js +21 -0
  96. package/dist/scripts/generate-web-module/__tests__/plumbing.spec.js.map +1 -0
  97. package/dist/scripts/generate-web-module/__tests__/selector.spec.d.ts +2 -0
  98. package/dist/scripts/generate-web-module/__tests__/selector.spec.d.ts.map +1 -0
  99. package/dist/scripts/generate-web-module/__tests__/selector.spec.js +25 -0
  100. package/dist/scripts/generate-web-module/__tests__/selector.spec.js.map +1 -0
  101. package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.d.ts +2 -0
  102. package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.d.ts.map +1 -0
  103. package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.js +55 -0
  104. package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.js.map +1 -0
  105. package/dist/scripts/generate-web-module/generator.d.ts.map +1 -1
  106. package/dist/scripts/generate-web-module/generator.js +14 -11
  107. package/dist/scripts/generate-web-module/generator.js.map +1 -1
  108. package/dist/scripts/generate-web-module/templates/components/container.template.d.ts +5 -1
  109. package/dist/scripts/generate-web-module/templates/components/container.template.d.ts.map +1 -1
  110. package/dist/scripts/generate-web-module/templates/components/container.template.js +43 -63
  111. package/dist/scripts/generate-web-module/templates/components/container.template.js.map +1 -1
  112. package/dist/scripts/generate-web-module/templates/components/content.template.d.ts +7 -4
  113. package/dist/scripts/generate-web-module/templates/components/content.template.d.ts.map +1 -1
  114. package/dist/scripts/generate-web-module/templates/components/content.template.js +103 -14
  115. package/dist/scripts/generate-web-module/templates/components/content.template.js.map +1 -1
  116. package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts +1 -1
  117. package/dist/scripts/generate-web-module/templates/components/editor.template.js +139 -150
  118. package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -1
  119. package/dist/scripts/generate-web-module/templates/components/index.d.ts +0 -1
  120. package/dist/scripts/generate-web-module/templates/components/index.d.ts.map +1 -1
  121. package/dist/scripts/generate-web-module/templates/components/index.js +1 -3
  122. package/dist/scripts/generate-web-module/templates/components/index.js.map +1 -1
  123. package/dist/scripts/generate-web-module/templates/components/list-container.template.js +2 -2
  124. package/dist/scripts/generate-web-module/templates/components/list.template.d.ts.map +1 -1
  125. package/dist/scripts/generate-web-module/templates/components/list.template.js +6 -1
  126. package/dist/scripts/generate-web-module/templates/components/list.template.js.map +1 -1
  127. package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -1
  128. package/dist/scripts/generate-web-module/templates/components/selector.template.js +48 -52
  129. package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -1
  130. package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts.map +1 -1
  131. package/dist/scripts/generate-web-module/templates/data/interface.template.js +38 -11
  132. package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -1
  133. package/dist/scripts/generate-web-module/templates/data/model.template.d.ts.map +1 -1
  134. package/dist/scripts/generate-web-module/templates/data/model.template.js +83 -26
  135. package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -1
  136. package/dist/scripts/generate-web-module/templates/data/service.template.d.ts.map +1 -1
  137. package/dist/scripts/generate-web-module/templates/data/service.template.js +21 -1
  138. package/dist/scripts/generate-web-module/templates/data/service.template.js.map +1 -1
  139. package/dist/scripts/generate-web-module/templates/index.d.ts +1 -1
  140. package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -1
  141. package/dist/scripts/generate-web-module/templates/index.js +1 -2
  142. package/dist/scripts/generate-web-module/templates/index.js.map +1 -1
  143. package/dist/scripts/generate-web-module/templates/module.template.d.ts.map +1 -1
  144. package/dist/scripts/generate-web-module/templates/module.template.js +11 -3
  145. package/dist/scripts/generate-web-module/templates/module.template.js.map +1 -1
  146. package/dist/scripts/generate-web-module/templates/table-hook.template.js +31 -1
  147. package/dist/scripts/generate-web-module/templates/table-hook.template.js.map +1 -1
  148. package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts +0 -1
  149. package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts.map +1 -1
  150. package/dist/scripts/generate-web-module/transformers/import-resolver.js +0 -1
  151. package/dist/scripts/generate-web-module/transformers/import-resolver.js.map +1 -1
  152. package/dist/scripts/generate-web-module/transformers/relationship-key.d.ts +5 -0
  153. package/dist/scripts/generate-web-module/transformers/relationship-key.d.ts.map +1 -0
  154. package/dist/scripts/generate-web-module/transformers/relationship-key.js +21 -0
  155. package/dist/scripts/generate-web-module/transformers/relationship-key.js.map +1 -0
  156. package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts.map +1 -1
  157. package/dist/scripts/generate-web-module/transformers/relationship-resolver.js +2 -0
  158. package/dist/scripts/generate-web-module/transformers/relationship-resolver.js.map +1 -1
  159. package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts +21 -0
  160. package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts.map +1 -1
  161. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +17 -0
  162. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -1
  163. package/dist/server/index.js +3 -3
  164. package/dist/server/index.mjs +1 -1
  165. package/dist/testing/index.d.mts +1 -2
  166. package/dist/testing/index.d.ts +1 -2
  167. package/package.json +1 -6
  168. package/scripts/generate-web-module/__tests__/container.spec.ts +58 -0
  169. package/scripts/generate-web-module/__tests__/data-service.spec.ts +18 -0
  170. package/scripts/generate-web-module/__tests__/editor.spec.ts +36 -0
  171. package/scripts/generate-web-module/__tests__/fixtures.ts +43 -0
  172. package/scripts/generate-web-module/__tests__/model-interface.spec.ts +77 -0
  173. package/scripts/generate-web-module/__tests__/plumbing.spec.ts +21 -0
  174. package/scripts/generate-web-module/__tests__/selector.spec.ts +24 -0
  175. package/scripts/generate-web-module/__tests__/table-module-list.spec.ts +59 -0
  176. package/scripts/generate-web-module/generator.ts +14 -13
  177. package/scripts/generate-web-module/templates/components/container.template.ts +51 -66
  178. package/scripts/generate-web-module/templates/components/content.template.ts +107 -15
  179. package/scripts/generate-web-module/templates/components/editor.template.ts +140 -152
  180. package/scripts/generate-web-module/templates/components/index.ts +0 -1
  181. package/scripts/generate-web-module/templates/components/list-container.template.ts +2 -2
  182. package/scripts/generate-web-module/templates/components/list.template.ts +6 -1
  183. package/scripts/generate-web-module/templates/components/selector.template.ts +48 -52
  184. package/scripts/generate-web-module/templates/data/interface.template.ts +44 -12
  185. package/scripts/generate-web-module/templates/data/model.template.ts +88 -28
  186. package/scripts/generate-web-module/templates/data/service.template.ts +23 -1
  187. package/scripts/generate-web-module/templates/index.ts +0 -1
  188. package/scripts/generate-web-module/templates/module.template.ts +13 -3
  189. package/scripts/generate-web-module/templates/table-hook.template.ts +31 -2
  190. package/scripts/generate-web-module/transformers/import-resolver.ts +0 -2
  191. package/scripts/generate-web-module/transformers/relationship-key.ts +18 -0
  192. package/scripts/generate-web-module/transformers/relationship-resolver.ts +2 -0
  193. package/scripts/generate-web-module/types/json-schema.interface.ts +9 -0
  194. package/scripts/generate-web-module/types/template-data.interface.ts +7 -0
  195. package/src/client/config.ts +1 -1
  196. package/src/components/index.ts +1 -0
  197. package/src/components/viewers/BlockNoteViewer.tsx +27 -0
  198. package/src/components/viewers/BlockNoteViewerContainer.tsx +10 -0
  199. package/src/core/registry/helpStore.ts +2 -19
  200. package/src/features/assistant-message/components/parts/MessageSourcesContainer.tsx +4 -0
  201. package/src/features/assistant-message/components/parts/tabs/ContentsTab.tsx +5 -5
  202. package/src/features/assistant-message/components/parts/tabs/ReferencesTab.tsx +3 -2
  203. package/src/features/help/components/HelpArticleBody.tsx +27 -19
  204. package/src/features/help/components/HelpHint.tsx +20 -11
  205. package/src/features/help/components/HelpSearchResultRow.tsx +16 -6
  206. package/src/features/help/components/HelpSideNav.tsx +54 -19
  207. package/src/features/help/components/__tests__/HelpAskAi.spec.tsx +1 -1
  208. package/src/features/help/components/__tests__/HelpHint.spec.tsx +17 -17
  209. package/src/features/help/components/__tests__/HelpSearchResultRow.spec.tsx +18 -19
  210. package/src/features/help/components/__tests__/HelpSideNav.spec.tsx +20 -22
  211. package/src/features/help/contexts/HelpContext.spec.tsx +1 -3
  212. package/src/features/help/index.ts +3 -6
  213. package/src/features/help/interfaces/help-content-config.interface.ts +0 -5
  214. package/src/features/help/server-entry.ts +6 -15
  215. package/src/features/help/types/help-article.types.ts +2 -4
  216. package/src/features/how-to/HowToModule.ts +14 -1
  217. package/src/features/how-to/components/forms/HowToEditor.tsx +108 -4
  218. package/src/features/how-to/data/HowTo.spec.ts +43 -0
  219. package/src/features/how-to/data/HowTo.ts +47 -4
  220. package/src/features/how-to/data/HowToInterface.ts +14 -1
  221. package/src/features/how-to/data/HowToService.ts +61 -0
  222. package/dist/BlockNoteEditor-3XYBZLWO.js.map +0 -1
  223. package/dist/chunk-3J7RQBF3.js +0 -123
  224. package/dist/chunk-3J7RQBF3.js.map +0 -1
  225. package/dist/chunk-7E3O52U5.js.map +0 -1
  226. package/dist/chunk-CFI4WZ5R.js.map +0 -1
  227. package/dist/chunk-CQID6RCF.mjs +0 -38
  228. package/dist/chunk-CQID6RCF.mjs.map +0 -1
  229. package/dist/chunk-CRTVAQEK.mjs.map +0 -1
  230. package/dist/chunk-MZTKPPET.mjs +0 -123
  231. package/dist/chunk-MZTKPPET.mjs.map +0 -1
  232. package/dist/chunk-UHO3KUUH.js.map +0 -1
  233. package/dist/chunk-YQQHAFBS.js +0 -38
  234. package/dist/chunk-YQQHAFBS.js.map +0 -1
  235. package/dist/features/help/server/createHelpAssetRouteHandler.d.mts +0 -11
  236. package/dist/features/help/server/createHelpAssetRouteHandler.d.ts +0 -11
  237. package/dist/features/help/server/createHelpAssetRouteHandler.js +0 -43
  238. package/dist/features/help/server/createHelpAssetRouteHandler.js.map +0 -1
  239. package/dist/features/help/server/createHelpAssetRouteHandler.mjs +0 -43
  240. package/dist/features/help/server/createHelpAssetRouteHandler.mjs.map +0 -1
  241. package/dist/help-content-config.interface-B9L02u9i.d.mts +0 -50
  242. package/dist/help-content-config.interface-B9L02u9i.d.ts +0 -50
  243. package/dist/scripts/generate-web-module/templates/components/details.template.d.ts +0 -14
  244. package/dist/scripts/generate-web-module/templates/components/details.template.d.ts.map +0 -1
  245. package/dist/scripts/generate-web-module/templates/components/details.template.js +0 -112
  246. package/dist/scripts/generate-web-module/templates/components/details.template.js.map +0 -1
  247. package/scripts/generate-web-module/templates/components/details.template.ts +0 -116
  248. package/src/features/help/components/mdx/Callout.tsx +0 -21
  249. package/src/features/help/components/mdx/EntityRef.tsx +0 -18
  250. package/src/features/help/components/mdx/KeyBinding.tsx +0 -6
  251. package/src/features/help/components/mdx/Related.tsx +0 -33
  252. package/src/features/help/components/mdx/Screenshot.tsx +0 -9
  253. package/src/features/help/components/mdx/Steps.tsx +0 -21
  254. package/src/features/help/components/mdx/Video.tsx +0 -8
  255. package/src/features/help/components/mdx/mdx-server-components.ts +0 -23
  256. package/src/features/help/components/mdx/mdxComponents.ts +0 -9
  257. package/src/features/help/hooks/useHelpArticle.ts +0 -7
  258. package/src/features/help/hooks/useHelpManifest.ts +0 -5
  259. package/src/features/help/server/__tests__/createHelpAssetRouteHandler.spec.ts +0 -43
  260. package/src/features/help/server/createHelpAssetRouteHandler.ts +0 -35
  261. package/src/features/help/server/generateHelpArticleMetadata.ts +0 -18
  262. package/src/features/help/server/generateHelpArticleStaticParams.ts +0 -7
  263. package/src/features/help/server/generateHelpModeStaticParams.ts +0 -5
  264. package/src/features/help/server/getHelpContent.ts +0 -17
  265. package/src/features/help/server/index.ts +0 -8
  266. package/src/features/help/server/serializeHelpArticle.tsx +0 -46
  267. package/src/features/help/utils/__tests__/helpNavigation.spec.ts +0 -70
  268. package/src/features/help/utils/helpNavigation.ts +0 -29
@@ -1,40 +1,132 @@
1
1
  /**
2
2
  * Content Template
3
3
  *
4
- * Generates {Module}Content.tsx component for displaying BlockNote content.
5
- * Only generated for Content-extending modules.
4
+ * Generates {Module}Content.tsx the prop-driven body rendered inside the
5
+ * container's "Details" tab. Always emitted (returns a string):
6
+ * - Content-extending modules render the BlockNote content.
7
+ * - Other modules render an AttributeElement layout of their fields and
8
+ * relationships.
6
9
  */
7
10
 
11
+ import { pluralize, toCamelCase } from "../../transformers/name-transformer";
8
12
  import { FrontendTemplateData } from "../../types/template-data.interface";
9
13
 
10
14
  /**
11
15
  * Generate the content display component file content
12
16
  *
13
17
  * @param data - Frontend template data
14
- * @returns Generated file content or null if not Content-extending
18
+ * @returns Generated file content (always a string)
15
19
  */
16
- export function generateContentTemplate(data: FrontendTemplateData): string | null {
20
+ export function generateContentTemplate(data: FrontendTemplateData): string {
17
21
  const { names, extendsContent } = data;
22
+ const camel = names.camelCase;
18
23
 
19
- // Only generate for Content-extending modules
20
- if (!extendsContent) {
21
- return null;
24
+ // Content-extending modules render their BlockNote body, prop-driven.
25
+ if (extendsContent) {
26
+ return `"use client";
27
+
28
+ import { ${names.pascalCase}Interface } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
29
+ import { BlockNoteEditorContainer, Card } from "@carlonicora/nextjs-jsonapi/components";
30
+
31
+ type ${names.pascalCase}ContentProps = { ${camel}: ${names.pascalCase}Interface };
32
+
33
+ export function ${names.pascalCase}Content({ ${camel} }: ${names.pascalCase}ContentProps) {
34
+ return (
35
+ <Card className="flex w-full flex-col p-4">
36
+ <BlockNoteEditorContainer id={${camel}.id} type="${camel}" initialContent={${camel}.content} />
37
+ </Card>
38
+ );
39
+ }
40
+ `;
22
41
  }
23
42
 
43
+ // Non-content modules render an AttributeElement layout of their fields.
44
+ const attributeElements = generateAttributeElements(data);
45
+
24
46
  return `"use client";
25
47
 
26
- import { use${names.pascalCase}Context } from "@/features/${data.importTargetDir}/${names.kebabCase}/contexts/${names.pascalCase}Context";
27
- import { BlockNoteEditorContainer } from "@carlonicora/nextjs-jsonapi/components";
28
- import { Card } from "@carlonicora/nextjs-jsonapi/components";
48
+ import { ${names.pascalCase}Interface } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
49
+ import { AttributeElement } from "@carlonicora/nextjs-jsonapi/components";
50
+ import { useTranslations } from "next-intl";
51
+
52
+ type ${names.pascalCase}ContentProps = { ${camel}: ${names.pascalCase}Interface };
29
53
 
30
- export default function ${names.pascalCase}Content() {
31
- const { ${names.camelCase} } = use${names.pascalCase}Context();
54
+ export function ${names.pascalCase}Content({ ${camel} }: ${names.pascalCase}ContentProps) {
55
+ const t = useTranslations();
32
56
 
33
57
  return (
34
- <Card className="flex w-full flex-col p-4">
35
- <BlockNoteEditorContainer id={${names.camelCase}.id} type="${names.camelCase}" initialContent={${names.camelCase}.content} />
36
- </Card>
58
+ <div className="flex flex-col gap-y-8">
59
+ <div className="flex flex-col gap-y-3">
60
+ <h3 className="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
61
+ {t(\`features.${names.camelCase}.sections.details\`)}
62
+ </h3>
63
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-3">
64
+ ${attributeElements}
65
+ </div>
66
+ </div>
67
+ </div>
37
68
  );
38
69
  }
39
70
  `;
40
71
  }
72
+
73
+ /**
74
+ * Generate attribute elements for display fields and relationships.
75
+ *
76
+ * Ported from the (removed) details template, but accessing the entity via the
77
+ * `camelCase` prop instead of context.
78
+ */
79
+ function generateAttributeElements(data: FrontendTemplateData): string {
80
+ const { names, fields, relationships } = data;
81
+ const camel = names.camelCase;
82
+ const elements: string[] = [];
83
+
84
+ // Add custom fields (excluding name, tldr, abstract, content which are handled elsewhere)
85
+ const displayableFields = fields.filter((f) => !["name", "tldr", "abstract", "content", "id"].includes(f.name));
86
+
87
+ displayableFields.forEach((field) => {
88
+ if (field.type === "string") {
89
+ elements.push(` <AttributeElement
90
+ title={t(\`features.${names.camelCase}.fields.${field.name}.label\`)}
91
+ value={${camel}.${field.name}}
92
+ />`);
93
+ } else if (field.type === "boolean") {
94
+ elements.push(` <AttributeElement
95
+ title={t(\`features.${names.camelCase}.fields.${field.name}.label\`)}
96
+ value={${camel}.${field.name} ? t(\`generic.yes\`) : t(\`generic.no\`)}
97
+ />`);
98
+ } else if (field.type === "number") {
99
+ elements.push(` <AttributeElement
100
+ title={t(\`features.${names.camelCase}.fields.${field.name}.label\`)}
101
+ value={${camel}.${field.name}?.toString()}
102
+ />`);
103
+ }
104
+ });
105
+
106
+ // Add relationship displays (basic - can be enhanced manually)
107
+ relationships
108
+ .filter((rel) => rel.variant !== "Author") // Skip author, usually shown in title
109
+ .forEach((rel) => {
110
+ const displayProp = rel.targetHasName ? "name" : "id";
111
+ if (rel.single) {
112
+ const propName = toCamelCase(rel.alias || rel.variant || rel.name);
113
+ elements.push(` {${camel}.${propName} && (
114
+ <AttributeElement
115
+ title={t(\`generic.relationships.${propName}.label\`)}
116
+ value={${camel}.${propName}.${displayProp}}
117
+ />
118
+ )}`);
119
+ } else {
120
+ const effectiveMany = rel.alias || rel.name;
121
+ const propName = pluralize(toCamelCase(effectiveMany));
122
+ elements.push(` {${camel}.${propName} && ${camel}.${propName}.length > 0 && (
123
+ <AttributeElement
124
+ title={t(\`entities.${pluralize(rel.name.toLowerCase())}\`, { count: 2 })}
125
+ value={${camel}.${propName}.map((item) => item.${displayProp}).join(", ")}
126
+ />
127
+ )}`);
128
+ }
129
+ });
130
+
131
+ return elements.join("\n");
132
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Editor Template
3
3
  *
4
- * Generates {Module}Editor.tsx dialog-based form component.
4
+ * Generates {Module}Editor.tsx form component using the EditorSheet wrapper.
5
5
  */
6
6
 
7
7
  import { FrontendTemplateData, FrontendField, FrontendRelationship } from "../../types/template-data.interface";
@@ -23,15 +23,16 @@ import {
23
23
  * @returns Generated file content
24
24
  */
25
25
  export function generateEditorTemplate(data: FrontendTemplateData): string {
26
- const { names, fields, relationships, extendsContent } = data;
27
- const hasNameField = extendsContent || fields.some((f) => f.name === "name");
26
+ const { names, relationships } = data;
28
27
  const i18nKey = names.pluralCamel.toLowerCase();
28
+ const camel = names.camelCase;
29
+ const displayProp = data.displayProp || "name";
29
30
 
30
31
  const imports = generateImports(data);
31
32
  const propsType = generatePropsType(data);
32
- const formSchema = generateFormSchema(data);
33
- const defaultValues = generateDefaultValues(data);
34
- const onSubmit = generateOnSubmit(data);
33
+ const schemaInner = generateFormSchemaInner(data);
34
+ const defaultsInner = generateDefaultValuesInner(data);
35
+ const onSubmitBody = generateOnSubmitBody(data);
35
36
  const formFields = generateFormFields(data);
36
37
 
37
38
  const hasAuthor = relationships.some((r) => r.variant === AUTHOR_VARIANT);
@@ -43,7 +44,7 @@ ${imports}
43
44
  ${propsType}
44
45
 
45
46
  function ${names.pascalCase}EditorInternal({
46
- ${names.camelCase},
47
+ ${camel},
47
48
  propagateChanges,
48
49
  trigger,
49
50
  forceShow,
@@ -52,72 +53,71 @@ function ${names.pascalCase}EditorInternal({
52
53
  onDialogOpenChange,
53
54
  }: ${names.pascalCase}EditorProps) {
54
55
  const router = useRouter();
55
- const generateUrl = usePageUrlGenerator();
56
56
  const t = useTranslations();
57
- ${hasAuthor ? ` const { currentUser } = useCurrentUserContext<UserInterface>();` : ""}
58
-
59
- ${formSchema}
57
+ ${hasAuthor ? ` const { currentUser } = useCurrentUserContext<UserInterface>();\n` : ""}
58
+ const formSchema = useMemo(
59
+ () =>
60
+ z.object({
61
+ ${schemaInner}
62
+ }),
63
+ [t],
64
+ );
60
65
 
61
- ${defaultValues}
66
+ const getDefaultValues = useCallback(
67
+ () => ({
68
+ ${defaultsInner}
69
+ }),
70
+ [${camel}],
71
+ );
62
72
 
63
73
  const form = useForm<z.infer<typeof formSchema>>({
64
74
  resolver: zodResolver(formSchema),
65
75
  defaultValues: getDefaultValues(),
66
76
  });
67
-
68
- const { dirtyFields } = form.formState;
69
-
70
- const isFormDirty = useCallback(() => {
71
- return Object.keys(dirtyFields).length > 0;
72
- }, [dirtyFields]);
73
-
74
- const { open, setOpen, handleOpenChange, discardDialogProps } = useEditorDialog(isFormDirty, {
75
- dialogOpen, onDialogOpenChange, forceShow, onClose,
76
- });
77
-
78
- useEffect(() => {
79
- if (!open) {
80
- form.reset(getDefaultValues());
81
- }
82
- }, [open]);
83
-
84
77
  ${
85
78
  hasAuthor
86
- ? ` useEffect(() => {
79
+ ? `
80
+ useEffect(() => {
87
81
  if (currentUser && !form.getValues("author")?.id) {
88
82
  form.setValue("author", { id: currentUser.id, name: currentUser.name, avatar: currentUser.avatar });
89
83
  }
90
- }, [currentUser]);`
84
+ }, [currentUser]);
85
+ `
91
86
  : ""
92
87
  }
93
-
94
- ${onSubmit}
95
-
96
88
  return (
97
- <>
98
- <Dialog open={open} onOpenChange={handleOpenChange}>
99
- {dialogOpen === undefined && (trigger ? <DialogTrigger>{trigger}</DialogTrigger> : <CommonEditorTrigger isEdit={!!${names.camelCase}} />)}
100
- <DialogContent
101
- className="flex max-h-[70vh] max-w-3xl flex-col overflow-y-auto"
102
- >
103
- <CommonEditorHeader type={t(\`entities.${i18nKey}\`, { count: 1 })}${hasNameField ? ` name={${names.camelCase}?.name}` : ""} />
104
- <Form {...form}>
105
- <form onSubmit={form.handleSubmit(onSubmit)} className="flex w-full flex-col gap-y-4">
106
- <div className="flex flex-col justify-between gap-x-4">
89
+ <EditorSheet
90
+ form={form}
91
+ entityType={t(\`entities.${i18nKey}\`, { count: 1 })}
92
+ entityName={${camel}?.${displayProp}}
93
+ isEdit={!!${camel}}
94
+ module={Modules.${names.pascalCase}}
95
+ propagateChanges={propagateChanges}
96
+ size="md"
97
+ onSubmit={async (values) => {
98
+ ${onSubmitBody}
99
+ }}
100
+ onRevalidate={revalidatePaths}
101
+ onNavigate={(url) => router.push(url)}
102
+ onReset={getDefaultValues}
103
+ onClose={onClose}
104
+ trigger={trigger}
105
+ forceShow={forceShow}
106
+ dialogOpen={dialogOpen}
107
+ onDialogOpenChange={onDialogOpenChange}
108
+ >
109
+ <div className="flex w-full flex-col gap-y-4">
107
110
  ${formFields}
108
- <CommonEditorButtons form={form} setOpen={handleOpenChange} isEdit={!!${names.camelCase}} />
109
- </div>
110
- </form>
111
- </Form>
112
- </DialogContent>
113
- </Dialog>
114
- <CommonEditorDiscardDialog {...discardDialogProps} />
115
- </>
111
+ </div>
112
+ </EditorSheet>
116
113
  );
117
114
  }
118
115
 
119
116
  export default function ${names.pascalCase}Editor(props: ${names.pascalCase}EditorProps) {
120
- const action = props.${names.camelCase} ? Action.Update : Action.Create;
117
+ const { hasPermissionToModule } = useCurrentUserContext();
118
+ const action = props.${camel} ? Action.Update : Action.Create;
119
+
120
+ if (!hasPermissionToModule({ module: Modules.${names.pascalCase}, action, data: props.${camel} })) return null;
121
121
 
122
122
  return <${names.pascalCase}EditorInternal {...props} />;
123
123
  }
@@ -141,6 +141,7 @@ function generateImports(data: FrontendTemplateData): string {
141
141
 
142
142
  // Relationship selector imports (deduplicated - multiple aliases may target the same entity)
143
143
  const hasAuthor = relationships.some((r) => r.variant === AUTHOR_VARIANT);
144
+ const hasRelationships = relationships.length > 0;
144
145
  const seenImports = new Set<string>();
145
146
 
146
147
  relationships.forEach((rel) => {
@@ -171,8 +172,9 @@ function generateImports(data: FrontendTemplateData): string {
171
172
  // Utility imports
172
173
  imports.push(`import { revalidatePaths } from "@/utils/revalidation";`);
173
174
 
174
- // Library component imports
175
- const componentImports: string[] = ["CommonEditorButtons", "CommonEditorDiscardDialog", "CommonEditorHeader", "CommonEditorTrigger", "errorToast", "useEditorDialog"];
175
+ // Library component imports — EditorSheet owns the dialog/header/buttons,
176
+ // so only the EditorSheet wrapper plus the Form* field components are needed.
177
+ const componentImports: string[] = ["EditorSheet"];
176
178
 
177
179
  // Check for field types that need specific components
178
180
  const hasContentField = fields.some((f) => f.isContentField || f.name === "content");
@@ -208,29 +210,24 @@ function generateImports(data: FrontendTemplateData): string {
208
210
  ${componentImports.join(",\n ")},
209
211
  } from "@carlonicora/nextjs-jsonapi/components";`);
210
212
 
211
- // Context imports
212
- if (hasAuthor) {
213
- imports.push(`import { useCurrentUserContext } from "@carlonicora/nextjs-jsonapi/contexts";`);
214
- }
213
+ // Context imports — needed both for the permission gate and the author auto-set.
214
+ imports.push(`import { useCurrentUserContext } from "@carlonicora/nextjs-jsonapi/contexts";`);
215
215
 
216
- // Core imports
217
- imports.push(`import { Modules } from "@carlonicora/nextjs-jsonapi/core";`);
218
- imports.push(`import { usePageUrlGenerator } from "@carlonicora/nextjs-jsonapi/client";`);
219
- imports.push(`import { Action } from "@carlonicora/nextjs-jsonapi/core";`);
220
- imports.push(`import { Dialog, DialogContent, DialogTrigger, Form } from "@carlonicora/nextjs-jsonapi/components";`);
221
-
222
- // Zod schema imports
223
- const zodSchemaImports = ["entityObjectSchema"];
216
+ // Core imports — entityObjectSchema only when relationships exist.
217
+ const coreImports = ["Action", "Modules"];
218
+ if (hasRelationships) {
219
+ coreImports.splice(1, 0, "entityObjectSchema");
220
+ }
224
221
  if (hasAuthor) {
225
- zodSchemaImports.push("userObjectSchema");
222
+ coreImports.push("userObjectSchema");
226
223
  }
227
- imports.push(`import { ${zodSchemaImports.join(", ")} } from "@carlonicora/nextjs-jsonapi/core";`);
224
+ imports.push(`import { ${coreImports.join(", ")} } from "@carlonicora/nextjs-jsonapi/core";`);
228
225
 
229
226
  // Other imports
230
227
  imports.push(`import { zodResolver } from "@hookform/resolvers/zod";`);
231
228
  imports.push(`import { useTranslations } from "next-intl";`);
232
- imports.push(`import { ReactNode, useCallback, useEffect } from "react";`);
233
- imports.push(`import { SubmitHandler, useForm } from "react-hook-form";`);
229
+ imports.push(`import { ReactNode, useCallback, useMemo${hasAuthor ? ", useEffect" : ""} } from "react";`);
230
+ imports.push(`import { useForm } from "react-hook-form";`);
234
231
  imports.push(`import { v4 } from "uuid";`);
235
232
  imports.push(`import { z } from "zod";`);
236
233
 
@@ -255,14 +252,18 @@ function generatePropsType(data: FrontendTemplateData): string {
255
252
  }
256
253
 
257
254
  /**
258
- * Generate form schema
255
+ * Generate the inner lines of the form schema (the body of `z.object({ ... })`).
256
+ *
257
+ * The `z.object({ ... })` wrapper now lives in the main template inside a
258
+ * `useMemo`, so this helper only emits the per-field lines (indented to sit
259
+ * inside that wrapper).
259
260
  */
260
- function generateFormSchema(data: FrontendTemplateData): string {
261
+ function generateFormSchemaInner(data: FrontendTemplateData): string {
261
262
  const { names, fields, relationships, extendsContent } = data;
262
263
  const schemaFields: string[] = [];
263
264
 
264
265
  // ID field
265
- schemaFields.push(` id: z.uuidv4(),`);
266
+ schemaFields.push(` id: z.uuidv4(),`);
266
267
 
267
268
  // Regular fields (excluding inherited)
268
269
  const fieldsToInclude = extendsContent
@@ -271,24 +272,24 @@ function generateFormSchema(data: FrontendTemplateData): string {
271
272
 
272
273
  // Add name field for Content-extending modules
273
274
  if (extendsContent) {
274
- schemaFields.push(` name: z.string().min(1, {
275
- message: t(\`features.${names.camelCase.toLowerCase()}.fields.name.error\`),
276
- }),`);
275
+ schemaFields.push(` name: z.string().min(1, {
276
+ message: t(\`features.${names.camelCase.toLowerCase()}.fields.name.error\`),
277
+ }),`);
277
278
  }
278
279
 
279
280
  fieldsToInclude.forEach((field) => {
280
281
  if (field.isContentField) {
281
- schemaFields.push(` ${field.name}: z.any(),`);
282
+ schemaFields.push(` ${field.name}: z.any(),`);
282
283
  } else if (field.type === "string") {
283
284
  if (field.nullable) {
284
- schemaFields.push(` ${field.name}: z.string().optional(),`);
285
+ schemaFields.push(` ${field.name}: z.string().optional(),`);
285
286
  } else {
286
- schemaFields.push(` ${field.name}: z.string().min(1, {
287
- message: t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.error\`),
288
- }),`);
287
+ schemaFields.push(` ${field.name}: z.string().min(1, {
288
+ message: t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.error\`),
289
+ }),`);
289
290
  }
290
291
  } else {
291
- schemaFields.push(` ${field.name}: ${field.zodSchema},`);
292
+ schemaFields.push(` ${field.name}: ${field.zodSchema},`);
292
293
  }
293
294
  });
294
295
 
@@ -297,16 +298,16 @@ function generateFormSchema(data: FrontendTemplateData): string {
297
298
  const fieldId = toCamelCase(rel.alias || rel.variant || rel.name);
298
299
  const fieldIdLower = fieldId.toLowerCase();
299
300
  if (rel.variant === AUTHOR_VARIANT) {
300
- schemaFields.push(` ${fieldId}: userObjectSchema.refine((data) => data.id && data.id.length > 0, {
301
- message: t(\`generic.relationships.author.error\`),
302
- }),`);
301
+ schemaFields.push(` ${fieldId}: userObjectSchema.refine((data) => data.id && data.id.length > 0, {
302
+ message: t(\`generic.relationships.author.error\`),
303
+ }),`);
303
304
  } else if (rel.single) {
304
305
  if (rel.nullable) {
305
- schemaFields.push(` ${fieldId}: entityObjectSchema.optional(),`);
306
+ schemaFields.push(` ${fieldId}: entityObjectSchema.optional(),`);
306
307
  } else {
307
- schemaFields.push(` ${fieldId}: entityObjectSchema.refine((data) => data.id && data.id.length > 0, {
308
- message: t(\`features.${names.camelCase.toLowerCase()}.relationships.${fieldIdLower}.error\`),
309
- }),`);
308
+ schemaFields.push(` ${fieldId}: entityObjectSchema.refine((data) => data.id && data.id.length > 0, {
309
+ message: t(\`features.${names.camelCase.toLowerCase()}.relationships.${fieldIdLower}.error\`),
310
+ }),`);
310
311
  }
311
312
  // Add relationship property fields to schema
312
313
  if (rel.fields && rel.fields.length > 0) {
@@ -314,48 +315,50 @@ function generateFormSchema(data: FrontendTemplateData): string {
314
315
  const optional = rel.nullable ? ".optional()" : "";
315
316
  switch (field.type) {
316
317
  case "number":
317
- schemaFields.push(` ${field.name}: z.number()${optional},`);
318
+ schemaFields.push(` ${field.name}: z.number()${optional},`);
318
319
  break;
319
320
  case "boolean":
320
- schemaFields.push(` ${field.name}: z.boolean()${optional},`);
321
+ schemaFields.push(` ${field.name}: z.boolean()${optional},`);
321
322
  break;
322
323
  case "date":
323
324
  case "datetime":
324
- schemaFields.push(` ${field.name}: z.date()${optional},`);
325
+ schemaFields.push(` ${field.name}: z.date()${optional},`);
325
326
  break;
326
327
  case "any":
327
- schemaFields.push(` ${field.name}: z.any()${optional},`);
328
+ schemaFields.push(` ${field.name}: z.any()${optional},`);
328
329
  break;
329
330
  case "string":
330
331
  default:
331
- schemaFields.push(` ${field.name}: z.string()${optional},`);
332
+ schemaFields.push(` ${field.name}: z.string()${optional},`);
332
333
  break;
333
334
  }
334
335
  });
335
336
  }
336
337
  } else {
337
- schemaFields.push(` ${fieldId}: z.array(entityObjectSchema).optional(),`);
338
+ schemaFields.push(` ${fieldId}: z.array(entityObjectSchema).optional(),`);
338
339
  }
339
340
  });
340
341
 
341
- return ` const formSchema = z.object({
342
- ${schemaFields.join("\n")}
343
- });`;
342
+ return schemaFields.join("\n");
344
343
  }
345
344
 
346
345
  /**
347
- * Generate default values function
346
+ * Generate the inner lines of the default-values object (the body of `({ ... })`).
347
+ *
348
+ * The `() => ({ ... })` wrapper now lives in the main template inside a
349
+ * `useCallback`, so this helper only emits the per-field default lines
350
+ * (indented to sit inside that wrapper).
348
351
  */
349
- function generateDefaultValues(data: FrontendTemplateData): string {
352
+ function generateDefaultValuesInner(data: FrontendTemplateData): string {
350
353
  const { names, fields, relationships, extendsContent } = data;
351
354
  const defaults: string[] = [];
352
355
 
353
356
  // ID default
354
- defaults.push(` id: ${names.camelCase}?.id || v4(),`);
357
+ defaults.push(` id: ${names.camelCase}?.id || v4(),`);
355
358
 
356
359
  // Name for Content-extending modules
357
360
  if (extendsContent) {
358
- defaults.push(` name: ${names.camelCase}?.name || "",`);
361
+ defaults.push(` name: ${names.camelCase}?.name || "",`);
359
362
  }
360
363
 
361
364
  // Field defaults
@@ -365,15 +368,15 @@ function generateDefaultValues(data: FrontendTemplateData): string {
365
368
 
366
369
  fieldsToInclude.forEach((field) => {
367
370
  if (field.isContentField) {
368
- defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || [],`);
371
+ defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || [],`);
369
372
  } else if (field.type === "string") {
370
- defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || "",`);
373
+ defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || "",`);
371
374
  } else if (field.type === "number") {
372
- defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || 0,`);
375
+ defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || 0,`);
373
376
  } else if (field.type === "boolean") {
374
- defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || false,`);
377
+ defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || false,`);
375
378
  } else {
376
- defaults.push(` ${field.name}: ${names.camelCase}?.${field.name},`);
379
+ defaults.push(` ${field.name}: ${names.camelCase}?.${field.name},`);
377
380
  }
378
381
  });
379
382
 
@@ -384,62 +387,64 @@ function generateDefaultValues(data: FrontendTemplateData): string {
384
387
  const pluralPropertyName = pluralize(toCamelCase(rel.alias || rel.name));
385
388
 
386
389
  if (rel.variant === AUTHOR_VARIANT) {
387
- defaults.push(` ${fieldId}: ${names.camelCase}?.${propertyName}
388
- ? { id: ${names.camelCase}.${propertyName}.id, name: ${names.camelCase}.${propertyName}.name, avatar: ${names.camelCase}.${propertyName}.avatar }
389
- : undefined,`);
390
+ defaults.push(` ${fieldId}: ${names.camelCase}?.${propertyName}
391
+ ? { id: ${names.camelCase}.${propertyName}.id, name: ${names.camelCase}.${propertyName}.name, avatar: ${names.camelCase}.${propertyName}.avatar }
392
+ : undefined,`);
390
393
  } else if (rel.single) {
391
394
  const displayProp = rel.targetHasName ? "name" : "id";
392
- defaults.push(` ${fieldId}: ${names.camelCase}?.${propertyName}
393
- ? { id: ${names.camelCase}.${propertyName}.id, name: ${names.camelCase}.${propertyName}.${displayProp} }
394
- : undefined,`);
395
+ defaults.push(` ${fieldId}: ${names.camelCase}?.${propertyName}
396
+ ? { id: ${names.camelCase}.${propertyName}.id, name: ${names.camelCase}.${propertyName}.${displayProp} }
397
+ : undefined,`);
395
398
  // Add relationship property field defaults
396
399
  if (rel.fields && rel.fields.length > 0) {
397
400
  rel.fields.forEach((field) => {
398
401
  switch (field.type) {
399
402
  case "number":
400
- defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name} ?? 0,`);
403
+ defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name} ?? 0,`);
401
404
  break;
402
405
  case "boolean":
403
- defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name} ?? false,`);
406
+ defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name} ?? false,`);
404
407
  break;
405
408
  case "date":
406
409
  case "datetime":
407
410
  case "any":
408
- defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name},`);
411
+ defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name},`);
409
412
  break;
410
413
  case "string":
411
414
  default:
412
- defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name} ?? "",`);
415
+ defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name} ?? "",`);
413
416
  break;
414
417
  }
415
418
  });
416
419
  }
417
420
  } else {
418
421
  const displayProp = rel.targetHasName ? "name" : "id";
419
- defaults.push(` ${fieldId}: ${names.camelCase}?.${pluralPropertyName}
420
- ? ${names.camelCase}.${pluralPropertyName}.map((item) => ({ id: item.id, name: item.${displayProp} }))
421
- : [],`);
422
+ defaults.push(` ${fieldId}: ${names.camelCase}?.${pluralPropertyName}
423
+ ? ${names.camelCase}.${pluralPropertyName}.map((item) => ({ id: item.id, name: item.${displayProp} }))
424
+ : [],`);
422
425
  }
423
426
  });
424
427
 
425
- return ` const getDefaultValues = () => ({
426
- ${defaults.join("\n")}
427
- });`;
428
+ return defaults.join("\n");
428
429
  }
429
430
 
430
431
  /**
431
- * Generate onSubmit handler
432
+ * Generate the body of the EditorSheet `onSubmit` callback.
433
+ *
434
+ * EditorSheet owns open/close, revalidate, navigate, propagate and error
435
+ * handling. This callback only builds the typed payload and RETURNS the
436
+ * service result so EditorSheet can run its lifecycle.
432
437
  */
433
- function generateOnSubmit(data: FrontendTemplateData): string {
438
+ function generateOnSubmitBody(data: FrontendTemplateData): string {
434
439
  const { names, fields, relationships, extendsContent } = data;
435
440
  const payloadFields: string[] = [];
436
441
 
437
442
  // ID
438
- payloadFields.push(` id: values.id,`);
443
+ payloadFields.push(` id: values.id,`);
439
444
 
440
445
  // Name for Content-extending modules
441
446
  if (extendsContent) {
442
- payloadFields.push(` name: values.name,`);
447
+ payloadFields.push(` name: values.name,`);
443
448
  }
444
449
 
445
450
  // Fields
@@ -448,7 +453,7 @@ function generateOnSubmit(data: FrontendTemplateData): string {
448
453
  : fields;
449
454
 
450
455
  fieldsToInclude.forEach((field) => {
451
- payloadFields.push(` ${field.name}: values.${field.name},`);
456
+ payloadFields.push(` ${field.name}: values.${field.name},`);
452
457
  });
453
458
 
454
459
  // Relationships
@@ -457,40 +462,23 @@ function generateOnSubmit(data: FrontendTemplateData): string {
457
462
  const payloadKey = rel.single ? `${fieldId}Id` : `${toCamelCase(rel.alias || rel.name)}Ids`;
458
463
 
459
464
  if (rel.single) {
460
- payloadFields.push(` ${payloadKey}: values.${fieldId}?.id,`);
465
+ payloadFields.push(` ${payloadKey}: values.${fieldId}?.id,`);
461
466
  // Add relationship property fields to payload
462
467
  if (rel.fields && rel.fields.length > 0) {
463
468
  rel.fields.forEach((field) => {
464
- payloadFields.push(` ${field.name}: values.${field.name},`);
469
+ payloadFields.push(` ${field.name}: values.${field.name},`);
465
470
  });
466
471
  }
467
472
  } else {
468
- payloadFields.push(` ${payloadKey}: values.${fieldId} ? values.${fieldId}.map((item) => item.id) : [],`);
473
+ payloadFields.push(` ${payloadKey}: values.${fieldId} ? values.${fieldId}.map((item) => item.id) : [],`);
469
474
  }
470
475
  });
471
476
 
472
- return ` const onSubmit: SubmitHandler<z.infer<typeof formSchema>> = async (values: z.infer<typeof formSchema>) => {
473
- const payload: ${names.pascalCase}Input = {
477
+ return ` const payload: ${names.pascalCase}Input = {
474
478
  ${payloadFields.join("\n")}
475
- };
476
-
477
- try {
478
- const updated${names.pascalCase} = ${names.camelCase} ? await ${names.pascalCase}Service.update(payload) : await ${names.pascalCase}Service.create(payload);
479
+ };
479
480
 
480
- setOpen(false);
481
- revalidatePaths(generateUrl({ page: Modules.${names.pascalCase}, id: updated${names.pascalCase}.id, language: \`[locale]\` }));
482
- if (${names.camelCase} && propagateChanges) {
483
- propagateChanges(updated${names.pascalCase});
484
- } else {
485
- router.push(generateUrl({ page: Modules.${names.pascalCase}, id: updated${names.pascalCase}.id }));
486
- }
487
- } catch (error) {
488
- errorToast({
489
- title: ${names.camelCase} ? t(\`generic.errors.update\`) : t(\`generic.errors.create\`),
490
- error,
491
- });
492
- }
493
- };`;
481
+ return ${names.camelCase} ? await ${names.pascalCase}Service.update(payload) : await ${names.pascalCase}Service.create(payload);`;
494
482
  }
495
483
 
496
484
  /**