Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/ycode/api/collections/[id]/fields/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ export async function POST(
order: body.order ?? 0,
reference_collection_id: body.reference_collection_id || null,
hidden: body.hidden ?? false,
is_computed: body.is_computed ?? false,
data: body.data || {},
is_published: false, // Always create as draft
is_published: false,
});

return noCache(
Expand Down
34 changes: 18 additions & 16 deletions app/ycode/components/CMS.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ interface SortableRowProps {
isCollectionPublished: boolean;
children: React.ReactNode;
statusValue: import('./CollectionStatusPill').ItemStatusValue | null;
onEdit: () => void;
onSetAsDraft: () => void;
onStageForPublish: () => void;
onSetAsPublished: () => void;
Expand All @@ -120,7 +121,7 @@ interface SortableRowProps {
lockInfo?: ItemLockInfo;
}

function SortableRow({ item, isSaving, isManualMode, isCollectionPublished, children, statusValue, onSetAsDraft, onStageForPublish, onSetAsPublished, onDuplicate, onDelete, lockInfo }: SortableRowProps) {
function SortableRow({ item, isSaving, isManualMode, isCollectionPublished, children, statusValue, onEdit, onSetAsDraft, onStageForPublish, onSetAsPublished, onDuplicate, onDelete, lockInfo }: SortableRowProps) {
const {
attributes,
listeners,
Expand All @@ -147,7 +148,9 @@ function SortableRow({ item, isSaving, isManualMode, isCollectionPublished, chil
<CollectionItemContextMenu
isPublishable={statusValue?.is_publishable ?? item.is_publishable}
hasPublishedVersion={statusValue?.is_published ?? false}
isModified={statusValue?.is_modified ?? false}
isCollectionPublished={isCollectionPublished}
onEdit={onEdit}
onSetAsDraft={onSetAsDraft}
onStageForPublish={onStageForPublish}
onSetAsPublished={onSetAsPublished}
Expand Down Expand Up @@ -1565,6 +1568,7 @@ const CMS = React.memo(function CMS() {
isManualMode={isManualMode}
isCollectionPublished={selectedCollection?.has_published_version ?? false}
statusValue={statusFieldId ? parseStatusValue(item.values[statusFieldId]) : null}
onEdit={() => handleEditItem(item)}
onSetAsDraft={() => handleSetItemStatus(item.id, 'draft')}
onStageForPublish={() => handleSetItemStatus(item.id, 'stage')}
onSetAsPublished={() => handleSetItemStatus(item.id, 'publish')}
Expand All @@ -1576,9 +1580,7 @@ const CMS = React.memo(function CMS() {
className="pl-5 pr-3 py-3 w-12"
onClick={(e) => {
e.stopPropagation();
if (!isManualMode) {
handleEditItem(item);
}
handleEditItem(item);
}}
>
<div className="flex">
Expand All @@ -1599,7 +1601,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4 py-5"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>
<CollectionStatusPill
statusValue={statusFieldId ? parseStatusValue(item.values[statusFieldId]) : null}
Expand All @@ -1616,7 +1618,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4 py-5 text-muted-foreground"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>
<span className="line-clamp-1 truncate">
{formatDateInTimezone(value, timezone, 'display')}
Expand All @@ -1637,7 +1639,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4 py-5 text-muted-foreground"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>
-
</td>
Expand All @@ -1648,7 +1650,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>
<div className="flex items-center gap-1 -my-1.5">
{assetIds.slice(0, 3).map((assetId, idx) => {
Expand Down Expand Up @@ -1716,7 +1718,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4 py-5 text-muted-foreground"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>
-
</td>
Expand All @@ -1727,7 +1729,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>
<div className="flex items-center gap-1 -my-1.5">
{assetIds.slice(0, 3).map((assetId, idx) => {
Expand Down Expand Up @@ -1763,7 +1765,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4 py-5 text-muted-foreground"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>

<ReferenceFieldCell
Expand All @@ -1783,7 +1785,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4 py-5 text-muted-foreground max-w-50"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>
<span className="block truncate">
{plainText || '-'}
Expand Down Expand Up @@ -1828,7 +1830,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4 py-5 text-muted-foreground max-w-50"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>
<span className="block truncate">
{displayValue}
Expand All @@ -1843,7 +1845,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4 py-5 text-muted-foreground"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>
<div className="flex items-center gap-2">
<div
Expand All @@ -1863,7 +1865,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4 py-5"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>
<div className="pointer-events-none">
<Checkbox
Expand All @@ -1880,7 +1882,7 @@ const CMS = React.memo(function CMS() {
<td
key={field.id}
className="px-4 py-5 text-muted-foreground"
onClick={() => !isManualMode && handleEditItem(item)}
onClick={() => handleEditItem(item)}
>
<span className="line-clamp-1 truncate">
{value || '-'}
Expand Down
12 changes: 10 additions & 2 deletions app/ycode/components/CollectionItemContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ interface CollectionItemContextMenuProps {
children: React.ReactNode;
isPublishable: boolean;
hasPublishedVersion: boolean;
isModified: boolean;
isCollectionPublished: boolean;
onEdit: () => void;
onSetAsDraft: () => void;
onStageForPublish: () => void;
onSetAsPublished: () => void;
Expand All @@ -20,7 +22,9 @@ export default function CollectionItemContextMenu({
children,
isPublishable,
hasPublishedVersion,
isModified,
isCollectionPublished,
onEdit,
onSetAsDraft,
onStageForPublish,
onSetAsPublished,
Expand All @@ -38,20 +42,24 @@ export default function CollectionItemContextMenu({
{children}
</ContextMenuTrigger>
<ContextMenuContent className="w-48">
<ContextMenuItem onClick={onEdit}>
Edit CMS item
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
disabled={!isPublishable && !hasPublishedVersion}
onClick={onSetAsDraft}
>
Set as draft
</ContextMenuItem>
<ContextMenuItem
disabled={isPublishable && !hasPublishedVersion}
disabled={isPublishable || hasPublishedVersion}
onClick={onStageForPublish}
>
Stage for publish
</ContextMenuItem>
<ContextMenuItem
disabled={!isCollectionPublished || (hasPublishedVersion && isPublishable)}
disabled={!isCollectionPublished || (hasPublishedVersion && isPublishable && !isModified)}
onClick={onSetAsPublished}
>
Set as published
Expand Down
17 changes: 11 additions & 6 deletions app/ycode/components/CollectionItemSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,24 +211,24 @@ export default function CollectionItemSheet({

// Reset form when editing item changes
useEffect(() => {
// Only include fillable/visible fields in form state to avoid
// sending computed fields (status, ID, timestamps) to the API
const editableFields = collectionFields.filter(f => f.fillable && !f.hidden);

if (editingItem) {
// Ensure all values are defined (not undefined)
const values: Record<string, any> = {};
collectionFields.forEach(field => {
editableFields.forEach(field => {
let value = editingItem.values[field.id] ?? '';
// Normalize boolean values to strings
if (field.type === 'boolean') {
value = normalizeBooleanValue(value);
}
values[field.id] = value;
});
form.reset(values);
} else {
// Reset with default values for new items
const defaultValues: Record<string, any> = {};
collectionFields.forEach(field => {
editableFields.forEach(field => {
let value = field.default || '';
// Normalize boolean values to strings
if (field.type === 'boolean') {
value = normalizeBooleanValue(value);
}
Expand Down Expand Up @@ -607,17 +607,20 @@ export default function CollectionItemSheet({
<Input
type="email"
placeholder={field.default || `Enter ${field.name.toLowerCase()}...`}
autoComplete="off"
{...formField}
/>
) : field.type === 'phone' ? (
<Input
type="tel"
placeholder={field.default || `Enter ${field.name.toLowerCase()}...`}
autoComplete="off"
{...formField}
/>
) : field.type === 'date' ? (
<Input
type="datetime-local"
autoComplete="off"
value={formatDateInTimezone(formField.value, timezone, 'datetime-local')}
onChange={(e) => {
const utcValue = localDatetimeToUTC(e.target.value, timezone);
Expand Down Expand Up @@ -786,6 +789,7 @@ export default function CollectionItemSheet({
<Input
ref={nameInputRef}
placeholder={field.default || `Enter ${field.name.toLowerCase()}...`}
autoComplete="off"
name={formField.name}
value={formField.value}
onChange={formField.onChange}
Expand All @@ -794,6 +798,7 @@ export default function CollectionItemSheet({
) : (
<Input
placeholder={field.default || `Enter ${field.name.toLowerCase()}...`}
autoComplete="off"
{...formField}
/>
)}
Expand Down
6 changes: 6 additions & 0 deletions app/ycode/components/FieldFormDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export default function FieldFormDialog({
value={fieldName}
onChange={(e) => setFieldName(e.target.value)}
placeholder="Field name"
autoComplete="off"
/>
</div>
</div>
Expand Down Expand Up @@ -351,13 +352,15 @@ export default function FieldFormDialog({
value={fieldDefault}
onChange={(e) => setFieldDefault(e.target.value)}
placeholder="0"
autoComplete="off"
/>
) : fieldType === 'date' ? (
<Input
id="field-default"
type="datetime-local"
value={fieldDefault}
onChange={(e) => setFieldDefault(e.target.value)}
autoComplete="off"
/>
) : fieldType === 'email' ? (
<Input
Expand All @@ -366,6 +369,7 @@ export default function FieldFormDialog({
value={fieldDefault}
onChange={(e) => setFieldDefault(e.target.value)}
placeholder="[email protected]"
autoComplete="off"
/>
) : fieldType === 'phone' ? (
<Input
Expand All @@ -374,13 +378,15 @@ export default function FieldFormDialog({
value={fieldDefault}
onChange={(e) => setFieldDefault(e.target.value)}
placeholder="+1 (555) 000-0000"
autoComplete="off"
/>
) : (
<Input
id="field-default"
value={fieldDefault}
onChange={(e) => setFieldDefault(e.target.value)}
placeholder="Default value"
autoComplete="off"
/>
)}
</div>
Expand Down
1 change: 0 additions & 1 deletion app/ycode/components/RichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,6 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
Code,
RichTextImageWithNodeView,
HorizontalRule,
RichTextImage,
];

// Always include heading extension so content with headings is preserved
Expand Down
4 changes: 2 additions & 2 deletions lib/collection-field-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ export const FIELD_TYPES_BY_CATEGORY = FIELD_TYPE_CATEGORIES.map(cat => ({
types: FIELD_TYPES.filter(t => t.category === cat.id),
}));

/** Valid field type values for API validation */
export const VALID_FIELD_TYPES: readonly string[] = FIELD_TYPES.map((t) => t.value);
/** Valid field type values for API validation (includes system types like 'status') */
export const VALID_FIELD_TYPES: readonly string[] = [...FIELD_TYPES.map((t) => t.value), 'status'];

/** Check if a field type supports setting a default value */
export function supportsDefaultValue(fieldType: CollectionFieldType | undefined): boolean {
Expand Down
Loading