diff --git a/packages/admin-ui/src/lib/core/src/common/generated-types.ts b/packages/admin-ui/src/lib/core/src/common/generated-types.ts index 7ba76a419e..9699c94882 100644 --- a/packages/admin-ui/src/lib/core/src/common/generated-types.ts +++ b/packages/admin-ui/src/lib/core/src/common/generated-types.ts @@ -2689,6 +2689,8 @@ export type ConfigArgDefinition = { name: Scalars['String']; type: Scalars['String']; list: Scalars['Boolean']; + required: Scalars['Boolean']; + defaultValue?: Maybe; label?: Maybe; description?: Maybe; ui?: Maybe; @@ -2715,6 +2717,7 @@ export type DeletionResponse = { export type ConfigArgInput = { name: Scalars['String']; + /** A JSON stringified representation of the actual value */ value: Scalars['String']; }; @@ -3728,6 +3731,7 @@ export type OrderAddress = { country?: Maybe; countryCode?: Maybe; phoneNumber?: Maybe; + customFields?: Maybe; }; export type OrderList = PaginatedList & { @@ -7441,7 +7445,7 @@ export type ConfigurableOperationDefFragment = ( & Pick & { args: Array<( { __typename?: 'ConfigArgDefinition' } - & Pick + & Pick )> } ); diff --git a/packages/admin-ui/src/lib/core/src/common/utilities/configurable-operation-utils.ts b/packages/admin-ui/src/lib/core/src/common/utilities/configurable-operation-utils.ts index bd27228314..7d19b05eb8 100644 --- a/packages/admin-ui/src/lib/core/src/common/utilities/configurable-operation-utils.ts +++ b/packages/admin-ui/src/lib/core/src/common/utilities/configurable-operation-utils.ts @@ -77,28 +77,28 @@ export function toConfigurableOperationInput( }; } +export function configurableOperationValueIsValid( + def?: ConfigurableOperationDefinition, + value?: { code: string; args: { [key: string]: string } }, +) { + if (!def || !value) { + return false; + } + if (def.code !== value.code) { + return false; + } + for (const argDef of def.args) { + const argVal = value.args[argDef.name]; + if (argDef.required && (argVal == null || argVal === '' || argVal === '0')) { + return false; + } + } + return true; +} + /** * Returns a default value based on the type of the config arg. */ export function getDefaultConfigArgValue(arg: ConfigArgDefinition): any { - return arg.list ? [] : getDefaultConfigArgSingleValue(arg.type as ConfigArgType); -} - -export function getDefaultConfigArgSingleValue(type: ConfigArgType | CustomFieldType): any { - switch (type) { - case 'boolean': - return 'false'; - case 'int': - case 'float': - return '0'; - case 'ID': - return ''; - case 'string': - case 'localeString': - return ''; - case 'datetime': - return new Date(); - default: - assertNever(type); - } + return arg.list ? [] : arg.defaultValue || null; // getDefaultConfigArgSingleValue(arg.type as ConfigArgType); } diff --git a/packages/admin-ui/src/lib/core/src/common/utilities/interpolate-description.spec.ts b/packages/admin-ui/src/lib/core/src/common/utilities/interpolate-description.spec.ts index f45f25be7e..ef4102666f 100644 --- a/packages/admin-ui/src/lib/core/src/common/utilities/interpolate-description.spec.ts +++ b/packages/admin-ui/src/lib/core/src/common/utilities/interpolate-description.spec.ts @@ -5,7 +5,7 @@ import { interpolateDescription } from './interpolate-description'; describe('interpolateDescription()', () => { it('works for single argument', () => { const operation: Partial = { - args: [{ name: 'foo', type: 'string', list: false }], + args: [{ name: 'foo', type: 'string', list: false, required: false }], description: 'The value is { foo }', }; const result = interpolateDescription(operation as any, { foo: 'val' }); @@ -16,8 +16,8 @@ describe('interpolateDescription()', () => { it('works for multiple arguments', () => { const operation: Partial = { args: [ - { name: 'foo', type: 'string', list: false }, - { name: 'bar', type: 'string', list: false }, + { name: 'foo', type: 'string', list: false, required: false }, + { name: 'bar', type: 'string', list: false, required: false }, ], description: 'The value is { foo } and { bar }', }; @@ -28,7 +28,7 @@ describe('interpolateDescription()', () => { it('is case-insensitive', () => { const operation: Partial = { - args: [{ name: 'foo', type: 'string', list: false }], + args: [{ name: 'foo', type: 'string', list: false, required: false }], description: 'The value is { FOo }', }; const result = interpolateDescription(operation as any, { foo: 'val' }); @@ -39,8 +39,8 @@ describe('interpolateDescription()', () => { it('ignores whitespaces in interpolation', () => { const operation: Partial = { args: [ - { name: 'foo', type: 'string', list: false }, - { name: 'bar', type: 'string', list: false }, + { name: 'foo', type: 'string', list: false, required: false }, + { name: 'bar', type: 'string', list: false, required: false }, ], description: 'The value is {foo} and { bar }', }; @@ -51,7 +51,15 @@ describe('interpolateDescription()', () => { it('formats currency-form-input value as a decimal', () => { const operation: Partial = { - args: [{ name: 'price', type: 'int', list: false, ui: { component: 'currency-form-input' } }], + args: [ + { + name: 'price', + type: 'int', + list: false, + ui: { component: 'currency-form-input' }, + required: false, + }, + ], description: 'The price is { price }', }; const result = interpolateDescription(operation as any, { price: 1234 }); @@ -61,7 +69,7 @@ describe('interpolateDescription()', () => { it('formats Date object as human-readable', () => { const operation: Partial = { - args: [{ name: 'date', type: 'datetime', list: false }], + args: [{ name: 'date', type: 'datetime', list: false, required: false }], description: 'The date is { date }', }; const date = new Date('2017-09-15 00:00:00'); @@ -72,7 +80,7 @@ describe('interpolateDescription()', () => { it('formats date string object as human-readable', () => { const operation: Partial = { - args: [{ name: 'date', type: 'datetime', list: false }], + args: [{ name: 'date', type: 'datetime', list: false, required: false }], description: 'The date is { date }', }; const date = '2017-09-15'; @@ -83,7 +91,7 @@ describe('interpolateDescription()', () => { it('correctly interprets falsy-looking values', () => { const operation: Partial = { - args: [{ name: 'foo', type: 'int', list: false }], + args: [{ name: 'foo', type: 'int', list: false, required: false }], description: 'The value is { foo }', }; const result = interpolateDescription(operation as any, { foo: 0 }); diff --git a/packages/admin-ui/src/lib/core/src/data/definitions/shared-definitions.ts b/packages/admin-ui/src/lib/core/src/data/definitions/shared-definitions.ts index 82428e609b..ed38803a87 100644 --- a/packages/admin-ui/src/lib/core/src/data/definitions/shared-definitions.ts +++ b/packages/admin-ui/src/lib/core/src/data/definitions/shared-definitions.ts @@ -15,6 +15,8 @@ export const CONFIGURABLE_OPERATION_DEF_FRAGMENT = gql` args { name type + required + defaultValue list ui label diff --git a/packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.ts b/packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.ts index ef3367538e..d8a670148b 100644 --- a/packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.ts +++ b/packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.ts @@ -133,7 +133,7 @@ export class ConfigurableInputComponent implements OnChanges, OnDestroy, Control if (value === undefined) { value = getDefaultConfigArgValue(arg); } - const validators = arg.list ? undefined : Validators.required; + const validators = arg.list ? undefined : arg.required ? Validators.required : undefined; this.form.addControl(arg.name, new FormControl(value, validators)); } } diff --git a/packages/admin-ui/src/lib/core/src/shared/components/currency-input/currency-input.component.ts b/packages/admin-ui/src/lib/core/src/shared/components/currency-input/currency-input.component.ts index b5b633e500..9ef6b1162c 100644 --- a/packages/admin-ui/src/lib/core/src/shared/components/currency-input/currency-input.component.ts +++ b/packages/admin-ui/src/lib/core/src/shared/components/currency-input/currency-input.component.ts @@ -9,7 +9,7 @@ import { SimpleChanges, } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { DataService } from '../../../data/providers/data.service'; @@ -41,20 +41,21 @@ export class CurrencyInputComponent implements ControlValueAccessor, OnInit, OnC onChange: (val: any) => void; onTouch: () => void; _decimalValue: string; + private currencyCode$ = new BehaviorSubject(''); constructor(private dataService: DataService, private changeDetectorRef: ChangeDetectorRef) {} ngOnInit() { const languageCode$ = this.dataService.client.uiState().mapStream(data => data.uiState.language); - const shouldPrefix$ = languageCode$.pipe( - map(languageCode => { - if (!this.currencyCode) { + const shouldPrefix$ = combineLatest(languageCode$, this.currencyCode$).pipe( + map(([languageCode, currencyCode]) => { + if (!currencyCode) { return ''; } const locale = languageCode.replace(/_/g, '-'); const localised = new Intl.NumberFormat(locale, { style: 'currency', - currency: this.currencyCode, + currency: currencyCode, currencyDisplay: 'symbol', }).format(undefined as any); return localised.indexOf('NaN') > 0; @@ -68,6 +69,9 @@ export class CurrencyInputComponent implements ControlValueAccessor, OnInit, OnC if ('value' in changes) { this.writeValue(changes['value'].currentValue); } + if ('currencyCode' in changes) { + this.currencyCode$.next(this.currencyCode); + } } registerOnChange(fn: any) { diff --git a/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.ts b/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.ts index 61ddc92f1e..6df8215754 100644 --- a/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.ts +++ b/packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.ts @@ -29,10 +29,7 @@ import { switchMap, take, takeUntil } from 'rxjs/operators'; import { FormInputComponent } from '../../../common/component-registry-types'; import { ConfigArgDefinition, CustomFieldConfig } from '../../../common/generated-types'; -import { - getConfigArgValue, - getDefaultConfigArgSingleValue, -} from '../../../common/utilities/configurable-operation-utils'; +import { getConfigArgValue } from '../../../common/utilities/configurable-operation-utils'; import { ComponentRegistryService } from '../../../providers/component-registry/component-registry.service'; type InputListItem = { @@ -211,7 +208,7 @@ export class DynamicFormInputComponent } this.listItems.push({ id: this.listId++, - control: new FormControl(getDefaultConfigArgSingleValue(this.def.type as ConfigArgType)), + control: new FormControl((this.def as ConfigArgDefinition).defaultValue ?? null), }); this.renderList$.next(); } diff --git a/packages/admin-ui/src/lib/order/src/components/fulfill-order-dialog/fulfill-order-dialog.component.ts b/packages/admin-ui/src/lib/order/src/components/fulfill-order-dialog/fulfill-order-dialog.component.ts index 4eac64ca73..c5037c42c4 100644 --- a/packages/admin-ui/src/lib/order/src/components/fulfill-order-dialog/fulfill-order-dialog.component.ts +++ b/packages/admin-ui/src/lib/order/src/components/fulfill-order-dialog/fulfill-order-dialog.component.ts @@ -4,6 +4,7 @@ import { configurableDefinitionToInstance, ConfigurableOperation, ConfigurableOperationDefinition, + configurableOperationValueIsValid, DataService, Dialog, FulfillOrderInput, @@ -77,8 +78,10 @@ export class FulfillOrderDialogComponent implements Dialog, O 0, ); const formIsValid = - this.fulfillmentHandlerDef?.args.length === 0 || - (this.fulfillmentHandlerControl.valid && this.fulfillmentHandlerControl.touched); + configurableOperationValueIsValid( + this.fulfillmentHandlerDef, + this.fulfillmentHandlerControl.value, + ) && this.fulfillmentHandlerControl.valid; return formIsValid && 0 < totalCount; } diff --git a/packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.ts b/packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.ts index 429d5b9e9d..c6ee32e170 100644 --- a/packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.ts +++ b/packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.ts @@ -70,7 +70,7 @@ export class ShippingMethodDetailComponent code: ['', Validators.required], name: ['', Validators.required], description: '', - fulfillmentHandler: '', + fulfillmentHandler: ['', Validators.required], checker: {}, calculator: {}, customFields: this.formBuilder.group(