@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,391 @@
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 { resolveSelector, probe, Selectors } from '../selectors.js';
20
+ // =============================================================================
21
+ // IMPLEMENTATION
22
+ // =============================================================================
23
+ /**
24
+ * Navigate through the Acuity wizard to reach the client form.
25
+ *
26
+ * Flow: Service page → Book → Calendar → Time slot → Select and continue
27
+ */
28
+ export const navigateToBooking = (params) => Effect.gen(function* () {
29
+ const { acquirePage, config } = yield* BrowserService;
30
+ const page = yield* acquirePage;
31
+ // Step 1: Load service selection page
32
+ yield* Effect.tryPromise({
33
+ try: () => page.goto(config.baseUrl, { waitUntil: 'networkidle', timeout: config.timeout }),
34
+ catch: (e) => new WizardStepError({
35
+ step: 'navigate',
36
+ message: `Failed to load service page: ${e instanceof Error ? e.message : String(e)}`,
37
+ cause: e,
38
+ }),
39
+ });
40
+ // Step 2: Find and click target service's "Book" button
41
+ const { appointmentTypeId, calendarId } = yield* selectService(page, params.serviceName, params.appointmentTypeId);
42
+ // Step 3: Navigate calendar to target date and click
43
+ const targetDate = parseDate(params.datetime);
44
+ yield* navigateCalendar(page, targetDate);
45
+ yield* selectDay(page, targetDate);
46
+ // Step 4: Select matching time slot
47
+ const targetTime = parseTime(params.datetime);
48
+ yield* selectTimeSlot(page, targetTime);
49
+ // Step 5: Click "Select and continue" → land on client form
50
+ yield* clickSelectAndContinue(page);
51
+ // Verify we landed on the client form
52
+ const landingStep = yield* detectLandingStep(page);
53
+ return {
54
+ url: page.url(),
55
+ landingStep,
56
+ appointmentTypeId,
57
+ calendarId,
58
+ selectedDate: targetDate.toISOString().split('T')[0],
59
+ selectedTime: targetTime,
60
+ };
61
+ });
62
+ // =============================================================================
63
+ // STEP 2: SERVICE SELECTION
64
+ // =============================================================================
65
+ /**
66
+ * Find the service by name and click its "Book" button.
67
+ * After clicking, waits for URL to include /appointment/<id>/calendar/<id>.
68
+ */
69
+ const selectService = (page, serviceName, expectedAppointmentTypeId) => Effect.gen(function* () {
70
+ // Wait for service list to load
71
+ yield* resolveSelector(page, Selectors.serviceList, 10000).pipe(Effect.catchTag('SelectorError', () => Effect.fail(new WizardStepError({
72
+ step: 'navigate',
73
+ message: 'Service list did not load within timeout',
74
+ }))));
75
+ // Find the service item matching our target name
76
+ const serviceItem = yield* Effect.tryPromise({
77
+ try: async () => {
78
+ const items = await page.$$(Selectors.serviceList[0]);
79
+ for (const item of items) {
80
+ const nameEl = await item.$(Selectors.serviceName[0]);
81
+ const name = await nameEl?.textContent();
82
+ if (name && name.trim().toLowerCase().includes(serviceName.toLowerCase())) {
83
+ return item;
84
+ }
85
+ }
86
+ return null;
87
+ },
88
+ catch: (e) => new WizardStepError({
89
+ step: 'navigate',
90
+ message: `Error searching services: ${e instanceof Error ? e.message : String(e)}`,
91
+ cause: e,
92
+ }),
93
+ });
94
+ if (!serviceItem) {
95
+ return yield* Effect.fail(new WizardStepError({
96
+ step: 'navigate',
97
+ message: `Service "${serviceName}" not found on the page`,
98
+ }));
99
+ }
100
+ // Click the "Book" button within this service item
101
+ const bookBtn = yield* Effect.tryPromise({
102
+ try: () => serviceItem.$(Selectors.serviceBookButton[0]),
103
+ catch: (e) => new WizardStepError({
104
+ step: 'navigate',
105
+ message: `Error finding Book button: ${e instanceof Error ? e.message : String(e)}`,
106
+ cause: e,
107
+ }),
108
+ });
109
+ if (!bookBtn) {
110
+ return yield* Effect.fail(new WizardStepError({
111
+ step: 'navigate',
112
+ message: `"Book" button not found for service "${serviceName}"`,
113
+ }));
114
+ }
115
+ yield* Effect.tryPromise({
116
+ try: async () => {
117
+ await bookBtn.click();
118
+ await page.waitForURL(/\/appointment\/\d+\/calendar\/\d+/, { timeout: 10000 });
119
+ },
120
+ catch: (e) => new WizardStepError({
121
+ step: 'navigate',
122
+ message: `Failed to navigate after clicking Book: ${e instanceof Error ? e.message : String(e)}`,
123
+ cause: e,
124
+ }),
125
+ });
126
+ // Extract IDs from URL: /appointment/<aptId>/calendar/<calId>
127
+ const url = page.url();
128
+ const appointmentMatch = url.match(/\/appointment\/(\d+)/);
129
+ const calendarMatch = url.match(/\/calendar\/(\d+)/);
130
+ const appointmentTypeId = appointmentMatch?.[1] ?? null;
131
+ const calendarId = calendarMatch?.[1] ?? null;
132
+ // Verify appointment type ID if caller provided an expected value
133
+ if (expectedAppointmentTypeId && appointmentTypeId !== expectedAppointmentTypeId) {
134
+ return yield* Effect.fail(new WizardStepError({
135
+ step: 'navigate',
136
+ message: `Expected appointment type ${expectedAppointmentTypeId} but got ${appointmentTypeId}`,
137
+ }));
138
+ }
139
+ return { appointmentTypeId, calendarId };
140
+ });
141
+ // =============================================================================
142
+ // STEP 3: CALENDAR NAVIGATION
143
+ // =============================================================================
144
+ /**
145
+ * Navigate the react-calendar to the target month using prev/next buttons.
146
+ * Stops after 12 iterations to prevent infinite loops.
147
+ */
148
+ const navigateCalendar = (page, targetDate) => Effect.gen(function* () {
149
+ // Wait for calendar to load
150
+ yield* resolveSelector(page, Selectors.calendar, 10000).pipe(Effect.catchTag('SelectorError', () => Effect.fail(new WizardStepError({
151
+ step: 'navigate',
152
+ message: 'Calendar did not load within timeout',
153
+ }))));
154
+ // Wait for the month label to render (may lag behind calendar container)
155
+ yield* Effect.tryPromise({
156
+ try: () => page.waitForSelector(Selectors.calendarMonth[0], { timeout: 5000 }),
157
+ catch: () => null,
158
+ }).pipe(Effect.orElseSucceed(() => null));
159
+ const targetMonth = targetDate.getMonth();
160
+ const targetYear = targetDate.getFullYear();
161
+ for (let i = 0; i < 12; i++) {
162
+ // Retry month detection up to 3 times with brief waits
163
+ let current = null;
164
+ for (let retry = 0; retry < 3; retry++) {
165
+ current = yield* getCurrentCalendarMonth(page);
166
+ if (current)
167
+ break;
168
+ yield* Effect.tryPromise({
169
+ try: () => page.waitForTimeout(500),
170
+ catch: () => null,
171
+ }).pipe(Effect.orElseSucceed(() => null));
172
+ }
173
+ if (!current) {
174
+ return yield* Effect.fail(new WizardStepError({
175
+ step: 'navigate',
176
+ message: 'Could not determine current calendar month after retries',
177
+ }));
178
+ }
179
+ if (current.month === targetMonth && current.year === targetYear) {
180
+ return; // Already on the correct month
181
+ }
182
+ // Determine navigation direction
183
+ const currentFirst = new Date(current.year, current.month, 1);
184
+ const targetFirst = new Date(targetYear, targetMonth, 1);
185
+ if (targetFirst > currentFirst) {
186
+ yield* clickCalendarNav(page, 'next');
187
+ }
188
+ else {
189
+ yield* clickCalendarNav(page, 'prev');
190
+ }
191
+ // Wait for calendar to re-render
192
+ yield* Effect.tryPromise({
193
+ try: () => page.waitForTimeout(500),
194
+ catch: () => new WizardStepError({ step: 'navigate', message: 'Calendar nav wait interrupted' }),
195
+ });
196
+ }
197
+ });
198
+ const MONTH_NAMES = [
199
+ 'january', 'february', 'march', 'april', 'may', 'june',
200
+ 'july', 'august', 'september', 'october', 'november', 'december',
201
+ ];
202
+ const getCurrentCalendarMonth = (page) => Effect.tryPromise({
203
+ try: async () => {
204
+ for (const selector of Selectors.calendarMonth) {
205
+ const text = await page
206
+ .$eval(selector, (el) => el.textContent?.trim() ?? null)
207
+ .catch(() => null);
208
+ if (text) {
209
+ // react-calendar label may contain nested spans — extract visible text
210
+ // Patterns: "March 2026", "March\n2026", "March 2026 "
211
+ const match = text.match(/([A-Za-z]+)\s+(\d{4})/);
212
+ if (match) {
213
+ const monthIndex = MONTH_NAMES.indexOf(match[1].toLowerCase());
214
+ if (monthIndex >= 0) {
215
+ return { month: monthIndex, year: parseInt(match[2], 10) };
216
+ }
217
+ }
218
+ }
219
+ }
220
+ return null;
221
+ },
222
+ catch: () => null,
223
+ }).pipe(Effect.orElseSucceed(() => null));
224
+ const clickCalendarNav = (page, direction) => Effect.gen(function* () {
225
+ const selectors = direction === 'prev' ? Selectors.calendarPrev : Selectors.calendarNext;
226
+ const btn = yield* resolveSelector(page, selectors, 3000).pipe(Effect.catchTag('SelectorError', () => Effect.fail(new WizardStepError({
227
+ step: 'navigate',
228
+ message: `Calendar ${direction} button not found`,
229
+ }))));
230
+ yield* Effect.tryPromise({
231
+ try: () => btn.element.click(),
232
+ catch: (e) => new WizardStepError({
233
+ step: 'navigate',
234
+ message: `Failed to click calendar ${direction}: ${e instanceof Error ? e.message : String(e)}`,
235
+ cause: e,
236
+ }),
237
+ });
238
+ });
239
+ // =============================================================================
240
+ // STEP 3B: DAY SELECTION
241
+ // =============================================================================
242
+ /**
243
+ * Click the calendar tile matching our target day number.
244
+ * Skips disabled tiles and neighboring-month tiles.
245
+ */
246
+ const selectDay = (page, targetDate) => Effect.gen(function* () {
247
+ const dayOfMonth = targetDate.getDate();
248
+ const clicked = yield* Effect.tryPromise({
249
+ try: async () => {
250
+ const tiles = await page.$$(Selectors.calendarDay[0]);
251
+ for (const tile of tiles) {
252
+ const isDisabled = await tile.evaluate((el) => el.disabled);
253
+ if (isDisabled)
254
+ continue;
255
+ // Skip neighboring-month tiles (e.g. Feb 28 showing in March view)
256
+ const classes = await tile.getAttribute('class') ?? '';
257
+ if (classes.includes('neighboringMonth'))
258
+ continue;
259
+ const text = await tile.textContent();
260
+ const dayNum = parseInt(text?.trim() ?? '', 10);
261
+ if (dayNum === dayOfMonth) {
262
+ await tile.click();
263
+ return true;
264
+ }
265
+ }
266
+ return false;
267
+ },
268
+ catch: (e) => new WizardStepError({
269
+ step: 'navigate',
270
+ message: `Error selecting day ${dayOfMonth}: ${e instanceof Error ? e.message : String(e)}`,
271
+ cause: e,
272
+ }),
273
+ });
274
+ if (!clicked) {
275
+ return yield* Effect.fail(new WizardStepError({
276
+ step: 'navigate',
277
+ message: `Day ${dayOfMonth} not available on calendar`,
278
+ }));
279
+ }
280
+ // Wait for time slots to appear
281
+ yield* resolveSelector(page, Selectors.timeSlotContainer, 10000).pipe(Effect.catchTag('SelectorError', () => Effect.fail(new WizardStepError({
282
+ step: 'navigate',
283
+ message: 'Time slots did not appear after selecting day',
284
+ }))));
285
+ });
286
+ // =============================================================================
287
+ // STEP 4: TIME SLOT SELECTION
288
+ // =============================================================================
289
+ /**
290
+ * Click the time slot matching our target time.
291
+ * Slot text contains time + availability info: "10:00 AM1 spot left"
292
+ */
293
+ const selectTimeSlot = (page, targetTime) => Effect.gen(function* () {
294
+ const clicked = yield* Effect.tryPromise({
295
+ try: async () => {
296
+ const slots = await page.$$(Selectors.timeSlot[0]);
297
+ for (const slot of slots) {
298
+ const text = await slot.textContent();
299
+ if (text && text.includes(targetTime)) {
300
+ await slot.click();
301
+ return true;
302
+ }
303
+ }
304
+ return false;
305
+ },
306
+ catch: (e) => new WizardStepError({
307
+ step: 'navigate',
308
+ message: `Error selecting time slot: ${e instanceof Error ? e.message : String(e)}`,
309
+ cause: e,
310
+ }),
311
+ });
312
+ if (!clicked) {
313
+ return yield* Effect.fail(new WizardStepError({
314
+ step: 'navigate',
315
+ message: `Time slot "${targetTime}" not available`,
316
+ }));
317
+ }
318
+ // Wait for the selection menu to appear
319
+ yield* Effect.tryPromise({
320
+ try: () => page.waitForTimeout(1000),
321
+ catch: () => new WizardStepError({ step: 'navigate', message: 'Timeout after time slot click' }),
322
+ });
323
+ });
324
+ // =============================================================================
325
+ // STEP 5: "SELECT AND CONTINUE"
326
+ // =============================================================================
327
+ /**
328
+ * Click the "Select and continue" menu item.
329
+ * This is an <li role="menuitem">, NOT a button.
330
+ * After clicking, waits for URL to include /datetime/.
331
+ */
332
+ const clickSelectAndContinue = (page) => Effect.gen(function* () {
333
+ const menuItem = yield* resolveSelector(page, Selectors.selectAndContinue, 5000).pipe(Effect.catchTag('SelectorError', () => Effect.fail(new WizardStepError({
334
+ step: 'navigate',
335
+ message: '"Select and continue" option not found after selecting time slot',
336
+ }))));
337
+ yield* Effect.tryPromise({
338
+ try: async () => {
339
+ await menuItem.element.click();
340
+ // Wait for navigation to client form page
341
+ await page.waitForURL(/\/datetime\//, { timeout: 10000 });
342
+ },
343
+ catch: (e) => new WizardStepError({
344
+ step: 'navigate',
345
+ message: `Failed to advance to client form: ${e instanceof Error ? e.message : String(e)}`,
346
+ cause: e,
347
+ }),
348
+ });
349
+ });
350
+ // =============================================================================
351
+ // HELPERS
352
+ // =============================================================================
353
+ const detectLandingStep = (page) => Effect.gen(function* () {
354
+ const hasClientForm = yield* probe(page, 'firstNameInput');
355
+ if (hasClientForm)
356
+ return 'client-form';
357
+ const hasTimeSlots = yield* probe(page, 'timeSlot');
358
+ if (hasTimeSlots)
359
+ return 'time-slots';
360
+ const hasCalendar = yield* probe(page, 'calendarDay');
361
+ if (hasCalendar)
362
+ return 'calendar';
363
+ const hasServiceList = yield* probe(page, 'serviceList');
364
+ if (hasServiceList)
365
+ return 'service-selection';
366
+ return 'unknown';
367
+ });
368
+ /**
369
+ * Parse a Date from ISO 8601 datetime string.
370
+ */
371
+ const parseDate = (datetime) => {
372
+ const d = new Date(datetime);
373
+ if (isNaN(d.getTime())) {
374
+ throw new Error(`Invalid datetime: ${datetime}`);
375
+ }
376
+ return d;
377
+ };
378
+ /**
379
+ * Extract formatted time from ISO 8601 for matching against slot text.
380
+ * Returns "10:00 AM" format to match Acuity's "10:00 AM1 spot left" text.
381
+ */
382
+ const parseTime = (datetime) => {
383
+ const d = new Date(datetime);
384
+ const hours = d.getHours();
385
+ const minutes = d.getMinutes();
386
+ const ampm = hours >= 12 ? 'PM' : 'AM';
387
+ const h = hours % 12 || 12;
388
+ const m = minutes.toString().padStart(2, '0');
389
+ return `${h}:${m} ${ampm}`;
390
+ };
391
+ //# sourceMappingURL=navigate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigate.js","sourceRoot":"","sources":["../../../src/middleware/steps/navigate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Wizard Step: Read Available Dates from Acuity Calendar
3
+ *
4
+ * Navigates to the service calendar via click-through (not query params)
5
+ * and reads which calendar tiles are enabled (not disabled).
6
+ *
7
+ * Returns available dates for the currently visible month.
8
+ * Callers should advance months if needed.
9
+ */
10
+ import { Effect } from 'effect';
11
+ import { BrowserService } from '../browser-service.js';
12
+ import { WizardStepError } from '../errors.js';
13
+ export interface ReadAvailabilityParams {
14
+ /** Service name to match against the service list */
15
+ readonly serviceName: string;
16
+ /** Appointment type ID (used to verify correct service selected) */
17
+ readonly appointmentTypeId?: string;
18
+ /** Target month (YYYY-MM) — navigates calendar if provided */
19
+ readonly targetMonth?: string;
20
+ /** How many months ahead to scan (default 2) */
21
+ readonly monthsToScan?: number;
22
+ }
23
+ export interface AvailableDateResult {
24
+ readonly date: string;
25
+ readonly slots: number;
26
+ }
27
+ /**
28
+ * Read available dates by navigating through the Acuity wizard to the calendar.
29
+ *
30
+ * Flow:
31
+ * 1. Load service page → find service → click "Book"
32
+ * 2. Land on calendar page
33
+ * 3. Read enabled (non-disabled) tiles for current month
34
+ * 4. Optionally advance to next months and read more
35
+ */
36
+ export declare const readAvailableDates: (params: ReadAvailabilityParams) => Effect.Effect<AvailableDateResult[], import("../errors.js").BrowserError | WizardStepError, import("effect/Scope").Scope | BrowserService>;
37
+ //# sourceMappingURL=read-availability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-availability.d.ts","sourceRoot":"","sources":["../../../src/middleware/steps/read-availability.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAO/C,MAAM,WAAW,sBAAsB;IACtC,qDAAqD;IACrD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gDAAgD;IAChD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAMD;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,sBAAsB,+IAwC9D,CAAC"}