From be082f7e74dce5b0727e29c9639a4e78cce20f78 Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Thu, 26 Mar 2026 14:19:52 +0200 Subject: [PATCH 1/7] feat: add LocationService.createInputField method Add support for creating input fields via the public V2 API endpoint POST /input-fields. Includes a type-safe discriminated union covering all 9 input field types (TEXT, SELECT, EMAIL, PHONE_NUMBER, FIRST_NAME, LAST_NAME, URL, DATE, NUMERIC) with per-type properties. --- .../input-field-creation-request.ts | 120 ++++++++++++++++++ .../location/location.service.spec.ts | 104 +++++++++++++++ .../lib/services/location/location.service.ts | 11 ++ .../src/lib/services/location/location.ts | 22 ++++ .../javascript-api/src/public-api/model.ts | 17 +++ 5 files changed, 274 insertions(+) create mode 100644 packages/javascript-api/src/lib/model/input-field/input-field-creation-request.ts 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..a4279544 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/input-field-creation-request.ts @@ -0,0 +1,120 @@ +import { UUID } from '../uuid.js'; + +export type InputFieldType = + | 'TEXT' + | 'SELECT' + | 'EMAIL' + | 'PHONE_NUMBER' + | 'FIRST_NAME' + | 'LAST_NAME' + | 'URL' + | 'DATE' + | 'NUMERIC'; + +export interface InputFieldTranslation { + languageCode: string; + title?: string; + visitorFacingTitle?: string; +} + +export interface SelectOptionTranslation { + languageCode: string; + title?: string; +} + +export interface SelectOption { + /** Client-generated UUID for this option. */ + id: UUID; + title: string; + color?: string; + translations?: SelectOptionTranslation[]; +} + +export interface NumericFieldConstraints { + min?: number; + max?: number; + scale: number; +} + +interface InputFieldBase { + /** Client-generated UUID for this input field. */ + id: UUID; + location: { id: number }; + isMandatoryBeforeAdded: boolean; + isMandatoryBeforeServed: boolean; + isMandatoryInRemoteSignIn: boolean; + isVisibleInWaitingDrawer: boolean; + isVisibleInServingDrawer: boolean; + visibleForLines: { id: number }[]; + showInRemoteSignIn: boolean; +} + +export interface TextFieldCreationRequest extends InputFieldBase { + type: 'TEXT'; + title: string; + visitorFacingTitle?: string; + translations?: InputFieldTranslation[]; +} + +export interface SelectFieldCreationRequest extends InputFieldBase { + type: 'SELECT'; + title: string; + visitorFacingTitle?: string; + multiSelect: boolean; + options: SelectOption[]; + translations?: InputFieldTranslation[]; +} + +export interface EmailFieldCreationRequest extends InputFieldBase { + type: 'EMAIL'; + isRequiredInAppointments: boolean; +} + +export interface PhoneNumberFieldCreationRequest extends InputFieldBase { + type: 'PHONE_NUMBER'; +} + +export interface FirstNameFieldCreationRequest extends InputFieldBase { + type: 'FIRST_NAME'; +} + +export interface LastNameFieldCreationRequest extends InputFieldBase { + type: 'LAST_NAME'; + isRequiredInAppointments?: boolean; +} + +export interface UrlFieldCreationRequest extends InputFieldBase { + type: 'URL'; + title: string; + translations?: InputFieldTranslation[]; +} + +export interface DateFieldCreationRequest extends InputFieldBase { + type: 'DATE'; + title: string; + visitorFacingTitle?: string; + translations?: InputFieldTranslation[]; +} + +export interface NumericFieldCreationRequest extends InputFieldBase { + type: 'NUMERIC'; + title: string; + visitorFacingTitle?: string; + translations?: InputFieldTranslation[]; + constraints?: NumericFieldConstraints; +} + +/** + * 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/services/location/location.service.spec.ts b/packages/javascript-api/src/lib/services/location/location.service.spec.ts index e069834c..b85f9e96 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,10 @@ import * as sinon from 'sinon'; import { Desk } from '../../model/desk'; +import { + InputFieldCreationRequest, + SelectFieldCreationRequest, + NumericFieldCreationRequest, +} from '../../model/input-field/input-field-creation-request'; import { Qminder } from '../../qminder'; import { LocationService } from './location.service'; @@ -225,6 +230,105 @@ 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(); + }); + }); + 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..6eb2e33f 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,14 @@ export const LocationService = { * Calls the following HTTP API: `PUT /locations//opening-hours/exceptions` */ setOpeningHoursExceptions, + + /** + * Create a new input field for a location. + * + * The input field `id` and all `SelectOption` `id` values must be client-generated UUID v4 strings. + * The `location.id` in the request body identifies which location the field belongs to. + * + * Calls the following HTTP API: `POST /input-fields` (with V2 header) + */ + 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..44e60a46 100644 --- a/packages/javascript-api/src/lib/services/location/location.ts +++ b/packages/javascript-api/src/lib/services/location/location.ts @@ -1,4 +1,5 @@ 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'; @@ -55,3 +56,24 @@ export async function setOpeningHoursExceptions( headers: V2_HEADERS, }); } + +/** + * Create a new input field for a location. + * + * The input field `id` and all `SelectOption` `id` values must be client-generated UUID v4 strings. + * The `location.id` in the request body identifies which location the field belongs to. + * + * Calls the following HTTP API: `POST /input-fields` (with V2 header) + * + * @param inputField the input field creation request + * @returns a promise that resolves when the input field has been created + */ +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/public-api/model.ts b/packages/javascript-api/src/public-api/model.ts index 95e1f81b..c4548120 100644 --- a/packages/javascript-api/src/public-api/model.ts +++ b/packages/javascript-api/src/public-api/model.ts @@ -34,3 +34,20 @@ 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, + InputFieldType, + InputFieldTranslation, + SelectOption, + SelectOptionTranslation, + NumericFieldConstraints, + TextFieldCreationRequest, + SelectFieldCreationRequest, + EmailFieldCreationRequest, + PhoneNumberFieldCreationRequest, + FirstNameFieldCreationRequest, + LastNameFieldCreationRequest, + UrlFieldCreationRequest, + DateFieldCreationRequest, + NumericFieldCreationRequest, +} from '../lib/model/input-field/input-field-creation-request.js'; From 46c07fb57f3fe10e04ecf51eb5487df6ad4d1750 Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Thu, 26 Mar 2026 14:21:22 +0200 Subject: [PATCH 2/7] style: format test file with prettier --- .../src/lib/services/location/location.service.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 b85f9e96..65d732dc 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 @@ -250,7 +250,11 @@ describe('Location service', function () { visibleForLines: [{ id: 1 }, { id: 2 }], showInRemoteSignIn: false, translations: [ - { languageCode: 'et', title: 'E-posti aadress', visitorFacingTitle: 'Sinu e-post' }, + { + languageCode: 'et', + title: 'E-posti aadress', + visitorFacingTitle: 'Sinu e-post', + }, ], }; From 7504454035d437ef54c632a0e54d5847825b1fbb Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Thu, 26 Mar 2026 14:24:24 +0200 Subject: [PATCH 3/7] refactor: split input field types into dedicated files Follow existing one-type-per-file pattern (ticket-extra-option.ts, opening-hours-time.ts, etc.) by splitting the monolithic input-field-creation-request.ts into 16 individual files. --- .../date-field-creation-request.ts | 9 ++ .../email-field-creation-request.ts | 6 + .../first-name-field-creation-request.ts | 5 + .../lib/model/input-field/input-field-base.ts | 14 +++ .../input-field-creation-request.ts | 114 ++---------------- .../input-field/input-field-translation.ts | 5 + .../lib/model/input-field/input-field-type.ts | 10 ++ .../last-name-field-creation-request.ts | 6 + .../input-field/numeric-field-constraints.ts | 5 + .../numeric-field-creation-request.ts | 11 ++ .../phone-number-field-creation-request.ts | 5 + .../select-field-creation-request.ts | 12 ++ .../input-field/select-option-translation.ts | 4 + .../lib/model/input-field/select-option.ts | 10 ++ .../text-field-creation-request.ts | 9 ++ .../input-field/url-field-creation-request.ts | 8 ++ .../location/location.service.spec.ts | 8 +- .../javascript-api/src/public-api/model.ts | 33 +++-- 18 files changed, 147 insertions(+), 127 deletions(-) create mode 100644 packages/javascript-api/src/lib/model/input-field/date-field-creation-request.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/email-field-creation-request.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/first-name-field-creation-request.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/input-field-base.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/input-field-translation.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/input-field-type.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/last-name-field-creation-request.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/numeric-field-constraints.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/numeric-field-creation-request.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/phone-number-field-creation-request.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/select-field-creation-request.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/select-option-translation.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/select-option.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/text-field-creation-request.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/url-field-creation-request.ts 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..1dbf3d2e --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/input-field-base.ts @@ -0,0 +1,14 @@ +import { UUID } from '../uuid.js'; + +export interface InputFieldBase { + /** Client-generated UUID for this input field. */ + id: UUID; + location: { id: number }; + isMandatoryBeforeAdded: boolean; + isMandatoryBeforeServed: boolean; + isMandatoryInRemoteSignIn: boolean; + isVisibleInWaitingDrawer: boolean; + isVisibleInServingDrawer: boolean; + visibleForLines: { id: number }[]; + 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 index a4279544..d0b9dfdd 100644 --- 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 @@ -1,108 +1,12 @@ -import { UUID } from '../uuid.js'; - -export type InputFieldType = - | 'TEXT' - | 'SELECT' - | 'EMAIL' - | 'PHONE_NUMBER' - | 'FIRST_NAME' - | 'LAST_NAME' - | 'URL' - | 'DATE' - | 'NUMERIC'; - -export interface InputFieldTranslation { - languageCode: string; - title?: string; - visitorFacingTitle?: string; -} - -export interface SelectOptionTranslation { - languageCode: string; - title?: string; -} - -export interface SelectOption { - /** Client-generated UUID for this option. */ - id: UUID; - title: string; - color?: string; - translations?: SelectOptionTranslation[]; -} - -export interface NumericFieldConstraints { - min?: number; - max?: number; - scale: number; -} - -interface InputFieldBase { - /** Client-generated UUID for this input field. */ - id: UUID; - location: { id: number }; - isMandatoryBeforeAdded: boolean; - isMandatoryBeforeServed: boolean; - isMandatoryInRemoteSignIn: boolean; - isVisibleInWaitingDrawer: boolean; - isVisibleInServingDrawer: boolean; - visibleForLines: { id: number }[]; - showInRemoteSignIn: boolean; -} - -export interface TextFieldCreationRequest extends InputFieldBase { - type: 'TEXT'; - title: string; - visitorFacingTitle?: string; - translations?: InputFieldTranslation[]; -} - -export interface SelectFieldCreationRequest extends InputFieldBase { - type: 'SELECT'; - title: string; - visitorFacingTitle?: string; - multiSelect: boolean; - options: SelectOption[]; - translations?: InputFieldTranslation[]; -} - -export interface EmailFieldCreationRequest extends InputFieldBase { - type: 'EMAIL'; - isRequiredInAppointments: boolean; -} - -export interface PhoneNumberFieldCreationRequest extends InputFieldBase { - type: 'PHONE_NUMBER'; -} - -export interface FirstNameFieldCreationRequest extends InputFieldBase { - type: 'FIRST_NAME'; -} - -export interface LastNameFieldCreationRequest extends InputFieldBase { - type: 'LAST_NAME'; - isRequiredInAppointments?: boolean; -} - -export interface UrlFieldCreationRequest extends InputFieldBase { - type: 'URL'; - title: string; - translations?: InputFieldTranslation[]; -} - -export interface DateFieldCreationRequest extends InputFieldBase { - type: 'DATE'; - title: string; - visitorFacingTitle?: string; - translations?: InputFieldTranslation[]; -} - -export interface NumericFieldCreationRequest extends InputFieldBase { - type: 'NUMERIC'; - title: string; - visitorFacingTitle?: string; - translations?: InputFieldTranslation[]; - constraints?: NumericFieldConstraints; -} +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. 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/input-field-type.ts b/packages/javascript-api/src/lib/model/input-field/input-field-type.ts new file mode 100644 index 00000000..bd065b64 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/input-field-type.ts @@ -0,0 +1,10 @@ +export type InputFieldType = + | 'TEXT' + | 'SELECT' + | 'EMAIL' + | 'PHONE_NUMBER' + | 'FIRST_NAME' + | 'LAST_NAME' + | 'URL' + | 'DATE' + | 'NUMERIC'; 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/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..9a6b22c1 --- /dev/null +++ b/packages/javascript-api/src/lib/model/input-field/select-option.ts @@ -0,0 +1,10 @@ +import { UUID } from '../uuid.js'; +import { SelectOptionTranslation } from './select-option-translation.js'; + +export interface SelectOption { + /** Client-generated UUID for this option. */ + 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 65d732dc..3c82fe7f 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,10 +1,8 @@ import * as sinon from 'sinon'; import { Desk } from '../../model/desk'; -import { - InputFieldCreationRequest, - SelectFieldCreationRequest, - NumericFieldCreationRequest, -} from '../../model/input-field/input-field-creation-request'; +import { InputFieldCreationRequest } from '../../model/input-field/input-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'; diff --git a/packages/javascript-api/src/public-api/model.ts b/packages/javascript-api/src/public-api/model.ts index c4548120..70a2a574 100644 --- a/packages/javascript-api/src/public-api/model.ts +++ b/packages/javascript-api/src/public-api/model.ts @@ -34,20 +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, - InputFieldType, - InputFieldTranslation, - SelectOption, - SelectOptionTranslation, - NumericFieldConstraints, - TextFieldCreationRequest, - SelectFieldCreationRequest, - EmailFieldCreationRequest, - PhoneNumberFieldCreationRequest, - FirstNameFieldCreationRequest, - LastNameFieldCreationRequest, - UrlFieldCreationRequest, - DateFieldCreationRequest, - NumericFieldCreationRequest, -} from '../lib/model/input-field/input-field-creation-request.js'; +export { InputFieldCreationRequest } from '../lib/model/input-field/input-field-creation-request.js'; +export { InputFieldBase } from '../lib/model/input-field/input-field-base.js'; +export { InputFieldType } from '../lib/model/input-field/input-field-type.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'; From 306509ed35f4d2918a7e712a810cb17df63ef846 Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Thu, 26 Mar 2026 14:26:04 +0200 Subject: [PATCH 4/7] refactor: extract LocationRef and LineRef types Replace inline { id: number } with named LocationRef and LineRef interfaces in InputFieldBase, matching the server's ref pattern. --- .../src/lib/model/input-field/input-field-base.ts | 6 ++++-- .../javascript-api/src/lib/model/input-field/line-ref.ts | 3 +++ .../src/lib/model/input-field/location-ref.ts | 3 +++ packages/javascript-api/src/public-api/model.ts | 2 ++ 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 packages/javascript-api/src/lib/model/input-field/line-ref.ts create mode 100644 packages/javascript-api/src/lib/model/input-field/location-ref.ts 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 index 1dbf3d2e..2024ed87 100644 --- 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 @@ -1,14 +1,16 @@ +import { LineRef } from './line-ref.js'; +import { LocationRef } from './location-ref.js'; import { UUID } from '../uuid.js'; export interface InputFieldBase { /** Client-generated UUID for this input field. */ id: UUID; - location: { id: number }; + location: LocationRef; isMandatoryBeforeAdded: boolean; isMandatoryBeforeServed: boolean; isMandatoryInRemoteSignIn: boolean; isVisibleInWaitingDrawer: boolean; isVisibleInServingDrawer: boolean; - visibleForLines: { id: number }[]; + visibleForLines: LineRef[]; showInRemoteSignIn: 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/public-api/model.ts b/packages/javascript-api/src/public-api/model.ts index 70a2a574..a9b7bfac 100644 --- a/packages/javascript-api/src/public-api/model.ts +++ b/packages/javascript-api/src/public-api/model.ts @@ -36,6 +36,8 @@ 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 { InputFieldBase } from '../lib/model/input-field/input-field-base.js'; +export { LocationRef } from '../lib/model/input-field/location-ref.js'; +export { LineRef } from '../lib/model/input-field/line-ref.js'; export { InputFieldType } from '../lib/model/input-field/input-field-type.js'; export { InputFieldTranslation } from '../lib/model/input-field/input-field-translation.js'; export { SelectOption } from '../lib/model/input-field/select-option.js'; From 8feee768002cbc59ce3820c805758467d3f4ac2f Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Thu, 26 Mar 2026 14:27:14 +0200 Subject: [PATCH 5/7] chore: remove unnecessary UUID comments --- .../src/lib/model/input-field/input-field-base.ts | 1 - .../javascript-api/src/lib/model/input-field/select-option.ts | 1 - .../src/lib/services/location/location.service.ts | 3 --- packages/javascript-api/src/lib/services/location/location.ts | 3 --- 4 files changed, 8 deletions(-) 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 index 2024ed87..4f6da999 100644 --- 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 @@ -3,7 +3,6 @@ import { LocationRef } from './location-ref.js'; import { UUID } from '../uuid.js'; export interface InputFieldBase { - /** Client-generated UUID for this input field. */ id: UUID; location: LocationRef; isMandatoryBeforeAdded: boolean; 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 index 9a6b22c1..36575350 100644 --- a/packages/javascript-api/src/lib/model/input-field/select-option.ts +++ b/packages/javascript-api/src/lib/model/input-field/select-option.ts @@ -2,7 +2,6 @@ import { UUID } from '../uuid.js'; import { SelectOptionTranslation } from './select-option-translation.js'; export interface SelectOption { - /** Client-generated UUID for this option. */ id: UUID; title: string; color?: string; 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 6eb2e33f..1cd7501c 100644 --- a/packages/javascript-api/src/lib/services/location/location.service.ts +++ b/packages/javascript-api/src/lib/services/location/location.service.ts @@ -99,9 +99,6 @@ export const LocationService = { /** * Create a new input field for a location. * - * The input field `id` and all `SelectOption` `id` values must be client-generated UUID v4 strings. - * The `location.id` in the request body identifies which location the field belongs to. - * * Calls the following HTTP API: `POST /input-fields` (with V2 header) */ createInputField, diff --git a/packages/javascript-api/src/lib/services/location/location.ts b/packages/javascript-api/src/lib/services/location/location.ts index 44e60a46..743fded3 100644 --- a/packages/javascript-api/src/lib/services/location/location.ts +++ b/packages/javascript-api/src/lib/services/location/location.ts @@ -60,9 +60,6 @@ export async function setOpeningHoursExceptions( /** * Create a new input field for a location. * - * The input field `id` and all `SelectOption` `id` values must be client-generated UUID v4 strings. - * The `location.id` in the request body identifies which location the field belongs to. - * * Calls the following HTTP API: `POST /input-fields` (with V2 header) * * @param inputField the input field creation request From b3e3565a53704e88134a87c2aed35fcdb08fe3fd Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Thu, 26 Mar 2026 15:06:04 +0200 Subject: [PATCH 6/7] refactor: extract shared V2_HEADERS constant and add FIRST_NAME test Move the duplicated V2 API version header into a shared v2-headers.ts module, reused by both location and ticket services. Remove unused InputFieldType. Add test for minimal FIRST_NAME field creation. --- .../lib/model/input-field/input-field-type.ts | 10 -------- .../location/location.service.spec.ts | 25 +++++++++++++++++++ .../src/lib/services/location/location.ts | 3 +-- .../src/lib/services/ticket/ticket.ts | 9 +++---- .../src/lib/services/v2-headers.ts | 1 + .../javascript-api/src/public-api/model.ts | 1 - 6 files changed, 30 insertions(+), 19 deletions(-) delete mode 100644 packages/javascript-api/src/lib/model/input-field/input-field-type.ts create mode 100644 packages/javascript-api/src/lib/services/v2-headers.ts diff --git a/packages/javascript-api/src/lib/model/input-field/input-field-type.ts b/packages/javascript-api/src/lib/model/input-field/input-field-type.ts deleted file mode 100644 index bd065b64..00000000 --- a/packages/javascript-api/src/lib/model/input-field/input-field-type.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type InputFieldType = - | 'TEXT' - | 'SELECT' - | 'EMAIL' - | 'PHONE_NUMBER' - | 'FIRST_NAME' - | 'LAST_NAME' - | 'URL' - | 'DATE' - | 'NUMERIC'; 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 3c82fe7f..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,6 +1,7 @@ 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'; @@ -329,6 +330,30 @@ describe('Location service', function () { }), ).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 () { diff --git a/packages/javascript-api/src/lib/services/location/location.ts b/packages/javascript-api/src/lib/services/location/location.ts index 743fded3..e37426a5 100644 --- a/packages/javascript-api/src/lib/services/location/location.ts +++ b/packages/javascript-api/src/lib/services/location/location.ts @@ -5,8 +5,7 @@ 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( 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 a9b7bfac..059fa90c 100644 --- a/packages/javascript-api/src/public-api/model.ts +++ b/packages/javascript-api/src/public-api/model.ts @@ -38,7 +38,6 @@ export { InputFieldCreationRequest } from '../lib/model/input-field/input-field- export { InputFieldBase } from '../lib/model/input-field/input-field-base.js'; export { LocationRef } from '../lib/model/input-field/location-ref.js'; export { LineRef } from '../lib/model/input-field/line-ref.js'; -export { InputFieldType } from '../lib/model/input-field/input-field-type.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'; From 753f9756cf5cce547d520b056f9e5cd1e0e2f1f3 Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Mon, 30 Mar 2026 12:18:50 +0300 Subject: [PATCH 7/7] fix: address PR review feedback for createInputField Remove duplicate JSDoc from implementation function, remove V2 header implementation detail from service docs, and remove internal InputFieldBase from public API exports. --- .../src/lib/services/location/location.service.ts | 2 +- .../javascript-api/src/lib/services/location/location.ts | 8 -------- packages/javascript-api/src/public-api/model.ts | 1 - 3 files changed, 1 insertion(+), 10 deletions(-) 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 1cd7501c..00394d7f 100644 --- a/packages/javascript-api/src/lib/services/location/location.service.ts +++ b/packages/javascript-api/src/lib/services/location/location.service.ts @@ -99,7 +99,7 @@ export const LocationService = { /** * Create a new input field for a location. * - * Calls the following HTTP API: `POST /input-fields` (with V2 header) + * 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 e37426a5..4730b6d8 100644 --- a/packages/javascript-api/src/lib/services/location/location.ts +++ b/packages/javascript-api/src/lib/services/location/location.ts @@ -56,14 +56,6 @@ export async function setOpeningHoursExceptions( }); } -/** - * Create a new input field for a location. - * - * Calls the following HTTP API: `POST /input-fields` (with V2 header) - * - * @param inputField the input field creation request - * @returns a promise that resolves when the input field has been created - */ export async function createInputField( inputField: InputFieldCreationRequest, ): Promise { diff --git a/packages/javascript-api/src/public-api/model.ts b/packages/javascript-api/src/public-api/model.ts index 059fa90c..dca6a840 100644 --- a/packages/javascript-api/src/public-api/model.ts +++ b/packages/javascript-api/src/public-api/model.ts @@ -35,7 +35,6 @@ 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 { InputFieldBase } from '../lib/model/input-field/input-field-base.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';