@carlonicora/nextjs-jsonapi 1.110.0 → 1.111.1
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-K7IIWM3S.mjs → BlockNoteEditor-A5LNFWHW.mjs} +2 -2
- package/dist/{BlockNoteEditor-7OKW6HHN.js → BlockNoteEditor-HNQNKCFP.js} +9 -9
- package/dist/{BlockNoteEditor-7OKW6HHN.js.map → BlockNoteEditor-HNQNKCFP.js.map} +1 -1
- package/dist/billing/index.js +299 -299
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-AKXUNE3A.js → chunk-DRJUN4R6.js} +22 -19
- package/dist/chunk-DRJUN4R6.js.map +1 -0
- package/dist/{chunk-TXVVZLUQ.mjs → chunk-LYLGU2BK.mjs} +67 -64
- package/dist/chunk-LYLGU2BK.mjs.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.js +2 -2
- package/dist/components/index.mjs +1 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/features/help/index.js +30 -30
- package/dist/features/help/index.mjs +1 -1
- package/dist/scripts/generate-web-module/__tests__/container.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/container.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/container.spec.js +54 -0
- package/dist/scripts/generate-web-module/__tests__/container.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/data-service.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/data-service.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/data-service.spec.js +19 -0
- package/dist/scripts/generate-web-module/__tests__/data-service.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/editor.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/editor.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/editor.spec.js +35 -0
- package/dist/scripts/generate-web-module/__tests__/editor.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/fixtures.d.ts +4 -0
- package/dist/scripts/generate-web-module/__tests__/fixtures.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/fixtures.js +45 -0
- package/dist/scripts/generate-web-module/__tests__/fixtures.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/model-interface.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/model-interface.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/model-interface.spec.js +74 -0
- package/dist/scripts/generate-web-module/__tests__/model-interface.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/plumbing.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/plumbing.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/plumbing.spec.js +21 -0
- package/dist/scripts/generate-web-module/__tests__/plumbing.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/selector.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/selector.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/selector.spec.js +25 -0
- package/dist/scripts/generate-web-module/__tests__/selector.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.js +55 -0
- package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/generator.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/generator.js +14 -11
- package/dist/scripts/generate-web-module/generator.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/container.template.d.ts +5 -1
- package/dist/scripts/generate-web-module/templates/components/container.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/container.template.js +43 -63
- package/dist/scripts/generate-web-module/templates/components/container.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/content.template.d.ts +7 -4
- package/dist/scripts/generate-web-module/templates/components/content.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/content.template.js +103 -14
- package/dist/scripts/generate-web-module/templates/components/content.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts +1 -1
- package/dist/scripts/generate-web-module/templates/components/editor.template.js +139 -150
- package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/index.d.ts +0 -1
- package/dist/scripts/generate-web-module/templates/components/index.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/index.js +1 -3
- package/dist/scripts/generate-web-module/templates/components/index.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/list-container.template.js +2 -2
- package/dist/scripts/generate-web-module/templates/components/list.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/list.template.js +6 -1
- package/dist/scripts/generate-web-module/templates/components/list.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/selector.template.js +48 -52
- package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/interface.template.js +38 -11
- package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/model.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/model.template.js +83 -26
- package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/service.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/service.template.js +21 -1
- package/dist/scripts/generate-web-module/templates/data/service.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/index.d.ts +1 -1
- package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/index.js +1 -2
- package/dist/scripts/generate-web-module/templates/index.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/module.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/module.template.js +11 -3
- package/dist/scripts/generate-web-module/templates/module.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/table-hook.template.js +31 -1
- package/dist/scripts/generate-web-module/templates/table-hook.template.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts +0 -1
- package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/transformers/import-resolver.js +0 -1
- package/dist/scripts/generate-web-module/transformers/import-resolver.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/relationship-key.d.ts +5 -0
- package/dist/scripts/generate-web-module/transformers/relationship-key.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/relationship-key.js +21 -0
- package/dist/scripts/generate-web-module/transformers/relationship-key.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js +2 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js.map +1 -1
- package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts +21 -0
- package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +17 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/generate-web-module/__tests__/container.spec.ts +58 -0
- package/scripts/generate-web-module/__tests__/data-service.spec.ts +18 -0
- package/scripts/generate-web-module/__tests__/editor.spec.ts +36 -0
- package/scripts/generate-web-module/__tests__/fixtures.ts +43 -0
- package/scripts/generate-web-module/__tests__/model-interface.spec.ts +77 -0
- package/scripts/generate-web-module/__tests__/plumbing.spec.ts +21 -0
- package/scripts/generate-web-module/__tests__/selector.spec.ts +24 -0
- package/scripts/generate-web-module/__tests__/table-module-list.spec.ts +59 -0
- package/scripts/generate-web-module/generator.ts +14 -13
- package/scripts/generate-web-module/templates/components/container.template.ts +51 -66
- package/scripts/generate-web-module/templates/components/content.template.ts +107 -15
- package/scripts/generate-web-module/templates/components/editor.template.ts +140 -152
- package/scripts/generate-web-module/templates/components/index.ts +0 -1
- package/scripts/generate-web-module/templates/components/list-container.template.ts +2 -2
- package/scripts/generate-web-module/templates/components/list.template.ts +6 -1
- package/scripts/generate-web-module/templates/components/selector.template.ts +48 -52
- package/scripts/generate-web-module/templates/data/interface.template.ts +44 -12
- package/scripts/generate-web-module/templates/data/model.template.ts +88 -28
- package/scripts/generate-web-module/templates/data/service.template.ts +23 -1
- package/scripts/generate-web-module/templates/index.ts +0 -1
- package/scripts/generate-web-module/templates/module.template.ts +13 -3
- package/scripts/generate-web-module/templates/table-hook.template.ts +31 -2
- package/scripts/generate-web-module/transformers/import-resolver.ts +0 -2
- package/scripts/generate-web-module/transformers/relationship-key.ts +18 -0
- package/scripts/generate-web-module/transformers/relationship-resolver.ts +2 -0
- package/scripts/generate-web-module/types/json-schema.interface.ts +9 -0
- package/scripts/generate-web-module/types/template-data.interface.ts +7 -0
- package/src/features/notification/components/modals/NotificationModal.tsx +2 -5
- package/src/features/notification/contexts/NotificationContext.tsx +29 -13
- package/src/features/notification/contexts/__tests__/NotificationContext.spec.tsx +70 -0
- package/dist/chunk-AKXUNE3A.js.map +0 -1
- package/dist/chunk-TXVVZLUQ.mjs.map +0 -1
- package/dist/scripts/generate-web-module/templates/components/details.template.d.ts +0 -14
- package/dist/scripts/generate-web-module/templates/components/details.template.d.ts.map +0 -1
- package/dist/scripts/generate-web-module/templates/components/details.template.js +0 -112
- package/dist/scripts/generate-web-module/templates/components/details.template.js.map +0 -1
- package/scripts/generate-web-module/templates/components/details.template.ts +0 -116
- /package/dist/{BlockNoteEditor-K7IIWM3S.mjs.map → BlockNoteEditor-A5LNFWHW.mjs.map} +0 -0
|
@@ -1,40 +1,132 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Content Template
|
|
3
3
|
*
|
|
4
|
-
* Generates {Module}Content.tsx
|
|
5
|
-
*
|
|
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
|
|
18
|
+
* @returns Generated file content (always a string)
|
|
15
19
|
*/
|
|
16
|
-
export function generateContentTemplate(data: FrontendTemplateData): string
|
|
20
|
+
export function generateContentTemplate(data: FrontendTemplateData): string {
|
|
17
21
|
const { names, extendsContent } = data;
|
|
22
|
+
const camel = names.camelCase;
|
|
18
23
|
|
|
19
|
-
//
|
|
20
|
-
if (
|
|
21
|
-
return
|
|
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 {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
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
|
|
31
|
-
const
|
|
54
|
+
export function ${names.pascalCase}Content({ ${camel} }: ${names.pascalCase}ContentProps) {
|
|
55
|
+
const t = useTranslations();
|
|
32
56
|
|
|
33
57
|
return (
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
|
|
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
|
|
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,
|
|
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
|
|
33
|
-
const
|
|
34
|
-
const
|
|
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
|
-
${
|
|
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
|
-
|
|
57
|
+
${hasAuthor ? ` const { currentUser } = useCurrentUserContext<UserInterface>();\n` : ""}
|
|
58
|
+
const formSchema = useMemo(
|
|
59
|
+
() =>
|
|
60
|
+
z.object({
|
|
61
|
+
${schemaInner}
|
|
62
|
+
}),
|
|
63
|
+
[t],
|
|
64
|
+
);
|
|
60
65
|
|
|
61
|
-
|
|
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
|
-
? `
|
|
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
|
-
|
|
99
|
-
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
222
|
+
coreImports.push("userObjectSchema");
|
|
226
223
|
}
|
|
227
|
-
imports.push(`import { ${
|
|
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 {
|
|
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
|
|
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(`
|
|
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(`
|
|
275
|
-
|
|
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(`
|
|
282
|
+
schemaFields.push(` ${field.name}: z.any(),`);
|
|
282
283
|
} else if (field.type === "string") {
|
|
283
284
|
if (field.nullable) {
|
|
284
|
-
schemaFields.push(`
|
|
285
|
+
schemaFields.push(` ${field.name}: z.string().optional(),`);
|
|
285
286
|
} else {
|
|
286
|
-
schemaFields.push(`
|
|
287
|
-
|
|
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(`
|
|
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(`
|
|
301
|
-
|
|
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(`
|
|
306
|
+
schemaFields.push(` ${fieldId}: entityObjectSchema.optional(),`);
|
|
306
307
|
} else {
|
|
307
|
-
schemaFields.push(`
|
|
308
|
-
|
|
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(`
|
|
318
|
+
schemaFields.push(` ${field.name}: z.number()${optional},`);
|
|
318
319
|
break;
|
|
319
320
|
case "boolean":
|
|
320
|
-
schemaFields.push(`
|
|
321
|
+
schemaFields.push(` ${field.name}: z.boolean()${optional},`);
|
|
321
322
|
break;
|
|
322
323
|
case "date":
|
|
323
324
|
case "datetime":
|
|
324
|
-
schemaFields.push(`
|
|
325
|
+
schemaFields.push(` ${field.name}: z.date()${optional},`);
|
|
325
326
|
break;
|
|
326
327
|
case "any":
|
|
327
|
-
schemaFields.push(`
|
|
328
|
+
schemaFields.push(` ${field.name}: z.any()${optional},`);
|
|
328
329
|
break;
|
|
329
330
|
case "string":
|
|
330
331
|
default:
|
|
331
|
-
schemaFields.push(`
|
|
332
|
+
schemaFields.push(` ${field.name}: z.string()${optional},`);
|
|
332
333
|
break;
|
|
333
334
|
}
|
|
334
335
|
});
|
|
335
336
|
}
|
|
336
337
|
} else {
|
|
337
|
-
schemaFields.push(`
|
|
338
|
+
schemaFields.push(` ${fieldId}: z.array(entityObjectSchema).optional(),`);
|
|
338
339
|
}
|
|
339
340
|
});
|
|
340
341
|
|
|
341
|
-
return
|
|
342
|
-
${schemaFields.join("\n")}
|
|
343
|
-
});`;
|
|
342
|
+
return schemaFields.join("\n");
|
|
344
343
|
}
|
|
345
344
|
|
|
346
345
|
/**
|
|
347
|
-
* Generate default
|
|
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
|
|
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(`
|
|
357
|
+
defaults.push(` id: ${names.camelCase}?.id || v4(),`);
|
|
355
358
|
|
|
356
359
|
// Name for Content-extending modules
|
|
357
360
|
if (extendsContent) {
|
|
358
|
-
defaults.push(`
|
|
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(`
|
|
371
|
+
defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || [],`);
|
|
369
372
|
} else if (field.type === "string") {
|
|
370
|
-
defaults.push(`
|
|
373
|
+
defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || "",`);
|
|
371
374
|
} else if (field.type === "number") {
|
|
372
|
-
defaults.push(`
|
|
375
|
+
defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || 0,`);
|
|
373
376
|
} else if (field.type === "boolean") {
|
|
374
|
-
defaults.push(`
|
|
377
|
+
defaults.push(` ${field.name}: ${names.camelCase}?.${field.name} || false,`);
|
|
375
378
|
} else {
|
|
376
|
-
defaults.push(`
|
|
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(`
|
|
388
|
-
|
|
389
|
-
|
|
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(`
|
|
393
|
-
|
|
394
|
-
|
|
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(`
|
|
403
|
+
defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name} ?? 0,`);
|
|
401
404
|
break;
|
|
402
405
|
case "boolean":
|
|
403
|
-
defaults.push(`
|
|
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(`
|
|
411
|
+
defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name},`);
|
|
409
412
|
break;
|
|
410
413
|
case "string":
|
|
411
414
|
default:
|
|
412
|
-
defaults.push(`
|
|
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(`
|
|
420
|
-
|
|
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
|
|
426
|
-
${defaults.join("\n")}
|
|
427
|
-
});`;
|
|
428
|
+
return defaults.join("\n");
|
|
428
429
|
}
|
|
429
430
|
|
|
430
431
|
/**
|
|
431
|
-
* Generate onSubmit
|
|
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
|
|
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(`
|
|
443
|
+
payloadFields.push(` id: values.id,`);
|
|
439
444
|
|
|
440
445
|
// Name for Content-extending modules
|
|
441
446
|
if (extendsContent) {
|
|
442
|
-
payloadFields.push(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
469
|
+
payloadFields.push(` ${field.name}: values.${field.name},`);
|
|
465
470
|
});
|
|
466
471
|
}
|
|
467
472
|
} else {
|
|
468
|
-
payloadFields.push(`
|
|
473
|
+
payloadFields.push(` ${payloadKey}: values.${fieldId} ? values.${fieldId}.map((item) => item.id) : [],`);
|
|
469
474
|
}
|
|
470
475
|
});
|
|
471
476
|
|
|
472
|
-
return `
|
|
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
|
-
|
|
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
|
/**
|