@tummycrypt/acuity-middleware 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/adapters/acuity-scraper.d.ts +8 -0
  2. package/dist/adapters/acuity-scraper.d.ts.map +1 -0
  3. package/dist/adapters/acuity-scraper.js +8 -0
  4. package/dist/adapters/acuity-scraper.js.map +1 -0
  5. package/dist/adapters/types.d.ts +8 -0
  6. package/dist/adapters/types.d.ts.map +1 -0
  7. package/dist/adapters/types.js +8 -0
  8. package/dist/adapters/types.js.map +1 -0
  9. package/dist/core/types.d.ts +10 -0
  10. package/dist/core/types.d.ts.map +1 -0
  11. package/dist/core/types.js +2 -0
  12. package/dist/core/types.js.map +1 -0
  13. package/dist/index.d.ts +17 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +18 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/middleware/acuity-wizard.d.ts +49 -0
  18. package/dist/middleware/acuity-wizard.d.ts.map +1 -0
  19. package/dist/middleware/acuity-wizard.js +265 -0
  20. package/dist/middleware/acuity-wizard.js.map +1 -0
  21. package/dist/middleware/browser-service.d.ts +53 -0
  22. package/dist/middleware/browser-service.d.ts.map +1 -0
  23. package/dist/middleware/browser-service.js +105 -0
  24. package/dist/middleware/browser-service.js.map +1 -0
  25. package/dist/middleware/errors.d.ts +58 -0
  26. package/dist/middleware/errors.d.ts.map +1 -0
  27. package/dist/middleware/errors.js +43 -0
  28. package/dist/middleware/errors.js.map +1 -0
  29. package/{src/middleware/index.ts → dist/middleware/index.d.ts} +5 -52
  30. package/dist/middleware/index.d.ts.map +1 -0
  31. package/dist/middleware/index.js +38 -0
  32. package/dist/middleware/index.js.map +1 -0
  33. package/dist/middleware/logger.d.ts +26 -0
  34. package/dist/middleware/logger.d.ts.map +1 -0
  35. package/dist/middleware/logger.js +65 -0
  36. package/dist/middleware/logger.js.map +1 -0
  37. package/dist/middleware/remote-adapter.d.ts +45 -0
  38. package/dist/middleware/remote-adapter.d.ts.map +1 -0
  39. package/dist/middleware/remote-adapter.js +178 -0
  40. package/dist/middleware/remote-adapter.js.map +1 -0
  41. package/dist/middleware/selector-health.d.ts +44 -0
  42. package/dist/middleware/selector-health.d.ts.map +1 -0
  43. package/dist/middleware/selector-health.js +144 -0
  44. package/dist/middleware/selector-health.js.map +1 -0
  45. package/dist/middleware/selectors.d.ts +108 -0
  46. package/dist/middleware/selectors.d.ts.map +1 -0
  47. package/dist/middleware/selectors.js +249 -0
  48. package/dist/middleware/selectors.js.map +1 -0
  49. package/dist/middleware/server.d.ts +34 -0
  50. package/dist/middleware/server.d.ts.map +1 -0
  51. package/dist/middleware/server.js +377 -0
  52. package/dist/middleware/server.js.map +1 -0
  53. package/dist/middleware/service-resolver.d.ts +46 -0
  54. package/dist/middleware/service-resolver.d.ts.map +1 -0
  55. package/dist/middleware/service-resolver.js +274 -0
  56. package/dist/middleware/service-resolver.js.map +1 -0
  57. package/dist/middleware/slot-parser.d.ts +29 -0
  58. package/dist/middleware/slot-parser.d.ts.map +1 -0
  59. package/dist/middleware/slot-parser.js +50 -0
  60. package/dist/middleware/slot-parser.js.map +1 -0
  61. package/dist/middleware/steps/__tests__/fixtures.d.ts +14 -0
  62. package/dist/middleware/steps/__tests__/fixtures.d.ts.map +1 -0
  63. package/dist/middleware/steps/__tests__/fixtures.js +204 -0
  64. package/dist/middleware/steps/__tests__/fixtures.js.map +1 -0
  65. package/dist/middleware/steps/bypass-payment.d.ts +54 -0
  66. package/dist/middleware/steps/bypass-payment.d.ts.map +1 -0
  67. package/dist/middleware/steps/bypass-payment.js +164 -0
  68. package/dist/middleware/steps/bypass-payment.js.map +1 -0
  69. package/dist/middleware/steps/extract-business.d.ts +93 -0
  70. package/dist/middleware/steps/extract-business.d.ts.map +1 -0
  71. package/dist/middleware/steps/extract-business.js +170 -0
  72. package/dist/middleware/steps/extract-business.js.map +1 -0
  73. package/dist/middleware/steps/extract.d.ts +41 -0
  74. package/dist/middleware/steps/extract.d.ts.map +1 -0
  75. package/dist/middleware/steps/extract.js +128 -0
  76. package/dist/middleware/steps/extract.js.map +1 -0
  77. package/dist/middleware/steps/fill-form.d.ts +45 -0
  78. package/dist/middleware/steps/fill-form.d.ts.map +1 -0
  79. package/dist/middleware/steps/fill-form.js +262 -0
  80. package/dist/middleware/steps/fill-form.js.map +1 -0
  81. package/dist/middleware/steps/index.d.ts +12 -0
  82. package/dist/middleware/steps/index.d.ts.map +1 -0
  83. package/dist/middleware/steps/index.js +12 -0
  84. package/dist/middleware/steps/index.js.map +1 -0
  85. package/dist/middleware/steps/navigate.d.ts +51 -0
  86. package/dist/middleware/steps/navigate.d.ts.map +1 -0
  87. package/dist/middleware/steps/navigate.js +391 -0
  88. package/dist/middleware/steps/navigate.js.map +1 -0
  89. package/dist/middleware/steps/read-availability.d.ts +37 -0
  90. package/dist/middleware/steps/read-availability.d.ts.map +1 -0
  91. package/dist/middleware/steps/read-availability.js +298 -0
  92. package/dist/middleware/steps/read-availability.js.map +1 -0
  93. package/dist/middleware/steps/read-slots.d.ts +33 -0
  94. package/dist/middleware/steps/read-slots.d.ts.map +1 -0
  95. package/dist/middleware/steps/read-slots.js +295 -0
  96. package/dist/middleware/steps/read-slots.js.map +1 -0
  97. package/dist/middleware/steps/read-via-url.d.ts +39 -0
  98. package/dist/middleware/steps/read-via-url.d.ts.map +1 -0
  99. package/dist/middleware/steps/read-via-url.js +141 -0
  100. package/dist/middleware/steps/read-via-url.js.map +1 -0
  101. package/dist/middleware/steps/submit.d.ts +22 -0
  102. package/dist/middleware/steps/submit.d.ts.map +1 -0
  103. package/dist/middleware/steps/submit.js +112 -0
  104. package/dist/middleware/steps/submit.js.map +1 -0
  105. package/dist/middleware/wizard-calendar.d.ts +37 -0
  106. package/dist/middleware/wizard-calendar.d.ts.map +1 -0
  107. package/dist/middleware/wizard-calendar.js +177 -0
  108. package/dist/middleware/wizard-calendar.js.map +1 -0
  109. package/dist/middleware/wizard-service.d.ts +30 -0
  110. package/dist/middleware/wizard-service.d.ts.map +1 -0
  111. package/dist/middleware/wizard-service.js +89 -0
  112. package/dist/middleware/wizard-service.js.map +1 -0
  113. package/dist/server.d.ts +6 -0
  114. package/dist/server.d.ts.map +1 -0
  115. package/{src/server.ts → dist/server.js} +1 -0
  116. package/dist/server.js.map +1 -0
  117. package/package.json +16 -4
  118. package/.github/workflows/build-paper.yml +0 -39
  119. package/.github/workflows/ci.yml +0 -37
  120. package/Dockerfile +0 -53
  121. package/docs/blog-post.mdx +0 -240
  122. package/docs/paper/IEEEtran.bst +0 -2409
  123. package/docs/paper/IEEEtran.cls +0 -6347
  124. package/docs/paper/acuity-middleware-paper.tex +0 -375
  125. package/docs/paper/balance.sty +0 -87
  126. package/docs/paper/references.bib +0 -231
  127. package/docs/paper.md +0 -400
  128. package/flake.nix +0 -32
  129. package/modal-app.py +0 -82
  130. package/src/adapters/acuity-scraper.ts +0 -543
  131. package/src/adapters/types.ts +0 -193
  132. package/src/core/types.ts +0 -325
  133. package/src/index.ts +0 -75
  134. package/src/middleware/acuity-wizard.ts +0 -456
  135. package/src/middleware/browser-service.ts +0 -183
  136. package/src/middleware/errors.ts +0 -70
  137. package/src/middleware/remote-adapter.ts +0 -246
  138. package/src/middleware/selectors.ts +0 -308
  139. package/src/middleware/server.ts +0 -372
  140. package/src/middleware/steps/bypass-payment.ts +0 -226
  141. package/src/middleware/steps/extract.ts +0 -174
  142. package/src/middleware/steps/fill-form.ts +0 -359
  143. package/src/middleware/steps/index.ts +0 -27
  144. package/src/middleware/steps/navigate.ts +0 -537
  145. package/src/middleware/steps/read-availability.ts +0 -399
  146. package/src/middleware/steps/read-slots.ts +0 -405
  147. package/src/middleware/steps/submit.ts +0 -168
  148. package/tsconfig.json +0 -25
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Wizard Step: Extract Confirmation Data
3
+ *
4
+ * Reads the confirmation page to extract booking details:
5
+ * appointment ID, confirmation code, service, date/time, provider.
6
+ * Maps the scraped data to the Booking type.
7
+ */
8
+ import { Effect } from 'effect';
9
+ import { BrowserService } from '../browser-service.js';
10
+ import { WizardStepError } from '../errors.js';
11
+ import { probe, Selectors } from '../selectors.js';
12
+ // =============================================================================
13
+ // IMPLEMENTATION
14
+ // =============================================================================
15
+ /**
16
+ * Extract booking confirmation data from the current page.
17
+ * Assumes we're already on the confirmation page.
18
+ */
19
+ export const extractConfirmation = () => Effect.gen(function* () {
20
+ const { acquirePage, screenshot, config } = yield* BrowserService;
21
+ const page = yield* acquirePage;
22
+ // Verify we're on the confirmation page (check selectors, URL, and body text)
23
+ const onConfirmation = yield* probe(page, 'confirmationPage');
24
+ const urlMatch = /\/(confirmation|confirmed|thank-you|complete)/i.test(page.url());
25
+ const bodyMatch = yield* Effect.tryPromise({
26
+ try: () => page.$eval('body', (el) => {
27
+ const text = el.textContent?.toLowerCase() ?? '';
28
+ return text.includes('booking confirmed') ||
29
+ text.includes('appointment confirmed') ||
30
+ text.includes('successfully booked') ||
31
+ text.includes('your appointment is scheduled');
32
+ }).catch(() => false),
33
+ catch: () => false,
34
+ }).pipe(Effect.orElseSucceed(() => false));
35
+ if (!onConfirmation && !urlMatch && !bodyMatch) {
36
+ return yield* Effect.fail(new WizardStepError({
37
+ step: 'extract',
38
+ message: 'Not on confirmation page - cannot extract booking data',
39
+ }));
40
+ }
41
+ // Extract each piece of data
42
+ const appointmentId = yield* extractText(page, Selectors.confirmationId);
43
+ const serviceName = yield* extractText(page, Selectors.confirmationService);
44
+ const datetime = yield* extractText(page, Selectors.confirmationDatetime);
45
+ // Get the full page text as fallback for parsing
46
+ const rawText = yield* Effect.tryPromise({
47
+ try: () => page.textContent('body').then((t) => t?.trim() ?? ''),
48
+ catch: () => '',
49
+ }).pipe(Effect.orElseSucceed(() => ''));
50
+ // Try to extract confirmation code from raw text if not found via selector
51
+ const confirmationCode = appointmentId ?? extractConfirmationFromText(rawText);
52
+ // Take success screenshot for audit trail
53
+ if (config.screenshotOnFailure) {
54
+ yield* screenshot('booking-confirmation').pipe(Effect.ignore);
55
+ }
56
+ return {
57
+ appointmentId,
58
+ confirmationCode,
59
+ serviceName,
60
+ datetime,
61
+ providerName: null, // Not extracted from confirmation page
62
+ rawText,
63
+ };
64
+ });
65
+ // =============================================================================
66
+ // MAPPING
67
+ // =============================================================================
68
+ /**
69
+ * Map extracted confirmation data + original request into a Booking object.
70
+ */
71
+ export const toBooking = (confirmation, request, paymentRef, paymentProcessor, service) => ({
72
+ id: confirmation.appointmentId ?? `wizard-${Date.now()}`,
73
+ serviceId: request.serviceId,
74
+ serviceName: confirmation.serviceName ?? service?.name ?? 'Unknown Service',
75
+ providerId: request.providerId,
76
+ providerName: confirmation.providerName ?? undefined,
77
+ datetime: request.datetime,
78
+ endTime: computeEndTime(request.datetime, service?.duration ?? 60),
79
+ duration: service?.duration ?? 60,
80
+ price: service?.price ?? 0,
81
+ currency: service?.currency ?? 'USD',
82
+ client: request.client,
83
+ status: 'confirmed',
84
+ confirmationCode: confirmation.confirmationCode ?? undefined,
85
+ paymentStatus: 'paid',
86
+ paymentRef: `[${paymentProcessor.toUpperCase()}] Transaction: ${paymentRef}`,
87
+ createdAt: new Date().toISOString(),
88
+ });
89
+ // =============================================================================
90
+ // HELPERS
91
+ // =============================================================================
92
+ const extractText = (page, candidates) => Effect.gen(function* () {
93
+ for (const selector of candidates) {
94
+ const text = yield* Effect.tryPromise({
95
+ try: () => page.$eval(selector, (el) => el.textContent?.trim() ?? null).catch(() => null),
96
+ catch: () => null,
97
+ }).pipe(Effect.orElseSucceed(() => null));
98
+ if (text)
99
+ return text;
100
+ }
101
+ return null;
102
+ });
103
+ const extractConfirmationFromText = (text) => {
104
+ // Look for patterns like "Confirmation #12345" or "Appointment ID: 12345"
105
+ const patterns = [
106
+ /confirmation\s*#?\s*(\w+)/i,
107
+ /appointment\s*id\s*:?\s*(\w+)/i,
108
+ /booking\s*#?\s*(\w+)/i,
109
+ /reference\s*:?\s*(\w+)/i,
110
+ ];
111
+ for (const pattern of patterns) {
112
+ const match = text.match(pattern);
113
+ if (match?.[1])
114
+ return match[1];
115
+ }
116
+ return null;
117
+ };
118
+ const computeEndTime = (datetime, durationMinutes) => {
119
+ try {
120
+ const start = new Date(datetime);
121
+ const end = new Date(start.getTime() + durationMinutes * 60 * 1000);
122
+ return end.toISOString();
123
+ }
124
+ catch {
125
+ return datetime;
126
+ }
127
+ };
128
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../../src/middleware/steps/extract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Wizard Step: Fill Client Form Fields
3
+ *
4
+ * Fills standard fields (name, email, phone), custom intake fields
5
+ * (radio buttons, "How did you hear" checkboxes, medication, terms),
6
+ * and advances past the client info step to the payment page.
7
+ *
8
+ * Acuity form requirements (verified 2026-02-26):
9
+ * - Standard: firstName, lastName, email, phone
10
+ * - 3 yes/no radio groups (aria-required, NO name/id attrs)
11
+ * - "How did you hear" multi-checkbox (at least 1 required)
12
+ * - Medication textarea (fields[field-16606770])
13
+ * - Terms checkbox (fields[field-13933959])
14
+ * - ALL must be filled before "Continue to Payment" advances
15
+ */
16
+ import { Effect } from 'effect';
17
+ import { BrowserService } from '../browser-service.js';
18
+ import { WizardStepError } from '../errors.js';
19
+ import type { ClientInfo } from '../../core/types.js';
20
+ export interface FillFormParams {
21
+ readonly client: ClientInfo;
22
+ readonly customFields?: Record<string, string>;
23
+ /** Answer for yes/no radio questions (default: "no") */
24
+ readonly intakeRadioAnswer?: 'yes' | 'no';
25
+ /** Which "How did you hear" checkbox to select (default: "Internet search") */
26
+ readonly howDidYouHear?: string;
27
+ /** Medication text (default: "None") */
28
+ readonly medication?: string;
29
+ }
30
+ export interface FillFormResult {
31
+ readonly fieldsCompleted: string[];
32
+ readonly customFieldsCompleted: string[];
33
+ readonly intakeFieldsCompleted: string[];
34
+ readonly advanced: boolean;
35
+ }
36
+ /**
37
+ * Fill the client information form and advance to the payment page.
38
+ */
39
+ export declare const fillFormFields: (params: FillFormParams) => Effect.Effect<{
40
+ fieldsCompleted: string[];
41
+ customFieldsCompleted: string[];
42
+ intakeFieldsCompleted: string[];
43
+ advanced: boolean;
44
+ }, import("../errors.js").BrowserError | WizardStepError, import("effect/Scope").Scope | BrowserService>;
45
+ //# sourceMappingURL=fill-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fill-form.d.ts","sourceRoot":"","sources":["../../../src/middleware/steps/fill-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAMtD,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,wDAAwD;IACxD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IAC1C,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,wCAAwC;IACxC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC3B;AAMD;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,cAAc;;;;;wGAsEnD,CAAC"}
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Wizard Step: Fill Client Form Fields
3
+ *
4
+ * Fills standard fields (name, email, phone), custom intake fields
5
+ * (radio buttons, "How did you hear" checkboxes, medication, terms),
6
+ * and advances past the client info step to the payment page.
7
+ *
8
+ * Acuity form requirements (verified 2026-02-26):
9
+ * - Standard: firstName, lastName, email, phone
10
+ * - 3 yes/no radio groups (aria-required, NO name/id attrs)
11
+ * - "How did you hear" multi-checkbox (at least 1 required)
12
+ * - Medication textarea (fields[field-16606770])
13
+ * - Terms checkbox (fields[field-13933959])
14
+ * - ALL must be filled before "Continue to Payment" advances
15
+ */
16
+ import { Effect } from 'effect';
17
+ import { BrowserService } from '../browser-service.js';
18
+ import { WizardStepError } from '../errors.js';
19
+ import { resolveSelector, Selectors } from '../selectors.js';
20
+ // =============================================================================
21
+ // IMPLEMENTATION
22
+ // =============================================================================
23
+ /**
24
+ * Fill the client information form and advance to the payment page.
25
+ */
26
+ export const fillFormFields = (params) => Effect.gen(function* () {
27
+ const { acquirePage } = yield* BrowserService;
28
+ const page = yield* acquirePage;
29
+ const fieldsCompleted = [];
30
+ const intakeFieldsCompleted = [];
31
+ // Fill or verify each standard field
32
+ yield* fillField(page, Selectors.firstNameInput, params.client.firstName, 'firstName');
33
+ fieldsCompleted.push('firstName');
34
+ yield* fillField(page, Selectors.lastNameInput, params.client.lastName, 'lastName');
35
+ fieldsCompleted.push('lastName');
36
+ yield* fillField(page, Selectors.emailInput, params.client.email, 'email');
37
+ fieldsCompleted.push('email');
38
+ if (params.client.phone) {
39
+ yield* fillField(page, Selectors.phoneInput, params.client.phone, 'phone');
40
+ fieldsCompleted.push('phone');
41
+ }
42
+ // Fill custom intake fields (by field ID)
43
+ const customFieldsCompleted = [];
44
+ if (params.customFields) {
45
+ for (const [fieldId, value] of Object.entries(params.customFields)) {
46
+ const filled = yield* fillCustomField(page, fieldId, value);
47
+ if (filled)
48
+ customFieldsCompleted.push(fieldId);
49
+ }
50
+ }
51
+ // Fill intake radio buttons (yes/no questions)
52
+ const radioAnswer = params.intakeRadioAnswer ?? 'no';
53
+ yield* fillIntakeRadios(page, radioAnswer);
54
+ intakeFieldsCompleted.push('radioButtons');
55
+ // Fill "How did you hear" checkbox
56
+ const hearOption = params.howDidYouHear ?? 'Internet search';
57
+ yield* fillHowDidYouHear(page, hearOption);
58
+ intakeFieldsCompleted.push('howDidYouHear');
59
+ // Fill medication textarea
60
+ const medication = params.medication ?? 'None';
61
+ yield* fillMedication(page, medication);
62
+ intakeFieldsCompleted.push('medication');
63
+ // Fill terms checkbox
64
+ yield* fillTermsCheckbox(page);
65
+ intakeFieldsCompleted.push('termsCheckbox');
66
+ // Click continue/next to advance past client form
67
+ const advanced = yield* advancePastForm(page);
68
+ return {
69
+ fieldsCompleted,
70
+ customFieldsCompleted,
71
+ intakeFieldsCompleted,
72
+ advanced,
73
+ };
74
+ }).pipe(Effect.catchTag('SelectorError', (e) => Effect.fail(new WizardStepError({
75
+ step: 'fill-form',
76
+ message: `Form field not found: ${e.message}`,
77
+ cause: e,
78
+ }))));
79
+ // =============================================================================
80
+ // HELPERS
81
+ // =============================================================================
82
+ /**
83
+ * Fill a form field. If the field already has the correct value
84
+ * (from URL pre-fill), skip it. Otherwise clear and fill.
85
+ */
86
+ const fillField = (page, candidates, value, fieldName) => Effect.gen(function* () {
87
+ const { element, selector } = yield* resolveSelector(page, candidates, 5000);
88
+ // Check current value
89
+ const currentValue = yield* Effect.tryPromise({
90
+ try: () => page.$eval(selector, (el) => el.value),
91
+ catch: () => '',
92
+ }).pipe(Effect.orElseSucceed(() => ''));
93
+ // Skip if already correct
94
+ if (currentValue.trim().toLowerCase() === value.trim().toLowerCase()) {
95
+ return;
96
+ }
97
+ // Clear and fill
98
+ yield* Effect.tryPromise({
99
+ try: async () => {
100
+ await element.click({ clickCount: 3 }); // Select all
101
+ await element.fill(value);
102
+ },
103
+ catch: (e) => new WizardStepError({
104
+ step: 'fill-form',
105
+ message: `Failed to fill ${fieldName}: ${e instanceof Error ? e.message : String(e)}`,
106
+ cause: e,
107
+ }),
108
+ });
109
+ });
110
+ /**
111
+ * Fill a custom Acuity intake field by field ID.
112
+ * Acuity custom fields use `field:XXXX` name pattern.
113
+ */
114
+ const fillCustomField = (page, fieldId, value) => Effect.gen(function* () {
115
+ const selectors = [
116
+ `[name="fields[field-${fieldId}]"]`,
117
+ `input[id*="${fieldId}"]`,
118
+ `textarea[name*="${fieldId}"]`,
119
+ `[data-field-id="${fieldId}"]`,
120
+ ];
121
+ const result = yield* resolveSelector(page, selectors, 2000).pipe(Effect.map((resolved) => resolved), Effect.orElseSucceed(() => null));
122
+ if (!result)
123
+ return false;
124
+ yield* Effect.tryPromise({
125
+ try: async () => {
126
+ const tagName = await result.element.evaluate((el) => el.tagName.toLowerCase());
127
+ if (tagName === 'select') {
128
+ await page.selectOption(result.selector, value);
129
+ }
130
+ else if (tagName === 'textarea') {
131
+ await result.element.fill(value);
132
+ }
133
+ else {
134
+ const inputType = await result.element.evaluate((el) => el.type);
135
+ if (inputType === 'checkbox') {
136
+ const checked = await result.element.isChecked();
137
+ if ((value === 'true') !== checked) {
138
+ await result.element.click();
139
+ }
140
+ }
141
+ else {
142
+ await result.element.fill(value);
143
+ }
144
+ }
145
+ },
146
+ catch: () => null,
147
+ }).pipe(Effect.orElseSucceed(() => null));
148
+ return true;
149
+ });
150
+ /**
151
+ * Fill intake radio buttons.
152
+ *
153
+ * Acuity's radio buttons have NO name or id attributes — they are purely
154
+ * React-controlled. The proven strategy is to click the <label> element
155
+ * wrapping each radio via Playwright's locator().nth() API, which dispatches
156
+ * OS-level mouse events that React's event delegation handles correctly.
157
+ */
158
+ const fillIntakeRadios = (page, answer) => Effect.tryPromise({
159
+ try: async () => {
160
+ const selectorKey = answer === 'no' ? Selectors.radioNoLabel : Selectors.radioYesLabel;
161
+ const labelLocator = page.locator(selectorKey[0]);
162
+ const count = await labelLocator.count();
163
+ for (let i = 0; i < count; i++) {
164
+ await labelLocator.nth(i).scrollIntoViewIfNeeded();
165
+ await labelLocator.nth(i).click({ timeout: 5000 });
166
+ await page.waitForTimeout(200);
167
+ }
168
+ },
169
+ catch: (e) => new WizardStepError({
170
+ step: 'fill-form',
171
+ message: `Failed to fill radio buttons: ${e instanceof Error ? e.message : String(e)}`,
172
+ cause: e,
173
+ }),
174
+ });
175
+ /**
176
+ * Select at least one "How did you hear" checkbox.
177
+ *
178
+ * These checkboxes have plain-text name attributes like "Internet search".
179
+ * Uses the same label-click locator strategy as radio buttons.
180
+ */
181
+ const fillHowDidYouHear = (page, option) => Effect.tryPromise({
182
+ try: async () => {
183
+ const labelLocator = page.locator(`label:has(input[type="checkbox"][name="${option}"])`);
184
+ const count = await labelLocator.count();
185
+ if (count > 0) {
186
+ await labelLocator.first().scrollIntoViewIfNeeded();
187
+ await labelLocator.first().click({ timeout: 3000 });
188
+ }
189
+ else {
190
+ // Fallback: click the first non-terms checkbox
191
+ const fallback = page.locator('input[type="checkbox"]:not([name*="field-13933959"])');
192
+ const fallbackCount = await fallback.count();
193
+ if (fallbackCount > 0) {
194
+ const parent = page.locator('label:has(input[type="checkbox"]:not([name*="field-13933959"]))');
195
+ await parent.first().scrollIntoViewIfNeeded();
196
+ await parent.first().click({ timeout: 3000 });
197
+ }
198
+ }
199
+ },
200
+ catch: (e) => new WizardStepError({
201
+ step: 'fill-form',
202
+ message: `Failed to fill "How did you hear": ${e instanceof Error ? e.message : String(e)}`,
203
+ cause: e,
204
+ }),
205
+ });
206
+ /**
207
+ * Fill the medication textarea.
208
+ */
209
+ const fillMedication = (page, text) => Effect.tryPromise({
210
+ try: async () => {
211
+ for (const selector of Selectors.medicationField) {
212
+ const el = await page.$(selector);
213
+ if (el) {
214
+ await el.fill(text);
215
+ return;
216
+ }
217
+ }
218
+ },
219
+ catch: () => undefined,
220
+ }).pipe(Effect.orElseSucceed(() => undefined));
221
+ /**
222
+ * Check the terms agreement checkbox via label click.
223
+ */
224
+ const fillTermsCheckbox = (page) => Effect.tryPromise({
225
+ try: async () => {
226
+ const isChecked = await page
227
+ .$eval(Selectors.termsCheckbox[0], (el) => el.checked)
228
+ .catch(() => false);
229
+ if (!isChecked) {
230
+ const label = page.locator(`label:has(${Selectors.termsCheckbox[0]})`);
231
+ await label.scrollIntoViewIfNeeded();
232
+ await label.click({ timeout: 3000 });
233
+ }
234
+ },
235
+ catch: () => undefined,
236
+ }).pipe(Effect.orElseSucceed(() => undefined));
237
+ /**
238
+ * Click "Continue to Payment" to advance past the client form.
239
+ *
240
+ * Verified 2026-02-26: "Continue to Payment" navigates to a SEPARATE
241
+ * payment page at URL .../datetime/<ISO>/payment.
242
+ */
243
+ const advancePastForm = (page) => Effect.gen(function* () {
244
+ const continueBtn = yield* resolveSelector(page, Selectors.continueToPayment, 5000).pipe(Effect.catchTag('SelectorError', () => Effect.fail(new WizardStepError({
245
+ step: 'fill-form',
246
+ message: '"Continue to Payment" button not found after filling form',
247
+ }))));
248
+ yield* Effect.tryPromise({
249
+ try: async () => {
250
+ await continueBtn.element.click();
251
+ // Wait for navigation to payment page (URL ends in /payment)
252
+ await page.waitForURL((url) => url.href.includes('/payment'), { timeout: 15000 });
253
+ },
254
+ catch: (e) => new WizardStepError({
255
+ step: 'fill-form',
256
+ message: `Failed to advance to payment page: ${e instanceof Error ? e.message : String(e)}`,
257
+ cause: e,
258
+ }),
259
+ });
260
+ return true;
261
+ });
262
+ //# sourceMappingURL=fill-form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fill-form.js","sourceRoot":"","sources":["../../../src/middleware/steps/fill-form.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Wizard Steps - Individual programs for each stage of the Acuity wizard.
3
+ */
4
+ export { navigateToBooking, type NavigateParams, type NavigateResult } from './navigate.js';
5
+ export { fillFormFields, type FillFormParams, type FillFormResult } from './fill-form.js';
6
+ export { bypassPayment, generateCouponCode, type BypassPaymentResult, } from './bypass-payment.js';
7
+ export { submitBooking, type SubmitResult } from './submit.js';
8
+ export { extractConfirmation, toBooking, type ConfirmationData, } from './extract.js';
9
+ export { readAvailableDates, type ReadAvailabilityParams, type AvailableDateResult, } from './read-availability.js';
10
+ export { readTimeSlots, type ReadSlotsParams, type SlotResult, } from './read-slots.js';
11
+ export { extractBusinessFromPage, extractBusinessFromHtml, extractBusinessServices, fetchBusinessData, businessToServices, type AcuityAppointmentType, type AcuityCalendar, type AcuityBusinessData, } from './extract-business.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/middleware/steps/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC1F,OAAO,EACN,aAAa,EACb,kBAAkB,EAClB,KAAK,mBAAmB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EACN,mBAAmB,EACnB,SAAS,EACT,KAAK,gBAAgB,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,kBAAkB,EAClB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACN,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,UAAU,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACN,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACvB,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Wizard Steps - Individual programs for each stage of the Acuity wizard.
3
+ */
4
+ export { navigateToBooking } from './navigate.js';
5
+ export { fillFormFields } from './fill-form.js';
6
+ export { bypassPayment, generateCouponCode, } from './bypass-payment.js';
7
+ export { submitBooking } from './submit.js';
8
+ export { extractConfirmation, toBooking, } from './extract.js';
9
+ export { readAvailableDates, } from './read-availability.js';
10
+ export { readTimeSlots, } from './read-slots.js';
11
+ export { extractBusinessFromPage, extractBusinessFromHtml, extractBusinessServices, fetchBusinessData, businessToServices, } from './extract-business.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/middleware/steps/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,iBAAiB,EAA4C,MAAM,eAAe,CAAC;AAC5F,OAAO,EAAE,cAAc,EAA4C,MAAM,gBAAgB,CAAC;AAC1F,OAAO,EACN,aAAa,EACb,kBAAkB,GAElB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAqB,MAAM,aAAa,CAAC;AAC/D,OAAO,EACN,mBAAmB,EACnB,SAAS,GAET,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,kBAAkB,GAGlB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACN,aAAa,GAGb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACN,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,GAIlB,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Wizard Step: Navigate Through Acuity Booking Wizard
3
+ *
4
+ * Acuity's React SPA (2026) does NOT support deep-linking via query params.
5
+ * Instead, we click through the 5-step wizard:
6
+ * 1. Service page (massageithaca.as.me) → find service → click "Book"
7
+ * 2. Calendar page → navigate to target month → click target day
8
+ * 3. Time slots → click matching slot → "Select and continue"
9
+ * 4. Land on client form (fields empty — filling is a separate step)
10
+ *
11
+ * URL progression:
12
+ * /schedule/<hash>
13
+ * /schedule/<hash>/appointment/<aptId>/calendar/<calId>
14
+ * /schedule/<hash>/appointment/<aptId>/calendar/<calId>/datetime/<ISO>
15
+ */
16
+ import { Effect } from 'effect';
17
+ import { BrowserService } from '../browser-service.js';
18
+ import { WizardStepError } from '../errors.js';
19
+ import type { ClientInfo } from '../../core/types.js';
20
+ export interface NavigateParams {
21
+ /** Appointment type name (matched against .appointment-type-name text) */
22
+ readonly serviceName: string;
23
+ /** Target datetime in ISO 8601 (e.g. "2026-03-15T10:00:00-05:00") */
24
+ readonly datetime: string;
25
+ /** Client info (not used for navigation — kept for API compat) */
26
+ readonly client: ClientInfo;
27
+ /** Appointment type ID — if known, verified against URL after "Book" click */
28
+ readonly appointmentTypeId?: string;
29
+ }
30
+ export interface NavigateResult {
31
+ readonly url: string;
32
+ readonly landingStep: 'client-form' | 'service-selection' | 'calendar' | 'time-slots' | 'unknown';
33
+ readonly appointmentTypeId: string | null;
34
+ readonly calendarId: string | null;
35
+ readonly selectedDate: string;
36
+ readonly selectedTime: string;
37
+ }
38
+ /**
39
+ * Navigate through the Acuity wizard to reach the client form.
40
+ *
41
+ * Flow: Service page → Book → Calendar → Time slot → Select and continue
42
+ */
43
+ export declare const navigateToBooking: (params: NavigateParams) => Effect.Effect<{
44
+ url: string;
45
+ landingStep: "calendar" | "client-form" | "service-selection" | "time-slots" | "unknown";
46
+ appointmentTypeId: string | null;
47
+ calendarId: string | null;
48
+ selectedDate: string;
49
+ selectedTime: string;
50
+ }, import("../errors.js").BrowserError | WizardStepError, import("effect/Scope").Scope | BrowserService>;
51
+ //# sourceMappingURL=navigate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigate.d.ts","sourceRoot":"","sources":["../../../src/middleware/steps/navigate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAMtD,MAAM,WAAW,cAAc;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,aAAa,GAAG,mBAAmB,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,CAAC;IAClG,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B;AAMD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,cAAc;;;;;;;wGA8CrD,CAAC"}