@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.
- package/dist/adapters/acuity-scraper.d.ts +8 -0
- package/dist/adapters/acuity-scraper.d.ts.map +1 -0
- package/dist/adapters/acuity-scraper.js +8 -0
- package/dist/adapters/acuity-scraper.js.map +1 -0
- package/dist/adapters/types.d.ts +8 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +8 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/acuity-wizard.d.ts +49 -0
- package/dist/middleware/acuity-wizard.d.ts.map +1 -0
- package/dist/middleware/acuity-wizard.js +265 -0
- package/dist/middleware/acuity-wizard.js.map +1 -0
- package/dist/middleware/browser-service.d.ts +53 -0
- package/dist/middleware/browser-service.d.ts.map +1 -0
- package/dist/middleware/browser-service.js +105 -0
- package/dist/middleware/browser-service.js.map +1 -0
- package/dist/middleware/errors.d.ts +58 -0
- package/dist/middleware/errors.d.ts.map +1 -0
- package/dist/middleware/errors.js +43 -0
- package/dist/middleware/errors.js.map +1 -0
- package/{src/middleware/index.ts → dist/middleware/index.d.ts} +5 -52
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +38 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/logger.d.ts +26 -0
- package/dist/middleware/logger.d.ts.map +1 -0
- package/dist/middleware/logger.js +65 -0
- package/dist/middleware/logger.js.map +1 -0
- package/dist/middleware/remote-adapter.d.ts +45 -0
- package/dist/middleware/remote-adapter.d.ts.map +1 -0
- package/dist/middleware/remote-adapter.js +178 -0
- package/dist/middleware/remote-adapter.js.map +1 -0
- package/dist/middleware/selector-health.d.ts +44 -0
- package/dist/middleware/selector-health.d.ts.map +1 -0
- package/dist/middleware/selector-health.js +144 -0
- package/dist/middleware/selector-health.js.map +1 -0
- package/dist/middleware/selectors.d.ts +108 -0
- package/dist/middleware/selectors.d.ts.map +1 -0
- package/dist/middleware/selectors.js +249 -0
- package/dist/middleware/selectors.js.map +1 -0
- package/dist/middleware/server.d.ts +34 -0
- package/dist/middleware/server.d.ts.map +1 -0
- package/dist/middleware/server.js +377 -0
- package/dist/middleware/server.js.map +1 -0
- package/dist/middleware/service-resolver.d.ts +46 -0
- package/dist/middleware/service-resolver.d.ts.map +1 -0
- package/dist/middleware/service-resolver.js +274 -0
- package/dist/middleware/service-resolver.js.map +1 -0
- package/dist/middleware/slot-parser.d.ts +29 -0
- package/dist/middleware/slot-parser.d.ts.map +1 -0
- package/dist/middleware/slot-parser.js +50 -0
- package/dist/middleware/slot-parser.js.map +1 -0
- package/dist/middleware/steps/__tests__/fixtures.d.ts +14 -0
- package/dist/middleware/steps/__tests__/fixtures.d.ts.map +1 -0
- package/dist/middleware/steps/__tests__/fixtures.js +204 -0
- package/dist/middleware/steps/__tests__/fixtures.js.map +1 -0
- package/dist/middleware/steps/bypass-payment.d.ts +54 -0
- package/dist/middleware/steps/bypass-payment.d.ts.map +1 -0
- package/dist/middleware/steps/bypass-payment.js +164 -0
- package/dist/middleware/steps/bypass-payment.js.map +1 -0
- package/dist/middleware/steps/extract-business.d.ts +93 -0
- package/dist/middleware/steps/extract-business.d.ts.map +1 -0
- package/dist/middleware/steps/extract-business.js +170 -0
- package/dist/middleware/steps/extract-business.js.map +1 -0
- package/dist/middleware/steps/extract.d.ts +41 -0
- package/dist/middleware/steps/extract.d.ts.map +1 -0
- package/dist/middleware/steps/extract.js +128 -0
- package/dist/middleware/steps/extract.js.map +1 -0
- package/dist/middleware/steps/fill-form.d.ts +45 -0
- package/dist/middleware/steps/fill-form.d.ts.map +1 -0
- package/dist/middleware/steps/fill-form.js +262 -0
- package/dist/middleware/steps/fill-form.js.map +1 -0
- package/dist/middleware/steps/index.d.ts +12 -0
- package/dist/middleware/steps/index.d.ts.map +1 -0
- package/dist/middleware/steps/index.js +12 -0
- package/dist/middleware/steps/index.js.map +1 -0
- package/dist/middleware/steps/navigate.d.ts +51 -0
- package/dist/middleware/steps/navigate.d.ts.map +1 -0
- package/dist/middleware/steps/navigate.js +391 -0
- package/dist/middleware/steps/navigate.js.map +1 -0
- package/dist/middleware/steps/read-availability.d.ts +37 -0
- package/dist/middleware/steps/read-availability.d.ts.map +1 -0
- package/dist/middleware/steps/read-availability.js +298 -0
- package/dist/middleware/steps/read-availability.js.map +1 -0
- package/dist/middleware/steps/read-slots.d.ts +33 -0
- package/dist/middleware/steps/read-slots.d.ts.map +1 -0
- package/dist/middleware/steps/read-slots.js +295 -0
- package/dist/middleware/steps/read-slots.js.map +1 -0
- package/dist/middleware/steps/read-via-url.d.ts +39 -0
- package/dist/middleware/steps/read-via-url.d.ts.map +1 -0
- package/dist/middleware/steps/read-via-url.js +141 -0
- package/dist/middleware/steps/read-via-url.js.map +1 -0
- package/dist/middleware/steps/submit.d.ts +22 -0
- package/dist/middleware/steps/submit.d.ts.map +1 -0
- package/dist/middleware/steps/submit.js +112 -0
- package/dist/middleware/steps/submit.js.map +1 -0
- package/dist/middleware/wizard-calendar.d.ts +37 -0
- package/dist/middleware/wizard-calendar.d.ts.map +1 -0
- package/dist/middleware/wizard-calendar.js +177 -0
- package/dist/middleware/wizard-calendar.js.map +1 -0
- package/dist/middleware/wizard-service.d.ts +30 -0
- package/dist/middleware/wizard-service.d.ts.map +1 -0
- package/dist/middleware/wizard-service.js +89 -0
- package/dist/middleware/wizard-service.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/{src/server.ts → dist/server.js} +1 -0
- package/dist/server.js.map +1 -0
- package/package.json +16 -4
- package/.github/workflows/build-paper.yml +0 -39
- package/.github/workflows/ci.yml +0 -37
- package/Dockerfile +0 -53
- package/docs/blog-post.mdx +0 -240
- package/docs/paper/IEEEtran.bst +0 -2409
- package/docs/paper/IEEEtran.cls +0 -6347
- package/docs/paper/acuity-middleware-paper.tex +0 -375
- package/docs/paper/balance.sty +0 -87
- package/docs/paper/references.bib +0 -231
- package/docs/paper.md +0 -400
- package/flake.nix +0 -32
- package/modal-app.py +0 -82
- package/src/adapters/acuity-scraper.ts +0 -543
- package/src/adapters/types.ts +0 -193
- package/src/core/types.ts +0 -325
- package/src/index.ts +0 -75
- package/src/middleware/acuity-wizard.ts +0 -456
- package/src/middleware/browser-service.ts +0 -183
- package/src/middleware/errors.ts +0 -70
- package/src/middleware/remote-adapter.ts +0 -246
- package/src/middleware/selectors.ts +0 -308
- package/src/middleware/server.ts +0 -372
- package/src/middleware/steps/bypass-payment.ts +0 -226
- package/src/middleware/steps/extract.ts +0 -174
- package/src/middleware/steps/fill-form.ts +0 -359
- package/src/middleware/steps/index.ts +0 -27
- package/src/middleware/steps/navigate.ts +0 -537
- package/src/middleware/steps/read-availability.ts +0 -399
- package/src/middleware/steps/read-slots.ts +0 -405
- package/src/middleware/steps/submit.ts +0 -168
- 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"}
|