diff --git a/packages/javascript-api/src/lib/model/input-field/date-field-creation-request.ts b/packages/javascript-api/src/lib/model/input-field/date-field-creation-request.ts new file mode 100644 index 00000000..684e8a07 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/date-field-creation-request.ts @@ -0,0 +1,9 @@ +import { InputFieldBase } from './input-field-base.js'; +import { InputFieldTranslation } from './input-field-translation.js'; + +export interface DateFieldCreationRequest extends InputFieldBase { + type: 'DATE'; + title: string; + visitorFacingTitle?: string; + translations?: InputFieldTranslation[]; +} diff --git a/packages/javascript-api/src/lib/model/input-field/email-field-creation-request.ts b/packages/javascript-api/src/lib/model/input-field/email-field-creation-request.ts new file mode 100644 index 00000000..5e343ad7 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/email-field-creation-request.ts @@ -0,0 +1,6 @@ +import { InputFieldBase } from './input-field-base.js'; + +export interface EmailFieldCreationRequest extends InputFieldBase { + type: 'EMAIL'; + isRequiredInAppointments: boolean; +} diff --git a/packages/javascript-api/src/lib/model/input-field/first-name-field-creation-request.ts b/packages/javascript-api/src/lib/model/input-field/first-name-field-creation-request.ts new file mode 100644 index 00000000..8f10feff --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/first-name-field-creation-request.ts @@ -0,0 +1,5 @@ +import { InputFieldBase } from './input-field-base.js'; + +export interface FirstNameFieldCreationRequest extends InputFieldBase { + type: 'FIRST_NAME'; +} diff --git a/packages/javascript-api/src/lib/model/input-field/input-field-base.ts b/packages/javascript-api/src/lib/model/input-field/input-field-base.ts new file mode 100644 index 00000000..4f6da999 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/input-field-base.ts @@ -0,0 +1,15 @@ +import { LineRef } from './line-ref.js'; +import { LocationRef } from './location-ref.js'; +import { UUID } from '../uuid.js'; + +export interface InputFieldBase { + id: UUID; + location: LocationRef; + isMandatoryBeforeAdded: boolean; + isMandatoryBeforeServed: boolean; + isMandatoryInRemoteSignIn: boolean; + isVisibleInWaitingDrawer: boolean; + isVisibleInServingDrawer: boolean; + visibleForLines: LineRef[]; + showInRemoteSignIn: boolean; +} diff --git a/packages/javascript-api/src/lib/model/input-field/input-field-creation-request.ts b/packages/javascript-api/src/lib/model/input-field/input-field-creation-request.ts new file mode 100644 index 00000000..d0b9dfdd --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/input-field-creation-request.ts @@ -0,0 +1,24 @@ +import { DateFieldCreationRequest } from './date-field-creation-request.js'; +import { EmailFieldCreationRequest } from './email-field-creation-request.js'; +import { FirstNameFieldCreationRequest } from './first-name-field-creation-request.js'; +import { LastNameFieldCreationRequest } from './last-name-field-creation-request.js'; +import { NumericFieldCreationRequest } from './numeric-field-creation-request.js'; +import { PhoneNumberFieldCreationRequest } from './phone-number-field-creation-request.js'; +import { SelectFieldCreationRequest } from './select-field-creation-request.js'; +import { TextFieldCreationRequest } from './text-field-creation-request.js'; +import { UrlFieldCreationRequest } from './url-field-creation-request.js'; + +/** + * A discriminated union of all input field creation request types. + * The `type` field determines which properties are available. + */ +export type InputFieldCreationRequest = + | TextFieldCreationRequest + | SelectFieldCreationRequest + | EmailFieldCreationRequest + | PhoneNumberFieldCreationRequest + | FirstNameFieldCreationRequest + | LastNameFieldCreationRequest + | UrlFieldCreationRequest + | DateFieldCreationRequest + | NumericFieldCreationRequest; diff --git a/packages/javascript-api/src/lib/model/input-field/input-field-translation.ts b/packages/javascript-api/src/lib/model/input-field/input-field-translation.ts new file mode 100644 index 00000000..707e63d3 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/input-field-translation.ts @@ -0,0 +1,5 @@ +export interface InputFieldTranslation { + languageCode: string; + title?: string; + visitorFacingTitle?: string; +} diff --git a/packages/javascript-api/src/lib/model/input-field/last-name-field-creation-request.ts b/packages/javascript-api/src/lib/model/input-field/last-name-field-creation-request.ts new file mode 100644 index 00000000..0f549d07 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/last-name-field-creation-request.ts @@ -0,0 +1,6 @@ +import { InputFieldBase } from './input-field-base.js'; + +export interface LastNameFieldCreationRequest extends InputFieldBase { + type: 'LAST_NAME'; + isRequiredInAppointments?: boolean; +} diff --git a/packages/javascript-api/src/lib/model/input-field/line-ref.ts b/packages/javascript-api/src/lib/model/input-field/line-ref.ts new file mode 100644 index 00000000..cc2a7d87 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/line-ref.ts @@ -0,0 +1,3 @@ +export interface LineRef { + id: number; +} diff --git a/packages/javascript-api/src/lib/model/input-field/location-ref.ts b/packages/javascript-api/src/lib/model/input-field/location-ref.ts new file mode 100644 index 00000000..e973637a --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/location-ref.ts @@ -0,0 +1,3 @@ +export interface LocationRef { + id: number; +} diff --git a/packages/javascript-api/src/lib/model/input-field/numeric-field-constraints.ts b/packages/javascript-api/src/lib/model/input-field/numeric-field-constraints.ts new file mode 100644 index 00000000..e1f5f347 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/numeric-field-constraints.ts @@ -0,0 +1,5 @@ +export interface NumericFieldConstraints { + min?: number; + max?: number; + scale: number; +} diff --git a/packages/javascript-api/src/lib/model/input-field/numeric-field-creation-request.ts b/packages/javascript-api/src/lib/model/input-field/numeric-field-creation-request.ts new file mode 100644 index 00000000..45da9b76 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/numeric-field-creation-request.ts @@ -0,0 +1,11 @@ +import { InputFieldBase } from './input-field-base.js'; +import { InputFieldTranslation } from './input-field-translation.js'; +import { NumericFieldConstraints } from './numeric-field-constraints.js'; + +export interface NumericFieldCreationRequest extends InputFieldBase { + type: 'NUMERIC'; + title: string; + visitorFacingTitle?: string; + translations?: InputFieldTranslation[]; + constraints?: NumericFieldConstraints; +} diff --git a/packages/javascript-api/src/lib/model/input-field/phone-number-field-creation-request.ts b/packages/javascript-api/src/lib/model/input-field/phone-number-field-creation-request.ts new file mode 100644 index 00000000..1d8c70d6 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/phone-number-field-creation-request.ts @@ -0,0 +1,5 @@ +import { InputFieldBase } from './input-field-base.js'; + +export interface PhoneNumberFieldCreationRequest extends InputFieldBase { + type: 'PHONE_NUMBER'; +} diff --git a/packages/javascript-api/src/lib/model/input-field/select-field-creation-request.ts b/packages/javascript-api/src/lib/model/input-field/select-field-creation-request.ts new file mode 100644 index 00000000..4c84dd5d --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/select-field-creation-request.ts @@ -0,0 +1,12 @@ +import { InputFieldBase } from './input-field-base.js'; +import { InputFieldTranslation } from './input-field-translation.js'; +import { SelectOption } from './select-option.js'; + +export interface SelectFieldCreationRequest extends InputFieldBase { + type: 'SELECT'; + title: string; + visitorFacingTitle?: string; + multiSelect: boolean; + options: SelectOption[]; + translations?: InputFieldTranslation[]; +} diff --git a/packages/javascript-api/src/lib/model/input-field/select-option-translation.ts b/packages/javascript-api/src/lib/model/input-field/select-option-translation.ts new file mode 100644 index 00000000..91c07c69 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/select-option-translation.ts @@ -0,0 +1,4 @@ +export interface SelectOptionTranslation { + languageCode: string; + title?: string; +} diff --git a/packages/javascript-api/src/lib/model/input-field/select-option.ts b/packages/javascript-api/src/lib/model/input-field/select-option.ts new file mode 100644 index 00000000..36575350 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/select-option.ts @@ -0,0 +1,9 @@ +import { UUID } from '../uuid.js'; +import { SelectOptionTranslation } from './select-option-translation.js'; + +export interface SelectOption { + id: UUID; + title: string; + color?: string; + translations?: SelectOptionTranslation[]; +} diff --git a/packages/javascript-api/src/lib/model/input-field/text-field-creation-request.ts b/packages/javascript-api/src/lib/model/input-field/text-field-creation-request.ts new file mode 100644 index 00000000..2a9bb412 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/text-field-creation-request.ts @@ -0,0 +1,9 @@ +import { InputFieldBase } from './input-field-base.js'; +import { InputFieldTranslation } from './input-field-translation.js'; + +export interface TextFieldCreationRequest extends InputFieldBase { + type: 'TEXT'; + title: string; + visitorFacingTitle?: string; + translations?: InputFieldTranslation[]; +} diff --git a/packages/javascript-api/src/lib/model/input-field/url-field-creation-request.ts b/packages/javascript-api/src/lib/model/input-field/url-field-creation-request.ts new file mode 100644 index 00000000..d2bba2b0 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/url-field-creation-request.ts @@ -0,0 +1,8 @@ +import { InputFieldBase } from './input-field-base.js'; +import { InputFieldTranslation } from './input-field-translation.js'; + +export interface UrlFieldCreationRequest extends InputFieldBase { + type: 'URL'; + title: string; + translations?: InputFieldTranslation[]; +} diff --git a/packages/javascript-api/src/lib/services/location/location.service.spec.ts b/packages/javascript-api/src/lib/services/location/location.service.spec.ts index e069834c..0154d355 100644 --- a/packages/javascript-api/src/lib/services/location/location.service.spec.ts +++ b/packages/javascript-api/src/lib/services/location/location.service.spec.ts @@ -1,5 +1,9 @@ import * as sinon from 'sinon'; import { Desk } from '../../model/desk'; +import { InputFieldCreationRequest } from '../../model/input-field/input-field-creation-request'; +import { FirstNameFieldCreationRequest } from '../../model/input-field/first-name-field-creation-request'; +import { NumericFieldCreationRequest } from '../../model/input-field/numeric-field-creation-request'; +import { SelectFieldCreationRequest } from '../../model/input-field/select-field-creation-request'; import { Qminder } from '../../qminder'; import { LocationService } from './location.service'; @@ -225,6 +229,133 @@ describe('Location service', function () { }); }); + describe('createInputField()', function () { + beforeEach(function () { + requestStub.withArgs('input-fields').resolves({}); + }); + + it('sends a TEXT field with correct URL, method, body and headers', async function () { + const textField: InputFieldCreationRequest = { + type: 'TEXT', + id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + location: { id: LOCATION_ID }, + title: 'Email address', + visitorFacingTitle: 'Your email', + isMandatoryBeforeAdded: false, + isMandatoryBeforeServed: false, + isMandatoryInRemoteSignIn: false, + isVisibleInWaitingDrawer: true, + isVisibleInServingDrawer: true, + visibleForLines: [{ id: 1 }, { id: 2 }], + showInRemoteSignIn: false, + translations: [ + { + languageCode: 'et', + title: 'E-posti aadress', + visitorFacingTitle: 'Sinu e-post', + }, + ], + }; + + await LocationService.createInputField(textField); + expect( + requestStub.calledWith('input-fields', { + method: 'POST', + body: JSON.stringify(textField), + headers: { 'X-Qminder-API-Version': '2020-09-01' }, + }), + ).toBeTruthy(); + }); + + it('sends a SELECT field with options', async function () { + const selectField: SelectFieldCreationRequest = { + type: 'SELECT', + id: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + location: { id: LOCATION_ID }, + title: 'Service type', + multiSelect: false, + options: [ + { + id: 'c3d4e5f6-a7b8-9012-cdef-123456789012', + title: 'Documents', + color: '#FF0000', + translations: [{ languageCode: 'et', title: 'Dokumendid' }], + }, + { + id: 'd4e5f6a7-b8c9-0123-defa-234567890123', + title: 'Consultation', + }, + ], + isMandatoryBeforeAdded: true, + isMandatoryBeforeServed: false, + isMandatoryInRemoteSignIn: false, + isVisibleInWaitingDrawer: true, + isVisibleInServingDrawer: true, + visibleForLines: [], + showInRemoteSignIn: false, + }; + + await LocationService.createInputField(selectField); + expect( + requestStub.calledWith('input-fields', { + method: 'POST', + body: JSON.stringify(selectField), + headers: { 'X-Qminder-API-Version': '2020-09-01' }, + }), + ).toBeTruthy(); + }); + + it('sends a NUMERIC field with constraints', async function () { + const numericField: NumericFieldCreationRequest = { + type: 'NUMERIC', + id: 'e5f6a7b8-c9d0-1234-efab-345678901234', + location: { id: LOCATION_ID }, + title: 'Amount', + constraints: { min: 0, max: 1000, scale: 2 }, + isMandatoryBeforeAdded: false, + isMandatoryBeforeServed: true, + isMandatoryInRemoteSignIn: false, + isVisibleInWaitingDrawer: false, + isVisibleInServingDrawer: true, + visibleForLines: [], + showInRemoteSignIn: false, + }; + + await LocationService.createInputField(numericField); + expect( + requestStub.calledWith('input-fields', { + method: 'POST', + body: JSON.stringify(numericField), + headers: { 'X-Qminder-API-Version': '2020-09-01' }, + }), + ).toBeTruthy(); + }); + + it('sends a FIRST_NAME field with only base properties', async function () { + const firstNameField: FirstNameFieldCreationRequest = { + type: 'FIRST_NAME', + id: 'f6a7b8c9-d0e1-2345-faba-456789012345', + location: { id: LOCATION_ID }, + isMandatoryBeforeAdded: false, + isMandatoryBeforeServed: false, + isMandatoryInRemoteSignIn: false, + isVisibleInWaitingDrawer: true, + isVisibleInServingDrawer: true, + visibleForLines: [], + showInRemoteSignIn: false, + }; + + await LocationService.createInputField(firstNameField); + expect( + requestStub.calledWith('input-fields', { + method: 'POST', + body: JSON.stringify(firstNameField), + headers: { 'X-Qminder-API-Version': '2020-09-01' }, + }), + ).toBeTruthy(); + }); + }); + afterEach(function () { requestStub.restore(); }); diff --git a/packages/javascript-api/src/lib/services/location/location.service.ts b/packages/javascript-api/src/lib/services/location/location.service.ts index 5f19e679..00394d7f 100644 --- a/packages/javascript-api/src/lib/services/location/location.service.ts +++ b/packages/javascript-api/src/lib/services/location/location.service.ts @@ -1,4 +1,5 @@ import { + createInputField, details, getDesks, list, @@ -94,4 +95,11 @@ export const LocationService = { * Calls the following HTTP API: `PUT /locations//opening-hours/exceptions` */ setOpeningHoursExceptions, + + /** + * Create a new input field for a location. + * + * Calls the following HTTP API: `POST /input-fields` + */ + createInputField, }; diff --git a/packages/javascript-api/src/lib/services/location/location.ts b/packages/javascript-api/src/lib/services/location/location.ts index f09cac66..4730b6d8 100644 --- a/packages/javascript-api/src/lib/services/location/location.ts +++ b/packages/javascript-api/src/lib/services/location/location.ts @@ -1,11 +1,11 @@ import { Desk } from '../../model/desk.js'; +import { InputFieldCreationRequest } from '../../model/input-field/input-field-creation-request.js'; import { Location } from '../../model/location.js'; import { OpeningHours } from '../../model/opening-hours.js'; import { OpeningHoursException } from '../../model/opening-hours-exception.js'; import { extractId, IdOrObject } from '../../util/id-or-object.js'; import { ApiBase } from '../api-base/api-base.js'; - -const V2_HEADERS = { 'X-Qminder-API-Version': '2020-09-01' } as const; +import { V2_HEADERS } from '../v2-headers.js'; export function list(): Promise { return ApiBase.request('v1/locations/').then( @@ -55,3 +55,13 @@ export async function setOpeningHoursExceptions( headers: V2_HEADERS, }); } + +export async function createInputField( + inputField: InputFieldCreationRequest, +): Promise { + await ApiBase.request('input-fields', { + method: 'POST', + body: JSON.stringify(inputField), + headers: V2_HEADERS, + }); +} diff --git a/packages/javascript-api/src/lib/services/ticket/ticket.ts b/packages/javascript-api/src/lib/services/ticket/ticket.ts index ce259abe..3366051d 100644 --- a/packages/javascript-api/src/lib/services/ticket/ticket.ts +++ b/packages/javascript-api/src/lib/services/ticket/ticket.ts @@ -10,6 +10,7 @@ import { extractIdToNumber, } from '../../util/id-or-object.js'; import { ApiBase } from '../api-base/api-base.js'; +import { V2_HEADERS } from '../v2-headers.js'; import { ResponseValidationError } from '../../model/errors/response-validation-error.js'; import { ExternalData } from '../../model/ticket/external-data.js'; @@ -329,9 +330,7 @@ export async function create( const result: TicketCreatedResponse = await ApiBase.request('tickets', { method: 'POST', body, - headers: { - 'X-Qminder-API-Version': '2020-09-01', - }, + headers: V2_HEADERS, }); if (!result.id) { throw new ResponseValidationError('Response does not contain "id"'); @@ -360,9 +359,7 @@ export async function edit( await ApiBase.request(`tickets/${ticketId}`, { method: 'PATCH', body, - headers: { - 'X-Qminder-API-Version': '2020-09-01', - }, + headers: V2_HEADERS, }); } diff --git a/packages/javascript-api/src/lib/services/v2-headers.ts b/packages/javascript-api/src/lib/services/v2-headers.ts new file mode 100644 index 00000000..3fae697b --- /dev/null +++ b/packages/javascript-api/src/lib/services/v2-headers.ts @@ -0,0 +1 @@ +export const V2_HEADERS = { 'X-Qminder-API-Version': '2020-09-01' } as const; diff --git a/packages/javascript-api/src/public-api/model.ts b/packages/javascript-api/src/public-api/model.ts index 95e1f81b..dca6a840 100644 --- a/packages/javascript-api/src/public-api/model.ts +++ b/packages/javascript-api/src/public-api/model.ts @@ -34,3 +34,19 @@ export { export { TicketLabelRequest } from '../lib/model/ticket/ticket-label-request.js'; export { TicketType } from '../lib/model/ticket/ticket-type.js'; export { UUID } from '../lib/model/uuid.js'; +export { InputFieldCreationRequest } from '../lib/model/input-field/input-field-creation-request.js'; +export { LocationRef } from '../lib/model/input-field/location-ref.js'; +export { LineRef } from '../lib/model/input-field/line-ref.js'; +export { InputFieldTranslation } from '../lib/model/input-field/input-field-translation.js'; +export { SelectOption } from '../lib/model/input-field/select-option.js'; +export { SelectOptionTranslation } from '../lib/model/input-field/select-option-translation.js'; +export { NumericFieldConstraints } from '../lib/model/input-field/numeric-field-constraints.js'; +export { TextFieldCreationRequest } from '../lib/model/input-field/text-field-creation-request.js'; +export { SelectFieldCreationRequest } from '../lib/model/input-field/select-field-creation-request.js'; +export { EmailFieldCreationRequest } from '../lib/model/input-field/email-field-creation-request.js'; +export { PhoneNumberFieldCreationRequest } from '../lib/model/input-field/phone-number-field-creation-request.js'; +export { FirstNameFieldCreationRequest } from '../lib/model/input-field/first-name-field-creation-request.js'; +export { LastNameFieldCreationRequest } from '../lib/model/input-field/last-name-field-creation-request.js'; +export { UrlFieldCreationRequest } from '../lib/model/input-field/url-field-creation-request.js'; +export { DateFieldCreationRequest } from '../lib/model/input-field/date-field-creation-request.js'; +export { NumericFieldCreationRequest } from '../lib/model/input-field/numeric-field-creation-request.js';