@patientos/website-kit 0.2.3 → 0.2.5

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.
@@ -59,11 +59,13 @@ import {
59
59
  portalSend,
60
60
  readPortalSignedInHint,
61
61
  resetSession,
62
+ restoreApplicationRunToken,
63
+ saveApplicationRunToken,
62
64
  saveFlowRecord,
63
65
  signOutOfPortal,
64
66
  writePortalSignedInHint,
65
67
  zoneAbbr
66
- } from "./chunk-BHVDY6SC.js";
68
+ } from "./chunk-FSZ726DM.js";
67
69
  import {
68
70
  __export
69
71
  } from "./chunk-MLKGABMK.js";
@@ -403,6 +405,7 @@ function normalizeAuPhone(input) {
403
405
  const cleaned = input.replace(/[\s()-]/g, "");
404
406
  if (cleaned.startsWith("+")) return cleaned;
405
407
  if (cleaned.startsWith("0")) return `+61${cleaned.slice(1)}`;
408
+ if (/^(?:13\d{4}|1300\d{6}|1800\d{6})$/.test(cleaned)) return `+61${cleaned}`;
406
409
  if (cleaned.startsWith("61")) return `+${cleaned}`;
407
410
  return cleaned;
408
411
  }
@@ -524,7 +527,7 @@ function toSessionInput(v) {
524
527
  dateOfBirth: v.dateOfBirth
525
528
  };
526
529
  }
