riverbankcms 0.18.0 → 0.19.0

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.
@@ -1478,7 +1478,7 @@ function resolveFlexibleBalanceDueAt(input) {
1478
1478
  switch (input.dueDateRule.kind) {
1479
1479
  case "relative_to_start_date": return subtractDaysFromIso(input.bookingStartAt, input.dueDateRule.daysBeforeStart);
1480
1480
  case "fixed_calendar_date": return input.dueDateRule.dueAt;
1481
- default: return assertNever$4(input.dueDateRule);
1481
+ default: return assertNever$5(input.dueDateRule);
1482
1482
  }
1483
1483
  }
1484
1484
  function calculateFlexibleBalanceMinimumPaymentCents(input) {
@@ -1533,7 +1533,7 @@ function resolveFlexibleBalancePaymentEligibility(input) {
1533
1533
  suggestedPayFullAmountCents: input.remainingBalanceCents
1534
1534
  };
1535
1535
  }
1536
- default: return assertNever$4(input.status);
1536
+ default: return assertNever$5(input.status);
1537
1537
  }
1538
1538
  }
1539
1539
  function isFlexibleBalanceReminderSuppressed(input) {
@@ -1569,7 +1569,7 @@ function buildFlexibleBalanceReminderSchedule(input) {
1569
1569
  })
1570
1570
  }));
1571
1571
  }
1572
- function assertNever$4(value) {
1572
+ function assertNever$5(value) {
1573
1573
  throw new Error(`Unexpected value: ${String(value)}`);
1574
1574
  }
1575
1575
 
@@ -1601,6 +1601,10 @@ function createOriginalPaymentRefundCommand(options) {
1601
1601
  protectedDepositOverride
1602
1602
  };
1603
1603
  }
1604
+ /** Narrow an already typed schedule-write response by its receipt discriminant. */
1605
+ function isAcceptedEventScheduleUpdate(response) {
1606
+ return "kind" in response && response.kind === "accepted_unavailable";
1607
+ }
1604
1608
 
1605
1609
  //#endregion
1606
1610
  //#region ../core/src/booking-instalments.ts
@@ -1609,7 +1613,7 @@ function isDepositBearingBookingInstalmentPlan(plan) {
1609
1613
  case "deposit_plus_one_remainder":
1610
1614
  case "deposit_plus_scheduled_instalments": return true;
1611
1615
  case "equal_scheduled_instalments": return false;
1612
- default: return assertNever$3(plan);
1616
+ default: return assertNever$4(plan);
1613
1617
  }
1614
1618
  }
1615
1619
  function getBookingInstalmentDepositRefundability(plan) {
@@ -1642,7 +1646,7 @@ function addIntervalPreservingMonthAnchor(isoString, intervalCount, intervalUnit
1642
1646
  date.setUTCDate(date.getUTCDate() + intervalCount * 7);
1643
1647
  return date.toISOString();
1644
1648
  case "month": return addMonthsPreservingAnchorDay(date, intervalCount).toISOString();
1645
- default: return assertNever$3(intervalUnit);
1649
+ default: return assertNever$4(intervalUnit);
1646
1650
  }
1647
1651
  }
1648
1652
  function getBookingInstalmentPlanIntervalCount(plan) {
@@ -1650,7 +1654,7 @@ function getBookingInstalmentPlanIntervalCount(plan) {
1650
1654
  case "deposit_plus_one_remainder": return plan.remainderDueCount;
1651
1655
  case "deposit_plus_scheduled_instalments":
1652
1656
  case "equal_scheduled_instalments": return plan.intervalCount;
1653
- default: return assertNever$3(plan);
1657
+ default: return assertNever$4(plan);
1654
1658
  }
1655
1659
  }
1656
1660
  function omitZeroAmountFutureRows(rows) {
@@ -1674,7 +1678,7 @@ function formatOrderInstalmentStatusLabel(input) {
1674
1678
  case "cancelled": return "Cancelled";
1675
1679
  case "cancelled_as_paid_early": return "Paid early";
1676
1680
  case "refunded": return "Refunded";
1677
- default: return assertNever$3(input.status);
1681
+ default: return assertNever$4(input.status);
1678
1682
  }
1679
1683
  }
1680
1684
  const NEXT_DUE_STATUSES = new Set(["scheduled", "failed"]);
@@ -1695,7 +1699,7 @@ function addMonthsPreservingAnchorDay(date, intervalCount) {
1695
1699
  nextMonth.setUTCDate(Math.min(anchorDay, lastDayOfTargetMonth));
1696
1700
  return nextMonth;
1697
1701
  }
