betterstart-cli 0.0.17 → 0.0.19
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/assets/adapters/next/plugins/blog/schemas/menus.json +1 -0
- package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +1 -0
- package/dist/assets/adapters/next/plugins/portfolio/plugin.ts +17 -0
- package/dist/assets/adapters/next/plugins/portfolio/schemas/portfolio.json +128 -0
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +2 -2
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/snippets-tab.tsx +1 -0
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-dialog.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-drawer.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-item.tsx +17 -7
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-preview.tsx +1 -0
- package/dist/assets/adapters/next/templates/init/data/navigation.ts +2 -1
- package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-source-mode.tsx +3 -3
- package/dist/assets/adapters/next/templates/init/hooks/use-media.ts +2 -1
- package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +3 -3
- package/dist/assets/shared-assets/react-admin/custom/content-editor/media-gallery-block.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/content-editor/table-add-controls.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +219 -40
- package/dist/assets/shared-assets/react-admin/custom/media-field-empty-state.tsx +99 -0
- package/dist/assets/shared-assets/react-admin/custom/media-field.tsx +181 -0
- package/dist/assets/shared-assets/react-admin/custom/sortable-gallery-item.tsx +38 -38
- package/dist/assets/shared-assets/react-admin/schema.json +64 -2
- package/dist/assets/shared-assets/react-admin/ui/pagination.tsx +1 -1
- package/dist/cli.js +474 -143
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/shared-assets/react-admin/custom/gallery-thumbnail.tsx +0 -39
- package/dist/assets/shared-assets/react-admin/custom/media-gallery-field.tsx +0 -182
package/dist/cli.js
CHANGED
|
@@ -125,7 +125,7 @@ function createUninstallCommand(runtime) {
|
|
|
125
125
|
return new Command("uninstall").description("Remove all Admin files and undo modifications made by betterstart init").option("-f, --force", "Skip all confirmation prompts", false).option("--cwd <path>", "Project root path").action((options) => runtime.runUninstall(options));
|
|
126
126
|
}
|
|
127
127
|
function createUpdateCommand(runtime) {
|
|
128
|
-
return new Command("update").alias("update-component").description("Update Admin components from the latest CLI templates").argument("[components...]", "Component names to update (e.g., media-
|
|
128
|
+
return new Command("update").alias("update-component").description("Update Admin components from the latest CLI templates").argument("[components...]", "Component names to update (e.g., media-field button)").option("--list", "List all available components").option("--all", "Update all components").option("--shadcn-preset <preset>", "Apply a shadcn preset to Admin UI components").option("--only <parts>", "Apply only supported shadcn preset parts: theme").option("--cwd <path>", "Project root path").action(
|
|
129
129
|
(components, options) => runtime.runUpdate(components, options)
|
|
130
130
|
);
|
|
131
131
|
}
|
|
@@ -867,9 +867,27 @@ var blogPlugin = {
|
|
|
867
867
|
configure: "none"
|
|
868
868
|
};
|
|
869
869
|
|
|
870
|
+
// adapters/next/plugins/portfolio/plugin.ts
|
|
871
|
+
var portfolioPlugin = {
|
|
872
|
+
id: "portfolio",
|
|
873
|
+
title: "Portfolio",
|
|
874
|
+
description: "Portfolio content module",
|
|
875
|
+
kind: "content",
|
|
876
|
+
version: "1.0.0",
|
|
877
|
+
dependencies: [],
|
|
878
|
+
conflicts: [],
|
|
879
|
+
schemaFiles: ["portfolio.json"],
|
|
880
|
+
files: [],
|
|
881
|
+
packageDependencies: [],
|
|
882
|
+
devDependencies: [],
|
|
883
|
+
envSections: [],
|
|
884
|
+
configure: "none"
|
|
885
|
+
};
|
|
886
|
+
|
|
870
887
|
// adapters/next/plugin-registry.ts
|
|
871
888
|
var pluginRegistry = {
|
|
872
|
-
blog: blogPlugin
|
|
889
|
+
blog: blogPlugin,
|
|
890
|
+
portfolio: portfolioPlugin
|
|
873
891
|
};
|
|
874
892
|
function listPluginDefinitions() {
|
|
875
893
|
return Object.values(pluginRegistry);
|
|
@@ -1724,6 +1742,7 @@ import path11 from "path";
|
|
|
1724
1742
|
// core-engine/schema/constants.ts
|
|
1725
1743
|
var FIELD_TYPES = {
|
|
1726
1744
|
STRING: "string",
|
|
1745
|
+
EMAIL: "email",
|
|
1727
1746
|
NUMBER: "number",
|
|
1728
1747
|
BOOLEAN: "boolean",
|
|
1729
1748
|
TEXT: "text",
|
|
@@ -1796,6 +1815,18 @@ var FORM_FIELD_TYPES = {
|
|
|
1796
1815
|
LIST: "list",
|
|
1797
1816
|
DYNAMIC_FIELDS: "dynamicFields"
|
|
1798
1817
|
};
|
|
1818
|
+
var AUTO_FIELDS = {
|
|
1819
|
+
ID: "id",
|
|
1820
|
+
CREATED_AT: "createdAt",
|
|
1821
|
+
UPDATED_AT: "updatedAt",
|
|
1822
|
+
PUBLISHED: "published",
|
|
1823
|
+
SLUG: "slug"
|
|
1824
|
+
};
|
|
1825
|
+
var FORM_SUBMISSION_FIELDS = {
|
|
1826
|
+
SUBMITTED_AT: "submittedAt",
|
|
1827
|
+
IP_ADDRESS: "ipAddress",
|
|
1828
|
+
USER_AGENT: "userAgent"
|
|
1829
|
+
};
|
|
1799
1830
|
function isFieldType(value) {
|
|
1800
1831
|
return Object.values(FIELD_TYPES).includes(value);
|
|
1801
1832
|
}
|
|
@@ -1956,6 +1987,11 @@ function loadSchema(schemasDir, name) {
|
|
|
1956
1987
|
const parsed = parseJson(content, filePath);
|
|
1957
1988
|
const obj = parsed;
|
|
1958
1989
|
const type = obj.type;
|
|
1990
|
+
if (type === void 0) {
|
|
1991
|
+
throw new Error(
|
|
1992
|
+
`Schema "${name}" is missing required top-level "type". Expected "entity", "single", or "form".`
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1959
1995
|
if (type === "form") {
|
|
1960
1996
|
return { type: "form", schema: parsed, filePath };
|
|
1961
1997
|
}
|
|
@@ -1996,6 +2032,14 @@ var ENTITY_SLUG_FIELD_NAME = "slug";
|
|
|
1996
2032
|
var ENTITY_SLUG_FIELD_TYPES = /* @__PURE__ */ new Set(["string", "varchar", "text"]);
|
|
1997
2033
|
var GENERATED_SCHEMA_TITLE_FIELD_TYPES = /* @__PURE__ */ new Set(["string", "varchar", "text"]);
|
|
1998
2034
|
var FORM_SCHEMA_TITLE_FIELD_TYPES = /* @__PURE__ */ new Set(["text"]);
|
|
2035
|
+
var FORM_SUBMISSION_COLUMN_NAMES = [
|
|
2036
|
+
AUTO_FIELDS.ID,
|
|
2037
|
+
FORM_SUBMISSION_FIELDS.SUBMITTED_AT,
|
|
2038
|
+
FORM_SUBMISSION_FIELDS.IP_ADDRESS,
|
|
2039
|
+
FORM_SUBMISSION_FIELDS.USER_AGENT,
|
|
2040
|
+
AUTO_FIELDS.CREATED_AT,
|
|
2041
|
+
AUTO_FIELDS.UPDATED_AT
|
|
2042
|
+
];
|
|
1999
2043
|
function isEntitySlugField(field) {
|
|
2000
2044
|
return field.name === ENTITY_SLUG_FIELD_NAME;
|
|
2001
2045
|
}
|
|
@@ -2039,52 +2083,84 @@ function createGeneratedSlugField(fields) {
|
|
|
2039
2083
|
...sourceField ? { defaultValueFrom: sourceField } : {}
|
|
2040
2084
|
};
|
|
2041
2085
|
}
|
|
2042
|
-
function
|
|
2043
|
-
|
|
2044
|
-
|
|
2086
|
+
function normalizeEntitySlugField(field, sourceField) {
|
|
2087
|
+
return {
|
|
2088
|
+
...field,
|
|
2089
|
+
required: true,
|
|
2090
|
+
...sourceField && !field.hidden ? { defaultValueFrom: sourceField } : {}
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
function createGeneratedSlugFieldForTitle(fields, titleField) {
|
|
2094
|
+
const slugField = createGeneratedSlugField(fields);
|
|
2095
|
+
return titleField.slot ? { ...slugField, slot: titleField.slot } : slugField;
|
|
2096
|
+
}
|
|
2097
|
+
function orderEntityIdentityFields(fields) {
|
|
2098
|
+
const titleIndex = fields.findIndex(isGeneratedSchemaTitleField);
|
|
2099
|
+
const slugIndex = fields.findIndex(isEntitySlugField);
|
|
2100
|
+
if (titleIndex < 0 || slugIndex < 0 || titleIndex === 0 && slugIndex === 1) return fields;
|
|
2101
|
+
const titleField = fields[titleIndex];
|
|
2102
|
+
const slugField = fields[slugIndex];
|
|
2103
|
+
return [
|
|
2104
|
+
titleField,
|
|
2105
|
+
slugField,
|
|
2106
|
+
...fields.filter((_, index) => index !== titleIndex && index !== slugIndex)
|
|
2107
|
+
];
|
|
2108
|
+
}
|
|
2109
|
+
function normalizeEntityIdentityFields(fields, rootFields, state) {
|
|
2110
|
+
const normalizedFields = [];
|
|
2111
|
+
for (const field of fields) {
|
|
2045
2112
|
if (isEntitySlugField(field)) {
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
...sourceField && !field.hidden ? { defaultValueFrom: sourceField } : {}
|
|
2051
|
-
};
|
|
2113
|
+
if (state.keptAuthoredSlugField) continue;
|
|
2114
|
+
state.keptAuthoredSlugField = true;
|
|
2115
|
+
normalizedFields.push(normalizeEntitySlugField(field, state.sourceField));
|
|
2116
|
+
continue;
|
|
2052
2117
|
}
|
|
2053
2118
|
if ((field.type === "group" || field.type === "section") && field.fields) {
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2119
|
+
normalizedFields.push({
|
|
2120
|
+
...field,
|
|
2121
|
+
fields: normalizeEntityIdentityFields(field.fields, rootFields, state)
|
|
2122
|
+
});
|
|
2123
|
+
continue;
|
|
2057
2124
|
}
|
|
2058
2125
|
if (field.type === "tabs" && field.tabs) {
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2126
|
+
normalizedFields.push({
|
|
2127
|
+
...field,
|
|
2128
|
+
tabs: field.tabs.map((tab) => ({
|
|
2129
|
+
...tab,
|
|
2130
|
+
fields: normalizeEntityIdentityFields(tab.fields ?? [], rootFields, state)
|
|
2131
|
+
}))
|
|
2063
2132
|
});
|
|
2064
|
-
|
|
2133
|
+
continue;
|
|
2065
2134
|
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2135
|
+
normalizedFields.push(field);
|
|
2136
|
+
if (!state.hasAuthoredSlugField && !state.generatedSlugInserted && isGeneratedSchemaTitleField(field)) {
|
|
2137
|
+
normalizedFields.push(createGeneratedSlugFieldForTitle(rootFields, field));
|
|
2138
|
+
state.generatedSlugInserted = true;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return orderEntityIdentityFields(normalizedFields);
|
|
2069
2142
|
}
|
|
2070
2143
|
function ensureEntitySlugField(schema) {
|
|
2071
2144
|
const sourceField = hasPersistedTitleField(schema.fields) ? "title" : void 0;
|
|
2072
|
-
const
|
|
2073
|
-
|
|
2145
|
+
const hasAuthoredSlugField = flattenFields(schema.fields).some(isEntitySlugField);
|
|
2146
|
+
const state = {
|
|
2147
|
+
sourceField,
|
|
2148
|
+
hasAuthoredSlugField,
|
|
2149
|
+
keptAuthoredSlugField: false,
|
|
2150
|
+
generatedSlugInserted: false
|
|
2151
|
+
};
|
|
2152
|
+
const normalizedFields = normalizeEntityIdentityFields(schema.fields, schema.fields, state);
|
|
2153
|
+
if (hasAuthoredSlugField || state.generatedSlugInserted) {
|
|
2074
2154
|
return {
|
|
2075
2155
|
...schema,
|
|
2076
|
-
fields:
|
|
2156
|
+
fields: normalizedFields
|
|
2077
2157
|
};
|
|
2078
2158
|
}
|
|
2079
|
-
const
|
|
2080
|
-
const titleIndex = schema.fields.findIndex(
|
|
2081
|
-
(field) => field.name === GENERATED_SCHEMA_TITLE_FIELD_NAME && isStringLikeSlugField(field)
|
|
2082
|
-
);
|
|
2083
|
-
const insertIndex = titleIndex >= 0 ? titleIndex + 1 : Math.min(schema.fields.length, 1);
|
|
2159
|
+
const insertIndex = Math.min(normalizedFields.length, 1);
|
|
2084
2160
|
const fields = [
|
|
2085
|
-
...
|
|
2086
|
-
|
|
2087
|
-
...
|
|
2161
|
+
...normalizedFields.slice(0, insertIndex),
|
|
2162
|
+
createGeneratedSlugField(schema.fields),
|
|
2163
|
+
...normalizedFields.slice(insertIndex)
|
|
2088
2164
|
];
|
|
2089
2165
|
return {
|
|
2090
2166
|
...schema,
|
|
@@ -2198,6 +2274,23 @@ function collectInvalidFormColumnTypeErrors(schema, errors) {
|
|
|
2198
2274
|
}
|
|
2199
2275
|
}
|
|
2200
2276
|
}
|
|
2277
|
+
function collectInvalidFormColumnReferenceErrors(schema, errors) {
|
|
2278
|
+
const fieldNames = new Set(
|
|
2279
|
+
getAllFormSchemaFields(schema).map((field) => field.name).filter((name) => typeof name === "string" && name.length > 0)
|
|
2280
|
+
);
|
|
2281
|
+
for (const columnName of FORM_SUBMISSION_COLUMN_NAMES) {
|
|
2282
|
+
fieldNames.add(columnName);
|
|
2283
|
+
}
|
|
2284
|
+
for (const column of schema.columns || []) {
|
|
2285
|
+
if (!column.accessorKey || !column.header) {
|
|
2286
|
+
errors.push(`Form column is missing required properties: ${JSON.stringify(column)}`);
|
|
2287
|
+
} else if (!fieldNames.has(column.accessorKey)) {
|
|
2288
|
+
errors.push(
|
|
2289
|
+
`Form column accessorKey "${column.accessorKey}" does not match any field or generated submission property. Available: ${Array.from(fieldNames).join(", ")}`
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2201
2294
|
function validateEntitySchema(schema) {
|
|
2202
2295
|
const errors = [];
|
|
2203
2296
|
collectSchemaSlotLayoutErrors(schema, errors);
|
|
@@ -2217,6 +2310,9 @@ function validateEntitySchema(schema) {
|
|
|
2217
2310
|
if (!Array.isArray(normalizedSchema.fields) || normalizedSchema.fields.length === 0) {
|
|
2218
2311
|
errors.push('Schema must have at least one field via "fields" or "slot"');
|
|
2219
2312
|
}
|
|
2313
|
+
if (normalizedSchema.type !== "entity") {
|
|
2314
|
+
errors.push('Entity schema must have `type: "entity"`');
|
|
2315
|
+
}
|
|
2220
2316
|
if (!Array.isArray(normalizedSchema.columns) || normalizedSchema.columns.length === 0) {
|
|
2221
2317
|
errors.push("Schema must have at least one column");
|
|
2222
2318
|
}
|
|
@@ -2279,6 +2375,9 @@ function validateSingleSchema(schema) {
|
|
|
2279
2375
|
if (!Array.isArray(normalizedSchema.fields) || normalizedSchema.fields.length === 0) {
|
|
2280
2376
|
errors.push('Schema must have at least one field via "fields" or "slot"');
|
|
2281
2377
|
}
|
|
2378
|
+
if (normalizedSchema.type !== "single") {
|
|
2379
|
+
errors.push('Single schema must have `type: "single"`');
|
|
2380
|
+
}
|
|
2282
2381
|
for (const field of normalizedSchema.fields || []) {
|
|
2283
2382
|
if (field.type === "separator") continue;
|
|
2284
2383
|
if (!field.name || !field.type) {
|
|
@@ -2338,6 +2437,7 @@ function validateFormSchema(schema) {
|
|
|
2338
2437
|
}
|
|
2339
2438
|
collectMissingFormSchemaTitleErrors(schema, errors);
|
|
2340
2439
|
collectInvalidFormColumnTypeErrors(schema, errors);
|
|
2440
|
+
collectInvalidFormColumnReferenceErrors(schema, errors);
|
|
2341
2441
|
return errors;
|
|
2342
2442
|
}
|
|
2343
2443
|
function validateLoadedSchema(loaded) {
|
|
@@ -3715,6 +3815,7 @@ function toDrizzleType(field, requiredImports) {
|
|
|
3715
3815
|
requiredImports.add("uuid");
|
|
3716
3816
|
return "uuid().defaultRandom()";
|
|
3717
3817
|
case "string":
|
|
3818
|
+
case "email":
|
|
3718
3819
|
case "varchar":
|
|
3719
3820
|
requiredImports.add("varchar");
|
|
3720
3821
|
return field.length ? `varchar({ length: ${field.length} })` : `varchar({ length: ${DRIZZLE_DEFAULTS.VARCHAR_LENGTH} })`;
|
|
@@ -3871,6 +3972,7 @@ function toTypeScriptType(field, mode = "output") {
|
|
|
3871
3972
|
case "boolean":
|
|
3872
3973
|
return "boolean";
|
|
3873
3974
|
case "string":
|
|
3975
|
+
case "email":
|
|
3874
3976
|
case "varchar":
|
|
3875
3977
|
case "text":
|
|
3876
3978
|
case "markdown":
|
|
@@ -4045,7 +4147,7 @@ function generateFieldMapping(field, source = "input") {
|
|
|
4045
4147
|
if ((field.type === "date" || field.type === "timestamp" || field.type === "time") && !field.required) {
|
|
4046
4148
|
return `${source}.${field.name} && ${source}.${field.name} !== '' ? ${source}.${field.name} : null`;
|
|
4047
4149
|
}
|
|
4048
|
-
if (!field.required && ["string", "varchar", "text", "select"].includes(field.type)) {
|
|
4150
|
+
if (!field.required && ["string", "email", "varchar", "text", "select"].includes(field.type)) {
|
|
4049
4151
|
return `${source}.${field.name} && ${source}.${field.name} !== '' ? ${source}.${field.name} : null`;
|
|
4050
4152
|
}
|
|
4051
4153
|
return `${source}.${field.name}`;
|
|
@@ -4311,6 +4413,7 @@ ${blocks.join("\n\n")}
|
|
|
4311
4413
|
// adapters/next/generators/entity-filters.ts
|
|
4312
4414
|
var FILTERABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
4313
4415
|
"string",
|
|
4416
|
+
"email",
|
|
4314
4417
|
"varchar",
|
|
4315
4418
|
"text",
|
|
4316
4419
|
"boolean",
|
|
@@ -4552,6 +4655,8 @@ function sampleSchemaFieldValue(field) {
|
|
|
4552
4655
|
switch (field.type) {
|
|
4553
4656
|
case "boolean":
|
|
4554
4657
|
return true;
|
|
4658
|
+
case "email":
|
|
4659
|
+
return "developer@example.com";
|
|
4555
4660
|
case "number":
|
|
4556
4661
|
case "decimal":
|
|
4557
4662
|
return 1;
|
|
@@ -5323,21 +5428,19 @@ function buildFormDevModeSnippets(schema, actionImportPath, adminImportAlias) {
|
|
|
5323
5428
|
const fields = getAllFormSchemaFields(schema);
|
|
5324
5429
|
const submissionInput = createFormInputObject(fields);
|
|
5325
5430
|
const actionModulePath = resolveActionModulePath(actionImportPath, adminImportAlias);
|
|
5431
|
+
const formComponentModulePath = `${adminImportAlias ?? DEFAULT_ADMIN_IMPORT_ALIAS}/components/forms/${toKebabCase(schema.name)}-form`;
|
|
5326
5432
|
const submissionData = createFormSubmissionObject(fields);
|
|
5327
5433
|
const submitResponse = { success: true, submission: submissionData };
|
|
5328
5434
|
const submissionsResponse = { submissions: [submissionData], total: 1 };
|
|
5329
5435
|
const deleteResponse = { success: true };
|
|
5330
5436
|
const formLabel2 = schema.label;
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
payloadExample: createPayloadExample(submissionInput),
|
|
5339
|
-
responseExample: createResponseExample(submitResponse),
|
|
5340
|
-
code: `'use client'
|
|
5437
|
+
const formComponentCode = `import { ${pascal}Form } from '${formComponentModulePath}'
|
|
5438
|
+
|
|
5439
|
+
export default function ${pascal}Page() {
|
|
5440
|
+
return <${pascal}Form />
|
|
5441
|
+
}
|
|
5442
|
+
`;
|
|
5443
|
+
const submitActionCode = `'use client'
|
|
5341
5444
|
|
|
5342
5445
|
import * as React from 'react'
|
|
5343
5446
|
import {
|
|
@@ -5364,7 +5467,25 @@ export function ${pascal}SubmitButton() {
|
|
|
5364
5467
|
</button>
|
|
5365
5468
|
)
|
|
5366
5469
|
}
|
|
5367
|
-
|
|
5470
|
+
`;
|
|
5471
|
+
return [
|
|
5472
|
+
{
|
|
5473
|
+
id: "jsx",
|
|
5474
|
+
label: `${formLabel2} Component`,
|
|
5475
|
+
kind: "jsx",
|
|
5476
|
+
icon: "file-code",
|
|
5477
|
+
language: "tsx",
|
|
5478
|
+
code: formComponentCode
|
|
5479
|
+
},
|
|
5480
|
+
{
|
|
5481
|
+
id: "submit",
|
|
5482
|
+
label: `Submit ${formLabel2}`,
|
|
5483
|
+
kind: "mutation",
|
|
5484
|
+
icon: "send",
|
|
5485
|
+
language: "tsx",
|
|
5486
|
+
payloadExample: createPayloadExample(submissionInput),
|
|
5487
|
+
responseExample: createResponseExample(submitResponse),
|
|
5488
|
+
code: submitActionCode
|
|
5368
5489
|
},
|
|
5369
5490
|
{
|
|
5370
5491
|
id: "submissions",
|
|
@@ -5458,7 +5579,7 @@ function generateColumnDef(col) {
|
|
|
5458
5579
|
case "email":
|
|
5459
5580
|
cellDef = `cell: ({ row }) => {
|
|
5460
5581
|
const email = row.getValue('${col.accessorKey}') as string
|
|
5461
|
-
return
|
|
5582
|
+
return <div className="text-sm font-medium">{email || '-'}</div>
|
|
5462
5583
|
}`;
|
|
5463
5584
|
break;
|
|
5464
5585
|
case "date":
|
|
@@ -7267,7 +7388,7 @@ export { CACHE_TAG, CACHE_TAG_BY_ID } from './types'
|
|
|
7267
7388
|
// adapters/next/generators/form-pipeline/form-component-shared.ts
|
|
7268
7389
|
import fs16 from "fs";
|
|
7269
7390
|
import path20 from "path";
|
|
7270
|
-
var ADMIN_CUSTOM_COMPONENTS = /* @__PURE__ */ new Set(["media-
|
|
7391
|
+
var ADMIN_CUSTOM_COMPONENTS = /* @__PURE__ */ new Set(["media-field"]);
|
|
7271
7392
|
function resolveUiImport(cwd, componentName) {
|
|
7272
7393
|
const componentDirectory = ADMIN_CUSTOM_COMPONENTS.has(componentName) ? "components/custom" : "components/ui";
|
|
7273
7394
|
const locations = [componentDirectory, `src/${componentDirectory}`];
|
|
@@ -7348,6 +7469,26 @@ ${decls}
|
|
|
7348
7469
|
function getListFields(fields) {
|
|
7349
7470
|
return fields.filter((f) => f.name && f.type === "list" && f.fields && f.fields.length > 0);
|
|
7350
7471
|
}
|
|
7472
|
+
function hasFormDescriptionFields(fields) {
|
|
7473
|
+
return fields.some((field) => {
|
|
7474
|
+
if (field.hint) return true;
|
|
7475
|
+
if ((field.type === "group" || field.type === "list") && field.fields) {
|
|
7476
|
+
return hasFormDescriptionFields(field.fields);
|
|
7477
|
+
}
|
|
7478
|
+
return false;
|
|
7479
|
+
});
|
|
7480
|
+
}
|
|
7481
|
+
function hasSelectFields(fields) {
|
|
7482
|
+
return fields.some((field) => {
|
|
7483
|
+
if (field.type === "select" && field.options && field.options.length > 0) {
|
|
7484
|
+
return true;
|
|
7485
|
+
}
|
|
7486
|
+
if ((field.type === "group" || field.type === "list") && field.fields) {
|
|
7487
|
+
return hasSelectFields(field.fields);
|
|
7488
|
+
}
|
|
7489
|
+
return false;
|
|
7490
|
+
});
|
|
7491
|
+
}
|
|
7351
7492
|
function renderFieldsJSX(fields) {
|
|
7352
7493
|
return fields.filter((f) => !f.hidden).map((f) => {
|
|
7353
7494
|
if (f.type === "dynamicFields") return "";
|
|
@@ -7599,7 +7740,7 @@ function generateFileUploadFieldJSX(field, name, label, hintJSX, requiredStar) {
|
|
|
7599
7740
|
<FormItem${formItemProps(field.height)}>
|
|
7600
7741
|
${formLabelWithDescriptionLine(name, label, requiredStar, hintJSX, " ")}
|
|
7601
7742
|
<FormControl>
|
|
7602
|
-
<
|
|
7743
|
+
<MediaField
|
|
7603
7744
|
value={field.value}
|
|
7604
7745
|
onChange={field.onChange}
|
|
7605
7746
|
onBlur={field.onBlur}
|
|
@@ -7767,13 +7908,15 @@ ${fieldsJSX}
|
|
|
7767
7908
|
const successHandler = redirectUrl ? `window.location.href = ${JSON.stringify(redirectUrl)}` : "setSubmitted(true)";
|
|
7768
7909
|
const hasRadio = allFields.some((f) => f.type === "radio");
|
|
7769
7910
|
const hasFileUpload = allFields.some((f) => f.type === "file" || f.type === "upload");
|
|
7911
|
+
const hasFormDescriptions = hasFormDescriptionFields(allFields);
|
|
7912
|
+
const hasSelectControls = hasSelectFields(allFields);
|
|
7770
7913
|
const buttonImport = resolveUiImport(cwd, "button");
|
|
7771
7914
|
const formImport = resolveUiImport(cwd, "form");
|
|
7772
7915
|
const inputImport = resolveUiImport(cwd, "input");
|
|
7773
7916
|
const textareaImport = resolveUiImport(cwd, "textarea");
|
|
7774
|
-
const selectImport = resolveUiImport(cwd, "select");
|
|
7917
|
+
const selectImport = hasSelectControls ? resolveUiImport(cwd, "select") : "";
|
|
7775
7918
|
const radioGroupImport = resolveUiImport(cwd, "radio-group");
|
|
7776
|
-
const mediaUploadImport = resolveUiImport(cwd, "media-
|
|
7919
|
+
const mediaUploadImport = resolveUiImport(cwd, "media-field");
|
|
7777
7920
|
const content = buildComponentSource({
|
|
7778
7921
|
pascal,
|
|
7779
7922
|
kebab,
|
|
@@ -7781,6 +7924,8 @@ ${fieldsJSX}
|
|
|
7781
7924
|
hasRadio,
|
|
7782
7925
|
hasFileUpload,
|
|
7783
7926
|
hasListFields,
|
|
7927
|
+
hasFormDescriptions,
|
|
7928
|
+
hasSelectControls,
|
|
7784
7929
|
actionImportPath,
|
|
7785
7930
|
buttonImport,
|
|
7786
7931
|
formImport,
|
|
@@ -7850,17 +7995,16 @@ import { Button } from '${p15.buttonImport}'
|
|
|
7850
7995
|
import {
|
|
7851
7996
|
Form,
|
|
7852
7997
|
FormControl,
|
|
7853
|
-
FormDescription,
|
|
7854
|
-
FormField,
|
|
7998
|
+
${p15.hasFormDescriptions ? " FormDescription,\n" : ""} FormField,
|
|
7855
7999
|
FormItem,
|
|
7856
8000
|
FormLabel,
|
|
7857
8001
|
FormMessage,
|
|
7858
8002
|
} from '${p15.formImport}'
|
|
7859
8003
|
import { Input } from '${p15.inputImport}'${p15.hasFileUpload ? `
|
|
7860
|
-
import {
|
|
8004
|
+
import { MediaField } from '${p15.mediaUploadImport}'` : ""}
|
|
7861
8005
|
${p15.hasRadio ? `import { RadioGroup, RadioGroupItem } from '${p15.radioGroupImport}'
|
|
7862
8006
|
` : ""}
|
|
7863
|
-
import { Textarea } from '${p15.textareaImport}'
|
|
8007
|
+
import { Textarea } from '${p15.textareaImport}'${p15.hasSelectControls ? `
|
|
7864
8008
|
import {
|
|
7865
8009
|
Select,
|
|
7866
8010
|
SelectContent,
|
|
@@ -7868,6 +8012,7 @@ import {
|
|
|
7868
8012
|
SelectTrigger,
|
|
7869
8013
|
SelectValue,
|
|
7870
8014
|
} from '${p15.selectImport}'
|
|
8015
|
+
` : ""}
|
|
7871
8016
|
|
|
7872
8017
|
const formSchema = z.object({
|
|
7873
8018
|
${p15.zodFields}
|
|
@@ -8015,13 +8160,15 @@ ${buildFieldArrayDecls(listFields)}
|
|
|
8015
8160
|
` : "";
|
|
8016
8161
|
const hasRadio = fields.some((f) => f.type === "radio");
|
|
8017
8162
|
const hasFileUpload = fields.some((f) => f.type === "file" || f.type === "upload");
|
|
8163
|
+
const hasFormDescriptions = hasFormDescriptionFields(rawFields);
|
|
8164
|
+
const hasSelectControls = hasSelectFields(rawFields);
|
|
8018
8165
|
const buttonImport = resolveUiImport(cwd, "button");
|
|
8019
8166
|
const formImport = resolveUiImport(cwd, "form");
|
|
8020
8167
|
const inputImport = resolveUiImport(cwd, "input");
|
|
8021
8168
|
const textareaImport = resolveUiImport(cwd, "textarea");
|
|
8022
|
-
const selectImport = resolveUiImport(cwd, "select");
|
|
8169
|
+
const selectImport = hasSelectControls ? resolveUiImport(cwd, "select") : "";
|
|
8023
8170
|
const radioGroupImport = resolveUiImport(cwd, "radio-group");
|
|
8024
|
-
const mediaUploadImport = resolveUiImport(cwd, "media-
|
|
8171
|
+
const mediaUploadImport = resolveUiImport(cwd, "media-field");
|
|
8025
8172
|
const queryClientImport = hasFileUpload ? `
|
|
8026
8173
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'` : "";
|
|
8027
8174
|
const queryClientSetup = hasFileUpload ? `
|
|
@@ -8050,16 +8197,15 @@ import { Button } from '${buttonImport}'
|
|
|
8050
8197
|
import {
|
|
8051
8198
|
Form,
|
|
8052
8199
|
FormControl,
|
|
8053
|
-
FormDescription,
|
|
8054
|
-
FormField,
|
|
8200
|
+
${hasFormDescriptions ? " FormDescription,\n" : ""} FormField,
|
|
8055
8201
|
FormItem,
|
|
8056
8202
|
FormLabel,
|
|
8057
8203
|
FormMessage,
|
|
8058
8204
|
} from '${formImport}'
|
|
8059
8205
|
import { Input } from '${inputImport}'${hasFileUpload ? `
|
|
8060
|
-
import {
|
|
8206
|
+
import { MediaField } from '${mediaUploadImport}'` : ""}${hasRadio ? `
|
|
8061
8207
|
import { RadioGroup, RadioGroupItem } from '${radioGroupImport}'` : ""}
|
|
8062
|
-
import { Textarea } from '${textareaImport}'
|
|
8208
|
+
import { Textarea } from '${textareaImport}'${hasSelectControls ? `
|
|
8063
8209
|
import {
|
|
8064
8210
|
Select,
|
|
8065
8211
|
SelectContent,
|
|
@@ -8067,6 +8213,7 @@ import {
|
|
|
8067
8213
|
SelectTrigger,
|
|
8068
8214
|
SelectValue,
|
|
8069
8215
|
} from '${selectImport}'
|
|
8216
|
+
` : ""}
|
|
8070
8217
|
|
|
8071
8218
|
const formSchema = z.object({
|
|
8072
8219
|
${zodFields}
|
|
@@ -9435,7 +9582,7 @@ ${updateBeforePublishBlock}
|
|
|
9435
9582
|
(typeof value === 'number' && Number.isNaN(value))
|
|
9436
9583
|
? null
|
|
9437
9584
|
: value
|
|
9438
|
-
} else if (!field.required && ['date', 'timestamp', 'time', 'string', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
|
|
9585
|
+
} else if (!field.required && ['date', 'timestamp', 'time', 'string', 'email', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
|
|
9439
9586
|
processedData[key] = value && value !== '' ? value : null
|
|
9440
9587
|
} else {
|
|
9441
9588
|
processedData[key] = value
|
|
@@ -10676,7 +10823,7 @@ function buildEntityContext(schema, nextMajorVersion) {
|
|
|
10676
10823
|
);
|
|
10677
10824
|
const hasCreatableSelectFields = creatableSelectFields.length > 0;
|
|
10678
10825
|
const selectFields = collectReadSelectFields(allDbFields);
|
|
10679
|
-
const
|
|
10826
|
+
const hasSelectFields2 = selectFields.length > 0;
|
|
10680
10827
|
const dateRangeFilterFields = new Set(
|
|
10681
10828
|
resolvedFilters.filter((filter) => filter.type === "date-range").map((filter) => filter.field)
|
|
10682
10829
|
);
|
|
@@ -10829,7 +10976,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
|
|
|
10829
10976
|
Singular,
|
|
10830
10977
|
hasListRels
|
|
10831
10978
|
);
|
|
10832
|
-
const selectResultMapping =
|
|
10979
|
+
const selectResultMapping = hasSelectFields2 ? wrapWithSelectResolution(baseResultMapping, camelPlural, Singular) : baseResultMapping;
|
|
10833
10980
|
const resultMapping = hasMediaFields ? wrapWithMediaResolution(selectResultMapping, camelPlural, Singular) : selectResultMapping;
|
|
10834
10981
|
const baseListResultMapping = hasHtmlOutput ? buildResultMapping(
|
|
10835
10982
|
listDbFields,
|
|
@@ -10839,7 +10986,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
|
|
|
10839
10986
|
Singular,
|
|
10840
10987
|
hasListRels
|
|
10841
10988
|
) : baseResultMapping;
|
|
10842
|
-
const selectListResultMapping =
|
|
10989
|
+
const selectListResultMapping = hasSelectFields2 ? wrapWithSelectResolution(baseListResultMapping, camelPlural, Singular) : baseListResultMapping;
|
|
10843
10990
|
const listResultMapping = hasMediaFields ? wrapWithMediaResolution(selectListResultMapping, camelPlural, Singular) : selectListResultMapping;
|
|
10844
10991
|
const fieldMeta = createFields.map((f) => `{ name: '${f.name}', type: '${f.type}', required: ${f.required ?? false} }`).join(",\n ");
|
|
10845
10992
|
const createMappings = createFields.map((f) => ` ${f.name}: ${f.name === "slug" ? "resolvedSlug" : generateFieldMapping(f)}`).join(",\n");
|
|
@@ -10870,7 +11017,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
|
|
|
10870
11017
|
return ${singleRowMapping}`;
|
|
10871
11018
|
})();
|
|
10872
11019
|
const singleRowResolvers = [
|
|
10873
|
-
...
|
|
11020
|
+
...hasSelectFields2 ? [`resolve${Singular}SelectFields`] : [],
|
|
10874
11021
|
...hasMediaFields ? [`resolve${Singular}MediaFields`] : []
|
|
10875
11022
|
];
|
|
10876
11023
|
const singleRowReturn = singleRowResolvers.length > 0 ? wrapSingleRowReturnWithResolvers(baseSingleRowReturn, singleRowResolvers) : baseSingleRowReturn;
|
|
@@ -10904,7 +11051,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
|
|
|
10904
11051
|
hasDraft,
|
|
10905
11052
|
hasSearch,
|
|
10906
11053
|
hasFilters,
|
|
10907
|
-
hasSelectFields,
|
|
11054
|
+
hasSelectFields: hasSelectFields2,
|
|
10908
11055
|
hasCreatableSelectFields,
|
|
10909
11056
|
searchFields,
|
|
10910
11057
|
htmlOutputFields,
|
|
@@ -11120,7 +11267,7 @@ function generateSingleActions(schema, actionsDir, options = {}) {
|
|
|
11120
11267
|
);
|
|
11121
11268
|
const hasCreatableSelectFields = creatableSelectFields.length > 0;
|
|
11122
11269
|
const selectFields = collectReadSelectFields(dbFields);
|
|
11123
|
-
const
|
|
11270
|
+
const hasSelectFields2 = selectFields.length > 0;
|
|
11124
11271
|
const relationshipFields = dbFields.filter(
|
|
11125
11272
|
(f) => f.type === "relationship" && f.relationship && !f.multiple
|
|
11126
11273
|
);
|
|
@@ -11233,7 +11380,7 @@ ${allUpsertInterfaceFields}
|
|
|
11233
11380
|
const m2mReadBlock = hasM2M ? `
|
|
11234
11381
|
${m2mReadQueries}
|
|
11235
11382
|
${m2mAssignments}` : "";
|
|
11236
|
-
const singleRowReturn =
|
|
11383
|
+
const singleRowReturn = hasSelectFields2 ? `const row = result[0]
|
|
11237
11384
|
const singleRow = ${singleRowMapping}${m2mReadBlock}
|
|
11238
11385
|
const [resolvedSelectRow] = await resolve${Singular}SelectFields([singleRow])
|
|
11239
11386
|
return resolvedSelectRow` : `const row = result[0]
|
|
@@ -11252,7 +11399,7 @@ ${m2mAssignments}` : "";
|
|
|
11252
11399
|
...hasCreatableSelectFields ? [`${tableVar}SelectOptions`] : []
|
|
11253
11400
|
])
|
|
11254
11401
|
).sort();
|
|
11255
|
-
const selectResolutionFunctions =
|
|
11402
|
+
const selectResolutionFunctions = hasSelectFields2 ? `
|
|
11256
11403
|
${generateSingleResolveSelectFieldsFunction(Singular, selectFields, `${tableVar}SelectOptions`, hasCreatableSelectFields)}
|
|
11257
11404
|
` : "";
|
|
11258
11405
|
const getContent = `'use server'
|
|
@@ -11263,7 +11410,7 @@ import { eq } from 'drizzle-orm'
|
|
|
11263
11410
|
import { alias } from 'drizzle-orm/pg-core'
|
|
11264
11411
|
${cacheReadImport}
|
|
11265
11412
|
import { ${cacheTagsVar} } from './types'
|
|
11266
|
-
import type { ${Singular}${
|
|
11413
|
+
import type { ${Singular}${hasSelectFields2 ? ", SelectOptionValue" : ""} } from './types'
|
|
11267
11414
|
|
|
11268
11415
|
${buildAuthorshipAliasBlock()}
|
|
11269
11416
|
|
|
@@ -11362,7 +11509,7 @@ ${m2mInputDeclarations ? `${m2mInputDeclarations}
|
|
|
11362
11509
|
(typeof value === 'number' && Number.isNaN(value))
|
|
11363
11510
|
? null
|
|
11364
11511
|
: value
|
|
11365
|
-
} else if (!field.required && ['date', 'timestamp', 'time', 'string', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
|
|
11512
|
+
} else if (!field.required && ['date', 'timestamp', 'time', 'string', 'email', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
|
|
11366
11513
|
processedData[key] = value && value !== '' ? value : null
|
|
11367
11514
|
} else {
|
|
11368
11515
|
processedData[key] = value
|
|
@@ -12722,6 +12869,8 @@ function getFormFieldType(field) {
|
|
|
12722
12869
|
switch (field.type) {
|
|
12723
12870
|
case "boolean":
|
|
12724
12871
|
return "checkbox";
|
|
12872
|
+
case "email":
|
|
12873
|
+
return "email";
|
|
12725
12874
|
case "text":
|
|
12726
12875
|
return "textarea";
|
|
12727
12876
|
case "markdown":
|
|
@@ -12753,6 +12902,10 @@ function getZodType(field) {
|
|
|
12753
12902
|
return field.required ? `z.number({ message: '${label} is required' })` : "z.number().nullable().optional()";
|
|
12754
12903
|
case "boolean":
|
|
12755
12904
|
return "z.boolean()";
|
|
12905
|
+
case "email": {
|
|
12906
|
+
const base = field.required ? `z.string().min(1, '${label} is required').email('Please enter a valid email address')` : `z.string().optional().refine((val) => !val || val.trim() === '' || z.string().email().safeParse(val).success, { message: 'Please enter a valid email address' })`;
|
|
12907
|
+
return field.length ? `${base}.max(${field.length})` : base;
|
|
12908
|
+
}
|
|
12756
12909
|
case "string":
|
|
12757
12910
|
case "varchar":
|
|
12758
12911
|
case "text":
|
|
@@ -12891,6 +13044,40 @@ function collectRelationshipFields(fields, _tabFieldNames) {
|
|
|
12891
13044
|
collect(fields);
|
|
12892
13045
|
return result;
|
|
12893
13046
|
}
|
|
13047
|
+
function getTabNestedFields(tab) {
|
|
13048
|
+
return [
|
|
13049
|
+
...tab.fields ?? [],
|
|
13050
|
+
...tab.slot?.main?.fields ?? [],
|
|
13051
|
+
...tab.slot?.sidebar?.fields ?? []
|
|
13052
|
+
];
|
|
13053
|
+
}
|
|
13054
|
+
function collectTabIconNames(fields) {
|
|
13055
|
+
const icons = /* @__PURE__ */ new Set();
|
|
13056
|
+
function collect(fieldsToCheck) {
|
|
13057
|
+
for (const field of fieldsToCheck) {
|
|
13058
|
+
if (field.fields) collect(field.fields);
|
|
13059
|
+
if (field.tabs) {
|
|
13060
|
+
for (const tab of field.tabs) {
|
|
13061
|
+
const icon = tab.icon?.trim();
|
|
13062
|
+
if (icon) icons.add(toPascalCase(icon));
|
|
13063
|
+
collect(getTabNestedFields(tab));
|
|
13064
|
+
}
|
|
13065
|
+
}
|
|
13066
|
+
}
|
|
13067
|
+
}
|
|
13068
|
+
collect(fields);
|
|
13069
|
+
return Array.from(icons);
|
|
13070
|
+
}
|
|
13071
|
+
function renderTabsTrigger(tab, indent, options = {}) {
|
|
13072
|
+
const iconName = tab.icon?.trim() ? toPascalCase(tab.icon.trim()) : null;
|
|
13073
|
+
const className = options.className ? ` className="${options.className}"` : "";
|
|
13074
|
+
if (!iconName)
|
|
13075
|
+
return `${indent}<TabsTrigger value="${tab.name}"${className}>${tab.label}</TabsTrigger>`;
|
|
13076
|
+
return `${indent}<TabsTrigger value="${tab.name}"${className}>
|
|
13077
|
+
${indent} <${iconName} className="size-4 shrink-0" />
|
|
13078
|
+
${indent} <span>${tab.label}</span>
|
|
13079
|
+
${indent}</TabsTrigger>`;
|
|
13080
|
+
}
|
|
12894
13081
|
function selectDefaultValues(f) {
|
|
12895
13082
|
if (f.default === void 0 || f.default === null || f.default === "") return [];
|
|
12896
13083
|
const values = Array.isArray(f.default) ? f.default : [f.default];
|
|
@@ -13165,9 +13352,7 @@ function buildUiImports(ctx) {
|
|
|
13165
13352
|
if (ctx.hasBoolean) uiImports.push("import { Checkbox } from '@admin/components/ui/checkbox'");
|
|
13166
13353
|
if (ctx.hasTextarea) uiImports.push("import { Textarea } from '@admin/components/ui/textarea'");
|
|
13167
13354
|
if (ctx.hasImage || ctx.hasVideo || ctx.hasMedia)
|
|
13168
|
-
uiImports.push(
|
|
13169
|
-
"import { MediaGalleryField } from '@admin/components/custom/media-gallery-field'"
|
|
13170
|
-
);
|
|
13355
|
+
uiImports.push("import { MediaField } from '@admin/components/custom/media-field'");
|
|
13171
13356
|
if (ctx.hasGallery)
|
|
13172
13357
|
uiImports.push("import { GalleryField } from '@admin/components/custom/gallery-field'");
|
|
13173
13358
|
if (ctx.hasIcon || ctx.hasIconPostfix)
|
|
@@ -13382,7 +13567,7 @@ ${indent} render={({ field: formField }) => (
|
|
|
13382
13567
|
${indent} <FormItem${formItemProps2(field)}>
|
|
13383
13568
|
${indent} ${labelWithDescription(formLabel(field, nestedLabel), nestedHint, `${indent} `)}
|
|
13384
13569
|
${indent} <FormControl>
|
|
13385
|
-
${indent} <
|
|
13570
|
+
${indent} <MediaField value={formField.value} onChange={formField.onChange} onBlur={formField.onBlur} disabled={isPending}${acceptProp} />
|
|
13386
13571
|
${indent} </FormControl>
|
|
13387
13572
|
${indent} <FormMessage />
|
|
13388
13573
|
${indent} </FormItem>
|
|
@@ -13677,10 +13862,10 @@ function generateFieldJSXCore(field, indent = " ", context = {}) {
|
|
|
13677
13862
|
if (field.type === "separator") return renderSeparatorField(field, indent);
|
|
13678
13863
|
if (field.type === "boolean") return renderBooleanField(field, indent, label, hintJSX, context);
|
|
13679
13864
|
if (field.type === "image")
|
|
13680
|
-
return
|
|
13865
|
+
return renderMediaField(field, indent, label, hintJSX, context, "image/*");
|
|
13681
13866
|
if (field.type === "video")
|
|
13682
|
-
return
|
|
13683
|
-
if (field.type === "media") return
|
|
13867
|
+
return renderMediaField(field, indent, label, hintJSX, context, "video/*");
|
|
13868
|
+
if (field.type === "media") return renderMediaField(field, indent, label, hintJSX, context);
|
|
13684
13869
|
if (field.type === "gallery") return renderGalleryField(field, indent, label, hintJSX, context);
|
|
13685
13870
|
if (field.type === "icon") return renderIconField(field, indent, label, hintJSX);
|
|
13686
13871
|
if (field.type === "date") return renderDateField(field, indent, label, hintJSX, context);
|
|
@@ -13745,7 +13930,7 @@ ${indent} </FormItem>
|
|
|
13745
13930
|
${indent} )}
|
|
13746
13931
|
${indent}/>`;
|
|
13747
13932
|
}
|
|
13748
|
-
function
|
|
13933
|
+
function renderMediaField(field, indent, label, hintJSX, context, accept) {
|
|
13749
13934
|
const acceptProp = accept ? `
|
|
13750
13935
|
${indent} accept="${accept}"` : "";
|
|
13751
13936
|
const defaultValueFromHandler = defaultValueFromSourceHandler(field, context);
|
|
@@ -13760,7 +13945,7 @@ ${indent} render={({ field: formField }) => (
|
|
|
13760
13945
|
${indent} <FormItem${formItemProps2(field)}>
|
|
13761
13946
|
${indent} ${formLabelWithDescription(field, label, hintJSX, `${indent} `)}
|
|
13762
13947
|
${indent} <FormControl>
|
|
13763
|
-
${indent} <
|
|
13948
|
+
${indent} <MediaField
|
|
13764
13949
|
${indent} value={formField.value}
|
|
13765
13950
|
${indent} onChange={${onChange}}
|
|
13766
13951
|
${indent} onBlur={formField.onBlur}
|
|
@@ -14481,6 +14666,23 @@ ${indent}/>`;
|
|
|
14481
14666
|
}
|
|
14482
14667
|
|
|
14483
14668
|
// adapters/next/generators/form/form-entity.ts
|
|
14669
|
+
function escapeRegExp(value) {
|
|
14670
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14671
|
+
}
|
|
14672
|
+
function usesIdentifier(source, identifier) {
|
|
14673
|
+
return new RegExp(`\\b${escapeRegExp(identifier)}\\b`).test(source);
|
|
14674
|
+
}
|
|
14675
|
+
function filterNamedImportsByUsage(importDeclarations, source) {
|
|
14676
|
+
return importDeclarations.flatMap((declaration) => {
|
|
14677
|
+
const match = declaration.match(/^import\s+\{\s*([\s\S]*?)\s*\}\s+from\s+(.+)$/);
|
|
14678
|
+
if (!match) return [declaration];
|
|
14679
|
+
const importedNames = match[1].split(",").map((name) => name.trim()).filter(Boolean);
|
|
14680
|
+
const usedNames = importedNames.filter((name) => usesIdentifier(source, name));
|
|
14681
|
+
if (usedNames.length === 0) return [];
|
|
14682
|
+
if (usedNames.length === importedNames.length) return [declaration];
|
|
14683
|
+
return [`import { ${usedNames.join(", ")} } from ${match[2]}`];
|
|
14684
|
+
});
|
|
14685
|
+
}
|
|
14484
14686
|
function collectSidebarSectionHeadingFields(fields) {
|
|
14485
14687
|
const headingFields = [];
|
|
14486
14688
|
for (const field of fields) {
|
|
@@ -14641,10 +14843,25 @@ function ${slugValueHelperName}(value: unknown): string {
|
|
|
14641
14843
|
slugValueHelperName: hasSlugTitleSource ? slugValueHelperName : void 0
|
|
14642
14844
|
});
|
|
14643
14845
|
const cardFieldIndent = " ";
|
|
14846
|
+
function hasRenderableTabsField(fields, excludedField) {
|
|
14847
|
+
for (const field of fields) {
|
|
14848
|
+
if (field.hidden) continue;
|
|
14849
|
+
if (field.type === "tabs" && field !== excludedField) return true;
|
|
14850
|
+
if ((field.type === "group" || field.type === "section") && field.fields) {
|
|
14851
|
+
if (hasRenderableTabsField(field.fields, excludedField)) return true;
|
|
14852
|
+
}
|
|
14853
|
+
if (field.type === "tabs" && field.tabs) {
|
|
14854
|
+
for (const tab of field.tabs) {
|
|
14855
|
+
if (hasRenderableTabsField(tab.fields ?? [], excludedField)) return true;
|
|
14856
|
+
}
|
|
14857
|
+
}
|
|
14858
|
+
}
|
|
14859
|
+
return false;
|
|
14860
|
+
}
|
|
14644
14861
|
const renderNestedTabsField = (f, childContext = {}) => {
|
|
14645
14862
|
const indent = cardFieldIndent;
|
|
14646
14863
|
if (!f.tabs) return "";
|
|
14647
|
-
const tabsList = f.tabs.map((t) => `${indent}
|
|
14864
|
+
const tabsList = f.tabs.map((t) => renderTabsTrigger(t, `${indent} `)).join("\n");
|
|
14648
14865
|
const tabsContent = f.tabs.map((t) => {
|
|
14649
14866
|
const tabFields = (t.fields || []).map(
|
|
14650
14867
|
(tf) => generateFieldJSX2(tf, `${indent} `, {
|
|
@@ -14732,7 +14949,7 @@ ${indent} </CardHeader>`;
|
|
|
14732
14949
|
if (headingJSX) {
|
|
14733
14950
|
return `${indent}<Card className="gap-0">
|
|
14734
14951
|
${headingJSX}
|
|
14735
|
-
${indent} <CardContent className="space-y-
|
|
14952
|
+
${indent} <CardContent className="space-y-5 p-5">
|
|
14736
14953
|
${fieldsJSX}
|
|
14737
14954
|
${indent} </CardContent>
|
|
14738
14955
|
${indent}</Card>`;
|
|
@@ -14784,42 +15001,63 @@ ${cards.join("\n")}
|
|
|
14784
15001
|
).filter(Boolean).join("\n");
|
|
14785
15002
|
return [commonMainFieldsJSX, tabFieldsJSX].filter(Boolean).join("\n");
|
|
14786
15003
|
};
|
|
14787
|
-
const
|
|
15004
|
+
const renderTopLevelTabFormBody = (tab, commonMainFieldsJSX, commonSidebarEntries) => {
|
|
14788
15005
|
const tabMainFieldsJSX = renderTabMainFields(tab, commonMainFieldsJSX);
|
|
14789
15006
|
const tabSidebarEntries = [
|
|
14790
15007
|
...commonSidebarEntries,
|
|
14791
15008
|
...collectSidebarFields(tab.fields || [], tab.name)
|
|
14792
15009
|
].map((entry) => ({ ...entry, jsx: renderStaticSidebarEntry(entry) })).filter((entry) => entry.jsx.length > 0);
|
|
14793
|
-
const sidebarColumn = renderSidebarColumn(tabSidebarEntries
|
|
14794
|
-
return `
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
|
|
15010
|
+
const sidebarColumn = renderSidebarColumn(tabSidebarEntries);
|
|
15011
|
+
return ` <div className="grid grid-cols-1 ${tabSidebarEntries.length > 0 ? "lg:grid-cols-[1fr_360px] " : ""}h-full min-h-0 gap-4 items-stretch">
|
|
15012
|
+
<div className="h-full min-h-0 min-w-0">
|
|
15013
|
+
<Card className="min-h-0 min-w-0 h-full">
|
|
15014
|
+
<CardContent className="space-y-5 gap-0 h-full min-h-0">
|
|
14798
15015
|
${tabMainFieldsJSX}
|
|
14799
|
-
|
|
14800
|
-
|
|
15016
|
+
</CardContent>
|
|
15017
|
+
</Card>
|
|
15018
|
+
</div>
|
|
14801
15019
|
${sidebarColumn}
|
|
14802
|
-
|
|
14803
|
-
</TabsContent>`;
|
|
15020
|
+
</div>`;
|
|
14804
15021
|
};
|
|
14805
|
-
const
|
|
14806
|
-
|
|
15022
|
+
const topLevelTabComponentName = (tab) => `${Singular}FormTab${toPascalCase(tab.name)}`;
|
|
15023
|
+
const topLevelTabFileBaseName = (tab) => `${schema.name}-form-tab-${toKebabCase(tab.name)}`;
|
|
15024
|
+
const renderTopLevelTabComponentSpecs = (tabsField2, commonFields) => {
|
|
15025
|
+
if (!tabsField2.tabs) return [];
|
|
14807
15026
|
const commonMainFieldsJSX = commonFields.map(renderMainField).filter(Boolean).join("\n");
|
|
14808
15027
|
const commonSidebarEntries = collectSidebarFields(commonFields);
|
|
14809
|
-
|
|
14810
|
-
|
|
14811
|
-
|
|
14812
|
-
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
${tabsContent}
|
|
14816
|
-
</Tabs>`;
|
|
15028
|
+
return tabsField2.tabs.map((tab) => ({
|
|
15029
|
+
tab,
|
|
15030
|
+
componentName: topLevelTabComponentName(tab),
|
|
15031
|
+
fileBaseName: topLevelTabFileBaseName(tab),
|
|
15032
|
+
body: renderTopLevelTabFormBody(tab, commonMainFieldsJSX, commonSidebarEntries)
|
|
15033
|
+
}));
|
|
14817
15034
|
};
|
|
15035
|
+
const renderTopLevelTabComponentSwitch = (spec) => ` {tab === ${JSON.stringify(spec.tab.name)} ? <${spec.componentName} state={state} /> : null}`;
|
|
14818
15036
|
const visibleFields = allFormFields.filter((f) => !(hasDraft && f.name === "published"));
|
|
14819
15037
|
const topLevelTabsField = visibleFields.find(
|
|
14820
15038
|
(field) => field.type === "tabs" && (field.slot ?? "main") === "main" && field.tabs?.length
|
|
14821
15039
|
);
|
|
14822
15040
|
const topLevelCommonFields = topLevelTabsField ? visibleFields.filter((field) => field !== topLevelTabsField) : visibleFields;
|
|
15041
|
+
const topLevelTabComponents = topLevelTabsField ? renderTopLevelTabComponentSpecs(topLevelTabsField, topLevelCommonFields) : [];
|
|
15042
|
+
const contentUsesTabsField = topLevelTabsField ? hasRenderableTabsField(topLevelCommonFields, topLevelTabsField) || topLevelTabsField.tabs?.some(
|
|
15043
|
+
(tab) => hasRenderableTabsField(tab.fields ?? [], topLevelTabsField)
|
|
15044
|
+
) === true : hasTabsField;
|
|
15045
|
+
const topLevelTabChildFields = topLevelTabsField?.tabs?.flatMap((tab) => [
|
|
15046
|
+
...tab.fields ?? [],
|
|
15047
|
+
...tab.slot?.main?.fields ?? [],
|
|
15048
|
+
...tab.slot?.sidebar?.fields ?? []
|
|
15049
|
+
]) ?? [];
|
|
15050
|
+
const contentTabIconNames = collectTabIconNames(
|
|
15051
|
+
topLevelTabsField ? [...topLevelCommonFields, ...topLevelTabChildFields] : allFormFields
|
|
15052
|
+
);
|
|
15053
|
+
const topLevelTabIconNames = Array.from(
|
|
15054
|
+
new Set(
|
|
15055
|
+
topLevelTabsField?.tabs?.flatMap((tab) => {
|
|
15056
|
+
const icon = tab.icon?.trim();
|
|
15057
|
+
return icon ? [toPascalCase(icon)] : [];
|
|
15058
|
+
}) ?? []
|
|
15059
|
+
)
|
|
15060
|
+
);
|
|
14823
15061
|
const mainFieldsJSX = topLevelCommonFields.map(renderMainField).filter(Boolean).join("\n");
|
|
14824
15062
|
const sidebarEntries = collectSidebarFields(topLevelCommonFields).map((entry) => ({ ...entry, jsx: renderConditionalSidebarEntry(entry) })).filter((entry) => entry.jsx.length > 0);
|
|
14825
15063
|
const hasSidebarFields = sidebarEntries.length > 0;
|
|
@@ -14842,10 +15080,10 @@ ${tabsContent}
|
|
|
14842
15080
|
const sidebarCardJSX = !hasSidebarFields ? "" : shouldGuardSidebarByTab ? ` {hasVisibleSidebarFields ? (
|
|
14843
15081
|
${renderSidebarColumn(sidebarEntries, { conditionalTabs: true })}
|
|
14844
15082
|
) : null}` : renderSidebarColumn(sidebarEntries, { conditionalTabs: true });
|
|
14845
|
-
const formBodyJSX = topLevelTabsField ?
|
|
15083
|
+
const formBodyJSX = topLevelTabsField ? topLevelTabComponents.map(renderTopLevelTabComponentSwitch).join("\n") : `${formGridOpen}
|
|
14846
15084
|
<div className="h-full min-w-0">
|
|
14847
15085
|
<Card className="min-w-0 h-full">
|
|
14848
|
-
<CardContent className="space-y-
|
|
15086
|
+
<CardContent className="space-y-5 gap-0 h-full">
|
|
14849
15087
|
${mainFieldsJSX}
|
|
14850
15088
|
</CardContent>
|
|
14851
15089
|
</Card>
|
|
@@ -14933,7 +15171,7 @@ ${sidebarCardJSX}
|
|
|
14933
15171
|
hasSelectCombobox,
|
|
14934
15172
|
hasCreatableSelect,
|
|
14935
15173
|
hasMultiSelect: multiSelectFields.length > 0,
|
|
14936
|
-
hasTabsField,
|
|
15174
|
+
hasTabsField: contentUsesTabsField,
|
|
14937
15175
|
hasRelationship,
|
|
14938
15176
|
hasSimpleList,
|
|
14939
15177
|
hasNestedList,
|
|
@@ -14951,7 +15189,7 @@ ${sidebarCardJSX}
|
|
|
14951
15189
|
if (!lucideIcons.includes(icon)) lucideIcons.push(icon);
|
|
14952
15190
|
}
|
|
14953
15191
|
};
|
|
14954
|
-
addLucideIcons(...sectionHeadingIconNames);
|
|
15192
|
+
addLucideIcons(...sectionHeadingIconNames, ...contentTabIconNames);
|
|
14955
15193
|
if (hasRelationship || hasSelectCombobox) addLucideIcons("Check", "ChevronsUpDown");
|
|
14956
15194
|
if (hasCreatableSelect) addLucideIcons("Plus");
|
|
14957
15195
|
if (hasNestedList) {
|
|
@@ -15094,12 +15332,12 @@ ${sidebarCardJSX}
|
|
|
15094
15332
|
"handleErrors"
|
|
15095
15333
|
]);
|
|
15096
15334
|
const hookReturnObject = hookReturnNames.map((name) => ` ${name}`).join(",\n");
|
|
15097
|
-
const contentUsesInitialData = hasDraft ||
|
|
15098
|
-
const
|
|
15335
|
+
const contentUsesInitialData = hasDraft || !topLevelTabsField && hasSidebarFields && showMetadataWidget;
|
|
15336
|
+
const standardContentStateNames = uniqueNames([
|
|
15099
15337
|
...contentUsesInitialData ? ["initialData"] : [],
|
|
15100
15338
|
"form",
|
|
15101
15339
|
...contentUsesPendingState ? ["isPending"] : [],
|
|
15102
|
-
...
|
|
15340
|
+
...contentUsesTabsField ? ["activeTab", "setActiveTab"] : [],
|
|
15103
15341
|
...relationshipHookReturnNames,
|
|
15104
15342
|
...creatableSelectReturnNames,
|
|
15105
15343
|
...staticMultiSelectReturnNames,
|
|
@@ -15108,8 +15346,15 @@ ${sidebarCardJSX}
|
|
|
15108
15346
|
"onSubmit",
|
|
15109
15347
|
"handleErrors"
|
|
15110
15348
|
]);
|
|
15349
|
+
const topLevelTabRouterStateNames = uniqueNames([
|
|
15350
|
+
...hasDraft ? ["initialData"] : [],
|
|
15351
|
+
"form",
|
|
15352
|
+
"onSubmit",
|
|
15353
|
+
"handleErrors"
|
|
15354
|
+
]);
|
|
15355
|
+
const contentStateNames = topLevelTabsField ? topLevelTabRouterStateNames : standardContentStateNames;
|
|
15111
15356
|
const contentStateDestructure = contentStateNames.map((name) => ` ${name}`).join(",\n");
|
|
15112
|
-
const needsCn = hasRelationship || hasSelectCombobox || shouldGuardSidebarByTab
|
|
15357
|
+
const needsCn = hasRelationship || hasSelectCombobox || shouldGuardSidebarByTab;
|
|
15113
15358
|
const metadataOffsetConst = showMetadataWidget ? "const SCROLL_BORDER_OFFSET = 8;\n\n" : "";
|
|
15114
15359
|
const tabParserConst = hasTabsField ? `const TAB_VALUES = [${tabNames.map((name) => JSON.stringify(name)).join(", ")}] as const
|
|
15115
15360
|
const TAB_PARSER = parseAsStringLiteral(TAB_VALUES).withDefault(TAB_VALUES[0])
|
|
@@ -15141,6 +15386,32 @@ export type TabValue = (typeof TAB_VALUES)[number]
|
|
|
15141
15386
|
)}
|
|
15142
15387
|
|
|
15143
15388
|
` : "";
|
|
15389
|
+
const topLevelTabsClassName = showMetadataWidget ? `className={cn('min-h-[calc(100svh-56px)] w-full gap-0', {
|
|
15390
|
+
'min-h-[calc(100svh-56px-56px)]': initialData?.id != null
|
|
15391
|
+
})}` : 'className="h-[calc(100svh-56px)] min-h-0 w-full gap-0"';
|
|
15392
|
+
const topLevelTabsShell = topLevelTabsField?.tabs ? ` <Tabs value={entityForm.activeTab} onValueChange={(value) => void entityForm.setActiveTab(value as TabValue)} ${topLevelTabsClassName}>
|
|
15393
|
+
<div className="flex h-14 shrink-0 w-full bg-background border-b border-border/80 px-4 items-center">
|
|
15394
|
+
<TabsList className="bg-transparent border-none">
|
|
15395
|
+
${topLevelTabsField.tabs.map((tab) => renderTabsTrigger(tab, " ", { className: "px-3" })).join("\n")}
|
|
15396
|
+
</TabsList>
|
|
15397
|
+
</div>
|
|
15398
|
+
|
|
15399
|
+
<main className="w-full flex-1 min-h-0 p-4">
|
|
15400
|
+
${topLevelTabsField.tabs.map(
|
|
15401
|
+
(tab) => ` <TabsContent value="${tab.name}" className="h-full min-h-0 w-full flex-1">
|
|
15402
|
+
<Form {...entityForm.form}>
|
|
15403
|
+
<${contentComponentName} state={entityForm} tab="${tab.name}" />
|
|
15404
|
+
</Form>
|
|
15405
|
+
</TabsContent>`
|
|
15406
|
+
).join("\n")}
|
|
15407
|
+
</main>
|
|
15408
|
+
</Tabs>` : "";
|
|
15409
|
+
const defaultShellBody = ` <main className="w-full p-4 h-full">
|
|
15410
|
+
<Form {...entityForm.form}>
|
|
15411
|
+
<${contentComponentName} state={entityForm} />
|
|
15412
|
+
</Form>
|
|
15413
|
+
</main>`;
|
|
15414
|
+
const shellBody = topLevelTabsField ? topLevelTabsShell : defaultShellBody;
|
|
15144
15415
|
const hookContent = `'use client'
|
|
15145
15416
|
|
|
15146
15417
|
import * as React from 'react'
|
|
@@ -15260,27 +15531,63 @@ ${hookReturnObject}
|
|
|
15260
15531
|
}
|
|
15261
15532
|
}
|
|
15262
15533
|
`;
|
|
15534
|
+
const topLevelTabComponentImports = topLevelTabComponents.map((spec) => `import { ${spec.componentName} } from './${spec.fileBaseName}'`).join("\n");
|
|
15535
|
+
const contentImports = topLevelTabsField ? `import type { TabValue, ${hookName} } from '${hookImportPath}'${topLevelTabComponentImports ? `
|
|
15536
|
+
${topLevelTabComponentImports}` : ""}` : `${lucideIcons.length > 0 ? `import { ${lucideIcons.join(", ")} } from 'lucide-react'
|
|
15537
|
+
` : ""}${hasTabsField ? `import type { TabValue, ${hookName} } from '${hookImportPath}'` : `import type { ${hookName} } from '${hookImportPath}'`}${needsCn ? "\nimport { cn } from '@admin/utils/shared/cn'" : ""}
|
|
15538
|
+
${uiImports.join("\n")}`;
|
|
15539
|
+
const contentSidebarVisibilityState = topLevelTabsField ? "" : sidebarVisibilityState;
|
|
15540
|
+
const renderTopLevelTabComponentFile = (spec) => {
|
|
15541
|
+
const body = spec.body;
|
|
15542
|
+
const tabStateNames = hookReturnNames.filter((name) => usesIdentifier(body, name));
|
|
15543
|
+
const tabStateDestructure = tabStateNames.length > 0 ? ` const {
|
|
15544
|
+
${tabStateNames.map((name) => ` ${name}`).join(",\n")}
|
|
15545
|
+
} = state
|
|
15546
|
+
|
|
15547
|
+
` : "";
|
|
15548
|
+
const tabLucideIcons = lucideIcons.filter((icon) => usesIdentifier(body, icon));
|
|
15549
|
+
const tabNeedsCn = /\bcn\s*\(/.test(body);
|
|
15550
|
+
const tabImports = [
|
|
15551
|
+
tabLucideIcons.length > 0 ? `import { ${tabLucideIcons.join(", ")} } from 'lucide-react'` : "",
|
|
15552
|
+
`import type { ${hookName} } from '${hookImportPath}'`,
|
|
15553
|
+
tabNeedsCn ? "import { cn } from '@admin/utils/shared/cn'" : "",
|
|
15554
|
+
...filterNamedImportsByUsage(uiImports, body)
|
|
15555
|
+
].filter(Boolean).join("\n");
|
|
15556
|
+
return createGeneratedFile(
|
|
15557
|
+
`${pagesDir}/${schema.name}/${spec.fileBaseName}.tsx`,
|
|
15558
|
+
`'use client'
|
|
15559
|
+
|
|
15560
|
+
${tabImports}
|
|
15561
|
+
|
|
15562
|
+
interface ${spec.componentName}Props {
|
|
15563
|
+
state: ReturnType<typeof ${hookName}>
|
|
15564
|
+
}
|
|
15565
|
+
|
|
15566
|
+
export function ${spec.componentName}({ state }: ${spec.componentName}Props) {
|
|
15567
|
+
${tabStateDestructure} return (
|
|
15568
|
+
${body}
|
|
15569
|
+
)
|
|
15570
|
+
}
|
|
15571
|
+
`
|
|
15572
|
+
);
|
|
15573
|
+
};
|
|
15574
|
+
const topLevelTabComponentFiles = topLevelTabComponents.map(renderTopLevelTabComponentFile);
|
|
15263
15575
|
const content = `'use client'
|
|
15264
|
-
|
|
15265
|
-
|
|
15266
|
-
import type { TabValue, ${hookName} } from '${hookImportPath}'` : `
|
|
15267
|
-
import type { ${hookName} } from '${hookImportPath}'`}${needsCn ? "\nimport { cn } from '@admin/utils/shared/cn'" : ""}
|
|
15268
|
-
${uiImports.join("\n")}
|
|
15576
|
+
|
|
15577
|
+
${contentImports}
|
|
15269
15578
|
|
|
15270
15579
|
interface ${contentComponentName}Props {
|
|
15271
15580
|
state: ReturnType<typeof ${hookName}>
|
|
15581
|
+
${topLevelTabsField ? " tab: TabValue\n" : ""}
|
|
15272
15582
|
}
|
|
15273
15583
|
|
|
15274
|
-
export function ${contentComponentName}({ state }: ${contentComponentName}Props) {
|
|
15584
|
+
export function ${contentComponentName}({ state${topLevelTabsField ? ", tab" : ""} }: ${contentComponentName}Props) {
|
|
15275
15585
|
const {
|
|
15276
15586
|
${contentStateDestructure}
|
|
15277
15587
|
} = state
|
|
15278
|
-
${
|
|
15588
|
+
${contentSidebarVisibilityState}
|
|
15279
15589
|
return (
|
|
15280
|
-
<form id="${schema.name}-form" onSubmit={form.handleSubmit((values) => onSubmit(values${hasDraft ? ", initialData?.published ?? false" : ""}), handleErrors)} ${topLevelTabsField ?
|
|
15281
|
-
'h-[calc(100svh-56px-56px-32px)]': initialData?.id != null,
|
|
15282
|
-
'h-[calc(100svh-56px-32px)]': initialData?.id == null
|
|
15283
|
-
})}` : 'className="h-[calc(100svh-56px-32px)] min-h-0"' : 'className="space-y-6 h-full"'}>
|
|
15590
|
+
<form id="${schema.name}-form" onSubmit={form.handleSubmit((values) => onSubmit(values${hasDraft ? ", initialData?.published ?? false" : ""}), handleErrors)} ${topLevelTabsField ? 'className="h-full"' : 'className="space-y-6 h-full"'}>
|
|
15284
15591
|
${formBodyJSX}
|
|
15285
15592
|
</form>
|
|
15286
15593
|
)
|
|
@@ -15288,11 +15595,15 @@ ${formBodyJSX}
|
|
|
15288
15595
|
`;
|
|
15289
15596
|
const shellContent = `'use client'
|
|
15290
15597
|
${showMetadataWidget ? "\nimport { usePageScrollThreshold } from '@admin/hooks/use-page-scroll-threshold'" : ""}
|
|
15598
|
+
${topLevelTabIconNames.length > 0 ? `import { ${topLevelTabIconNames.join(", ")} } from 'lucide-react'
|
|
15599
|
+
` : ""}
|
|
15291
15600
|
import { Button } from '@admin/components/ui/button'
|
|
15292
15601
|
import { Form } from '@admin/components/ui/form'
|
|
15293
|
-
import { PageHeader } from '@admin/components/shared/page-header'${showMetadataWidget ? "\nimport { EntityMetadata } from '@admin/components/shared/entity-metadata'\nimport { cn } from '@admin/utils/shared/cn'" : ""}
|
|
15602
|
+
${topLevelTabsField ? "import { Tabs, TabsContent, TabsList, TabsTrigger } from '@admin/components/ui/tabs'\n" : ""}import { PageHeader } from '@admin/components/shared/page-header'${showMetadataWidget ? "\nimport { EntityMetadata } from '@admin/components/shared/entity-metadata'\nimport { cn } from '@admin/utils/shared/cn'" : ""}
|
|
15294
15603
|
import type { ${Singular} } from '@admin/actions/${actionImportPath}'
|
|
15295
15604
|
${showMetadataWidget ? `import { getVersionsBy${Singular}Id, restore${Singular}Version } from '@admin/actions/${actionImportPath}'` : ""}
|
|
15605
|
+
${topLevelTabsField ? `import type { TabValue } from '${hookImportPath}'
|
|
15606
|
+
` : ""}
|
|
15296
15607
|
import { ${hookName} } from '${hookImportPath}'
|
|
15297
15608
|
import { ${contentComponentName} } from './${schema.name}-form-content'
|
|
15298
15609
|
|
|
@@ -15340,11 +15651,7 @@ ${hasDraft ? ` <div className="flex items-center gap-1">
|
|
|
15340
15651
|
</Button>`}
|
|
15341
15652
|
}
|
|
15342
15653
|
/>
|
|
15343
|
-
${metadataStrip}
|
|
15344
|
-
<Form {...entityForm.form}>
|
|
15345
|
-
<${contentComponentName} state={entityForm} />
|
|
15346
|
-
</Form>
|
|
15347
|
-
</main>
|
|
15654
|
+
${metadataStrip}${shellBody}
|
|
15348
15655
|
</>
|
|
15349
15656
|
)
|
|
15350
15657
|
}
|
|
@@ -15353,6 +15660,7 @@ ${metadataStrip} <main className="w-full p-4 h-full">
|
|
|
15353
15660
|
files: [
|
|
15354
15661
|
createGeneratedFile(hookPath, hookContent),
|
|
15355
15662
|
createGeneratedFile(`${pagesDir}/${schema.name}/${schema.name}-form-content.tsx`, content),
|
|
15663
|
+
...topLevelTabComponentFiles,
|
|
15356
15664
|
createGeneratedFile(`${pagesDir}/${schema.name}/${schema.name}-form.tsx`, shellContent)
|
|
15357
15665
|
]
|
|
15358
15666
|
};
|
|
@@ -15481,7 +15789,7 @@ function buildGroupFieldsJSX(group2, analysis, defaultValueFromSourceNames, crea
|
|
|
15481
15789
|
const indent = " ";
|
|
15482
15790
|
return group2.fields.map((f) => {
|
|
15483
15791
|
if (f.type === "tabs" && f.tabs) {
|
|
15484
|
-
const tabsList = f.tabs.map((t) =>
|
|
15792
|
+
const tabsList = f.tabs.map((t) => renderTabsTrigger(t, " ")).join("\n");
|
|
15485
15793
|
const tabsContent = f.tabs.map((t) => {
|
|
15486
15794
|
const tabFields = (t.fields || []).map(
|
|
15487
15795
|
(tf) => generateFieldJSX2(tf, " ", {
|
|
@@ -15704,7 +16012,7 @@ ${creatableSelectHandlers ? `${creatableSelectHandlers}
|
|
|
15704
16012
|
<CardHeader>
|
|
15705
16013
|
<CardTitle>${group2.title}</CardTitle>${descriptionLine}
|
|
15706
16014
|
</CardHeader>
|
|
15707
|
-
<CardContent className="space-y-
|
|
16015
|
+
<CardContent className="space-y-5">
|
|
15708
16016
|
${fieldsJSX}
|
|
15709
16017
|
</CardContent>
|
|
15710
16018
|
<CardFooter>${group2.hint ? `
|
|
@@ -15785,6 +16093,7 @@ function generateSingleForm(schema, pagesDir, options = {}) {
|
|
|
15785
16093
|
(group2) => analyzeGroup(group2.fields).hasNestedObjectList
|
|
15786
16094
|
);
|
|
15787
16095
|
const hasSimpleList = hasList && hasSimpleListField2(allFormFields);
|
|
16096
|
+
const tabIconNames = collectTabIconNames(allFormFields);
|
|
15788
16097
|
const allRelFields = [];
|
|
15789
16098
|
for (const g of cardGroups) {
|
|
15790
16099
|
const a = analyzeGroup(g.fields);
|
|
@@ -15830,12 +16139,17 @@ function generateSingleForm(schema, pagesDir, options = {}) {
|
|
|
15830
16139
|
CardHeader,
|
|
15831
16140
|
CardTitle
|
|
15832
16141
|
} from '@admin/components/ui/card'`);
|
|
15833
|
-
const lucideIcons = [
|
|
15834
|
-
|
|
15835
|
-
|
|
16142
|
+
const lucideIcons = [];
|
|
16143
|
+
const addLucideIcons = (...icons) => {
|
|
16144
|
+
for (const icon of icons) {
|
|
16145
|
+
if (!lucideIcons.includes(icon)) lucideIcons.push(icon);
|
|
16146
|
+
}
|
|
16147
|
+
};
|
|
16148
|
+
addLucideIcons("LoaderCircle", ...tabIconNames);
|
|
16149
|
+
if (hasRelationship || hasSelectCombobox) addLucideIcons("Check", "ChevronsUpDown");
|
|
16150
|
+
if (hasCreatableSelect) addLucideIcons("Plus");
|
|
15836
16151
|
if (hasNestedList) {
|
|
15837
|
-
|
|
15838
|
-
if (!lucideIcons.includes("X")) lucideIcons.push("X");
|
|
16152
|
+
addLucideIcons("Plus", "X");
|
|
15839
16153
|
}
|
|
15840
16154
|
const relHookImports = allRelFields.map((f) => {
|
|
15841
16155
|
const relPlural = toPascalCase(pluralize(f.relationship || ""));
|
|
@@ -18612,6 +18926,7 @@ import fs24 from "fs";
|
|
|
18612
18926
|
import path29 from "path";
|
|
18613
18927
|
var ENTITY_SINGLE_ADD_FIELD_TYPES = [
|
|
18614
18928
|
"string",
|
|
18929
|
+
"email",
|
|
18615
18930
|
"varchar",
|
|
18616
18931
|
"text",
|
|
18617
18932
|
"markdown",
|
|
@@ -18714,6 +19029,11 @@ function loadAuthoredGeneratedSchema(schemasDir, name) {
|
|
|
18714
19029
|
const filePath = filePaths[0];
|
|
18715
19030
|
const parsed = parseJsonObject(fs24.readFileSync(filePath, "utf-8"), filePath);
|
|
18716
19031
|
const type = parsed.type;
|
|
19032
|
+
if (type === void 0) {
|
|
19033
|
+
throw new Error(
|
|
19034
|
+
`Schema "${name}" is missing required top-level "type". Expected "entity", "single", or "form".`
|
|
19035
|
+
);
|
|
19036
|
+
}
|
|
18717
19037
|
if (type === "form") {
|
|
18718
19038
|
return {
|
|
18719
19039
|
kind: "form",
|
|
@@ -20388,7 +20708,7 @@ async function promptEntityIntegrationOptions(schema, field) {
|
|
|
20388
20708
|
summary.push(`table column: ${field.name}`);
|
|
20389
20709
|
}
|
|
20390
20710
|
}
|
|
20391
|
-
if (["string", "varchar", "text", "markdown", "richtext"].includes(field.type)) {
|
|
20711
|
+
if (["string", "email", "varchar", "text", "markdown", "richtext"].includes(field.type)) {
|
|
20392
20712
|
const addSearch = await promptConfirm2("Add this field to search?", false);
|
|
20393
20713
|
if (addSearch) {
|
|
20394
20714
|
schema.search = schema.search ?? { fields: [] };
|
|
@@ -20448,6 +20768,8 @@ function schemaColumnTypeForField(field) {
|
|
|
20448
20768
|
return "boolean";
|
|
20449
20769
|
case "select":
|
|
20450
20770
|
return "badge";
|
|
20771
|
+
case "email":
|
|
20772
|
+
return "email";
|
|
20451
20773
|
case "string":
|
|
20452
20774
|
case "varchar":
|
|
20453
20775
|
case "text":
|
|
@@ -20809,6 +21131,10 @@ ${styleText(
|
|
|
20809
21131
|
value: "resend",
|
|
20810
21132
|
label: "Resend"
|
|
20811
21133
|
},
|
|
21134
|
+
{
|
|
21135
|
+
value: "skip",
|
|
21136
|
+
label: "Skip"
|
|
21137
|
+
},
|
|
20812
21138
|
{
|
|
20813
21139
|
value: "cloudflare",
|
|
20814
21140
|
label: `Cloudflare ${styleText("dim", "(coming soon)")}`,
|
|
@@ -24884,7 +25210,7 @@ var STATIC_CUSTOM_DEPENDENCIES = {
|
|
|
24884
25210
|
"content-editor-rich-extensions",
|
|
24885
25211
|
"content-editor-editor-view-utils",
|
|
24886
25212
|
"markdown-utils",
|
|
24887
|
-
"media-
|
|
25213
|
+
"media-field",
|
|
24888
25214
|
"use-content-editor",
|
|
24889
25215
|
"use-content-editor-table-add-controls",
|
|
24890
25216
|
"use-content-editor-source-mode",
|
|
@@ -24974,7 +25300,7 @@ var STATIC_CUSTOM_DEPENDENCIES = {
|
|
|
24974
25300
|
"content-editor/use-tiptap-editor",
|
|
24975
25301
|
"tiptap-utils"
|
|
24976
25302
|
],
|
|
24977
|
-
"content-editor/media-gallery-block": ["media-
|
|
25303
|
+
"content-editor/media-gallery-block": ["media-field"],
|
|
24978
25304
|
"content-editor/mobile-toolbar-content": [
|
|
24979
25305
|
"content-editor/math-popover",
|
|
24980
25306
|
"content-editor/use-tiptap-editor",
|
|
@@ -24999,20 +25325,25 @@ var STATIC_CUSTOM_DEPENDENCIES = {
|
|
|
24999
25325
|
],
|
|
25000
25326
|
"date-range-picker": ["date-utils"],
|
|
25001
25327
|
"gallery-field": [
|
|
25002
|
-
"
|
|
25328
|
+
"card",
|
|
25329
|
+
"carousel",
|
|
25330
|
+
"media-field",
|
|
25331
|
+
"media-field-empty-state",
|
|
25003
25332
|
"media-gallery-dialog",
|
|
25333
|
+
"skeleton",
|
|
25004
25334
|
"sortable-gallery-item",
|
|
25005
25335
|
"use-media"
|
|
25006
25336
|
],
|
|
25007
25337
|
"icon-picker": ["icons-column-skeleton", "icons-data"],
|
|
25008
|
-
"
|
|
25009
|
-
|
|
25338
|
+
"media-field": [
|
|
25339
|
+
"media-field-empty-state",
|
|
25010
25340
|
"media-gallery-dialog",
|
|
25011
25341
|
"media-preview",
|
|
25012
25342
|
"media-fallback-utils",
|
|
25013
25343
|
"use-copy-to-clipboard",
|
|
25014
25344
|
"use-media"
|
|
25015
25345
|
],
|
|
25346
|
+
"media-field-empty-state": ["button", "card", "cn", "media-gallery-dialog"],
|
|
25016
25347
|
"nested-object-list-field": ["form-list-field-utils"],
|
|
25017
25348
|
"progressive-blur": ["use-page-boundary-blur-visibility"],
|
|
25018
25349
|
"upload-dropzone": ["upload-utils"]
|
|
@@ -25045,7 +25376,7 @@ var TIPTAP_TEMPLATE_DEPENDENCIES = [
|
|
|
25045
25376
|
"content-editor/source-mode",
|
|
25046
25377
|
"tiptap-utils",
|
|
25047
25378
|
"content-editor/media-gallery-block",
|
|
25048
|
-
"media-
|
|
25379
|
+
"media-field"
|
|
25049
25380
|
];
|
|
25050
25381
|
var TIPTAP_CONTENT_EDITOR_DIRECTORIES = ["tiptap-extension", "tiptap-node", "tiptap-ui"];
|
|
25051
25382
|
var CODEMIRROR_PACKAGE_DEPS = [
|
|
@@ -25455,7 +25786,7 @@ var TEMPLATE_REGISTRY = {
|
|
|
25455
25786
|
relPath: "hooks/content-editor/use-content-editor-source-mode.tsx",
|
|
25456
25787
|
content: () => readTemplate("hooks/content-editor/use-content-editor-source-mode.tsx"),
|
|
25457
25788
|
dependencies: [
|
|
25458
|
-
"media-
|
|
25789
|
+
"media-field",
|
|
25459
25790
|
"use-admin-theme",
|
|
25460
25791
|
"markdown-utils",
|
|
25461
25792
|
"content-editor-source-media-utils",
|
|
@@ -25476,7 +25807,7 @@ var TEMPLATE_REGISTRY = {
|
|
|
25476
25807
|
"tiptap",
|
|
25477
25808
|
"tiptap-utils",
|
|
25478
25809
|
"content-editor/media-gallery-block",
|
|
25479
|
-
"media-
|
|
25810
|
+
"media-field"
|
|
25480
25811
|
]
|
|
25481
25812
|
},
|
|
25482
25813
|
"use-content-editor-media-insertion": {
|