import { FieldDraft, FieldManagement, NonReferenceField, SourceState, } from '@wix/bex-core'; import { runInAction } from 'mobx'; import { standardFieldTypeLabels } from './standardFieldTypeLabels'; /** * Builds the source-neutral FieldManagement over a source's state. add/edit/ * delete call the schema's FieldActions, then merge the returned fields into * the loaded schema so the columns update from the response (no reload). The * creatable types, permissions, and can-edit/add rules come from the source. */ export const schemaFieldManagement = ( sourceState: SourceState, ): FieldManagement => { const draftToField = (draft: FieldDraft): NonReferenceField => ({ id: draft.key, displayName: draft.name, type: (draft.type ?? 'SHORT_TEXT') as NonReferenceField['type'], isPii: draft.containsPii, validation: { required: false }, }); return { get canAddFields() { const { schema, source } = sourceState; return schema ? source.canAddFields(schema) : false; }, addFieldDisabled: false, canEditField: (field) => { const { schema, source } = sourceState; return schema ? source.canEditField(field, schema) : false; }, supportsPii: sourceState.source.supportsPii, permissions: sourceState.source.fieldPermissions, // Self-contained: does the delete and reports its own toast (matching the // DataExtension source). Rethrows on failure so the shared delete modal stays // open for a retry. deleteField: async (fieldId) => { try { const fields = await sourceState.fieldActions?.deleteField?.(fieldId); if (fields) { runInAction(() => sourceState.mergeFields(fields)); } sourceState.notify({ type: 'SUCCESS', message: 'Field deleted', biName: 'cairo-field-action-delete-success', }); } catch (e) { sourceState.notify({ type: 'ERROR', message: "Couldn't delete field", biName: 'cairo-field-action-delete-error', }); throw e; } }, get creatableFieldTypes() { const t = sourceState.container.translate; return sourceState.source.creatableFieldTypes.map((ft) => { // The standard pair applies only when the source didn't custom-label // the entry — a custom label with the standard placeholder would show // contradictory copy (the modal falls back to its generic placeholder). const standard = ft.label == null ? standardFieldTypeLabels[ft.patternsType] : undefined; return { id: ft.patternsType, label: ft.label ?? (standard ? t(standard.labelKey) : ft.patternsType), namePlaceholder: ft.namePlaceholder ?? (standard ? t(standard.namePlaceholderKey) : undefined), patternsType: ft.patternsType, }; }); }, get existingFieldKeys() { return sourceState.fields.map((f) => f.id); }, addField: async (draft) => { const fields = await sourceState.fieldActions?.addField?.( draftToField(draft), ); if (fields) { runInAction(() => sourceState.mergeFields(fields)); } }, editField: async (fieldId, draft) => { const fields = await sourceState.fieldActions?.editField?.(fieldId, { displayName: draft.name, }); if (fields) { runInAction(() => sourceState.mergeFields(fields)); } }, getFieldDraft: (fieldId) => { const field = sourceState.schema?.fields[fieldId]; if (!field) { return undefined; } return { key: field.id, name: field.displayName, type: field.type, readApps: false, readUsers: false, writeApps: false, writeUsers: false, containsPii: field.isPii ?? false, }; }, }; };