1698
- function assertNever$3(value) {
1702
+ function assertNever$4(value) {
1699
1703
  throw new Error(`Unexpected value: ${String(value)}`);
1700
1704
  }
1701
1705
 
@@ -1830,6 +1834,42 @@ function validateBookingPaymentOptionKinds(kinds) {
1830
1834
  return ok(undefined);
1831
1835
  }
1832
1836
 
1837
+ //#endregion
1838
+ //#region ../core/src/date-time-formatter.ts
1839
+ /** Shared Intl engine, internal to core's date presentation owners. */
1840
+ const FORMATTER_CACHE_LIMIT$1 = 100;
1841
+ const dateTimeFormatterCache = new Map();
1842
+ function getDateTimeFormatter(locale, timeZone, format) {
1843
+ const formatKey = JSON.stringify(Object.entries(format).sort(([left], [right]) => left.localeCompare(right)));
1844
+ const key = `${locale}\u0000${timeZone}\u0000${formatKey}`;
1845
+ const cached = dateTimeFormatterCache.get(key);
1846
+ if (cached !== undefined) {
1847
+ return cached;
1848
+ }
1849
+ if (typeof locale !== "string" || Intl.DateTimeFormat.supportedLocalesOf([locale]).length === 0) {
1850
+ throw new RangeError("An explicit supported locale is required");
1851
+ }
1852
+ if (typeof timeZone !== "string" || timeZone.length === 0) {
1853
+ throw new RangeError("An explicit time zone is required");
1854
+ }
1855
+ const formatter = new Intl.DateTimeFormat(locale, {
1856
+ ...format,
1857
+ timeZone
1858
+ });
1859
+ if (dateTimeFormatterCache.size >= FORMATTER_CACHE_LIMIT$1) {
1860
+ dateTimeFormatterCache.clear();
1861
+ }
1862
+ dateTimeFormatterCache.set(key, formatter);
1863
+ return formatter;
1864
+ }
1865
+ function formatIntlRange(formatter, start, end) {
1866
+ const nativeFormatRange = Reflect.get(formatter, "formatRange");
1867
+ if (typeof nativeFormatRange !== "function") throw new Error("Intl.DateTimeFormat.formatRange is required");
1868
+ const output = Reflect.apply(nativeFormatRange, formatter, [start, end]);
1869
+ if (typeof output !== "string") throw new Error("Intl.DateTimeFormat.formatRange returned a non-string value");
1870
+ return output;
1871
+ }
1872
+
1833
1873
  //#endregion
1834
1874
  //#region ../core/src/booking-time-zone.ts
1835
1875
  const TIME_ZONE_VALIDITY_CACHE_LIMIT = 100;
@@ -1864,7 +1904,7 @@ function parseBookingTimeZone(timeZone) {
1864
1904
 
1865
1905
  //#endregion
1866
1906
  //#region ../core/src/booking-date-time.ts
1867
- const DEFAULT_LOCALE = "en-US";
1907
+ const DEFAULT_LOCALE$1 = "en-US";
1868
1908
  const FALLBACK_TIMEZONE = "UTC";
1869
1909
  const DEFAULT_BOOKING_DATE_TIME_FORMAT = {
1870
1910
  weekday: "short",
@@ -1879,8 +1919,6 @@ const START_OF_DAY = {
1879
1919
  second: 0
1880
1920
  };
1881
1921
  const LOCAL_DATE_PATTERN = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/u;
1882
- const DATE_TIME_FORMATTER_CACHE_LIMIT = 100;
1883
- const dateTimeFormatterCache = new Map();
1884
1922
  function getSafeTimeZone(timeZone) {
1885
1923
  const normalized = typeof timeZone === "string" ? timeZone.trim() : "";
1886
1924
  return normalized.length > 0 && isValidBookingTimeZone(normalized) ? normalized : FALLBACK_TIMEZONE;
@@ -1897,24 +1935,7 @@ function formatParseError(error) {
1897
1935
  }
1898
1936
  }
1899
1937
  function formatToPartsLookup(date, locale, timeZone, format) {
1900
- return new Map(getCachedDateTimeFormatter(locale, timeZone, format).formatToParts(date).map((part) => [part.type, part.value]));
1901
- }
1902
- function getCachedDateTimeFormatter(locale, timeZone, format) {
1903
- const formatKey = JSON.stringify(Object.entries(format).sort(([left], [right]) => left.localeCompare(right)));
1904
- const key = `${locale}\u0000${timeZone}\u0000${formatKey}`;
1905
- const cached = dateTimeFormatterCache.get(key);
1906
- if (cached !== undefined) {
1907
- return cached;
1908
- }
1909
- const formatter = new Intl.DateTimeFormat(locale, {
1910
- ...format,
1911
- timeZone
1912
- });
1913
- if (dateTimeFormatterCache.size >= DATE_TIME_FORMATTER_CACHE_LIMIT) {
1914
- dateTimeFormatterCache.clear();
1915
- }
1916
- dateTimeFormatterCache.set(key, formatter);
1917
- return formatter;
1938
+ return new Map(getDateTimeFormatter(locale, timeZone, format).formatToParts(date).map((part) => [part.type, part.value]));
1918
1939
  }
1919
1940
  function getZonedDateTimeParts(date, timeZone) {
1920
1941
  const parts = formatToPartsLookup(date, "en-GB", timeZone, {
@@ -2056,7 +2077,7 @@ function formatBookingInstant(isoString, options) {
2056
2077
  if (Number.isNaN(date.getTime())) {
2057
2078
  return isoString;
2058
2079
  }
2059
- return getCachedDateTimeFormatter(options.locale ?? DEFAULT_LOCALE, getSafeTimeZone(options.timeZone), options.format).format(date);
2080
+ return getDateTimeFormatter(options.locale ?? DEFAULT_LOCALE$1, getSafeTimeZone(options.timeZone), options.format).format(date);
2060
2081
  }
2061
2082
  function formatBookingDate(input, format = {
2062
2083
  weekday: "long",
@@ -2329,6 +2350,469 @@ function localScheduleDateTimeToUtcDate(localDate, localTime, timeZone) {
2329
2350
  return new Date(guess);
2330
2351
  }
2331
2352
 
2353
+ //#endregion
2354
+ //#region ../core/src/date-display.ts
2355
+ /** Site-language policy, shared by customer emails and public event adapters. */
2356
+ function inferSiteDateLocale(siteTimeZone) {
2357
+ if (siteTimeZone === null || siteTimeZone === undefined || siteTimeZone === "") return "en-US";
2358
+ if (siteTimeZone === "Europe/Dublin") return "en-IE";
2359
+ if (siteTimeZone.startsWith("Europe/")) return "en-GB";
2360
+ if (siteTimeZone.startsWith("Australia/")) return "en-AU";
2361
+ if (siteTimeZone === "Pacific/Auckland" || siteTimeZone === "Pacific/Chatham") return "en-NZ";
2362
+ return "en-US";
2363
+ }
2364
+ /** Callers resolve the facts once; all clocks consume the same label decision. */
2365
+ function resolveBookingDisplayContext(input) {
2366
+ return {
2367
+ context: {
2368
+ locale: input.locale,
2369
+ timeZone: input.sessionTimeZone
2370
+ },
2371
+ zone: input.surface === "email" && input.isOnline || input.sessionTimeZone !== input.siteTimeZone ? "always" : "omit"
2372
+ };
2373
+ }
2374
+ const SDK_STANDARD_TIME_FORMAT = {
2375
+ hour: "numeric",
2376
+ minute: "2-digit"
2377
+ };
2378
+ /** Public SDK option policy lives beside the display profile, never in its adapter. */
2379
+ function sdkPresentationProfile(context, dateFormat$1, time = {}) {
2380
+ const compact = time.timePresentation !== "standard" && new Intl.Locale(context.locale).language === "en";
2381
+ const standard = time.timePresentation === "standard" ? time.format ?? SDK_STANDARD_TIME_FORMAT : SDK_STANDARD_TIME_FORMAT;
2382
+ return {
2383
+ kind: "sdk",
2384
+ dateFormat: dateFormat$1,
2385
+ clock: compact ? {
2386
+ kind: "compact",
2387
+ localeSource: "context"
2388
+ } : {
2389
+ kind: "intl",
2390
+ format: standard
2391
+ },
2392
+ zone: "omit"
2393
+ };
2394
+ }
2395
+ function formatDate(value, context, options = {}) {
2396
+ return presentDate(value, context, displayProfile(options, "omit"));
2397
+ }
2398
+ function formatDateRange(start, endInclusive, context, options = {}) {
2399
+ return presentDateRange({
2400
+ kind: "civil",
2401
+ value: projectDate(start, context)
2402
+ }, {
2403
+ kind: "civil",
2404
+ value: projectDate(endInclusive, context)
2405
+ }, context, displayProfile(options, "omit"));
2406
+ }
2407
+ function formatDateTime(value, context, options) {
2408
+ return presentDateTime(value, context, displayProfile(options, options.zone));
2409
+ }
2410
+ function formatDateTimeRange(span, context, options) {
2411
+ return presentDateTimeRange(span, context, displayProfile(options, options.zone));
2412
+ }
2413
+ function formatTime(value, context, options) {
2414
+ return presentTime(value, context, displayProfile({}, options.zone));
2415
+ }
2416
+ function formatTimeRange(start, end, context, options) {
2417
+ return presentTimeRange({
2418
+ start,
2419
+ end
2420
+ }, context, displayProfile({}, options.zone));
2421
+ }
2422
+ /** Sole date projection/typography owner, used by display and scheduled adapters. */
2423
+ function presentDate(value, context, profile) {
2424
+ const date = dateCarrier(value);
2425
+ const zone = value.kind === "civil" ? "UTC" : context.timeZone;
2426
+ const output = getDateTimeFormatter(context.locale, zone, profile.dateFormat).format(date);
2427
+ if (profile.kind === "sdk") return output;
2428
+ const normalized = normalizeDisplay(output);
2429
+ return profile.dateFormat.weekday && new Intl.Locale(context.locale).language === "en" ? normalized.replace(/^([^,]+), /u, "$1 ") : normalized;
2430
+ }
2431
+ function presentDateRange(start, end, context, profile) {
2432
+ if (dateCarrier(end).getTime() < dateCarrier(start).getTime()) throw new RangeError("Date range end must be on or after its start");
2433
+ if (profile.kind === "display" && compareLocalScheduleDates(projectDate(start, context), projectDate(end, context)) === 0) {
2434
+ return presentDate(start, context, profile);
2435
+ }
2436
+ const output = formatIntlRange(getDateTimeFormatter(context.locale, start.kind === "civil" ? "UTC" : context.timeZone, profile.dateFormat), dateCarrier(start), dateCarrier(end));
2437
+ if (profile.kind === "sdk") return output;
2438
+ const startDate = projectDate(start, context);
2439
+ const endDate = projectDate(end, context);
2440
+ const separator = startDate.month === endDate.month && startDate.year === endDate.year ? "–" : " – ";
2441
+ return normalizeDisplay(output).replace(/\s*–\s*/gu, separator);
2442
+ }
2443
+ function presentTime(value, context, profile) {
2444
+ const clock = presentClock(value, context, profile.clock);
2445
+ return profile.zone === "always" ? `${clock} ${zoneLabel(value, context)}` : clock;
2446
+ }
2447
+ function presentDateTime(value, context, profile) {
2448
+ if (profile.clock.kind === "intl") {
2449
+ return getDateTimeFormatter(context.locale, context.timeZone, combinedFormat(profile.dateFormat, profile.clock)).format(new Date(value));
2450
+ }
2451
+ return `${presentDate({
2452
+ kind: "instant",
2453
+ value
2454
+ }, context, profile)}, ${presentTime(value, context, profile)}`;
2455
+ }
2456
+ function presentTimeRange(span, context, profile) {
2457
+ assertOrdered(span.start, span.end);
2458
+ if (profile.clock.kind === "intl") return presentNativeRange(span, context, profile.clock.format);
2459
+ const startClock = presentClock(span.start, context, profile.clock);
2460
+ const endClock = presentClock(span.end, context, profile.clock);
2461
+ const equal = profile.kind === "sdk" ? startClock === endClock : span.start === span.end;
2462
+ const clocks = equal ? startClock : `${startClock}–${endClock}`;
2463
+ const labels = decideRangeLabels(span, context, profile);
2464
+ switch (labels.kind) {
2465
+ case "omit": return clocks;
2466
+ case "shared": return `${clocks} ${labels.label}`;
2467
+ case "endpoints": return `${startClock} ${labels.start} – ${endClock} ${labels.end}`;
2468
+ }
2469
+ }
2470
+ function presentDateTimeRange(span, context, profile) {
2471
+ assertOrdered(span.start, span.end);
2472
+ if (profile.clock.kind === "intl") return presentNativeRange(span, context, combinedFormat(profile.dateFormat, profile.clock));
2473
+ const startDate = getLocalScheduleDate(new Date(span.start), context.timeZone);
2474
+ const endDate = getLocalScheduleDate(new Date(span.end), context.timeZone);
2475
+ if (compareLocalScheduleDates(startDate, endDate) === 0) {
2476
+ return `${presentDate({
2477
+ kind: "instant",
2478
+ value: span.start
2479
+ }, context, profile)}, ${presentTimeRange(span, context, profile)}`;
2480
+ }
2481
+ const labels = decideRangeLabels(span, context, profile);
2482
+ const startProfile = profile.kind === "display" && startDate.year === endDate.year ? {
2483
+ ...profile,
2484
+ dateFormat: {
2485
+ ...profile.dateFormat,
2486
+ year: undefined
2487
+ }
2488
+ } : profile;
2489
+ const start = presentRangeEndpoint(span.start, context, startProfile, labels.kind === "endpoints" ? labels.start : undefined);
2490
+ const end = presentRangeEndpoint(span.end, context, profile, labels.kind === "endpoints" ? labels.end : undefined);
2491
+ const separator = profile.kind === "sdk" ? "–" : " – ";
2492
+ return profile.kind === "sdk" && start === end ? start : `${start}${separator}${end}`;
2493
+ }
2494
+ /** Offset changes override ordinary venue omission for the approved display profile. */
2495
+ function decideRangeLabels(span, context, profile) {
2496
+ if (profile.kind === "sdk") return { kind: "omit" };
2497
+ const offsetsDiffer = offsetToken(span.start, context) !== offsetToken(span.end, context);
2498
+ if (!offsetsDiffer && profile.zone === "omit") return { kind: "omit" };
2499
+ const start = zoneLabel(span.start, context);
2500
+ const end = zoneLabel(span.end, context);
2501
+ return offsetsDiffer || start !== end ? {
2502
+ kind: "endpoints",
2503
+ start,
2504
+ end
2505
+ } : {
2506
+ kind: "shared",
2507
+ label: start
2508
+ };
2509
+ }
2510
+ function presentRangeEndpoint(value, context, profile, label) {
2511
+ const date = presentDate({
2512
+ kind: "instant",
2513
+ value
2514
+ }, context, profile);
2515
+ const time = label === undefined ? presentTime(value, context, profile) : `${presentClock(value, context, profile.clock)} ${label}`;
2516
+ return `${date}, ${time}`;
2517
+ }
2518
+ function presentNativeRange(span, context, format) {
2519
+ return formatIntlRange(getDateTimeFormatter(context.locale, context.timeZone, format), new Date(span.start), new Date(span.end));
2520
+ }
2521
+ function combinedFormat(date, clock) {
2522
+ return {
2523
+ ...date,
2524
+ ...clock.format
2525
+ };
2526
+ }
2527
+ function displayProfile(options, zone) {
2528
+ return {
2529
+ kind: "display",
2530
+ dateFormat: dateFormat(options),
2531
+ clock: {
2532
+ kind: "compact",
2533
+ localeSource: "product"
2534
+ },
2535
+ zone
2536
+ };
2537
+ }
2538
+ function dateCarrier(value) {
2539
+ return value.kind === "civil" ? civilCarrier(value.value) : new Date(value.value);
2540
+ }
2541
+ function presentClock(value, context, clock) {
2542
+ return clock.kind === "compact" ? formatBookingCompactEnglishTime({
2543
+ startsAt: value,
2544
+ timeZone: context.timeZone,
2545
+ locale: clock.localeSource === "product" ? "en-GB" : context.locale
2546
+ }) : getDateTimeFormatter(context.locale, context.timeZone, clock.format).format(new Date(value));
2547
+ }
2548
+ function offsetToken(value, context) {
2549
+ return getDateTimeFormatter("en-GB", context.timeZone, { timeZoneName: "longOffset" }).formatToParts(new Date(value)).find((part) => part.type === "timeZoneName")?.value ?? "";
2550
+ }
2551
+ function formatTimeAgo(value, now, context) {
2552
+ return formatRelative(value, now, context, "past");
2553
+ }
2554
+ function formatTimeUntil(value, now, context) {
2555
+ return formatRelative(value, now, context, "future");
2556
+ }
2557
+ function dateFormat(options) {
2558
+ const style = options.date ?? "long";
2559
+ return {
2560
+ day: style === "numeric" ? "2-digit" : "numeric",
2561
+ month: style === "numeric" ? "2-digit" : style,
2562
+ ...options.year === "omit" ? {} : { year: "numeric" },
2563
+ ...options.weekday ? { weekday: options.weekday } : {}
2564
+ };
2565
+ }
2566
+ function civilCarrier(value) {
2567
+ return new Date(Date.UTC(value.year, value.month - 1, value.day, 12));
2568
+ }
2569
+ function projectDate(value, context) {
2570
+ return value.kind === "civil" ? value.value : getLocalScheduleDate(new Date(value.value), context.timeZone);
2571
+ }
2572
+ function normalizeDisplay(value) {
2573
+ return value.replace(/[\u00a0\u202f]/gu, " ");
2574
+ }
2575
+ function zoneToken(value, context, locale) {
2576
+ return getDateTimeFormatter(locale, context.timeZone, { timeZoneName: "short" }).formatToParts(new Date(value)).find((part) => part.type === "timeZoneName")?.value ?? "";
2577
+ }
2578
+ function zoneLabel(value, context) {
2579
+ const tokens = [zoneToken(value, context, "en-GB"), zoneToken(value, context, "en-US")];
2580
+ const abbreviation = tokens.find((token) => /^[A-Z]{2,6}$/u.test(token));
2581
+ const fallback = context.timeZone.startsWith("Etc/") ? "local time" : `${context.timeZone.split("/").slice(-1)[0]?.replace(/_/gu, " ") ?? context.timeZone} time`;
2582
+ return abbreviation ?? fallback;
2583
+ }
2584
+ function assertOrdered(start, end) {
2585
+ if (new Date(end).getTime() < new Date(start).getTime()) {
2586
+ throw new RangeError("Date/time range end must be on or after its start");
2587
+ }
2588
+ }
2589
+ function calendarDayDifference(value, now, context) {
2590
+ return compareLocalScheduleDates(getLocalScheduleDate(new Date(value), context.timeZone), getLocalScheduleDate(new Date(now), context.timeZone)) / 864e5;
2591
+ }
2592
+ function formatRelative(value, now, context, direction) {
2593
+ const seconds = (new Date(value).getTime() - new Date(now).getTime()) / 1e3;
2594
+ const magnitude = Math.abs(seconds);
2595
+ if (magnitude < 60) return relativeNearNow(context.locale, direction);
2596
+ const days = calendarDayDifference(value, now, context);
2597
+ if (Math.abs(days) > 7) return formatDate({
2598
+ kind: "instant",
2599
+ value
2600
+ }, context);
2601
+ const formatter = new Intl.RelativeTimeFormat(context.locale, { numeric: "auto" });
2602
+ if (days !== 0) return formatter.format(days, "day");
2603
+ const unit = magnitude < 3600 ? "minute" : "hour";
2604
+ const signedCount = Math.sign(seconds) * Math.floor(magnitude / (unit === "minute" ? 60 : 3600));
2605
+ return formatter.format(signedCount, unit);
2606
+ }
2607
+ function relativeNearNow(locale, direction) {
2608
+ if (new Intl.Locale(locale).language === "en") return direction === "past" ? "just now" : "in a moment";
2609
+ return new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(0, "second");
2610
+ }
2611
+
2612
+ //#endregion
2613
+ //#region ../core/src/scheduled-date-time.ts
2614
+ const DEFAULT_LOCALE = "en-GB";
2615
+ const FORMATTER_CACHE_LIMIT = 100;
2616
+ const DEFAULT_DATE_FORMAT = {
2617
+ weekday: "long",
2618
+ year: "numeric",
2619
+ month: "long",
2620
+ day: "numeric"
2621
+ };
2622
+ const DEFAULT_RANGE_DATE_FORMAT = {
2623
+ weekday: "short",
2624
+ year: "numeric",
2625
+ month: "short",
2626
+ day: "numeric"
2627
+ };
2628
+ const parsedLocaleCache = new Map();
2629
+ var RiverbankDateTimeFormatError = class extends Error {
2630
+ constructor(details) {
2631
+ super(formatErrorMessage(details));
2632
+ this.name = "RiverbankDateTimeFormatError";
2633
+ this.code = details.code;
2634
+ this.field = details.field;
2635
+ this.value = details.value;
2636
+ }
2637
+ };
2638
+ /** Public contract adapters: admission/options here, all presentation in date-display. */
2639
+ function formatScheduledDate(input, options = {}) {
2640
+ const parsed = parseDateTime(input);
2641
+ return presentDate({
2642
+ kind: "instant",
2643
+ value: parsed.startsAt
2644
+ }, scheduledContext(parsed), sdkPresentationProfile(scheduledContext(parsed), options.format ?? DEFAULT_DATE_FORMAT));
2645
+ }
2646
+ function formatScheduledTime(input, options = {}) {
2647
+ const parsed = parseDateTime(input);
2648
+ return presentTime(parsed.startsAt, scheduledContext(parsed), sdkPresentationProfile(scheduledContext(parsed), {}, options));
2649
+ }
2650
+ function formatScheduledDateTime(input, options = {}) {
2651
+ const parsed = parseDateTime(input);
2652
+ return presentDateTime(parsed.startsAt, scheduledContext(parsed), sdkPresentationProfile(scheduledContext(parsed), options.dateFormat ?? DEFAULT_DATE_FORMAT, options.time));
2653
+ }
2654
+ function formatScheduledDateRange(input, options = {}) {
2655
+ const parsed = parseDateRange(input);
2656
+ return presentDateRange({
2657
+ kind: "instant",
2658
+ value: parsed.startsAt
2659
+ }, {
2660
+ kind: "instant",
2661
+ value: parsed.endsAt
2662
+ }, scheduledContext(parsed), sdkPresentationProfile(scheduledContext(parsed), options.format ?? DEFAULT_RANGE_DATE_FORMAT));
2663
+ }
2664
+ function formatScheduledTimeRange(input, options = {}) {
2665
+ const parsed = parseDateRange(input);
2666
+ return presentTimeRange({
2667
+ start: parsed.startsAt,
2668
+ end: parsed.endsAt
2669
+ }, scheduledContext(parsed), sdkPresentationProfile(scheduledContext(parsed), {}, options));
2670
+ }
2671
+ function formatScheduledDateTimeRange(input, options = {}) {
2672
+ const parsed = parseDateRange(input);
2673
+ return presentDateTimeRange({
2674
+ start: parsed.startsAt,
2675
+ end: parsed.endsAt
2676
+ }, scheduledContext(parsed), sdkPresentationProfile(scheduledContext(parsed), options.dateFormat ?? DEFAULT_RANGE_DATE_FORMAT, options.time));
2677
+ }
2678
+ function scheduledContext(input) {
2679
+ return {
2680
+ locale: input.locale,
2681
+ timeZone: input.timeZone
2682
+ };
2683
+ }
2684
+ function parseDateTime(input) {
2685
+ const locale = parseLocale(input.locale);
2686
+ assertStringTimeZone(input.timeZone);
2687
+ const result = parseScheduledBookingDateTime({
2688
+ ...input,
2689
+ locale: locale.locale
2690
+ });
2691
+ if (result.ok) {
2692
+ return {
2693
+ ...result.value,
2694
+ locale: locale.locale
2695
+ };
2696
+ }
2697
+ throw fromCoreParseError(result);
2698
+ }
2699
+ function parseDateRange(input) {
2700
+ const locale = parseLocale(input.locale);
2701
+ assertStringTimeZone(input.timeZone);
2702
+ assertRequiredEnd(input.endsAt);
2703
+ const result = parseScheduledBookingDateRange({
2704
+ ...input,
2705
+ locale: locale.locale
2706
+ });
2707
+ if (!result.ok) {
2708
+ throw fromCoreParseError(result);
2709
+ }
2710
+ if (result.value.endsAt === null || result.value.endsAt === undefined) {
2711
+ throw new RiverbankDateTimeFormatError({
2712
+ code: "missing_ends_at",
2713
+ field: "endsAt",
2714
+ value: ""
2715
+ });
2716
+ }
2717
+ if (new Date(result.value.endsAt).getTime() < new Date(result.value.startsAt).getTime()) {
2718
+ throw new RiverbankDateTimeFormatError({
2719
+ code: "end_before_start",
2720
+ field: "endsAt",
2721
+ value: input.endsAt
2722
+ });
2723
+ }
2724
+ return {
2725
+ ...result.value,
2726
+ endsAt: result.value.endsAt,
2727
+ locale: locale.locale
2728
+ };
2729
+ }
2730
+ function assertRequiredEnd(value) {
2731
+ if (value === null || value === undefined) {
2732
+ throw new RiverbankDateTimeFormatError({
2733
+ code: "missing_ends_at",
2734
+ field: "endsAt",
2735
+ value: ""
2736
+ });
2737
+ }
2738
+ }
2739
+ function parseLocale(value) {
2740
+ const locale = value === undefined ? DEFAULT_LOCALE : value;
2741
+ if (typeof locale !== "string") {
2742
+ throw new RiverbankDateTimeFormatError({
2743
+ code: "invalid_locale",
2744
+ field: "locale",
2745
+ value: ""
2746
+ });
2747
+ }
2748
+ const cached = parsedLocaleCache.get(locale);
2749
+ if (cached !== undefined) {
2750
+ return cached;
2751
+ }
2752
+ try {
2753
+ const parsed = new Intl.Locale(locale);
2754
+ const canonical = parsed.toString();
2755
+ if (Intl.DateTimeFormat.supportedLocalesOf([canonical]).length === 0) {
2756
+ throw new RangeError("Unsupported locale");
2757
+ }
2758
+ const result = { locale: canonical };
2759
+ if (parsedLocaleCache.size >= FORMATTER_CACHE_LIMIT) {
2760
+ parsedLocaleCache.clear();
2761
+ }
2762
+ parsedLocaleCache.set(locale, result);
2763
+ return result;
2764
+ } catch {
2765
+ throw new RiverbankDateTimeFormatError({
2766
+ code: "invalid_locale",
2767
+ field: "locale",
2768
+ value: typeof value === "string" ? value : ""
2769
+ });
2770
+ }
2771
+ }
2772
+ function assertStringTimeZone(value) {
2773
+ if (typeof value !== "string") {
2774
+ throw new RiverbankDateTimeFormatError({
2775
+ code: "invalid_time_zone",
2776
+ field: "timeZone",
2777
+ value: ""
2778
+ });
2779
+ }
2780
+ }
2781
+ function fromCoreParseError(error) {
2782
+ switch (error.reason) {
2783
+ case "invalid_starts_at": return new RiverbankDateTimeFormatError({
2784
+ code: error.reason,
2785
+ field: error.field,
2786
+ value: typeof error.value === "string" ? error.value : ""
2787
+ });
2788
+ case "invalid_ends_at": return new RiverbankDateTimeFormatError({
2789
+ code: error.reason,
2790
+ field: error.field,
2791
+ value: typeof error.value === "string" ? error.value : ""
2792
+ });
2793
+ case "invalid_time_zone": return new RiverbankDateTimeFormatError({
2794
+ code: error.reason,
2795
+ field: error.field,
2796
+ value: error.value
2797
+ });
2798
+ default: return assertNever$3(error);
2799
+ }
2800
+ }
2801
+ function formatErrorMessage(details) {
2802
+ switch (details.code) {
2803
+ case "invalid_starts_at": return `Expected startsAt to be a valid offset-aware ISO datetime, received "${details.value}"`;
2804
+ case "invalid_ends_at": return `Expected endsAt to be a valid offset-aware ISO datetime, received "${details.value}"`;
2805
+ case "missing_ends_at": return "Expected endsAt to be provided for a scheduled range";
2806
+ case "invalid_time_zone": return `Expected timeZone to be a valid IANA timezone, received "${details.value}"`;
2807
+ case "invalid_locale": return `Expected locale to be a supported BCP 47 locale, received "${details.value}"`;
2808
+ case "end_before_start": return `Expected endsAt to be on or after startsAt, received "${details.value}"`;
2809
+ default: return assertNever$3(details);
2810
+ }
2811
+ }
2812
+ function assertNever$3(value) {
2813
+ throw new Error(`Unhandled date-time variant: ${String(value)}`);
2814
+ }
2815
+
2332
2816
  //#endregion
2333
2817
  //#region ../core/src/appointment-service-categories.ts
2334
2818
  /**
@@ -3735,6 +4219,32 @@ function parsePublicSocialLinks(value) {
3735
4219
  }
3736
4220
  return ok(links);
3737
4221
  }
4222
+ /** Historical stored profiles read absent description as null. */
4223
+ function parsePublicBusinessDescription(value) {
4224
+ const description = trimmedOptionalString(value);
4225
+ return description === undefined || description !== null && description.length > 2e3 ? err({
4226
+ code: "invalid_business_description",
4227
+ path: "businessDescription",
4228
+ message: "Use a business description of 2,000 characters or fewer."
4229
+ }) : ok(description);
4230
+ }
4231
+ /** Public copy and visibility preferences share historical absent-field defaults. */
4232
+ function parsePublicProfilePresentation(value) {
4233
+ const businessDescription = parsePublicBusinessDescription(value.businessDescription);
4234
+ if (!businessDescription.ok) return businessDescription;
4235
+ const addressVisibility = parseAddressVisibility(value.addressVisibility);
4236
+ if (!addressVisibility.ok) return addressVisibility;
4237
+ const contactVisibility = parseContactVisibility(value.contactVisibility);
4238
+ if (!contactVisibility.ok) return contactVisibility;
4239
+ const premises = parsePremises(value.premises);
4240
+ if (!premises.ok) return premises;
4241
+ return ok({
4242
+ businessDescription: businessDescription.value,
4243
+ addressVisibility: addressVisibility.value,
4244
+ contactVisibility: contactVisibility.value,
4245
+ premises: premises.value
4246
+ });
4247
+ }
3738
4248
  function parsePublicProfile(value) {
3739
4249
  if (!isRecord$8(value)) {
3740
4250
  return err({
@@ -3743,25 +4253,19 @@ function parsePublicProfile(value) {
3743
4253
  message: "Public profile must be an object."
3744
4254
  });
3745
4255
  }
4256
+ const presentation = parsePublicProfilePresentation(value);
4257
+ if (!presentation.ok) return presentation;
3746
4258
  const contact = value.contact === null ? ok(null) : parsePublicContactDetails(value.contact);
3747
4259
  if (!contact.ok) return contact;
3748
4260
  const openingHours = value.openingHours === null ? ok(null) : parseWeeklyOpeningHours(value.openingHours);
3749
4261
  if (!openingHours.ok) return openingHours;
3750
4262
  const socialLinks = parsePublicSocialLinks(value.socialLinks);
3751
4263
  if (!socialLinks.ok) return socialLinks;
3752
- const addressVisibility = parseAddressVisibility(value.addressVisibility);
3753
- if (!addressVisibility.ok) return addressVisibility;
3754
- const contactVisibility = parseContactVisibility(value.contactVisibility);
3755
- if (!contactVisibility.ok) return contactVisibility;
3756
- const premises = parsePremises(value.premises);
3757
- if (!premises.ok) return premises;
3758
4264
  return ok({
4265
+ ...presentation.value,
3759
4266
  contact: contact.value,
3760
- contactVisibility: contactVisibility.value,
3761
4267
  openingHours: openingHours.value,
3762
- socialLinks: socialLinks.value,
3763
- addressVisibility: addressVisibility.value,
3764
- premises: premises.value
4268
+ socialLinks: socialLinks.value
3765
4269
  });
3766
4270
  }
3767
4271
  function parseFieldVisibility(value, path) {
@@ -3855,6 +4359,7 @@ function parseAddressVisibility(value) {
3855
4359
  }
3856
4360
  function emptyPublicProfile() {
3857
4361
  return {
4362
+ businessDescription: null,
3858
4363
  contact: null,
3859
4364
  contactVisibility: {
3860
4365
  phone: "shown",
@@ -4405,6 +4910,18 @@ function parseFooterEngagementSectionValue(value, sectionBase, path) {
4405
4910
  }
4406
4911
  }
4407
4912
 
4913
+ //#endregion
4914
+ //#region ../core/src/footer-navigation-layout.ts
4915
+ const footerNavigationLayouts = ["stack", "inline-wrap"];
4916
+ const defaultFooterNavigationLayout = "inline-wrap";
4917
+ function normalizeFooterNavigationLayout(value) {
4918
+ return value === "stack" ? "stack" : defaultFooterNavigationLayout;
4919
+ }
4920
+ /** The arrangement chooses presentation; the stored preference remains intact. */
4921
+ function effectiveFooterNavigationLayout(arrangement, stored) {
4922
+ return arrangement === "centred" ? "inline-wrap" : stored;
4923
+ }
4924
+
4408
4925
  //#endregion
4409
4926
  //#region ../core/src/footer-arrangement.ts
4410
4927
  const sectionKinds = [
@@ -4419,85 +4936,119 @@ const sectionKinds = [
4419
4936
  "legal-navigation",
4420
4937
  "legal"
4421
4938
  ];
4422
- const sectionKindOrder = new Map(sectionKinds.map((kind, index) => [kind, index]));
4423
- const visualGroupOrder = [
4939
+ const centredSectionKinds = [
4940
+ "brand",
4941
+ "supporting-text",
4942
+ "navigation",
4943
+ "contact",
4944
+ "business-address",
4945
+ "opening-hours",
4946
+ "social-links",
4947
+ "engagement",
4948
+ "legal",
4949
+ "legal-navigation"
4950
+ ];
4951
+ /** Section ownership is exhaustive for both arrangements; content never changes placement. */
4952
+ const rowSectionGroups = {
4953
+ brand: "identity",
4954
+ "supporting-text": "identity",
4955
+ "social-links": "identity",
4956
+ navigation: "main-navigation",
4957
+ "business-address": "visit",
4958
+ contact: "visit",
4959
+ "opening-hours": "visit",
4960
+ engagement: "engagement",
4961
+ "legal-navigation": "legal-ending",
4962
+ legal: "legal-ending"
4963
+ };
4964
+ const centredSectionGroups = {
4965
+ ...rowSectionGroups,
4966
+ "social-links": "social"
4967
+ };
4968
+ const rowGroupOrder = [
4424
4969
  "identity",
4425
4970
  "main-navigation",
4426
4971
  "visit",
4427
4972
  "engagement",
4428
4973
  "legal-ending"
4429
4974
  ];
4430
- function groupFor(kind) {
4431
- switch (kind) {
4432
- case "brand":
4433
- case "supporting-text":
4434
- case "social-links": return "identity";
4435
- case "navigation": return "main-navigation";
4436
- case "business-address":
4437
- case "contact":
4438
- case "opening-hours": return "visit";
4439
- case "engagement": return "engagement";
4440
- case "legal-navigation":
4441
- case "legal": return "legal-ending";
4442
- }
4975
+ const centredGroupOrder = [
4976
+ "identity",
4977
+ "main-navigation",
4978
+ "visit",
4979
+ "social",
4980
+ "engagement",
4981
+ "legal-ending"
4982
+ ];
4983
+ const groupSlots = {
4984
+ identity: "head",
4985
+ "main-navigation": "navigation",
4986
+ visit: "contact",
4987
+ social: "social",
4988
+ engagement: "panel",
4989
+ "legal-ending": "base"
4990
+ };
4991
+ const regionSlots = {
4992
+ "engagement-row": "panel",
4993
+ main: "stack",
4994
+ bottom: "base"
4995
+ };
4996
+ /** One policy owner for the served footer and its composition preview. */
4997
+ function footerArrangementPolicy(input) {
4998
+ const arrangement = input.arrangement ?? "simple";
4999
+ return {
5000
+ arrangement,
5001
+ kind: arrangement === "centred" || input.legalRegion === "separate" ? "separate-legal-region" : "integrated-legal",
5002
+ navigationLayout: effectiveFooterNavigationLayout(arrangement, normalizeFooterNavigationLayout(input.storedNavigationLayout))
5003
+ };
4443
5004
  }
4444
- function buildGroups(sections, navigationLayout) {
4445
- const groups = new Map();
4446
- for (const section of sections) {
4447
- const kind = groupFor(section.kind);
4448
- groups.set(kind, [...groups.get(kind) ?? [], section]);
4449
- }
4450
- return visualGroupOrder.flatMap((kind) => {
4451
- const groupSections = groups.get(kind);
4452
- return groupSections === undefined || groupSections.length === 0 ? [] : [{
5005
+ function buildGroups(sections, policy) {
5006
+ const centred = policy.arrangement === "centred";
5007
+ const order = centred ? centredGroupOrder : rowGroupOrder;
5008
+ const owners = centred ? centredSectionGroups : rowSectionGroups;
5009
+ const sectionOrder = centred ? centredSectionKinds : sectionKinds;
5010
+ return order.flatMap((kind) => {
5011
+ const groupSections = sections.filter((section) => owners[section.kind] === kind).sort((left, right) => sectionOrder.indexOf(left.kind) - sectionOrder.indexOf(right.kind));
5012
+ return groupSections.length === 0 ? [] : [{
4453
5013
  kind,
4454
- width: kind === "main-navigation" && navigationLayout === "inline-wrap" ? "full-row" : "column",
4455
- sections: [...groupSections].sort((left, right) => (sectionKindOrder.get(left.kind) ?? Number.MAX_SAFE_INTEGER) - (sectionKindOrder.get(right.kind) ?? Number.MAX_SAFE_INTEGER))
5014
+ width: kind === "main-navigation" && policy.navigationLayout === "inline-wrap" ? "full-row" : "column",
5015
+ sections: groupSections,
5016
+ ...centred ? { slot: groupSlots[kind] } : {}
4456
5017
  }];
4457
5018
  });
4458
5019
  }
4459
5020
  function membershipKey(sections) {
4460
5021
  const tokens = sections.map((section) => ({
4461
- rank: sectionKindOrder.get(section.kind) ?? Number.MAX_SAFE_INTEGER,
5022
+ rank: sectionKinds.indexOf(section.kind),
4462
5023
  value: section.kind === "engagement" ? `engagement:${section.treatment}` : section.kind
4463
- }));
4464
- tokens.sort((left, right) => left.rank - right.rank);
5024
+ })).sort((left, right) => left.rank - right.rank);
4465
5025
  return tokens.map(({ value }) => value).join("+") || "empty";
4466
5026
  }
5027
+ function renderRegion(kind, groups, policy) {
5028
+ if (groups.length === 0) return [];
5029
+ const columns = kind !== "main" || policy.arrangement === "centred" ? 1 : Math.max(1, Math.min(groups.filter((group) => group.width === "column").length, 5));
5030
+ return [{
5031
+ kind,
5032
+ columns,
5033
+ groups,
5034
+ ...policy.arrangement === "centred" ? { slot: regionSlots[kind] } : {}
5035
+ }];
5036
+ }
4467
5037
  function planFooterArrangement(renderableSections, policy) {
4468
5038
  const selectedSections = renderableSections.map(({ section }) => section);
4469
- const groups = buildGroups(selectedSections, policy.navigationLayout);
4470
- const engagement = selectedSections.find((section) => section.kind === "engagement");
4471
- const separateEngagement = engagement?.treatment === "separate-row" ? groups.filter((group) => group.kind === "engagement") : [];
4472
- const layoutGroups = separateEngagement.length === 0 ? groups : groups.filter((group) => group.kind !== "engagement");
4473
- const legalGroups = layoutGroups.filter((group) => group.kind === "legal-ending");
4474
- const unsortedMainGroups = policy.kind === "separate-legal-region" ? layoutGroups.filter((group) => group.kind !== "legal-ending") : layoutGroups;
4475
- const mainGroups = [...unsortedMainGroups.filter((group) => group.width === "column"), ...unsortedMainGroups.filter((group) => group.width === "full-row")];
4476
- const regions = [];
4477
- if (separateEngagement.length > 0) {
4478
- regions.push({
4479
- kind: "engagement-row",
4480
- columns: 1,
4481
- groups: separateEngagement
4482
- });
4483
- }
4484
- if (mainGroups.length > 0) {
4485
- regions.push({
4486
- kind: "main",
4487
- columns: Math.max(1, Math.min(mainGroups.filter((group) => group.width === "column").length, 5)),
4488
- groups: mainGroups
4489
- });
4490
- }
4491
- if (policy.kind === "separate-legal-region" && legalGroups.length > 0) {
4492
- regions.push({
4493
- kind: "bottom",
4494
- columns: 1,
4495
- groups: legalGroups
4496
- });
4497
- }
5039
+ const groups = buildGroups(selectedSections, policy);
5040
+ const separateEngagement = groups.filter((group) => group.kind === "engagement" && group.sections.some((section) => section.kind === "engagement" && section.treatment === "separate-row"));
5041
+ const layoutGroups = groups.filter((group) => !separateEngagement.includes(group));
5042
+ const legalGroups = policy.kind === "separate-legal-region" ? layoutGroups.filter((group) => group.kind === "legal-ending") : [];
5043
+ const unsortedMainGroups = layoutGroups.filter((group) => !legalGroups.includes(group));
5044
+ const mainGroups = policy.arrangement === "centred" ? unsortedMainGroups : [...unsortedMainGroups.filter((group) => group.width === "column"), ...unsortedMainGroups.filter((group) => group.width === "full-row")];
4498
5045
  return {
4499
5046
  membershipKey: membershipKey(selectedSections),
4500
- regions,
5047
+ regions: [
5048
+ ...renderRegion("engagement-row", separateEngagement, policy),
5049
+ ...renderRegion("main", mainGroups, policy),
5050
+ ...renderRegion("bottom", legalGroups, policy)
5051
+ ],
4501
5052
  mobileGroups: separateEngagement.length === 0 ? groups : [...separateEngagement, ...layoutGroups]
4502
5053
  };
4503
5054
  }
@@ -4962,14 +5513,6 @@ function normalizeLegacyFooterContentToV1(value, options = {}) {
4962
5513
  });
4963
5514
  }
4964
5515
 
4965
- //#endregion
4966
- //#region ../core/src/footer-navigation-layout.ts
4967
- const footerNavigationLayouts = ["stack", "inline-wrap"];
4968
- const defaultFooterNavigationLayout = "inline-wrap";
4969
- function normalizeFooterNavigationLayout(value) {
4970
- return value === "stack" ? "stack" : defaultFooterNavigationLayout;
4971
- }
4972
-
4973
5516
  //#endregion
4974
5517
  //#region ../core/src/mailing-list-setup.ts
4975
5518
  function isRecord$3(value) {
@@ -5033,6 +5576,7 @@ function resolveCollectedGlobalFields(form) {
5033
5576
  switch (form.fields.preset) {
5034
5577
  case "emailOnly": return phone;
5035
5578
  case "emailName": return ["name", ...phone];
5579
+ case "emailFirstName": return ["first_name", ...phone];
5036
5580
  case "emailFirstLast": return [
5037
5581
  "first_name",
5038
5582
  "last_name",
@@ -5049,6 +5593,7 @@ function resolveRequiredGlobalFields(fields) {
5049
5593
  switch (fields.preset) {
5050
5594
  case "emailOnly": return [];
5051
5595
  case "emailName": return fields.name === "required" ? ["name"] : [];
5596
+ case "emailFirstName": return fields.firstName === "required" ? ["first_name"] : [];
5052
5597
  case "emailFirstLast": return [...fields.firstName === "required" ? ["first_name"] : [], ...fields.lastName === "required" ? ["last_name"] : []];
5053
5598
  case "emailNameCompany": return [...fields.name === "required" ? ["name"] : [], ...fields.company === "required" ? ["company"] : []];
5054
5599
  }
@@ -5253,6 +5798,10 @@ function parseGlobalFields(value) {
5253
5798
  preset: "emailName",
5254
5799
  name: value.name
5255
5800
  } : null;
5801
+ case "emailFirstName": return isRequirement(value.firstName) ? {
5802
+ preset: "emailFirstName",
5803
+ firstName: value.firstName
5804
+ } : null;
5256
5805
  case "emailFirstLast": return isRequirement(value.firstName) && isRequirement(value.lastName) ? {
5257
5806
  preset: "emailFirstLast",
5258
5807
  firstName: value.firstName,
@@ -5640,36 +6189,85 @@ function parseMailingListSignupProfile(form, input) {
5640
6189
  }
5641
6190
  } : name;
5642
6191
  }
5643
- case "emailFirstLast": {
6192
+ case "emailFirstName": {
5644
6193
  const firstName = resolveProfileField("first_name", form.fields.firstName, input.firstName);
5645
- if (!firstName.ok) return firstName;
5646
- const lastName = resolveProfileField("last_name", form.fields.lastName, input.lastName);
5647
- return lastName.ok ? {
6194
+ return firstName.ok ? {
5648
6195
  ok: true,
5649
6196
  value: {
5650
- preset: "emailFirstLast",
6197
+ preset: "emailFirstName",
5651
6198
  email,
5652
6199
  phone,
5653
- firstName: firstName.value,
5654
- lastName: lastName.value
6200
+ firstName: firstName.value
5655
6201
  }
5656
- } : lastName;
6202
+ } : firstName;
5657
6203
  }
5658
- case "emailNameCompany": {
5659
- const name = resolveProfileField("name", form.fields.name, input.name);
5660
- if (!name.ok) return name;
5661
- const company = resolveProfileField("company", form.fields.company, input.company);
5662
- return company.ok ? {
5663
- ok: true,
5664
- value: {
5665
- preset: "emailNameCompany",
5666
- email,
5667
- phone,
5668
- name: name.value,
5669
- company: company.value
5670
- }
5671
- } : company;
6204
+ case "emailFirstLast": return parseFirstLastProfile(form.fields, {
6205
+ email,
6206
+ phone
6207
+ }, input);
6208
+ case "emailNameCompany": return parseNameCompanyProfile(form.fields, {
6209
+ email,
6210
+ phone
6211
+ }, input);
6212
+ }
6213
+ }
6214
+ function parseFirstLastProfile(fields, base, input) {
6215
+ const firstName = resolveProfileField("first_name", fields.firstName, input.firstName);
6216
+ if (!firstName.ok) return firstName;
6217
+ const lastName = resolveProfileField("last_name", fields.lastName, input.lastName);
6218
+ return lastName.ok ? {
6219
+ ok: true,
6220
+ value: {
6221
+ ...base,
6222
+ preset: "emailFirstLast",
6223
+ firstName: firstName.value,
6224
+ lastName: lastName.value
5672
6225
  }
6226
+ } : lastName;
6227
+ }
6228
+ function parseNameCompanyProfile(fields, base, input) {
6229
+ const name = resolveProfileField("name", fields.name, input.name);
6230
+ if (!name.ok) return name;
6231
+ const company = resolveProfileField("company", fields.company, input.company);
6232
+ return company.ok ? {
6233
+ ok: true,
6234
+ value: {
6235
+ ...base,
6236
+ preset: "emailNameCompany",
6237
+ name: name.value,
6238
+ company: company.value
6239
+ }
6240
+ } : company;
6241
+ }
6242
+ /** One projection consumed by native signup and external provider adapters. */
6243
+ function mailingListSignupProfileValues(profile) {
6244
+ const empty = {
6245
+ name: null,
6246
+ first_name: null,
6247
+ last_name: null,
6248
+ company: null,
6249
+ phone: profile.phone.tag === "collected" ? profile.phone.value : null
6250
+ };
6251
+ switch (profile.preset) {
6252
+ case "emailOnly": return empty;
6253
+ case "emailName": return {
6254
+ ...empty,
6255
+ name: profile.name.value
6256
+ };
6257
+ case "emailFirstName": return {
6258
+ ...empty,
6259
+ first_name: profile.firstName.value
6260
+ };
6261
+ case "emailFirstLast": return {
6262
+ ...empty,
6263
+ first_name: profile.firstName.value,
6264
+ last_name: profile.lastName.value
6265
+ };
6266
+ case "emailNameCompany": return {
6267
+ ...empty,
6268
+ name: profile.name.value,
6269
+ company: profile.company.value
6270
+ };
5673
6271
  }
5674
6272
  }
5675
6273
  function resolveProfileField(field, requirement, input) {
@@ -6061,7 +6659,7 @@ function resolveExportDateRange(range, today) {
6061
6659
  ok: true,
6062
6660
  window: {
6063
6661
  from: unsafeAsMoneyDateString("1970-01-01"),
6064
- to: unsafeAsMoneyDateString(formatDate(today))
6662
+ to: unsafeAsMoneyDateString(formatDate$1(today))
6065
6663
  }
6066
6664
  };
6067
6665
  }
@@ -6072,8 +6670,8 @@ function resolveExportDateRange(range, today) {
6072
6670
  return {
6073
6671
  ok: true,
6074
6672
  window: {
6075
- from: unsafeAsMoneyDateString(formatDate(from)),
6076
- to: unsafeAsMoneyDateString(formatDate(to))
6673
+ from: unsafeAsMoneyDateString(formatDate$1(from)),
6674
+ to: unsafeAsMoneyDateString(formatDate$1(to))
6077
6675
  }
6078
6676
  };
6079
6677
  }
@@ -6089,7 +6687,7 @@ function parseExportDate(value, field) {
6089
6687
  };
6090
6688
  }
6091
6689
  const date = new Date(`${value}T00:00:00.000Z`);
6092
- if (Number.isNaN(date.getTime()) || formatDate(date) !== value) {
6690
+ if (Number.isNaN(date.getTime()) || formatDate$1(date) !== value) {
6093
6691
  return {
6094
6692
  ok: false,
6095
6693
  error: {
@@ -6115,7 +6713,7 @@ function presetDays(preset) {
6115
6713
  function floorUtcDate(date) {
6116
6714
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
6117
6715
  }
6118
- function formatDate(date) {
6716
+ function formatDate$1(date) {
6119
6717
  return date.toISOString().slice(0, 10);
6120
6718
  }
6121
6719
  function describeExportDateBasis(basis) {
@@ -7579,4 +8177,4 @@ function mergeBag(stored, patch, options) {
7579
8177
  }
7580
8178
 
7581
8179
  //#endregion
7582
- export { toBookingTimeZone as $, unsafeAsCheckoutLineId as $t, footerNavigationLayouts as A, parsePositiveCents as At, normalizeLocationAddressText as B, parseCustomerId as Bt, parseMailingListBlockId as C, unsafeAsUploadSessionId as Cn, publicEventPricingType as Ct, assessMailingListTargeting as D, mapNonEmptyArray as Dt, parseMailingListRuntimeContext as E, ok as En, fromArray as Et, sameMailingListPlacementTargeting as F, formatCurrencyAmount as Ft, formatBookingDateParts as G, parseMembershipId as Gt, selectCategoryView as H, parseDatedOfferingCapacityHoldId as Ht, isCanonicalRichTextDoc as I, parseCurrencyCode as It, formatLocalScheduleTime as J, parseSiteId as Jt, formatBookingTime as K, parsePageId as Kt, parsePublicContactDetails as L, assertNever as Lt, parseMailingListPlacementTargeting as M, toNonNegativeCents as Mt, DEFAULT_MAILING_LIST_TARGETING as N, unsafeAsNonNegativeCents as Nt, DEFAULT_MAILING_LIST_SUCCESS_MESSAGE as O, isNonNegativeWholeCents as Ot, planMailingListChoicePresentation as P, unsafeAsPositiveCents as Pt, parseUtcIsoDateTimeString as Q, unsafeAsBookingSessionId as Qt, assertAllowedPublicUrl as R, formatDurationUnitLabel as Rt, buildMailingListSignupBlockSourceRef as S, unsafeAsStoragePath as Sn, publicEventPricingOptions as St, parseMailingListSignedSourceToken as T, err as Tn, resolvePricingOptionsPriceDisplay as Tt, buildBookingUtcDateWindow as U, parseEventSeriesId as Ut, toLocationCoordinates as V, parseDatedOfferingCapacityHoldAuditRef as Vt, formatBookingDate as W, parseMediaAssetId as Wt, getLocalScheduleDate as X, unsafeAsAppointmentResourceId as Xt, getBookingLocalDateKey as Y, parseUserId as Yt, getLocalScheduleTime as Z, unsafeAsBookingRequirementId as Zt, resolveWholeSeriesBookingPhase as _, unsafeAsSiteFontId as _n, formatBookingPriceDisplay as _t, canonicalizeConfiguredBookingVerticalConfig as a, unsafeAsEventOfferingId as an, isSettleableFlexibleBalanceContractStatus as at, parseManagementKey as b, unsafeAsStaffMemberId as bn, publicEventPricingCurrency as bt, resolveSiteVerticalBookingConfig as c, unsafeAsMediaAssetId as cn, clampConfiguredAmount as ct, ORDINARY_EVENT_PRESET_VALUES as d, unsafeAsPassId as dn, getBookingInstalmentProtectedDepositAmountCents as dt, unsafeAsDeclaredMimeType as en, toScheduledBookingDateRange as et, SURFACE_EVENT_PRESET_VALUES as f, unsafeAsPositiveByteCount as fn, omitZeroAmountFutureRows as ft, orderedLiveRunSessions as g, unsafeAsSafeDownloadFilename as gn, parseEventPageSurfaceSubject as gt, CANCELLED_SESSION_STATUS as h, unsafeAsProductId as hn, resolveFlexibleBalanceInitialPaymentCents as ht, areConfiguredBookingVerticalConfigsEqual as i, unsafeAsEventInstanceId as in, isLiveOrderInstalmentPlanStatus as it, parseFooterCompositionV1 as j, toCents as jt, parseMailingListProviderScope as k, parseNonNegativeCents as kt, DEFAULT_EVENT_TYPE_PRESETS as l, unsafeAsOccurrenceId as ln, getBookingInstalmentDepositRefundability as lt, publicReadFreshnessSeconds as m, unsafeAsPricingOptionId as mn, resolveFlexibleBalanceDueAt as mt, EVENT_PRESET_VOCABULARY as n, unsafeAsDetectedMimeType as nn, parseBookingTimeZone as nt, parseVerticalId as o, unsafeAsEventSeriesId as on, isSettleableOrderInstalmentPlanStatus as ot, WHOLE_SERIES_EVENT_PRESET_VALUES as p, unsafeAsPositiveSeconds as pn, splitAmountWithRemainderInFirst as pt, formatLocalScheduleDate as q, parseRetiredCourseId as qt, NAVIGATION_LOCATIONS as r, unsafeAsEntitlementId as rn, validateBookingPaymentOptionKinds as rt, parseVerticalPresetId as s, unsafeAsFileChecksumSha256 as sn, addIntervalPreservingMonthAnchor as st, resolveEventCatalogue as t, unsafeAsDemoCapability as tn, toScheduledBookingDateTime as tt, EVENT_PRESET_VALUES as u, unsafeAsOriginalFilename as un, getBookingInstalmentPlanIntervalCount as ut, createSecret as v, unsafeAsSiteId as vn, formatEventPricing as vt, parseMailingListContentVersionId as w, unsafeAsUserId as wn, resolveExactOrFreePriceDisplay as wt, stableJsonStringify as x, unsafeAsStorageBucketName as xn, publicEventPricingDisplay as xt, exposeSecret as y, unsafeAsSiteScopedStoragePath as yn, isPublicEventPricingOptionDisplayable as yt, isPresent as z, parseApiKeyId as zt };
8180
+ export { parseUtcIsoDateTimeString as $, unsafeAsBookingSessionId as $t, parseFooterCompositionV1 as A, parseNonNegativeCents as At, normalizeLocationAddressText as B, parseApiKeyId as Bt, parseMailingListBlockId as C, unsafeAsStoragePath as Cn, publicEventPricingOptions as Ct, assessMailingListTargeting as D, ok as Dn, fromArray as Dt, parseMailingListRuntimeContext as E, err as En, resolvePricingOptionsPriceDisplay as Et, sameMailingListPlacementTargeting as F, unsafeAsPositiveCents as Ft, formatBookingDate as G, parseMediaAssetId as Gt, selectCategoryView as H, parseDatedOfferingCapacityHoldAuditRef as Ht, isCanonicalRichTextDoc as I, formatCurrencyAmount as It, formatLocalScheduleDate as J, parseRetiredCourseId as Jt, formatBookingDateParts as K, parseMembershipId as Kt, parsePublicContactDetails as L, parseCurrencyCode as Lt, parseMailingListPlacementTargeting as M, toCents as Mt, DEFAULT_MAILING_LIST_TARGETING as N, toNonNegativeCents as Nt, DEFAULT_MAILING_LIST_SUCCESS_MESSAGE as O, mapNonEmptyArray as Ot, planMailingListChoicePresentation as P, unsafeAsNonNegativeCents as Pt, getLocalScheduleTime as Q, unsafeAsBookingRequirementId as Qt, assertAllowedPublicUrl as R, assertNever as Rt, buildMailingListSignupBlockSourceRef as S, unsafeAsStorageBucketName as Sn, publicEventPricingDisplay as St, parseMailingListSignedSourceToken as T, unsafeAsUserId as Tn, resolveExactOrFreePriceDisplay as Tt, inferSiteDateLocale as U, parseDatedOfferingCapacityHoldId as Ut, toLocationCoordinates as V, parseCustomerId as Vt, buildBookingUtcDateWindow as W, parseEventSeriesId as Wt, getBookingLocalDateKey as X, parseUserId as Xt, formatLocalScheduleTime as Y, parseSiteId as Yt, getLocalScheduleDate as Z, unsafeAsAppointmentResourceId as Zt, resolveWholeSeriesBookingPhase as _, unsafeAsSafeDownloadFilename as _n, parseEventPageSurfaceSubject as _t, canonicalizeConfiguredBookingVerticalConfig as a, unsafeAsEventInstanceId as an, isLiveOrderInstalmentPlanStatus as at, parseManagementKey as b, unsafeAsSiteScopedStoragePath as bn, isPublicEventPricingOptionDisplayable as bt, resolveSiteVerticalBookingConfig as c, unsafeAsFileChecksumSha256 as cn, addIntervalPreservingMonthAnchor as ct, ORDINARY_EVENT_PRESET_VALUES as d, unsafeAsOriginalFilename as dn, getBookingInstalmentPlanIntervalCount as dt, unsafeAsCheckoutLineId as en, toBookingTimeZone as et, SURFACE_EVENT_PRESET_VALUES as f, unsafeAsPassId as fn, getBookingInstalmentProtectedDepositAmountCents as ft, orderedLiveRunSessions as g, unsafeAsProductId as gn, resolveFlexibleBalanceInitialPaymentCents as gt, CANCELLED_SESSION_STATUS as h, unsafeAsPricingOptionId as hn, resolveFlexibleBalanceDueAt as ht, areConfiguredBookingVerticalConfigsEqual as i, unsafeAsEntitlementId as in, validateBookingPaymentOptionKinds as it, footerNavigationLayouts as j, parsePositiveCents as jt, parseMailingListProviderScope as k, isNonNegativeWholeCents as kt, DEFAULT_EVENT_TYPE_PRESETS as l, unsafeAsMediaAssetId as ln, clampConfiguredAmount as lt, publicReadFreshnessSeconds as m, unsafeAsPositiveSeconds as mn, splitAmountWithRemainderInFirst as mt, EVENT_PRESET_VOCABULARY as n, unsafeAsDemoCapability as nn, toScheduledBookingDateTime as nt, parseVerticalId as o, unsafeAsEventOfferingId as on, isSettleableFlexibleBalanceContractStatus as ot, WHOLE_SERIES_EVENT_PRESET_VALUES as p, unsafeAsPositiveByteCount as pn, omitZeroAmountFutureRows as pt, formatBookingTime as q, parsePageId as qt, NAVIGATION_LOCATIONS as r, unsafeAsDetectedMimeType as rn, parseBookingTimeZone as rt, parseVerticalPresetId as s, unsafeAsEventSeriesId as sn, isSettleableOrderInstalmentPlanStatus as st, resolveEventCatalogue as t, unsafeAsDeclaredMimeType as tn, toScheduledBookingDateRange as tt, EVENT_PRESET_VALUES as u, unsafeAsOccurrenceId as un, getBookingInstalmentDepositRefundability as ut, createSecret as v, unsafeAsSiteFontId as vn, formatBookingPriceDisplay as vt, parseMailingListContentVersionId as w, unsafeAsUploadSessionId as wn, publicEventPricingType as wt, stableJsonStringify as x, unsafeAsStaffMemberId as xn, publicEventPricingCurrency as xt, exposeSecret as y, unsafeAsSiteId as yn, formatEventPricing as yt, isPresent as z, formatDurationUnitLabel as zt };