527
- function SignInRamp({ redirect }) {
530
+ function SignInRamp({ redirect, prepareRedirect }) {
528
531
  const portal = usePortalClient();
529
532
  const [open, setOpen] = React2.useState(false);
530
533
  const [email, setEmail] = React2.useState("");
@@ -586,7 +589,7 @@ function SignInRamp({ redirect }) {
586
589
  onClick: () => {
587
590
  if (busy) return;
588
591
  setBusy(true);
589
- void portal.requestMagicLink(email.trim(), redirect ?? portal.returnPath()).then((reached) => setOutcome(reached ? "sent" : "failed")).catch(() => setOutcome("failed")).finally(() => {
592
+ void (prepareRedirect ? prepareRedirect() : Promise.resolve(redirect ?? portal.returnPath())).then((target) => portal.requestMagicLink(email.trim(), target)).then((reached) => setOutcome(reached ? "sent" : "failed")).catch(() => setOutcome("failed")).finally(() => {
590
593
  setBusy(false);
591
594
  });
592
595
  },
@@ -1180,18 +1183,56 @@ function Booking({
1180
1183
 
1181
1184
  // src/certificate-funnel.client.tsx
1182
1185
  import * as React16 from "react";
1186
+ import {
1187
+ PatientOSApiError as PatientOSApiError2
1188
+ } from "@patientos/public-sdk";
1189
+
1190
+ // src/application-run-command.ts
1191
+ var inMemoryPromotionKeys = /* @__PURE__ */ new Map();
1192
+ function retainedPromotionCommandKey(applicationRunId, storage2, createKey = () => crypto.randomUUID()) {
1193
+ const memoryKey = inMemoryPromotionKeys.get(applicationRunId);
1194
+ if (memoryKey) return memoryKey;
1195
+ const storageKey = `patientos:application-promotion:${applicationRunId}`;
1196
+ try {
1197
+ const retained = storage2?.getItem(storageKey);
1198
+ if (retained) {
1199
+ inMemoryPromotionKeys.set(applicationRunId, retained);
1200
+ return retained;
1201
+ }
1202
+ const created = createKey();
1203
+ inMemoryPromotionKeys.set(applicationRunId, created);
1204
+ storage2?.setItem(storageKey, created);
1205
+ return created;
1206
+ } catch {
1207
+ const created = createKey();
1208
+ inMemoryPromotionKeys.set(applicationRunId, created);
1209
+ return created;
1210
+ }
1211
+ }
1212
+ var ApplicationRunCommandRetrier = class {
1213
+ keys = /* @__PURE__ */ new Map();
1214
+ async act(client, applicationRunId, input) {
1215
+ const fingerprint = JSON.stringify({ applicationRunId, ...input });
1216
+ const idempotencyKey = this.keys.get(fingerprint) ?? crypto.randomUUID();
1217
+ this.keys.set(fingerprint, idempotencyKey);
1218
+ const command = { ...input, idempotencyKey };
1219
+ const run = await client.actApplicationRun(applicationRunId, command);
1220
+ this.keys.delete(fingerprint);
1221
+ return run;
1222
+ }
1223
+ };
1183
1224
 
1184
1225
  // src/certificate-funnel-machine.ts
1185
1226
  var FUNNEL_STEPS = [
1186
1227
  "service",
1187
1228
  "preflight",
1229
+ "intake",
1230
+ "slot",
1188
1231
  "identity",
1189
1232
  "confirm-identity",
1190
- "intake",
1191
1233
  "medicare",
1192
1234
  "consent",
1193
1235
  "payment",
1194
- "slot",
1195
1236
  "submitted",
1196
1237
  "unavailable",
1197
1238
  "blocked",
@@ -1200,14 +1241,18 @@ var FUNNEL_STEPS = [
1200
1241
  "session-expired"
1201
1242
  ];
1202
1243
  var REQUIRES_REQUEST = /* @__PURE__ */ new Set([
1203
- "intake",
1204
1244
  "medicare",
1205
1245
  "consent",
1206
1246
  "payment",
1207
- "slot",
1208
1247
  "submitted"
1209
1248
  ]);
1210
- var REQUIRES_PREFLIGHT = /* @__PURE__ */ new Set(["identity", "confirm-identity"]);
1249
+ var REQUIRES_APPLICATION_RUN = /* @__PURE__ */ new Set([
1250
+ "intake",
1251
+ "slot",
1252
+ "identity",
1253
+ "confirm-identity"
1254
+ ]);
1255
+ var REQUIRES_PREFLIGHT = /* @__PURE__ */ new Set(["identity", "confirm-identity", "intake", "slot"]);
1211
1256
  var REQUIRES_SERVICE = /* @__PURE__ */ new Set([
1212
1257
  "preflight",
1213
1258
  "identity",
@@ -1224,15 +1269,6 @@ var DEFAULT_SHAPE = {
1224
1269
  scheduled: true,
1225
1270
  intakeSteps: 1
1226
1271
  };
1227
- var SPINE_OFFSET = {
1228
- preflight: 1,
1229
- identity: 2,
1230
- // The signed-in confirmation REPLACES the anonymous form; it is the same step.
1231
- "confirm-identity": 2,
1232
- intake: 3,
1233
- medicare: 4,
1234
- consent: 5
1235
- };
1236
1272
  function intakeSpan(shape) {
1237
1273
  return Math.max(1, Math.trunc(shape.intakeSteps ?? 1) || 1);
1238
1274
  }
@@ -1247,14 +1283,17 @@ function totalSteps(shape) {
1247
1283
  }
1248
1284
  function stepNumberFor(step, shape) {
1249
1285
  const picker = shape.pinned ? 0 : 1;
1250
- const extra = intakeSpan(shape) - 1;
1286
+ const intake = intakeSpan(shape);
1251
1287
  if (step === "service") return shape.pinned ? void 0 : 1;
1252
- if (step === "payment") return shape.paid ? picker + 6 + extra : void 0;
1253
- if (step === "slot")
1254
- return shape.scheduled ? picker + 5 + extra + (shape.paid ? 2 : 1) : void 0;
1255
- const offset = SPINE_OFFSET[step];
1256
- if (offset === void 0) return void 0;
1257
- return picker + offset + (offset > SPINE_OFFSET.intake ? extra : 0);
1288
+ if (step === "preflight") return picker + 1;
1289
+ if (step === "intake") return picker + 2;
1290
+ if (step === "slot") return shape.scheduled ? picker + 2 + intake : void 0;
1291
+ const identity = picker + 2 + intake + (shape.scheduled ? 1 : 0);
1292
+ if (step === "identity" || step === "confirm-identity") return identity;
1293
+ if (step === "medicare") return identity + 1;
1294
+ if (step === "consent") return identity + 2;
1295
+ if (step === "payment") return shape.paid ? identity + 3 : void 0;
1296
+ return void 0;
1258
1297
  }
1259
1298
  function stepProgress(step, shape = DEFAULT_SHAPE, questionIndex = 0) {
1260
1299
  const total = totalSteps(shape);
@@ -1274,7 +1313,9 @@ function initialFunnelState(serviceKey) {
1274
1313
  // safety check, never past it: pinning chooses the product, not the screening.
1275
1314
  step: serviceKey ? "preflight" : "service",
1276
1315
  serviceKey: serviceKey ?? null,
1316
+ applicationRunId: null,
1277
1317
  requestId: null,
1318
+ scheduled: null,
1278
1319
  gate: null,
1279
1320
  failure: null
1280
1321
  };
@@ -1295,9 +1336,13 @@ function reduceFunnel(state, event) {
1295
1336
  case "SELECT_SERVICE":
1296
1337
  if (state.step !== "service") return stay2(state);
1297
1338
  return go2(state, { step: "preflight", serviceKey: event.serviceKey });
1298
- case "PREFLIGHT_CONFIRMED":
1339
+ case "RUN_STARTED":
1299
1340
  if (state.step !== "preflight") return stay2(state);
1300
- return go2(state, { step: "identity" });
1341
+ return go2(state, {
1342
+ step: "intake",
1343
+ applicationRunId: event.applicationRunId,
1344
+ scheduled: event.scheduled
1345
+ });
1301
1346
  case "NEED_DIFFERENT":
1302
1347
  return go2(state, { step: "unavailable" });
1303
1348
  case "SIGNED_IN":
@@ -1310,7 +1355,7 @@ function reduceFunnel(state, event) {
1310
1355
  }
1311
1356
  case "REQUEST_STARTED":
1312
1357
  if (state.step !== "identity" && state.step !== "confirm-identity") return stay2(state);
1313
- return go2(state, { step: "intake", requestId: event.requestId });
1358
+ return go2(state, { step: "medicare", requestId: event.requestId });
1314
1359
  case "GATE":
1315
1360
  return go2(state, {
1316
1361
  step: event.gate.kind === "escalate" ? "escalate" : "blocked",
@@ -1318,7 +1363,10 @@ function reduceFunnel(state, event) {
1318
1363
  });
1319
1364
  case "ANSWERS_COMPLETE":
1320
1365
  if (state.step !== "intake") return stay2(state);
1321
- return go2(state, { step: "medicare" });
1366
+ return go2(state, { step: state.scheduled ? "slot" : "identity" });
1367
+ case "RESERVED":
1368
+ if (state.step !== "slot") return stay2(state);
1369
+ return go2(state, { step: "identity" });
1322
1370
  case "MEDICARE_DONE":
1323
1371
  if (state.step !== "medicare") return stay2(state);
1324
1372
  return go2(state, { step: "consent" });
@@ -1347,12 +1395,21 @@ function reduceFunnel(state, event) {
1347
1395
  case "BOOKED":
1348
1396
  if (state.step !== "slot") return stay2(state);
1349
1397
  return go2(state, { step: "submitted" });
1398
+ case "RESERVATION_LOST":
1399
+ if (state.step !== "consent" && state.step !== "payment") return stay2(state);
1400
+ return go2(state, { step: "slot" });
1350
1401
  case "SESSION_EXPIRED":
1351
1402
  if (state.step === "session-expired") return stay2(state);
1352
1403
  return go2(state, { step: "session-expired" });
1353
1404
  case "RETRY_PAYMENT":
1354
1405
  if (state.step !== "payment-failed") return stay2(state);
1355
- return go2(state, { step: "preflight", requestId: null, failure: null });
1406
+ return go2(state, {
1407
+ step: "preflight",
1408
+ applicationRunId: null,
1409
+ requestId: null,
1410
+ scheduled: null,
1411
+ failure: null
1412
+ });
1356
1413
  case "RESTART": {
1357
1414
  const next = go2(initialFunnelState(), { serviceKey: null });
1358
1415
  return next;
@@ -1365,14 +1422,16 @@ function back(state) {
1365
1422
  switch (state.step) {
1366
1423
  case "preflight":
1367
1424
  return go2(state, { step: "service", serviceKey: null });
1425
+ case "intake":
1426
+ return go2(state, { step: "preflight", applicationRunId: null, scheduled: null });
1427
+ case "slot":
1428
+ return go2(state, { step: "intake" });
1368
1429
  case "identity":
1369
- return go2(state, { step: "preflight" });
1430
+ return go2(state, { step: state.scheduled ? "slot" : "intake" });
1370
1431
  case "confirm-identity":
1371
1432
  return go2(state, { step: "identity" });
1372
- case "intake":
1373
- return go2(state, { step: "identity" });
1374
1433
  case "medicare":
1375
- return go2(state, { step: "intake" });
1434
+ return go2(state, { step: "identity" });
1376
1435
  case "consent":
1377
1436
  return go2(state, { step: "medicare" });
1378
1437
  case "payment":
@@ -1380,7 +1439,13 @@ function back(state) {
1380
1439
  case "unavailable":
1381
1440
  return go2(state, { step: "service" });
1382
1441
  case "payment-failed":
1383
- return go2(state, { step: "preflight", requestId: null, failure: null });
1442
+ return go2(state, {
1443
+ step: "preflight",
1444
+ applicationRunId: null,
1445
+ requestId: null,
1446
+ scheduled: null,
1447
+ failure: null
1448
+ });
1384
1449
  default:
1385
1450
  return stay2(state);
1386
1451
  }
@@ -1389,10 +1454,6 @@ function canGoBack(state, opts = {}) {
1389
1454
  if (state.step === "preflight" && opts.pinnedService) return false;
1390
1455
  return back(state).effects.length > 0;
1391
1456
  }
1392
- function resumableRequestId(requestId, submittedSnapshot, currentSnapshot) {
1393
- if (!requestId || !submittedSnapshot || !currentSnapshot) return null;
1394
- return submittedSnapshot === currentSnapshot ? requestId : null;
1395
- }
1396
1457
  var TERMINAL_WALL = /* @__PURE__ */ new Set([
1397
1458
  "escalate",
1398
1459
  "blocked",
@@ -1413,18 +1474,26 @@ function popAction(current, search, pinnedService) {
1413
1474
  if (TERMINAL_WALL.has(current.step)) return { type: "refuse" };
1414
1475
  return { type: "restore", state: stepFromUrl(search, pinnedService) };
1415
1476
  }
1416
- var URL_KEYS = { step: "cf", requestId: "cfid", serviceKey: "cfsvc" };
1477
+ var URL_KEYS = {
1478
+ step: "cf",
1479
+ requestId: "cfid",
1480
+ applicationRunId: "cfrun",
1481
+ serviceKey: "cfsvc"
1482
+ };
1417
1483
  function stepFromUrl(search, pinnedService) {
1418
1484
  const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
1419
1485
  const rawStep = params.get(URL_KEYS.step) ?? "";
1420
1486
  const requestId = params.get(URL_KEYS.requestId) || null;
1487
+ const applicationRunId = params.get(URL_KEYS.applicationRunId) || null;
1421
1488
  const urlService = params.get(URL_KEYS.serviceKey) || null;
1422
1489
  const serviceKey = pinnedService ?? urlService;
1423
1490
  const known = FUNNEL_STEPS.includes(rawStep) ? rawStep : null;
1424
1491
  const base2 = {
1425
1492
  step: known ?? initialFunnelState(serviceKey).step,
1426
1493
  serviceKey,
1494
+ applicationRunId,
1427
1495
  requestId,
1496
+ scheduled: null,
1428
1497
  // Gate + failure detail are NOT in the URL: they are server verdicts with clinical
1429
1498
  // copy attached, and a URL that could assert "you are blocked" (or, worse, that you
1430
1499
  // are not) would be a forgeable claim about a safety decision.
@@ -1432,6 +1501,9 @@ function stepFromUrl(search, pinnedService) {
1432
1501
  failure: null
1433
1502
  };
1434
1503
  if (REQUIRES_REQUEST.has(base2.step) && !base2.requestId) {
1504
+ return { ...base2, step: applicationRunId ? "identity" : serviceKey ? "preflight" : "service" };
1505
+ }
1506
+ if (REQUIRES_APPLICATION_RUN.has(base2.step) && !base2.applicationRunId) {
1435
1507
  return { ...base2, step: serviceKey ? "preflight" : "service" };
1436
1508
  }
1437
1509
  if (base2.step === "payment-failed" && !base2.requestId) {
@@ -1443,7 +1515,7 @@ function stepFromUrl(search, pinnedService) {
1443
1515
  if (REQUIRES_SERVICE.has(base2.step) && !base2.serviceKey) {
1444
1516
  return { ...base2, step: "service" };
1445
1517
  }
1446
- if (REQUIRES_PREFLIGHT.has(base2.step)) {
1518
+ if (REQUIRES_PREFLIGHT.has(base2.step) && !base2.applicationRunId) {
1447
1519
  return { ...base2, step: base2.serviceKey ? "preflight" : "service" };
1448
1520
  }
1449
1521
  if (base2.step === "blocked" || base2.step === "escalate") {
@@ -1455,6 +1527,7 @@ function urlFromState(state, opts = {}) {
1455
1527
  const params = new URLSearchParams();
1456
1528
  params.set(URL_KEYS.step, state.step);
1457
1529
  if (state.requestId) params.set(URL_KEYS.requestId, state.requestId);
1530
+ if (state.applicationRunId) params.set(URL_KEYS.applicationRunId, state.applicationRunId);
1458
1531
  if (state.serviceKey && !opts.pinnedService) params.set(URL_KEYS.serviceKey, state.serviceKey);
1459
1532
  return params.toString();
1460
1533
  }
@@ -1684,17 +1757,17 @@ function PaymentStep({
1684
1757
  const res = await client.payConfirm(requestId);
1685
1758
  switch (res.status) {
1686
1759
  case "awaiting_slot":
1687
- onOutcome({ kind: "awaiting_slot" });
1760
+ await onOutcome({ kind: "awaiting_slot" });
1688
1761
  return;
1689
1762
  case "ready":
1690
- onOutcome({ kind: "ready" });
1763
+ await onOutcome({ kind: "ready" });
1691
1764
  return;
1692
1765
  case "requires_action":
1693
1766
  setError("Your bank needs to verify this card. Please try again.");
1694
1767
  setPhase("ready");
1695
1768
  return;
1696
1769
  case "payment_failed":
1697
- onOutcome({
1770
+ await onOutcome({
1698
1771
  kind: "failed",
1699
1772
  message: "Your card was declined and the pre-authorisation could not be completed."
1700
1773
  });
@@ -1938,7 +2011,7 @@ function PaymentStep({
1938
2011
  phase === "init_failed" && clinicPhone && /* @__PURE__ */ jsxs5("p", { className: "sk-cf__body", children: [
1939
2012
  "Still not working?",
1940
2013
  " ",
1941
- /* @__PURE__ */ jsxs5("a", { className: "sk-cf__link", href: `tel:${clinicPhone.replace(/[^\d+]/g, "")}`, children: [
2014
+ /* @__PURE__ */ jsxs5("a", { className: "sk-cf__link", href: `tel:${normalizeAuPhone(clinicPhone)}`, children: [
1942
2015
  "Call the clinic on ",
1943
2016
  clinicPhone
1944
2017
  ] })
@@ -2290,13 +2363,43 @@ function IdentityStep(props) {
2290
2363
  },
2291
2364
  [client]
2292
2365
  );
2366
+ const promotionIdempotencyRef = React8.useRef(null);
2367
+ const promotionIdempotencyKey = React8.useCallback((applicationRunId) => {
2368
+ if (promotionIdempotencyRef.current) return promotionIdempotencyRef.current;
2369
+ const retained = retainedPromotionCommandKey(
2370
+ applicationRunId,
2371
+ typeof window === "undefined" ? null : window.sessionStorage
2372
+ );
2373
+ promotionIdempotencyRef.current = retained;
2374
+ return retained;
2375
+ }, []);
2293
2376
  const startRequest = React8.useCallback(
2294
- async (capturedIdentity) => {
2295
- const { requestId } = await client.startRequest({ serviceKey: props.serviceKey });
2296
- onStarted(requestId, capturedIdentity);
2377
+ async (promotion) => {
2378
+ if (!props.applicationRunId) {
2379
+ return (await client.startRequest({ serviceKey: props.serviceKey })).requestId;
2380
+ }
2381
+ const result = await client.promoteApplicationRun(props.applicationRunId, {
2382
+ ...promotion,
2383
+ idempotencyKey: promotionIdempotencyKey(props.applicationRunId)
2384
+ });
2385
+ return result.requestId;
2297
2386
  },
2298
- [client, props.serviceKey, onStarted]
2387
+ [client, promotionIdempotencyKey, props.applicationRunId, props.serviceKey]
2299
2388
  );
2389
+ const prepareSignInRedirect = React8.useCallback(async () => {
2390
+ if (!props.applicationRunId || typeof window === "undefined") return portal.returnPath();
2391
+ const { handoffId } = await client.createApplicationRunHandoff(props.applicationRunId);
2392
+ let target;
2393
+ try {
2394
+ target = new URL(window.location.href);
2395
+ } catch {
2396
+ return portal.returnPath();
2397
+ }
2398
+ target.searchParams.set("cf", "confirm-identity");
2399
+ target.searchParams.set("cfrun", props.applicationRunId);
2400
+ target.searchParams.set("cfhandoff", handoffId);
2401
+ return portal.returnPath(target.href);
2402
+ }, [client, portal, props.applicationRunId]);
2300
2403
  async function handleConfirmSignedIn() {
2301
2404
  if (busy) return;
2302
2405
  setBusy(true);
@@ -2310,13 +2413,15 @@ function IdentityStep(props) {
2310
2413
  return;
2311
2414
  }
2312
2415
  adoptClaimToken(client, claim.claimToken);
2313
- await startRequest({
2416
+ const confirmedIdentity = {
2314
2417
  givenName: prefill?.givenName ?? "",
2315
2418
  familyName: prefill?.familyName ?? "",
2316
2419
  email: prefill?.email ?? "",
2317
2420
  phone: prefill?.phone ?? "",
2318
2421
  dateOfBirth: prefill?.dateOfBirth ?? ""
2319
- });
2422
+ };
2423
+ const requestId = await startRequest();
2424
+ onStarted(requestId, confirmedIdentity);
2320
2425
  } catch (err) {
2321
2426
  setBusy(false);
2322
2427
  setError(errorMessage(err, "We could not start your request. Please try again."));
@@ -2341,17 +2446,29 @@ function IdentityStep(props) {
2341
2446
  setBusy(true);
2342
2447
  setError(null);
2343
2448
  try {
2344
- await client.startSession({
2345
- // SPLIT names — never a joined `fullName` (PAT-776).
2346
- ...toSessionInput(identity),
2347
- turnstileToken: turnstileToken ?? void 0
2348
- });
2349
- persistClaimToken(client);
2350
- const { requestId } = await client.startRequest({ serviceKey: props.serviceKey });
2351
- await client.saveAddress(requestId, {
2352
- address: address.place.address,
2353
- placeId: address.place?.placeId ?? null
2354
- });
2449
+ let requestId;
2450
+ if (props.applicationRunId) {
2451
+ requestId = await startRequest({
2452
+ identity: toSessionInput(identity),
2453
+ address: {
2454
+ address: address.place.address,
2455
+ placeId: address.place?.placeId ?? null
2456
+ },
2457
+ turnstileToken: turnstileToken ?? void 0
2458
+ });
2459
+ persistClaimToken(client);
2460
+ } else {
2461
+ await client.startSession({
2462
+ ...toSessionInput(identity),
2463
+ turnstileToken: turnstileToken ?? void 0
2464
+ });
2465
+ persistClaimToken(client);
2466
+ requestId = await startRequest();
2467
+ await client.saveAddress(requestId, {
2468
+ address: address.place.address,
2469
+ placeId: address.place?.placeId ?? null
2470
+ });
2471
+ }
2355
2472
  onStarted(requestId, identity);
2356
2473
  } catch (err) {
2357
2474
  setBusy(false);
@@ -2375,6 +2492,10 @@ function IdentityStep(props) {
2375
2492
  setError(errorMessage(err, "We could not start your request. Please try again."));
2376
2493
  }
2377
2494
  }
2495
+ const reservationNote = props.reservationExpiresAt ? ` Your appointment time is reserved until ${new Intl.DateTimeFormat("en-AU", {
2496
+ hour: "numeric",
2497
+ minute: "2-digit"
2498
+ }).format(new Date(props.reservationExpiresAt))}.` : "";
2378
2499
  if (mode === "confirm-identity") {
2379
2500
  return /* @__PURE__ */ jsxs8(
2380
2501
  FlowShell,
@@ -2382,7 +2503,7 @@ function IdentityStep(props) {
2382
2503
  ...progress,
2383
2504
  stepKey: "confirm-identity",
2384
2505
  heading: "Confirm your details",
2385
- subheading: detailsSubheading(props.serviceLabel),
2506
+ subheading: `${detailsSubheading(props.serviceLabel)}${reservationNote}`,
2386
2507
  onBack,
2387
2508
  documentTitle,
2388
2509
  children: [
@@ -2407,12 +2528,12 @@ function IdentityStep(props) {
2407
2528
  ...progress,
2408
2529
  stepKey: "identity",
2409
2530
  heading: "Your details",
2410
- subheading: detailsSubheading(props.serviceLabel),
2531
+ subheading: `${detailsSubheading(props.serviceLabel)}${reservationNote}`,
2411
2532
  onBack,
2412
2533
  documentTitle,
2413
2534
  footer: probing ? void 0 : /* @__PURE__ */ jsx10(FlowButton, { type: "submit", form: "sk-cf-identity", variant: "primary", busy, children: busy ? "Starting\u2026" : "Continue" }),
2414
2535
  children: probing ? /* @__PURE__ */ jsx10(FlowLoading, { message: "Checking if you\u2019re signed in\u2026" }) : /* @__PURE__ */ jsxs8(Fragment3, { children: [
2415
- /* @__PURE__ */ jsx10(SignInRamp, { redirect: props.returnUrl }),
2536
+ /* @__PURE__ */ jsx10(SignInRamp, { redirect: props.returnUrl, prepareRedirect: prepareSignInRedirect }),
2416
2537
  /* @__PURE__ */ jsx10(FlowErrorSummary, { fields: invalid }),
2417
2538
  /* @__PURE__ */ jsxs8("form", { id: "sk-cf-identity", onSubmit: (e) => void handleSubmitAnonymous(e), noValidate: true, children: [
2418
2539
  /* @__PURE__ */ jsx10(SummarisedFieldErrors, { children: /* @__PURE__ */ jsx10(
@@ -2742,6 +2863,9 @@ function intakeRowsAction(rows) {
2742
2863
  function IntakeStep({
2743
2864
  client,
2744
2865
  requestId,
2866
+ applicationRunId,
2867
+ applicationRunRevision = 0,
2868
+ onApplicationRunChange,
2745
2869
  questionSetId,
2746
2870
  resolveError,
2747
2871
  onGate,
@@ -2755,6 +2879,15 @@ function IntakeStep({
2755
2879
  const [draft, setDraft] = React10.useState(null);
2756
2880
  const [busy, setBusy] = React10.useState(false);
2757
2881
  const [error2, setError] = React10.useState(null);
2882
+ const [runRevision, setRunRevision] = React10.useState(applicationRunRevision);
2883
+ const [completed, setCompleted] = React10.useState(false);
2884
+ const [emptyCompletionRetry, setEmptyCompletionRetry] = React10.useState(0);
2885
+ const commandRetrier = React10.useRef(new ApplicationRunCommandRetrier()).current;
2886
+ const pendingCompletionRevision = React10.useRef(null);
2887
+ const emptyCompletionAttempted = React10.useRef(false);
2888
+ React10.useEffect(() => {
2889
+ setRunRevision(applicationRunRevision);
2890
+ }, [applicationRunRevision]);
2758
2891
  const [reload2, setReload] = React10.useState(0);
2759
2892
  const progress = useStepProgress("intake", index);
2760
2893
  React10.useEffect(() => {
@@ -2783,8 +2916,28 @@ function IntakeStep({
2783
2916
  };
2784
2917
  }, [client, questionSetId, reload2]);
2785
2918
  React10.useEffect(() => {
2786
- if (questions && questions.length === 0 && !busy) onComplete();
2787
- }, [questions, busy, onComplete]);
2919
+ if (!questions || questions.length !== 0 || busy || completed || emptyCompletionAttempted.current) return;
2920
+ emptyCompletionAttempted.current = true;
2921
+ if (!applicationRunId) {
2922
+ setCompleted(true);
2923
+ onComplete();
2924
+ return;
2925
+ }
2926
+ setBusy(true);
2927
+ void commandRetrier.act(client, applicationRunId, {
2928
+ expectedRevision: runRevision,
2929
+ action: { type: "complete_screening" }
2930
+ }).then((run) => {
2931
+ setRunRevision(run.revision);
2932
+ onApplicationRunChange?.(run);
2933
+ const exit = gateToExit(run.gate);
2934
+ if (exit) onGate(exit);
2935
+ else {
2936
+ setCompleted(true);
2937
+ onComplete();
2938
+ }
2939
+ }).catch((e) => setError(errorMessage(e, "We could not complete the screening. Please try again."))).finally(() => setBusy(false));
2940
+ }, [questions, busy, completed, emptyCompletionRetry, applicationRunId, client, runRevision, onApplicationRunChange, onGate, onComplete]);
2788
2941
  const visible = React10.useMemo(
2789
2942
  () => questions ? visibleQuestions(questions, answers) : [],
2790
2943
  [questions, answers]
@@ -2794,17 +2947,60 @@ function IntakeStep({
2794
2947
  React10.useEffect(() => {
2795
2948
  setDraft(current ? answers[current.key] ?? null : null);
2796
2949
  }, [current?.key]);
2950
+ async function retryPendingCompletion() {
2951
+ if (busy || !applicationRunId || pendingCompletionRevision.current == null) return;
2952
+ setBusy(true);
2953
+ setError(null);
2954
+ try {
2955
+ const completedRun = await commandRetrier.act(client, applicationRunId, {
2956
+ expectedRevision: pendingCompletionRevision.current,
2957
+ action: { type: "complete_screening" }
2958
+ });
2959
+ pendingCompletionRevision.current = null;
2960
+ setRunRevision(completedRun.revision);
2961
+ onApplicationRunChange?.(completedRun);
2962
+ const completedExit = gateToExit(completedRun.gate);
2963
+ if (completedExit) onGate(completedExit);
2964
+ else {
2965
+ setCompleted(true);
2966
+ onComplete();
2967
+ }
2968
+ } catch (e) {
2969
+ setError(errorMessage(e, "We could not complete the screening. Please try again."));
2970
+ } finally {
2971
+ setBusy(false);
2972
+ }
2973
+ }
2797
2974
  async function record(value) {
2798
2975
  if (!current || busy) return;
2799
- const next = { ...answers, [current.key]: value };
2800
- setAnswers(next);
2976
+ if (applicationRunId && pendingCompletionRevision.current != null) {
2977
+ await retryPendingCompletion();
2978
+ return;
2979
+ }
2801
2980
  setBusy(true);
2802
2981
  setError(null);
2982
+ const next = { ...answers, [current.key]: value };
2983
+ setAnswers(next);
2803
2984
  try {
2804
- const { gate } = await client.saveAnswers(
2805
- requestId,
2806
- next
2807
- );
2985
+ let savedRun = null;
2986
+ let gate;
2987
+ if (applicationRunId) {
2988
+ savedRun = await commandRetrier.act(client, applicationRunId, {
2989
+ expectedRevision: runRevision,
2990
+ action: {
2991
+ type: "save_answers",
2992
+ answers: next
2993
+ }
2994
+ });
2995
+ setRunRevision(savedRun.revision);
2996
+ onApplicationRunChange?.(savedRun);
2997
+ gate = savedRun.gate;
2998
+ } else {
2999
+ gate = (await client.saveAnswers(
3000
+ requestId ?? "",
3001
+ next
3002
+ )).gate;
3003
+ }
2808
3004
  const exit = gateToExit(gate);
2809
3005
  if (exit) {
2810
3006
  onGate(exit);
@@ -2812,7 +3008,25 @@ function IntakeStep({
2812
3008
  }
2813
3009
  const nextVisible = questions ? visibleQuestions(questions, next) : [];
2814
3010
  if (index + 1 >= nextVisible.length) {
2815
- onComplete();
3011
+ if (applicationRunId && savedRun) {
3012
+ pendingCompletionRevision.current = savedRun.revision;
3013
+ const completedRun = await commandRetrier.act(client, applicationRunId, {
3014
+ expectedRevision: savedRun.revision,
3015
+ action: { type: "complete_screening" }
3016
+ });
3017
+ pendingCompletionRevision.current = null;
3018
+ setRunRevision(completedRun.revision);
3019
+ onApplicationRunChange?.(completedRun);
3020
+ const completedExit = gateToExit(completedRun.gate);
3021
+ if (completedExit) onGate(completedExit);
3022
+ else {
3023
+ setCompleted(true);
3024
+ onComplete();
3025
+ }
3026
+ } else {
3027
+ setCompleted(true);
3028
+ onComplete();
3029
+ }
2816
3030
  return;
2817
3031
  }
2818
3032
  setIndex(index + 1);
@@ -2858,6 +3072,23 @@ function IntakeStep({
2858
3072
  }
2859
3073
  );
2860
3074
  }
3075
+ if (questions.length === 0) {
3076
+ return /* @__PURE__ */ jsx12(
3077
+ IntakeInterstitial,
3078
+ {
3079
+ progress,
3080
+ documentTitle,
3081
+ onBack,
3082
+ error: error2,
3083
+ message: busy || !error2 ? "Completing your screening\u2026" : void 0,
3084
+ onRetry: error2 ? () => {
3085
+ emptyCompletionAttempted.current = false;
3086
+ setError(null);
3087
+ setEmptyCompletionRetry((value) => value + 1);
3088
+ } : void 0
3089
+ }
3090
+ );
3091
+ }
2861
3092
  if (!current) {
2862
3093
  return /* @__PURE__ */ jsx12(
2863
3094
  IntakeInterstitial,
@@ -2905,7 +3136,17 @@ function IntakeStep({
2905
3136
  idPrefix: `sk-cf-${index}`
2906
3137
  }
2907
3138
  ),
2908
- /* @__PURE__ */ jsx12(FlowError, { children: error2 })
3139
+ /* @__PURE__ */ jsx12(FlowError, { children: error2 }),
3140
+ error2 && pendingCompletionRevision.current != null && /* @__PURE__ */ jsx12(
3141
+ FlowButton,
3142
+ {
3143
+ type: "button",
3144
+ variant: "primary",
3145
+ busy,
3146
+ onClick: () => void retryPendingCompletion(),
3147
+ children: "Try again"
3148
+ }
3149
+ )
2909
3150
  ]
2910
3151
  }
2911
3152
  );
@@ -3309,7 +3550,7 @@ function ConsentStep({
3309
3550
  onGate(gate ?? { kind: "block" });
3310
3551
  return;
3311
3552
  }
3312
- onSubmitted(result.status, gate);
3553
+ await onSubmitted(result.status, gate);
3313
3554
  } catch (err) {
3314
3555
  setBusy(false);
3315
3556
  setError(errorMessage(err, "We could not submit your request. Please try again."));
@@ -3414,6 +3655,10 @@ function SlotCancelControl({
3414
3655
  function SlotStep({
3415
3656
  client,
3416
3657
  requestId,
3658
+ applicationRunId,
3659
+ applicationRunRevision = 0,
3660
+ onApplicationRunChange,
3661
+ replacementReservation = false,
3417
3662
  appointmentTypeId,
3418
3663
  isFree,
3419
3664
  onBooked,
@@ -3424,13 +3669,15 @@ function SlotStep({
3424
3669
  const [cancelling, setCancelling] = React13.useState(false);
3425
3670
  const [confirmCancel, setConfirmCancel] = React13.useState(false);
3426
3671
  const [cancelError, setCancelError] = React13.useState(null);
3672
+ const commandRetrier = React13.useRef(new ApplicationRunCommandRetrier()).current;
3427
3673
  const progress = useStepProgress("slot");
3428
3674
  async function releaseAndExit() {
3429
3675
  if (cancelling) return;
3430
3676
  setCancelling(true);
3431
3677
  setCancelError(null);
3432
3678
  try {
3433
- await client.cancelRequest(requestId);
3679
+ if (applicationRunId) await client.closeApplicationRun(applicationRunId);
3680
+ else await client.cancelRequest(requestId ?? "");
3434
3681
  onCancelled();
3435
3682
  } catch (err) {
3436
3683
  setCancelling(false);
@@ -3456,18 +3703,33 @@ function SlotStep({
3456
3703
  appointmentTypeId,
3457
3704
  mode: "request",
3458
3705
  onConfirm: async (slot, tz) => {
3459
- const hold = await client.createHold({
3460
- practitionerId: slot.practitionerId,
3461
- appointmentTypeId,
3462
- slotStart: slot.start,
3463
- serviceRequestId: requestId
3464
- });
3465
- await client.bookRequest(requestId, {
3466
- practitionerId: slot.practitionerId,
3467
- slotStart: slot.start,
3468
- holdId: hold.holdId,
3469
- holderToken: hold.holderToken
3470
- });
3706
+ if (applicationRunId) {
3707
+ const run = await commandRetrier.act(client, applicationRunId, {
3708
+ expectedRevision: applicationRunRevision,
3709
+ action: {
3710
+ type: "reserve_slot",
3711
+ practitionerId: slot.practitionerId,
3712
+ slotStart: slot.start
3713
+ }
3714
+ });
3715
+ onApplicationRunChange?.(run);
3716
+ } else {
3717
+ const hold = replacementReservation ? await client.replaceRequestSlot(requestId ?? "", {
3718
+ practitionerId: slot.practitionerId,
3719
+ slotStart: slot.start
3720
+ }) : await client.createHold({
3721
+ practitionerId: slot.practitionerId,
3722
+ appointmentTypeId,
3723
+ slotStart: slot.start,
3724
+ serviceRequestId: requestId
3725
+ });
3726
+ await client.bookRequest(requestId ?? "", {
3727
+ practitionerId: slot.practitionerId,
3728
+ slotStart: slot.start,
3729
+ holdId: hold.holdId,
3730
+ holderToken: hold.holderToken
3731
+ });
3732
+ }
3471
3733
  onBooked(formatBookedFor(slot.start, tz));
3472
3734
  }
3473
3735
  }
@@ -3479,16 +3741,17 @@ function SlotStep({
3479
3741
  /* @__PURE__ */ jsxs13("p", { className: "sk-slots__empty-note", children: [
3480
3742
  "Can\u2019t find a time that suits?",
3481
3743
  " ",
3482
- /* @__PURE__ */ jsxs13("a", { className: "sk-cf__link", href: `tel:${clinicPhone.replace(/[^\d+]/g, "")}`, children: [
3744
+ /* @__PURE__ */ jsxs13("a", { className: "sk-cf__link", href: `tel:${normalizeAuPhone(clinicPhone)}`, children: [
3483
3745
  "Call the clinic on ",
3484
3746
  clinicPhone
3485
3747
  ] })
3486
3748
  ] }),
3487
- isFree === false && /* @__PURE__ */ jsxs13("p", { className: "sk-slots__empty-note", children: [
3749
+ isFree === false && !applicationRunId && /* @__PURE__ */ jsxs13("p", { className: "sk-slots__empty-note", children: [
3488
3750
  "You haven\u2019t been charged \u2014 only a hold was placed on your card.",
3489
3751
  " ",
3490
3752
  HOLD_RELEASE_NOTE
3491
- ] })
3753
+ ] }),
3754
+ applicationRunId && /* @__PURE__ */ jsx15("p", { className: "sk-slots__empty-note", children: "We\u2019ll reserve this time for 10 minutes while you confirm your details and payment." })
3492
3755
  ] }),
3493
3756
  /* @__PURE__ */ jsx15(
3494
3757
  SlotCancelControl,
@@ -3907,7 +4170,7 @@ function BlockedStep({
3907
4170
  EMERGENCY2
3908
4171
  ] }) }),
3909
4172
  /* @__PURE__ */ jsx18("li", { children: /* @__PURE__ */ jsx18("a", { className: "sk-cf__link", href: "tel:1800022222", children: "Health advice, 24 hours \u2014 healthdirect on 1800 022 222" }) }),
3910
- clinicPhone && /* @__PURE__ */ jsx18("li", { children: /* @__PURE__ */ jsxs16("a", { className: "sk-cf__link", href: `tel:${clinicPhone.replace(/[^\d+]/g, "")}`, children: [
4173
+ clinicPhone && /* @__PURE__ */ jsx18("li", { children: /* @__PURE__ */ jsxs16("a", { className: "sk-cf__link", href: `tel:${normalizeAuPhone(clinicPhone)}`, children: [
3911
4174
  "Call the clinic on ",
3912
4175
  clinicPhone
3913
4176
  ] }) }),
@@ -3945,7 +4208,7 @@ function PaymentFailedStep({
3945
4208
  clinicPhone && /* @__PURE__ */ jsxs16("p", { className: "sk-cf__body", children: [
3946
4209
  "Still not working?",
3947
4210
  " ",
3948
- /* @__PURE__ */ jsxs16("a", { className: "sk-cf__link", href: `tel:${clinicPhone.replace(/[^\d+]/g, "")}`, children: [
4211
+ /* @__PURE__ */ jsxs16("a", { className: "sk-cf__link", href: `tel:${normalizeAuPhone(clinicPhone)}`, children: [
3949
4212
  "Call the clinic on ",
3950
4213
  clinicPhone
3951
4214
  ] })
@@ -3987,7 +4250,7 @@ function UnavailableStep({
3987
4250
  children: "Find a GP near you"
3988
4251
  }
3989
4252
  ) }),
3990
- clinicPhone && /* @__PURE__ */ jsx18("li", { children: /* @__PURE__ */ jsxs16("a", { className: "sk-cf__link", href: `tel:${clinicPhone.replace(/[^\d+]/g, "")}`, children: [
4253
+ clinicPhone && /* @__PURE__ */ jsx18("li", { children: /* @__PURE__ */ jsxs16("a", { className: "sk-cf__link", href: `tel:${normalizeAuPhone(clinicPhone)}`, children: [
3991
4254
  "Call the clinic on ",
3992
4255
  clinicPhone
3993
4256
  ] }) }),
@@ -4034,6 +4297,13 @@ function CertificateFunnelClient(props) {
4034
4297
  }
4035
4298
  return /* @__PURE__ */ jsx19(PortalProvider, { client: portalClient, children: /* @__PURE__ */ jsx19(Funnel, { ...props }) });
4036
4299
  }
4300
+ var RESERVATION_REQUIRED_STEPS = /* @__PURE__ */ new Set([
4301
+ "identity",
4302
+ "confirm-identity",
4303
+ "medicare",
4304
+ "consent",
4305
+ "payment"
4306
+ ]);
4037
4307
  function Funnel({
4038
4308
  publicApi,
4039
4309
  services = [],
@@ -4043,6 +4313,7 @@ function Funnel({
4043
4313
  serviceKey: pinnedService
4044
4314
  }) {
4045
4315
  const api = publicApi;
4316
+ const portalClient = usePortalClient();
4046
4317
  const rawClient = React16.useMemo(
4047
4318
  () => patientos({ publishableKey: api.publishableKey, apiBase: api.apiBase }),
4048
4319
  [api.publishableKey, api.apiBase]
@@ -4050,15 +4321,17 @@ function Funnel({
4050
4321
  const pinned = React16.useMemo(() => ({ pinnedService: !!pinnedService }), [pinnedService]);
4051
4322
  const [state, setState] = React16.useState(null);
4052
4323
  const [flow, setFlow] = React16.useState(null);
4324
+ const [applicationRun, setApplicationRun] = React16.useState(null);
4053
4325
  const [flowChecked, setFlowChecked] = React16.useState(false);
4054
4326
  const [capturing, setCapturing] = React16.useState(false);
4055
4327
  const [cancellingLostSlot, setCancellingLostSlot] = React16.useState(false);
4056
4328
  const [lostSlotCancelError, setLostSlotCancelError] = React16.useState(null);
4057
4329
  const [resolveError, setResolveError] = React16.useState(null);
4330
+ const [reopeningScreening, setReopeningScreening] = React16.useState(false);
4331
+ const [reopenScreeningError, setReopenScreeningError] = React16.useState(null);
4332
+ const applicationRunCommands = React16.useRef(new ApplicationRunCommandRetrier()).current;
4333
+ const beginAttemptRef = React16.useRef(null);
4058
4334
  const [identityDraft, setIdentityDraft] = React16.useState(null);
4059
- const [submittedIdentitySnapshot, setSubmittedIdentitySnapshot] = React16.useState(
4060
- null
4061
- );
4062
4335
  const onDraftChange = React16.useCallback((draft) => setIdentityDraft(draft), []);
4063
4336
  const [medicareDraft, setMedicareDraft] = React16.useState(null);
4064
4337
  const onMedicareDraftChange = React16.useCallback(
@@ -4096,6 +4369,7 @@ function Funnel({
4096
4369
  null
4097
4370
  );
4098
4371
  const [questionCounts, setQuestionCounts] = React16.useState({});
4372
+ const [enabledServiceKeys, setEnabledServiceKeys] = React16.useState(null);
4099
4373
  React16.useEffect(() => {
4100
4374
  let alive = true;
4101
4375
  void (async () => {
@@ -4108,7 +4382,10 @@ function Funnel({
4108
4382
  const nextFees = {};
4109
4383
  const nextDeliveryModes = {};
4110
4384
  const nextCounts = {};
4385
+ const nextEnabledKeys = /* @__PURE__ */ new Set();
4111
4386
  for (const svc of svcs) {
4387
+ if (svc.anonymousApplicationRuns === false) continue;
4388
+ nextEnabledKeys.add(svc.key);
4112
4389
  const option = pickDeliveryOption(options, svc.id);
4113
4390
  nextFees[svc.key] = effectiveFee(svc, option);
4114
4391
  if (option) nextDeliveryModes[svc.key] = option.deliveryMode;
@@ -4117,10 +4394,12 @@ function Funnel({
4117
4394
  setFees(nextFees);
4118
4395
  setDeliveryModes(nextDeliveryModes);
4119
4396
  setQuestionCounts(nextCounts);
4397
+ setEnabledServiceKeys(nextEnabledKeys);
4120
4398
  } catch {
4121
4399
  if (alive) {
4122
4400
  setFees({});
4123
4401
  setDeliveryModes({});
4402
+ setEnabledServiceKeys(/* @__PURE__ */ new Set());
4124
4403
  }
4125
4404
  }
4126
4405
  })();
@@ -4147,66 +4426,221 @@ function Funnel({
4147
4426
  return () => window.removeEventListener("popstate", onPop);
4148
4427
  }, [dispatch, pinnedService, pinned]);
4149
4428
  const requestId = state?.requestId ?? null;
4429
+ const applicationRunId = state?.applicationRunId ?? null;
4430
+ const flowId = requestId ?? applicationRunId;
4150
4431
  React16.useEffect(() => {
4151
- if (!requestId) {
4432
+ if (!flowId) {
4152
4433
  setFlow(null);
4153
4434
  setFlowChecked(false);
4435
+ setApplicationRun(null);
4154
4436
  return;
4155
4437
  }
4156
- if (flow) {
4157
- setFlowChecked(true);
4438
+ if (!flow) setFlow(loadFlowRecord(flowId));
4439
+ setFlowChecked(true);
4440
+ }, [flowId, flow]);
4441
+ const hydrateRunFlow = React16.useCallback(async (run) => {
4442
+ const existing = loadFlowRecord(run.id);
4443
+ if (existing) {
4444
+ setFlow(existing);
4158
4445
  return;
4159
4446
  }
4160
- setFlow(loadFlowRecord(requestId));
4161
- setFlowChecked(true);
4162
- }, [requestId, flow]);
4163
- const captureFlow = React16.useCallback(
4164
- async (newRequestId, serviceKey, identity) => {
4447
+ const [svc, options] = await Promise.all([
4448
+ findServiceByKey(client, run.serviceKey),
4449
+ once(client.subscribeDeliveryOptions)
4450
+ ]);
4451
+ const option = options.find((candidate) => candidate.id === run.serviceDeliveryOptionId);
4452
+ if (!svc || !option) throw new Error("This application option is no longer available.");
4453
+ const record = {
4454
+ serviceId: svc.id,
4455
+ serviceKey: svc.key,
4456
+ serviceLabel: svc.label,
4457
+ deliveryOptionId: option.id,
4458
+ appointmentTypeId: run.appointmentTypeId ?? "",
4459
+ feeAmount: option.feeOverride ?? svc.feeAmount,
4460
+ requiresPayment: svc.requiresPayment,
4461
+ deliveryMode: run.deliveryMode,
4462
+ givenName: "",
4463
+ familyName: "",
4464
+ dateOfBirth: null,
4465
+ questionSetId: run.questionSetId,
4466
+ bookedFor: void 0
4467
+ };
4468
+ saveFlowRecord(run.id, record);
4469
+ setFlow(record);
4470
+ }, [client]);
4471
+ React16.useEffect(() => {
4472
+ if (!applicationRunId || applicationRun?.id === applicationRunId) return;
4473
+ let alive = true;
4474
+ void (async () => {
4475
+ const params = new URLSearchParams(window.location.search);
4476
+ const handoffId = params.get("cfhandoff");
4477
+ if (handoffId) {
4478
+ const redeemed = await portalClient.redeemApplicationRunHandoff(handoffId);
4479
+ if (!redeemed || !alive) throw new Error("This sign-in continuation has expired.");
4480
+ rawClient.setApplicationRunToken(applicationRunId, redeemed.capabilityToken);
4481
+ saveApplicationRunToken(rawClient, applicationRunId);
4482
+ setApplicationRun(redeemed.run);
4483
+ await hydrateRunFlow(redeemed.run);
4484
+ const next = {
4485
+ ...stateRef.current ?? initialFunnelState(pinnedService),
4486
+ step: redeemed.run.deliveryMode === "scheduled" && !redeemed.run.reservation ? "slot" : "confirm-identity",
4487
+ applicationRunId,
4488
+ serviceKey: redeemed.run.serviceKey,
4489
+ scheduled: redeemed.run.deliveryMode === "scheduled"
4490
+ };
4491
+ dispatch({ type: "RESTORE", state: next });
4492
+ params.delete("cfhandoff");
4493
+ window.history.replaceState(null, "", `${window.location.pathname}?${params.toString()}${window.location.hash}`);
4494
+ return;
4495
+ }
4496
+ if (!restoreApplicationRunToken(rawClient, applicationRunId)) {
4497
+ throw new Error("This application can only be resumed from its secure sign-in link.");
4498
+ }
4499
+ const resumed = await rawClient.resumeApplicationRun(applicationRunId);
4500
+ if (!alive) return;
4501
+ setApplicationRun(resumed);
4502
+ await hydrateRunFlow(resumed);
4503
+ const current = stateRef.current;
4504
+ if (current) {
4505
+ dispatch({
4506
+ type: "RESTORE",
4507
+ state: {
4508
+ ...current,
4509
+ step: resumed.deliveryMode === "scheduled" && !resumed.reservation && RESERVATION_REQUIRED_STEPS.has(current.step) ? "slot" : current.step,
4510
+ serviceKey: resumed.serviceKey,
4511
+ scheduled: resumed.deliveryMode === "scheduled",
4512
+ gate: resumed.gate ? { kind: resumed.gate.verdict === "escalate" ? "escalate" : "block", ruleId: resumed.gate.ruleId, message: resumed.gate.message } : current.gate
4513
+ }
4514
+ });
4515
+ }
4516
+ })().catch((err) => {
4517
+ if (alive) setResolveError(err instanceof Error ? err.message : "We could not resume this application.");
4518
+ });
4519
+ return () => {
4520
+ alive = false;
4521
+ };
4522
+ }, [applicationRunId, applicationRun?.id, dispatch, hydrateRunFlow, pinnedService, portalClient, rawClient]);
4523
+ const reopenScreening = React16.useCallback(async () => {
4524
+ if (!applicationRunId || !applicationRun || applicationRun.status !== "screening_complete" && applicationRun.status !== "reserved") {
4525
+ return;
4526
+ }
4527
+ setReopeningScreening(true);
4528
+ setReopenScreeningError(null);
4529
+ try {
4530
+ const run = await applicationRunCommands.act(client, applicationRunId, {
4531
+ expectedRevision: applicationRun.revision,
4532
+ action: { type: "reopen_screening" }
4533
+ });
4534
+ setApplicationRun(run);
4535
+ } catch (err) {
4536
+ setReopenScreeningError(
4537
+ errorMessage(err, "We could not reopen your screening. Please try again.")
4538
+ );
4539
+ } finally {
4540
+ setReopeningScreening(false);
4541
+ }
4542
+ }, [applicationRun, applicationRunCommands, applicationRunId, client]);
4543
+ React16.useEffect(() => {
4544
+ if (state?.step === "intake") void reopenScreening();
4545
+ }, [reopenScreening, state?.step]);
4546
+ const beginRun = React16.useCallback(async (serviceKey) => {
4547
+ if (capturing) return;
4548
+ setCapturing(true);
4549
+ setResolveError(null);
4550
+ try {
4165
4551
  const svc = await findServiceByKey(client, serviceKey);
4166
- if (!svc) throw new Error("This certificate type is not currently available.");
4552
+ if (!svc || svc.anonymousApplicationRuns === false) {
4553
+ throw new Error("This certificate type is not currently available.");
4554
+ }
4167
4555
  const option = await findDeliveryOption(client, svc.id);
4168
4556
  if (!option) throw new Error("No booking option is available for this service.");
4557
+ let attempt = beginAttemptRef.current;
4558
+ if (!attempt || attempt.serviceKey !== serviceKey || attempt.serviceDeliveryOptionId !== option.id) {
4559
+ const bytes = new Uint8Array(32);
4560
+ crypto.getRandomValues(bytes);
4561
+ attempt = {
4562
+ serviceKey,
4563
+ serviceDeliveryOptionId: option.id,
4564
+ idempotencyKey: crypto.randomUUID(),
4565
+ capabilityToken: Array.from(
4566
+ bytes,
4567
+ (b) => b.toString(16).padStart(2, "0")
4568
+ ).join("")
4569
+ };
4570
+ beginAttemptRef.current = attempt;
4571
+ }
4572
+ const result = await client.beginApplicationRun(attempt);
4573
+ beginAttemptRef.current = null;
4574
+ saveApplicationRunToken(client, result.run.id);
4169
4575
  const record = {
4170
4576
  serviceId: svc.id,
4171
4577
  serviceKey: svc.key,
4172
4578
  serviceLabel: svc.label,
4173
4579
  deliveryOptionId: option.id,
4174
4580
  appointmentTypeId: option.appointmentTypeId ?? "",
4175
- // The EFFECTIVE fee: the option's override wins over the service base (PAT-710).
4176
4581
  feeAmount: option.feeOverride ?? svc.feeAmount,
4177
- // Whether money is taken AT ALL — a separate fact from the amount, and the one
4178
- // the consent wording branches on. A null fee alone could not distinguish
4179
- // "bulk billed" from "not resolved yet" (PAT-853).
4180
4582
  requiresPayment: svc.requiresPayment,
4181
4583
  deliveryMode: option.deliveryMode,
4182
- givenName: identity.givenName,
4183
- familyName: identity.familyName,
4184
- dateOfBirth: identity.dateOfBirth || null,
4584
+ givenName: "",
4585
+ familyName: "",
4586
+ dateOfBirth: null,
4185
4587
  questionSetId: svc.questionSetId
4186
4588
  };
4187
- saveFlowRecord(newRequestId, record);
4589
+ saveFlowRecord(result.run.id, record);
4188
4590
  setFlow(record);
4189
- },
4190
- [client]
4191
- );
4192
- const back2 = React16.useCallback(() => dispatch({ type: "BACK" }), [dispatch]);
4591
+ setApplicationRun(result.run);
4592
+ dispatch({
4593
+ type: "RUN_STARTED",
4594
+ applicationRunId: result.run.id,
4595
+ scheduled: result.run.deliveryMode === "scheduled"
4596
+ });
4597
+ } catch (err) {
4598
+ setResolveError(err instanceof Error ? err.message : "We could not start your application.");
4599
+ } finally {
4600
+ setCapturing(false);
4601
+ }
4602
+ }, [capturing, client, dispatch]);
4603
+ const back2 = React16.useCallback(() => {
4604
+ const runId = state?.applicationRunId;
4605
+ if (state?.step === "intake" && runId) {
4606
+ void client.closeApplicationRun(runId).finally(() => dispatch({ type: "BACK" }));
4607
+ return;
4608
+ }
4609
+ const returnsToIntake = state?.step === "slot" || state?.step === "identity" && state.scheduled === false;
4610
+ if (returnsToIntake && runId && applicationRun && (applicationRun.status === "screening_complete" || applicationRun.status === "reserved")) {
4611
+ void applicationRunCommands.act(client, runId, {
4612
+ expectedRevision: applicationRun.revision,
4613
+ action: { type: "reopen_screening" }
4614
+ }).then((run) => {
4615
+ setApplicationRun(run);
4616
+ dispatch({ type: "BACK" });
4617
+ }).catch((err) => {
4618
+ setResolveError(errorMessage(err, "We could not reopen your screening. Please try again."));
4619
+ });
4620
+ return;
4621
+ }
4622
+ dispatch({ type: "BACK" });
4623
+ }, [applicationRun, applicationRunCommands, client, dispatch, state]);
4193
4624
  const restart = React16.useCallback(() => {
4194
4625
  setIdentityDraft(null);
4195
- setSubmittedIdentitySnapshot(null);
4196
4626
  setMedicareDraft(null);
4197
4627
  dispatch({ type: "RESTART" });
4198
4628
  }, [dispatch]);
4199
4629
  const retryPayment = React16.useCallback(() => {
4200
- setSubmittedIdentitySnapshot(null);
4201
4630
  dispatch({ type: "RETRY_PAYMENT" });
4202
4631
  }, [dispatch]);
4203
4632
  const cancelLostSlot = React16.useCallback(async () => {
4204
4633
  const id = state?.requestId;
4205
- if (!id || cancellingLostSlot) return;
4634
+ const runId = state?.applicationRunId;
4635
+ if (!id && !runId || cancellingLostSlot) return;
4206
4636
  setCancellingLostSlot(true);
4207
4637
  setLostSlotCancelError(null);
4208
4638
  try {
4209
- await cancelRequestThenRestart(client, id, restart);
4639
+ if (id) await cancelRequestThenRestart(client, id, restart);
4640
+ else if (runId) {
4641
+ await client.closeApplicationRun(runId);
4642
+ restart();
4643
+ }
4210
4644
  setCancellingLostSlot(false);
4211
4645
  } catch (err) {
4212
4646
  setCancellingLostSlot(false);
@@ -4214,7 +4648,7 @@ function Funnel({
4214
4648
  errorCode(err) === "not_cancellable" ? "This request can no longer be cancelled \u2014 please call the clinic." : "We could not cancel your request. Please try again."
4215
4649
  );
4216
4650
  }
4217
- }, [cancellingLostSlot, client, restart, state?.requestId]);
4651
+ }, [cancellingLostSlot, client, restart, state?.applicationRunId, state?.requestId]);
4218
4652
  const chosenKey = state?.serviceKey ?? null;
4219
4653
  const step = state?.step;
4220
4654
  const shape = React16.useMemo(() => {
@@ -4240,16 +4674,32 @@ function Funnel({
4240
4674
  }, [flow, fees, pinnedService, chosenKey, step, questionCounts]);
4241
4675
  if (!state) return /* @__PURE__ */ jsx19(FlowLoading, {});
4242
4676
  const title = stepTitle(state.step, clinicName);
4243
- const service = services.find((s) => s.key === state.serviceKey) ?? null;
4677
+ const selectableServices = enabledServiceKeys ? services.filter((candidate) => enabledServiceKeys.has(candidate.key)) : [];
4678
+ const service = selectableServices.find((candidate) => candidate.key === state.serviceKey) ?? null;
4244
4679
  const serviceLabel = service?.label ?? "certificate";
4245
- const identitySnapshot = identityDraft ? JSON.stringify(identityDraft) : null;
4246
- const resumeRequestId = resumableRequestId(
4247
- state.requestId,
4248
- submittedIdentitySnapshot,
4249
- identitySnapshot
4250
- );
4251
4680
  const showBack = canGoBack(state, pinned);
4252
4681
  const flowLost = slotFlowIsLost(flowChecked, flow, capturing);
4682
+ const reservationWasLost = (err) => {
4683
+ const code2 = errorCode(err);
4684
+ return code2 === "reservation_expired" || code2 === "slot_taken";
4685
+ };
4686
+ async function bookReservedSlot() {
4687
+ const id = requestId;
4688
+ const reservation = applicationRun?.reservation;
4689
+ if (!id || !reservation) {
4690
+ throw new PatientOSApiError2({
4691
+ status: 409,
4692
+ code: "reservation_expired",
4693
+ message: "Your appointment reservation has expired. Please choose another time.",
4694
+ path: `/requests/${id ?? "unknown"}/book`
4695
+ });
4696
+ }
4697
+ await client.bookRequest(id, {
4698
+ practitionerId: reservation.practitionerId,
4699
+ slotStart: reservation.slotStart,
4700
+ holdId: reservation.holdId
4701
+ });
4702
+ }
4253
4703
  const quietMoney = HOLDLESS_STEPS.has(state.step) || state.step === "consent" || state.step === "payment";
4254
4704
  const rail = /* @__PURE__ */ jsx19(
4255
4705
  TrustRail,
@@ -4264,7 +4714,7 @@ function Funnel({
4264
4714
  return /* @__PURE__ */ jsx19(
4265
4715
  ServiceStep,
4266
4716
  {
4267
- services,
4717
+ services: selectableServices,
4268
4718
  fees,
4269
4719
  deliveryModes,
4270
4720
  heading: heading2,
@@ -4275,69 +4725,111 @@ function Funnel({
4275
4725
  }
4276
4726
  );
4277
4727
  case "preflight":
4278
- return /* @__PURE__ */ jsx19(
4279
- PreflightStep,
4280
- {
4281
- documentTitle: title,
4282
- onConfirm: () => dispatch({ type: "PREFLIGHT_CONFIRMED" }),
4283
- onExit: (gate) => dispatch({ type: "GATE", gate }),
4284
- onOutOfScope: () => dispatch({ type: "NEED_DIFFERENT" }),
4285
- onBack: showBack ? back2 : void 0
4286
- }
4287
- );
4728
+ if (!enabledServiceKeys) return /* @__PURE__ */ jsx19(FlowLoading, {});
4729
+ if (!state.serviceKey || !enabledServiceKeys.has(state.serviceKey)) {
4730
+ return /* @__PURE__ */ jsxs17(
4731
+ FlowShell,
4732
+ {
4733
+ ...stepProgress("preflight", shape),
4734
+ stepKey: "preflight:unavailable",
4735
+ heading: "This certificate type is unavailable",
4736
+ documentTitle: title,
4737
+ onBack: showBack ? back2 : void 0,
4738
+ children: [
4739
+ /* @__PURE__ */ jsx19(FlowError, { children: "This certificate type is not currently available through this website." }),
4740
+ /* @__PURE__ */ jsx19(
4741
+ FlowButton,
4742
+ {
4743
+ type: "button",
4744
+ variant: "primary",
4745
+ onClick: () => dispatch({ type: "NEED_DIFFERENT" }),
4746
+ children: "Choose another option"
4747
+ }
4748
+ )
4749
+ ]
4750
+ }
4751
+ );
4752
+ }
4753
+ return /* @__PURE__ */ jsxs17("div", { children: [
4754
+ /* @__PURE__ */ jsx19(
4755
+ PreflightStep,
4756
+ {
4757
+ documentTitle: title,
4758
+ onConfirm: () => void beginRun(state.serviceKey ?? ""),
4759
+ onExit: (gate) => dispatch({ type: "GATE", gate }),
4760
+ onOutOfScope: () => dispatch({ type: "NEED_DIFFERENT" }),
4761
+ onBack: showBack ? back2 : void 0
4762
+ }
4763
+ ),
4764
+ /* @__PURE__ */ jsx19(FlowError, { children: resolveError })
4765
+ ] });
4288
4766
  case "identity":
4289
4767
  case "confirm-identity":
4290
- return /* @__PURE__ */ jsxs17(
4291
- "div",
4292
- {
4293
- onSubmitCapture: (event) => {
4294
- if (!resumeRequestId) return;
4295
- event.preventDefault();
4296
- event.stopPropagation();
4297
- setResolveError(null);
4298
- dispatch({ type: "REQUEST_STARTED", requestId: resumeRequestId });
4299
- },
4300
- children: [
4301
- /* @__PURE__ */ jsx19(
4302
- IdentityStep,
4303
- {
4304
- client,
4305
- turnstileSiteKey: api.turnstileSiteKey,
4306
- serviceKey: state.serviceKey ?? "",
4307
- serviceLabel,
4308
- mode: state.step,
4309
- documentTitle: title,
4310
- onSignedIn: () => dispatch({ type: "SIGNED_IN" }),
4311
- onRejected: () => {
4312
- setIdentityDraft(null);
4313
- setMedicareDraft(null);
4314
- dispatch({ type: "IDENTITY_REJECTED" });
4315
- },
4316
- onBack: showBack ? back2 : void 0,
4317
- draft: identityDraft ?? void 0,
4318
- onDraftChange,
4319
- onStarted: (newRequestId, identity) => {
4320
- const capturedDraft = capturedIdentityDraft(identity, identityDraft);
4321
- setCapturing(true);
4322
- setIdentityDraft(capturedDraft);
4323
- setSubmittedIdentitySnapshot(JSON.stringify(capturedDraft));
4324
- dispatch({ type: "REQUEST_STARTED", requestId: newRequestId });
4325
- void captureFlow(newRequestId, state.serviceKey ?? "", identity).catch(
4326
- (e) => setResolveError(e instanceof Error ? e.message : "Something went wrong.")
4327
- ).finally(() => setCapturing(false));
4328
- }
4768
+ return /* @__PURE__ */ jsxs17("div", { children: [
4769
+ /* @__PURE__ */ jsx19(
4770
+ IdentityStep,
4771
+ {
4772
+ client,
4773
+ turnstileSiteKey: api.turnstileSiteKey,
4774
+ serviceKey: state.serviceKey ?? "",
4775
+ serviceLabel,
4776
+ applicationRunId: state.applicationRunId ?? void 0,
4777
+ reservationExpiresAt: applicationRun?.reservation?.expiresAt ?? null,
4778
+ mode: state.step,
4779
+ documentTitle: title,
4780
+ onSignedIn: () => dispatch({ type: "SIGNED_IN" }),
4781
+ onRejected: () => {
4782
+ setIdentityDraft(null);
4783
+ setMedicareDraft(null);
4784
+ dispatch({ type: "IDENTITY_REJECTED" });
4785
+ },
4786
+ onBack: showBack ? back2 : void 0,
4787
+ draft: identityDraft ?? void 0,
4788
+ onDraftChange,
4789
+ onStarted: (newRequestId, identity) => {
4790
+ const capturedDraft = capturedIdentityDraft(identity, identityDraft);
4791
+ setIdentityDraft(capturedDraft);
4792
+ if (flow) {
4793
+ const next = {
4794
+ ...flow,
4795
+ givenName: identity.givenName,
4796
+ familyName: identity.familyName,
4797
+ dateOfBirth: identity.dateOfBirth || null
4798
+ };
4799
+ saveFlowRecord(newRequestId, next);
4800
+ setFlow(next);
4329
4801
  }
4330
- ),
4331
- /* @__PURE__ */ jsx19(FlowError, { children: resolveError })
4332
- ]
4333
- }
4334
- );
4802
+ dispatch({ type: "REQUEST_STARTED", requestId: newRequestId });
4803
+ }
4804
+ }
4805
+ ),
4806
+ /* @__PURE__ */ jsx19(FlowError, { children: resolveError })
4807
+ ] });
4335
4808
  case "intake":
4809
+ if (reopeningScreening || reopenScreeningError || applicationRun?.status === "screening_complete" || applicationRun?.status === "reserved") {
4810
+ return /* @__PURE__ */ jsxs17(
4811
+ FlowShell,
4812
+ {
4813
+ ...stepProgress("intake", shape),
4814
+ stepKey: "intake:reopening",
4815
+ heading: "Review your screening",
4816
+ documentTitle: title,
4817
+ onBack: showBack ? back2 : void 0,
4818
+ children: [
4819
+ reopeningScreening && /* @__PURE__ */ jsx19(FlowLoading, { message: "Reopening your screening\u2026" }),
4820
+ /* @__PURE__ */ jsx19(FlowError, { children: reopenScreeningError }),
4821
+ reopenScreeningError && /* @__PURE__ */ jsx19(FlowButton, { type: "button", variant: "primary", onClick: () => void reopenScreening(), children: "Try again" })
4822
+ ]
4823
+ }
4824
+ );
4825
+ }
4336
4826
  return /* @__PURE__ */ jsx19(
4337
4827
  IntakeStep,
4338
4828
  {
4339
4829
  client,
4340
- requestId: state.requestId ?? "",
4830
+ applicationRunId: state.applicationRunId ?? void 0,
4831
+ applicationRunRevision: applicationRun?.revision ?? 0,
4832
+ onApplicationRunChange: setApplicationRun,
4341
4833
  questionSetId: flow ? flow.questionSetId ?? null : void 0,
4342
4834
  resolveError,
4343
4835
  documentTitle: title,
@@ -4388,7 +4880,22 @@ function Funnel({
4388
4880
  phone: identityDraft?.identity.phone ?? "",
4389
4881
  serviceLabel: service?.label ?? "",
4390
4882
  documentTitle: title,
4391
- onSubmitted: (status, gate) => dispatch({ type: "SUBMITTED", status, gate }),
4883
+ onSubmitted: async (status, gate) => {
4884
+ if (status === "awaiting_slot") {
4885
+ try {
4886
+ await bookReservedSlot();
4887
+ dispatch({ type: "SUBMITTED", status: "ready", gate });
4888
+ } catch (err) {
4889
+ if (reservationWasLost(err)) {
4890
+ dispatch({ type: "RESERVATION_LOST" });
4891
+ return;
4892
+ }
4893
+ throw err;
4894
+ }
4895
+ } else {
4896
+ dispatch({ type: "SUBMITTED", status, gate });
4897
+ }
4898
+ },
4392
4899
  onGate: (gate) => dispatch({ type: "GATE", gate }),
4393
4900
  onBack: showBack ? back2 : void 0
4394
4901
  }
@@ -4411,7 +4918,22 @@ function Funnel({
4411
4918
  feeAmount: flow?.feeAmount ?? null,
4412
4919
  serviceLabel,
4413
4920
  clinicPhone,
4414
- onOutcome: (outcome) => dispatch({ type: "PAY_OUTCOME", outcome })
4921
+ onOutcome: async (outcome) => {
4922
+ if (outcome.kind === "awaiting_slot") {
4923
+ try {
4924
+ await bookReservedSlot();
4925
+ dispatch({ type: "PAY_OUTCOME", outcome: { kind: "ready" } });
4926
+ } catch (err) {
4927
+ if (reservationWasLost(err)) {
4928
+ dispatch({ type: "RESERVATION_LOST" });
4929
+ return;
4930
+ }
4931
+ throw err;
4932
+ }
4933
+ } else {
4934
+ dispatch({ type: "PAY_OUTCOME", outcome });
4935
+ }
4936
+ }
4415
4937
  }
4416
4938
  )
4417
4939
  }
@@ -4421,20 +4943,24 @@ function Funnel({
4421
4943
  SlotStep,
4422
4944
  {
4423
4945
  client,
4424
- requestId: state.requestId ?? "",
4946
+ requestId: state.requestId ?? void 0,
4947
+ applicationRunId: state.requestId ? void 0 : state.applicationRunId ?? void 0,
4948
+ applicationRunRevision: applicationRun?.revision ?? 0,
4949
+ onApplicationRunChange: setApplicationRun,
4950
+ replacementReservation: Boolean(state.requestId && state.applicationRunId),
4425
4951
  appointmentTypeId: flow.appointmentTypeId,
4426
4952
  isFree: flowIsFree(flow),
4427
4953
  clinicPhone,
4428
4954
  onCancelled: restart,
4429
4955
  documentTitle: title,
4430
4956
  onBooked: (bookedFor) => {
4431
- const id = state.requestId;
4957
+ const id = state.requestId ?? state.applicationRunId;
4432
4958
  if (id && flow) {
4433
4959
  const next = { ...flow, bookedFor };
4434
4960
  saveFlowRecord(id, next);
4435
4961
  setFlow(next);
4436
4962
  }
4437
- dispatch({ type: "BOOKED" });
4963
+ dispatch({ type: state.requestId ? "BOOKED" : "RESERVED" });
4438
4964
  }
4439
4965
  }
4440
4966
  ) : flowLost ? (
@@ -4462,7 +4988,7 @@ function Funnel({
4462
4988
  clinicPhone && /* @__PURE__ */ jsxs17("p", { className: "sk-slots__empty-note", children: [
4463
4989
  "Can\u2019t find a time that suits?",
4464
4990
  " ",
4465
- /* @__PURE__ */ jsxs17("a", { className: "sk-cf__link", href: `tel:${clinicPhone.replace(/[^\d+]/g, "")}`, children: [
4991
+ /* @__PURE__ */ jsxs17("a", { className: "sk-cf__link", href: `tel:${normalizeAuPhone(clinicPhone)}`, children: [
4466
4992
  "Call the clinic on ",
4467
4993
  clinicPhone
4468
4994
  ] })
@@ -4556,14 +5082,14 @@ function SessionExpiredState({
4556
5082
  stepKey: "session-expired",
4557
5083
  heading: "Your secure session ended",
4558
5084
  documentTitle,
4559
- footer: portalUrl ? /* @__PURE__ */ jsx19("a", { className: "sk-flow__btn sk-flow__btn--primary", href: portalUrl, children: "Continue in the patient portal" }) : !requestStarted ? /* @__PURE__ */ jsx19(FlowButton, { type: "button", variant: "primary", onClick: onRestart, children: "Start again securely" }) : void 0,
5085
+ footer: portalUrl ? /* @__PURE__ */ jsx19("a", { className: "sk-flow__btn sk-flow__btn--primary", href: portalUrl, children: "Continue in the patient portal" }) : requestStarted ? void 0 : /* @__PURE__ */ jsx19(FlowButton, { type: "button", variant: "primary", onClick: onRestart, children: "Start again securely" }),
4560
5086
  children: [
4561
5087
  /* @__PURE__ */ jsxs17("p", { className: "sk-cf__body", children: [
4562
5088
  "This secure session is no longer available.",
4563
5089
  " ",
4564
5090
  requestStarted ? "Your request is still with the clinic." : "You can start again securely."
4565
5091
  ] }),
4566
- clinicPhone ? /* @__PURE__ */ jsx19(ClinicPhone, { clinicPhone }) : !portalUrl ? /* @__PURE__ */ jsx19("p", { className: "sk-cf__aside", children: "Please contact the clinic using the details on this website." }) : null
5092
+ clinicPhone ? /* @__PURE__ */ jsx19(ClinicPhone, { clinicPhone }) : portalUrl ? null : /* @__PURE__ */ jsx19("p", { className: "sk-cf__aside", children: "Please contact the clinic using the details on this website." })
4567
5093
  ]
4568
5094
  }
4569
5095
  );
@@ -10953,14 +11479,10 @@ function PortalAccountSurfaceClient({
10953
11479
  portalUrl,
10954
11480
  bookHref,
10955
11481
  initialState,
10956
- fetchImpl: rawFetchImpl,
10957
11482
  openUrl
10958
11483
  }) {
10959
11484
  const portalClient = usePortalClient();
10960
- const fetchImpl = React21.useMemo(
10961
- () => portalFetchForClient(portalClient, rawFetchImpl),
10962
- [portalClient, rawFetchImpl]
10963
- );
11485
+ const fetchImpl = React21.useMemo(() => portalFetchForClient(portalClient), [portalClient]);
10964
11486
  const [state, setState] = React21.useState(
10965
11487
  // `readPortalSignedInHint` is in the initialiser (not an effect) on purpose: the
10966
11488
  // whole point is that the FIRST paint already knows, so there is no placeholder