@aranova/tracking-next 0.20.1 → 0.21.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.
- package/README.md +80 -1
- package/dist/blog-server.js +6 -3
- package/dist/blog-server.js.map +1 -1
- package/dist/blog-server.mjs +6 -3
- package/dist/blog-server.mjs.map +1 -1
- package/dist/blog.js +4 -2
- package/dist/blog.js.map +1 -1
- package/dist/blog.mjs +4 -2
- package/dist/blog.mjs.map +1 -1
- package/dist/calendar-server.d.mts +53 -0
- package/dist/calendar-server.d.ts +53 -0
- package/dist/calendar-server.js +206 -0
- package/dist/calendar-server.js.map +1 -0
- package/dist/calendar-server.mjs +180 -0
- package/dist/calendar-server.mjs.map +1 -0
- package/dist/calendar.d.mts +112 -0
- package/dist/calendar.d.ts +112 -0
- package/dist/calendar.js +517 -0
- package/dist/calendar.js.map +1 -0
- package/dist/calendar.mjs +457 -0
- package/dist/calendar.mjs.map +1 -0
- package/dist/date-BHetnL7s.d.mts +26 -0
- package/dist/date-BHetnL7s.d.ts +26 -0
- package/dist/index.d.mts +60 -3
- package/dist/index.d.ts +60 -3
- package/dist/index.js +511 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +474 -18
- package/dist/index.mjs.map +1 -1
- package/dist/request-Doq36Rt0.d.mts +19 -0
- package/dist/request-Doq36Rt0.d.ts +19 -0
- package/dist/{sales-DrH6dY5F.d.mts → sales-B3BnYncU.d.mts} +5 -44
- package/dist/{sales-DrH6dY5F.d.ts → sales-CDjXRdmd.d.ts} +5 -44
- package/dist/sales.d.mts +3 -1
- package/dist/sales.d.ts +3 -1
- package/dist/sales.js +22 -17
- package/dist/sales.js.map +1 -1
- package/dist/sales.mjs +22 -17
- package/dist/sales.mjs.map +1 -1
- package/dist/transport-Bku8zftU.d.mts +721 -0
- package/dist/transport-DsOY6kcW.d.ts +721 -0
- package/package.json +22 -1
package/dist/index.mjs
CHANGED
|
@@ -946,6 +946,21 @@ function automaticThresholdMet(goal, eventType, metadata) {
|
|
|
946
946
|
}
|
|
947
947
|
}
|
|
948
948
|
|
|
949
|
+
// ../tracking-core/src/resources/intl/date.ts
|
|
950
|
+
function formatDateInTz(iso, timeZone, opts, locale) {
|
|
951
|
+
const date = new Date(iso);
|
|
952
|
+
if (Number.isNaN(date.getTime())) return iso;
|
|
953
|
+
return new Intl.DateTimeFormat(locale, {
|
|
954
|
+
year: "numeric",
|
|
955
|
+
month: "short",
|
|
956
|
+
day: "2-digit",
|
|
957
|
+
hour: "2-digit",
|
|
958
|
+
minute: "2-digit",
|
|
959
|
+
...opts,
|
|
960
|
+
timeZone
|
|
961
|
+
}).format(date);
|
|
962
|
+
}
|
|
963
|
+
|
|
949
964
|
// ../tracking-core/src/resources/sales/money.ts
|
|
950
965
|
var MINOR_UNIT_EXPONENT = {
|
|
951
966
|
USD: 2,
|
|
@@ -965,19 +980,6 @@ function formatMoney(cents, currency, locale) {
|
|
|
965
980
|
fromMinor(cents, currency)
|
|
966
981
|
);
|
|
967
982
|
}
|
|
968
|
-
function formatDateInTz(iso, timeZone, opts, locale) {
|
|
969
|
-
const date = new Date(iso);
|
|
970
|
-
if (Number.isNaN(date.getTime())) return iso;
|
|
971
|
-
return new Intl.DateTimeFormat(locale, {
|
|
972
|
-
year: "numeric",
|
|
973
|
-
month: "short",
|
|
974
|
-
day: "2-digit",
|
|
975
|
-
hour: "2-digit",
|
|
976
|
-
minute: "2-digit",
|
|
977
|
-
...opts,
|
|
978
|
-
timeZone
|
|
979
|
-
}).format(date);
|
|
980
|
-
}
|
|
981
983
|
|
|
982
984
|
// ../tracking-core/src/resources/tracking-config-runtime.ts
|
|
983
985
|
var CACHE_PREFIX2 = "_aranova_cfg_runtime_";
|
|
@@ -2680,7 +2682,7 @@ function attachPhoneClickCapture(client, config) {
|
|
|
2680
2682
|
};
|
|
2681
2683
|
}
|
|
2682
2684
|
|
|
2683
|
-
// ../tracking-core/src/resources/
|
|
2685
|
+
// ../tracking-core/src/resources/http/errors.ts
|
|
2684
2686
|
var AranovaApiError = class extends Error {
|
|
2685
2687
|
constructor(message, options) {
|
|
2686
2688
|
super(message);
|
|
@@ -2691,7 +2693,7 @@ var AranovaApiError = class extends Error {
|
|
|
2691
2693
|
}
|
|
2692
2694
|
};
|
|
2693
2695
|
|
|
2694
|
-
// ../tracking-core/src/resources/
|
|
2696
|
+
// ../tracking-core/src/resources/http/request.ts
|
|
2695
2697
|
function identityHeaders(config) {
|
|
2696
2698
|
const headers = { [API_KEY_HEADER]: config.apiKey };
|
|
2697
2699
|
if (config.sdkVersion) headers[SDK_VERSION_HEADER] = config.sdkVersion;
|
|
@@ -2703,8 +2705,8 @@ function identityHeaders(config) {
|
|
|
2703
2705
|
function joinUrl(endpoint, path) {
|
|
2704
2706
|
return `${endpoint.replace(/\/$/, "")}${path}`;
|
|
2705
2707
|
}
|
|
2706
|
-
async function
|
|
2707
|
-
const headers = identityHeaders(config);
|
|
2708
|
+
async function apiRequest(config, method, path, body, extraHeaders) {
|
|
2709
|
+
const headers = { ...identityHeaders(config), ...extraHeaders };
|
|
2708
2710
|
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
2709
2711
|
const response = await fetch(joinUrl(config.endpoint, path), {
|
|
2710
2712
|
method,
|
|
@@ -2733,6 +2735,9 @@ async function salesRequest(config, method, path, body) {
|
|
|
2733
2735
|
return await response.json();
|
|
2734
2736
|
}
|
|
2735
2737
|
|
|
2738
|
+
// ../tracking-core/src/resources/sales/transport.ts
|
|
2739
|
+
var salesRequest = apiRequest;
|
|
2740
|
+
|
|
2736
2741
|
// ../tracking-core/src/resources/sales/client.ts
|
|
2737
2742
|
function fireRecordedConversions(firing, input, recorded, sale, currency) {
|
|
2738
2743
|
if (!firing) return;
|
|
@@ -3378,7 +3383,7 @@ function GoogleAdsTracking(props) {
|
|
|
3378
3383
|
import { usePathname, useSearchParams } from "next/navigation";
|
|
3379
3384
|
|
|
3380
3385
|
// package.json
|
|
3381
|
-
var version = "0.
|
|
3386
|
+
var version = "0.21.0";
|
|
3382
3387
|
|
|
3383
3388
|
// src/factory.tsx
|
|
3384
3389
|
import {
|
|
@@ -3674,42 +3679,488 @@ function createTracking(options) {
|
|
|
3674
3679
|
}
|
|
3675
3680
|
return { TrackingProvider, useTracking };
|
|
3676
3681
|
}
|
|
3682
|
+
|
|
3683
|
+
// ../tracking-core/src/resources/calendar/transport.ts
|
|
3684
|
+
async function calendarRequest(config, method, path, body) {
|
|
3685
|
+
return apiRequest(config, method, `/calendar${path}`, body);
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
// ../tracking-core/src/resources/calendar/time.ts
|
|
3689
|
+
function resolveBrowserTimeZone() {
|
|
3690
|
+
try {
|
|
3691
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
3692
|
+
} catch {
|
|
3693
|
+
return "UTC";
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
function partsInTz(date, timeZone) {
|
|
3697
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
3698
|
+
timeZone,
|
|
3699
|
+
hour12: false,
|
|
3700
|
+
year: "numeric",
|
|
3701
|
+
month: "2-digit",
|
|
3702
|
+
day: "2-digit",
|
|
3703
|
+
hour: "2-digit",
|
|
3704
|
+
minute: "2-digit",
|
|
3705
|
+
second: "2-digit"
|
|
3706
|
+
});
|
|
3707
|
+
const out = {};
|
|
3708
|
+
for (const part of formatter.formatToParts(date)) {
|
|
3709
|
+
if (part.type !== "literal") out[part.type] = Number(part.value);
|
|
3710
|
+
}
|
|
3711
|
+
if (out.hour === 24) out.hour = 0;
|
|
3712
|
+
return out;
|
|
3713
|
+
}
|
|
3714
|
+
function offsetMinutesAt(date, timeZone) {
|
|
3715
|
+
const p = partsInTz(date, timeZone);
|
|
3716
|
+
const asUtc = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
|
|
3717
|
+
return (asUtc - Math.floor(date.getTime() / 1e3) * 1e3) / 6e4;
|
|
3718
|
+
}
|
|
3719
|
+
function instantFromWallClock(timeZone, year, month, day, hour = 0, minute = 0) {
|
|
3720
|
+
const naive = Date.UTC(year, month - 1, day, hour, minute);
|
|
3721
|
+
const firstGuess = new Date(naive - offsetMinutesAt(new Date(naive), timeZone) * 6e4);
|
|
3722
|
+
const corrected = naive - offsetMinutesAt(firstGuess, timeZone) * 6e4;
|
|
3723
|
+
return new Date(corrected);
|
|
3724
|
+
}
|
|
3725
|
+
function localDateKey(iso, timeZone) {
|
|
3726
|
+
const p = partsInTz(new Date(iso), timeZone);
|
|
3727
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
3728
|
+
return `${p.year}-${pad(p.month)}-${pad(p.day)}`;
|
|
3729
|
+
}
|
|
3730
|
+
function startOfDayInTz(dateKey, timeZone) {
|
|
3731
|
+
const [year, month, day] = dateKey.split("-").map(Number);
|
|
3732
|
+
return instantFromWallClock(timeZone, year, month, day, 0, 0);
|
|
3733
|
+
}
|
|
3734
|
+
function endOfDayInTz(dateKey, timeZone) {
|
|
3735
|
+
const start = startOfDayInTz(dateKey, timeZone);
|
|
3736
|
+
const p = partsInTz(start, timeZone);
|
|
3737
|
+
return instantFromWallClock(timeZone, p.year, p.month, p.day + 1, 0, 0);
|
|
3738
|
+
}
|
|
3739
|
+
function durationMinutes(startIso, endIso) {
|
|
3740
|
+
return Math.round((new Date(endIso).getTime() - new Date(startIso).getTime()) / 6e4);
|
|
3741
|
+
}
|
|
3742
|
+
function formatSlotRange(startIso, endIso, timeZone, locale) {
|
|
3743
|
+
const opts = { hour: "2-digit", minute: "2-digit", timeZone };
|
|
3744
|
+
const fmt = new Intl.DateTimeFormat(locale, opts);
|
|
3745
|
+
return `${fmt.format(new Date(startIso))} \u2013 ${fmt.format(new Date(endIso))}`;
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
// ../tracking-core/src/resources/calendar/read-client.ts
|
|
3749
|
+
function withBusiness(config, path) {
|
|
3750
|
+
if (!config.businessId) return path;
|
|
3751
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
3752
|
+
return `${path}${separator}business_id=${encodeURIComponent(config.businessId)}`;
|
|
3753
|
+
}
|
|
3754
|
+
function createCalendarReadClient(config) {
|
|
3755
|
+
return {
|
|
3756
|
+
async listCalendars() {
|
|
3757
|
+
return calendarRequest(config, "GET", withBusiness(config, "/calendars"));
|
|
3758
|
+
},
|
|
3759
|
+
async busy(calendarKey, query) {
|
|
3760
|
+
const params = new URLSearchParams({
|
|
3761
|
+
since: query.since,
|
|
3762
|
+
until: query.until
|
|
3763
|
+
});
|
|
3764
|
+
const timeZone = query.timeZone ?? config.defaultTimeZone ?? resolveBrowserTimeZone();
|
|
3765
|
+
params.set("time_zone", timeZone);
|
|
3766
|
+
return calendarRequest(
|
|
3767
|
+
config,
|
|
3768
|
+
"GET",
|
|
3769
|
+
withBusiness(config, `/${encodeURIComponent(calendarKey)}/busy?${params.toString()}`)
|
|
3770
|
+
);
|
|
3771
|
+
}
|
|
3772
|
+
};
|
|
3773
|
+
}
|
|
3774
|
+
function isCalendarError(error, code) {
|
|
3775
|
+
if (!(error instanceof AranovaApiError)) return false;
|
|
3776
|
+
return code === void 0 || error.code === code;
|
|
3777
|
+
}
|
|
3778
|
+
|
|
3779
|
+
// ../tracking-core/src/resources/calendar/schema.ts
|
|
3780
|
+
import { z as z13 } from "zod";
|
|
3781
|
+
var CALENDAR_METADATA_SCHEMA_VERSION = 1;
|
|
3782
|
+
var MAX_ATTENDEES = 20;
|
|
3783
|
+
var MAX_REMINDER_OVERRIDES = 5;
|
|
3784
|
+
var MAX_REMINDER_MINUTES = 40320;
|
|
3785
|
+
var MAX_BOOKING_MINUTES = 1440;
|
|
3786
|
+
var BOOKING_SOURCES = ["website", "phone", "walk_in", "import", "operator"];
|
|
3787
|
+
var BOOKING_STATUSES = ["pending", "confirmed", "cancelled", "no_show"];
|
|
3788
|
+
var GOOGLE_SYNC_STATES = ["queued", "synced", "uncertain", "failed", "drifted"];
|
|
3789
|
+
var CALENDAR_SYNC_STATUSES = [
|
|
3790
|
+
"pending_invite",
|
|
3791
|
+
"awaiting_calendar_selection",
|
|
3792
|
+
"connected",
|
|
3793
|
+
"needs_reconnect",
|
|
3794
|
+
"revoked",
|
|
3795
|
+
"disabled"
|
|
3796
|
+
];
|
|
3797
|
+
var CALENDAR_ERROR_CODES = [
|
|
3798
|
+
"slot_unavailable",
|
|
3799
|
+
"calendar_not_connected",
|
|
3800
|
+
"calendar_disconnected",
|
|
3801
|
+
"booking_not_found",
|
|
3802
|
+
"outside_booking_window",
|
|
3803
|
+
"rate_limited",
|
|
3804
|
+
"google_error"
|
|
3805
|
+
];
|
|
3806
|
+
var isoInstant = z13.string().datetime({ offset: true });
|
|
3807
|
+
function isKnownTimeZone(timeZone) {
|
|
3808
|
+
try {
|
|
3809
|
+
new Intl.DateTimeFormat(void 0, { timeZone });
|
|
3810
|
+
return true;
|
|
3811
|
+
} catch {
|
|
3812
|
+
return false;
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
var ianaTz = z13.string().min(1).max(64).regex(/^[A-Za-z0-9_+\-/]+$/).refine(isKnownTimeZone, { message: "unknown IANA time zone" });
|
|
3816
|
+
var bookingAttendeeSchema = z13.object({
|
|
3817
|
+
email: z13.string().max(320).email(),
|
|
3818
|
+
display_name: z13.string().max(200).nullable().optional(),
|
|
3819
|
+
optional: z13.boolean().default(false)
|
|
3820
|
+
}).strict();
|
|
3821
|
+
var bookingReminderSchema = z13.object({
|
|
3822
|
+
method: z13.enum(["email", "popup"]),
|
|
3823
|
+
minutes_before: z13.number().int().min(0).max(MAX_REMINDER_MINUTES)
|
|
3824
|
+
}).strict();
|
|
3825
|
+
var bookingRemindersSchema = z13.object({
|
|
3826
|
+
use_calendar_defaults: z13.boolean().default(true),
|
|
3827
|
+
overrides: z13.array(bookingReminderSchema).max(MAX_REMINDER_OVERRIDES).default([])
|
|
3828
|
+
}).strict();
|
|
3829
|
+
var bookingLocationSchema = z13.union([
|
|
3830
|
+
z13.object({ kind: z13.literal("google_meet") }).strict(),
|
|
3831
|
+
z13.object({ kind: z13.literal("physical"), address: z13.string().max(1024) }).strict(),
|
|
3832
|
+
z13.object({ kind: z13.literal("phone"), phone: z13.string().max(64) }).strict(),
|
|
3833
|
+
z13.object({ kind: z13.literal("custom"), text: z13.string().max(1024) }).strict()
|
|
3834
|
+
]);
|
|
3835
|
+
var bookingLocationNullableSchema = z13.union([...bookingLocationSchema.options, z13.null()]);
|
|
3836
|
+
var bookingCustomerSchema = z13.object({
|
|
3837
|
+
// Required: Google needs an address to send the invite that confirms the
|
|
3838
|
+
// booking, and that invite is the whole confirmation mechanism.
|
|
3839
|
+
email: z13.string().max(320).email(),
|
|
3840
|
+
name: z13.string().max(200).nullable().optional(),
|
|
3841
|
+
phone: z13.string().max(64).nullable().optional(),
|
|
3842
|
+
sms_consent: z13.boolean().default(false)
|
|
3843
|
+
}).strict();
|
|
3844
|
+
var jsonScalar = z13.union([z13.string(), z13.number(), z13.boolean(), z13.null()]);
|
|
3845
|
+
var bookingMetadataSchema = z13.object({
|
|
3846
|
+
schema_version: z13.number().int().default(CALENDAR_METADATA_SCHEMA_VERSION),
|
|
3847
|
+
source: z13.enum(BOOKING_SOURCES).nullable().optional(),
|
|
3848
|
+
service: z13.string().max(64).nullable().optional(),
|
|
3849
|
+
visitor_id: z13.string().max(64).nullable().optional(),
|
|
3850
|
+
session_id: z13.string().max(64).nullable().optional(),
|
|
3851
|
+
gclid: z13.string().max(256).nullable().optional(),
|
|
3852
|
+
fbclid: z13.string().max(256).nullable().optional(),
|
|
3853
|
+
utm_source: z13.string().max(128).nullable().optional(),
|
|
3854
|
+
utm_medium: z13.string().max(128).nullable().optional(),
|
|
3855
|
+
utm_campaign: z13.string().max(128).nullable().optional(),
|
|
3856
|
+
utm_term: z13.string().max(128).nullable().optional(),
|
|
3857
|
+
utm_content: z13.string().max(128).nullable().optional(),
|
|
3858
|
+
landing_page: z13.string().max(2048).nullable().optional(),
|
|
3859
|
+
referrer: z13.string().max(2048).nullable().optional(),
|
|
3860
|
+
notes: z13.string().max(2e3).nullable().optional(),
|
|
3861
|
+
custom: z13.record(jsonScalar).nullable().optional()
|
|
3862
|
+
}).strict();
|
|
3863
|
+
function refineEndXor(value, ctx) {
|
|
3864
|
+
const hasEnd = value.end_at !== void 0 && value.end_at !== null;
|
|
3865
|
+
const hasDuration = value.duration_minutes !== void 0 && value.duration_minutes !== null;
|
|
3866
|
+
if (hasEnd === hasDuration) {
|
|
3867
|
+
ctx.addIssue({
|
|
3868
|
+
code: z13.ZodIssueCode.custom,
|
|
3869
|
+
message: "provide exactly one of end_at or duration_minutes",
|
|
3870
|
+
path: ["end_at"]
|
|
3871
|
+
});
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
if (hasEnd && new Date(value.end_at) <= new Date(value.start_at)) {
|
|
3875
|
+
ctx.addIssue({
|
|
3876
|
+
code: z13.ZodIssueCode.custom,
|
|
3877
|
+
message: "end_at must be after start_at",
|
|
3878
|
+
path: ["end_at"]
|
|
3879
|
+
});
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
var bookingCreateSchema = z13.object({
|
|
3883
|
+
calendar_key: z13.string().min(1).max(64),
|
|
3884
|
+
// Free-form: appointment types are the client site's own vocabulary, not
|
|
3885
|
+
// configuration the dashboard holds.
|
|
3886
|
+
appointment_type: z13.string().max(64).nullable().optional(),
|
|
3887
|
+
start_at: isoInstant,
|
|
3888
|
+
end_at: isoInstant.nullable().optional(),
|
|
3889
|
+
duration_minutes: z13.number().int().positive().max(MAX_BOOKING_MINUTES).nullable().optional(),
|
|
3890
|
+
/** The booker's zone — distinct from the business's and the calendar's. */
|
|
3891
|
+
time_zone: ianaTz,
|
|
3892
|
+
title: z13.string().max(300).nullable().optional(),
|
|
3893
|
+
description: z13.string().max(8e3).nullable().optional(),
|
|
3894
|
+
location: bookingLocationNullableSchema.optional(),
|
|
3895
|
+
customer: bookingCustomerSchema,
|
|
3896
|
+
attendees: z13.array(bookingAttendeeSchema).max(MAX_ATTENDEES).default([]),
|
|
3897
|
+
send_invites: z13.boolean().default(true),
|
|
3898
|
+
reminders: bookingRemindersSchema.nullable().optional(),
|
|
3899
|
+
metadata: bookingMetadataSchema.nullable().optional(),
|
|
3900
|
+
idempotency_key: z13.string().max(128).nullable().optional(),
|
|
3901
|
+
buffer_before_minutes: z13.number().int().min(0).max(MAX_BOOKING_MINUTES).default(0),
|
|
3902
|
+
buffer_after_minutes: z13.number().int().min(0).max(MAX_BOOKING_MINUTES).default(0)
|
|
3903
|
+
}).strict().superRefine(refineEndXor);
|
|
3904
|
+
var bookingRescheduleSchema = z13.object({
|
|
3905
|
+
start_at: isoInstant,
|
|
3906
|
+
end_at: isoInstant.nullable().optional(),
|
|
3907
|
+
duration_minutes: z13.number().int().positive().max(MAX_BOOKING_MINUTES).nullable().optional(),
|
|
3908
|
+
time_zone: ianaTz,
|
|
3909
|
+
notify_attendees: z13.boolean().default(true),
|
|
3910
|
+
reason: z13.string().max(500).nullable().optional()
|
|
3911
|
+
}).strict().superRefine(refineEndXor);
|
|
3912
|
+
var bookingCancelSchema = z13.object({
|
|
3913
|
+
reason: z13.string().max(500).nullable().optional(),
|
|
3914
|
+
notify_attendees: z13.boolean().default(true)
|
|
3915
|
+
}).strict();
|
|
3916
|
+
|
|
3917
|
+
// ../tracking-core/src/resources/calendar/slots.ts
|
|
3918
|
+
function parseHhMm(value) {
|
|
3919
|
+
const [hour, minute] = value.split(":").map(Number);
|
|
3920
|
+
return [hour || 0, minute || 0];
|
|
3921
|
+
}
|
|
3922
|
+
function dateKeyOf(date, timeZone) {
|
|
3923
|
+
const fmt = new Intl.DateTimeFormat("en-CA", {
|
|
3924
|
+
timeZone,
|
|
3925
|
+
year: "numeric",
|
|
3926
|
+
month: "2-digit",
|
|
3927
|
+
day: "2-digit"
|
|
3928
|
+
});
|
|
3929
|
+
return fmt.format(date);
|
|
3930
|
+
}
|
|
3931
|
+
function weekdayFromDateKey(dateKey) {
|
|
3932
|
+
const [year, month, day] = dateKey.split("-").map(Number);
|
|
3933
|
+
return new Date(Date.UTC(year, month - 1, day, 12)).getUTCDay();
|
|
3934
|
+
}
|
|
3935
|
+
function startOfLocalDay(at, timeZone) {
|
|
3936
|
+
const [year, month, day] = dateKeyOf(at, timeZone).split("-").map(Number);
|
|
3937
|
+
return instantFromWallClock(timeZone, year, month, day, 12, 0);
|
|
3938
|
+
}
|
|
3939
|
+
function overlaps(aStart, aEnd, bStart, bEnd) {
|
|
3940
|
+
return aStart < bEnd && aEnd > bStart;
|
|
3941
|
+
}
|
|
3942
|
+
function computeSlots(options) {
|
|
3943
|
+
const {
|
|
3944
|
+
busy,
|
|
3945
|
+
openingHours,
|
|
3946
|
+
timeZone,
|
|
3947
|
+
durationMinutes: durationMinutes2,
|
|
3948
|
+
granularityMinutes = 15,
|
|
3949
|
+
minNoticeMinutes = 0,
|
|
3950
|
+
bufferBeforeMinutes = 0,
|
|
3951
|
+
bufferAfterMinutes = 0,
|
|
3952
|
+
windowDays = 30,
|
|
3953
|
+
now = /* @__PURE__ */ new Date()
|
|
3954
|
+
} = options;
|
|
3955
|
+
const earliest = now.getTime() + minNoticeMinutes * 6e4;
|
|
3956
|
+
const busyRanges = busy.map((interval) => [
|
|
3957
|
+
new Date(interval.start_at).getTime(),
|
|
3958
|
+
new Date(interval.end_at).getTime()
|
|
3959
|
+
]);
|
|
3960
|
+
const slots = [];
|
|
3961
|
+
let cursor = startOfLocalDay(now, timeZone);
|
|
3962
|
+
for (let dayOffset = 0; dayOffset < windowDays; dayOffset += 1) {
|
|
3963
|
+
const dateKey = dateKeyOf(cursor, timeZone);
|
|
3964
|
+
const [year, month, day] = dateKey.split("-").map(Number);
|
|
3965
|
+
cursor = instantFromWallClock(timeZone, year, month, day + 1, 12, 0);
|
|
3966
|
+
if (openingHours.closedDates?.includes(dateKey)) continue;
|
|
3967
|
+
const intervals = openingHours.overrides?.[dateKey] ?? openingHours.weekly[weekdayFromDateKey(dateKey)] ?? [];
|
|
3968
|
+
for (const interval of intervals) {
|
|
3969
|
+
const [openHour, openMinute] = parseHhMm(interval.start);
|
|
3970
|
+
const [closeHour, closeMinute] = parseHhMm(interval.end);
|
|
3971
|
+
const opensAt = instantFromWallClock(timeZone, year, month, day, openHour, openMinute);
|
|
3972
|
+
const closesAt = instantFromWallClock(timeZone, year, month, day, closeHour, closeMinute);
|
|
3973
|
+
for (let start = opensAt.getTime(); start + durationMinutes2 * 6e4 <= closesAt.getTime(); start += granularityMinutes * 6e4) {
|
|
3974
|
+
const end = start + durationMinutes2 * 6e4;
|
|
3975
|
+
if (start < earliest) continue;
|
|
3976
|
+
const blockedStart = start - bufferBeforeMinutes * 6e4;
|
|
3977
|
+
const blockedEnd = end + bufferAfterMinutes * 6e4;
|
|
3978
|
+
const taken = busyRanges.some(
|
|
3979
|
+
([busyStart, busyEnd]) => overlaps(blockedStart, blockedEnd, busyStart, busyEnd)
|
|
3980
|
+
);
|
|
3981
|
+
if (taken) continue;
|
|
3982
|
+
slots.push({
|
|
3983
|
+
start_at: new Date(start).toISOString(),
|
|
3984
|
+
end_at: new Date(end).toISOString()
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
slots.sort((a, b) => a.start_at.localeCompare(b.start_at));
|
|
3990
|
+
return { slots, nextAvailableAt: slots[0]?.start_at ?? null };
|
|
3991
|
+
}
|
|
3992
|
+
function groupSlotsByDay(slots, timeZone) {
|
|
3993
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
3994
|
+
for (const slot of slots) {
|
|
3995
|
+
const key = dateKeyOf(new Date(slot.start_at), timeZone);
|
|
3996
|
+
const bucket = byDay.get(key);
|
|
3997
|
+
if (bucket) bucket.push(slot);
|
|
3998
|
+
else byDay.set(key, [slot]);
|
|
3999
|
+
}
|
|
4000
|
+
return [...byDay.entries()].map(([date, daySlots]) => ({ date, slots: daySlots })).sort((a, b) => a.date.localeCompare(b.date));
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
// ../tracking-core/src/calendar-react.tsx
|
|
4004
|
+
import { createContext as createContext3, useCallback as useCallback4, useContext as useContext3, useEffect as useEffect5, useMemo as useMemo3, useState as useState4 } from "react";
|
|
4005
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
4006
|
+
var _calendarClientContext;
|
|
4007
|
+
function calendarClientContext() {
|
|
4008
|
+
return _calendarClientContext ?? (_calendarClientContext = createContext3(null));
|
|
4009
|
+
}
|
|
4010
|
+
function CalendarClientProvider({
|
|
4011
|
+
value,
|
|
4012
|
+
children
|
|
4013
|
+
}) {
|
|
4014
|
+
const Ctx = calendarClientContext();
|
|
4015
|
+
return /* @__PURE__ */ jsx6(Ctx.Provider, { value, children });
|
|
4016
|
+
}
|
|
4017
|
+
function useCalendarClient() {
|
|
4018
|
+
const client = useContext3(calendarClientContext());
|
|
4019
|
+
if (!client) {
|
|
4020
|
+
throw new Error(
|
|
4021
|
+
"useCalendarClient requires <CalendarClientProvider>. Wire it via createTracking({ calendar })."
|
|
4022
|
+
);
|
|
4023
|
+
}
|
|
4024
|
+
return client;
|
|
4025
|
+
}
|
|
4026
|
+
function useCalendarBusy(calendarKey, window2) {
|
|
4027
|
+
const client = useCalendarClient();
|
|
4028
|
+
const [busy, setBusy] = useState4([]);
|
|
4029
|
+
const [timeZone, setTimeZone] = useState4(resolveBrowserTimeZone());
|
|
4030
|
+
const [isLoading, setIsLoading] = useState4(true);
|
|
4031
|
+
const [error, setError] = useState4(null);
|
|
4032
|
+
const [nonce, setNonce] = useState4(0);
|
|
4033
|
+
useEffect5(() => {
|
|
4034
|
+
const controller = new AbortController();
|
|
4035
|
+
setIsLoading(true);
|
|
4036
|
+
client.busy(calendarKey, { since: window2.since, until: window2.until }).then((response) => {
|
|
4037
|
+
if (controller.signal.aborted) return;
|
|
4038
|
+
setBusy(response.busy);
|
|
4039
|
+
setTimeZone(response.time_zone);
|
|
4040
|
+
setError(null);
|
|
4041
|
+
}).catch((cause) => {
|
|
4042
|
+
if (controller.signal.aborted) return;
|
|
4043
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
4044
|
+
}).finally(() => {
|
|
4045
|
+
if (!controller.signal.aborted) setIsLoading(false);
|
|
4046
|
+
});
|
|
4047
|
+
return () => controller.abort();
|
|
4048
|
+
}, [client, calendarKey, window2.since, window2.until, nonce]);
|
|
4049
|
+
const refetch = useCallback4(() => setNonce((value) => value + 1), []);
|
|
4050
|
+
return { busy, timeZone, isLoading, error, refetch };
|
|
4051
|
+
}
|
|
4052
|
+
function useSlots(busy, timeZone, options) {
|
|
4053
|
+
const zone = options.timeZone ?? timeZone;
|
|
4054
|
+
return useMemo3(() => {
|
|
4055
|
+
const { slots, nextAvailableAt } = computeSlots({ ...options, busy, timeZone: zone });
|
|
4056
|
+
return { slots, days: groupSlotsByDay(slots, zone), nextAvailableAt };
|
|
4057
|
+
}, [busy, zone, options]);
|
|
4058
|
+
}
|
|
4059
|
+
function useBookingForm(options = {}) {
|
|
4060
|
+
const endpoint = options.endpoint ?? "/api/aranova/calendar/bookings";
|
|
4061
|
+
const [status, setStatus] = useState4("idle");
|
|
4062
|
+
const [error, setError] = useState4(null);
|
|
4063
|
+
const [booking, setBooking] = useState4(null);
|
|
4064
|
+
const submit = useCallback4(
|
|
4065
|
+
async (input) => {
|
|
4066
|
+
setStatus("submitting");
|
|
4067
|
+
setError(null);
|
|
4068
|
+
try {
|
|
4069
|
+
const response = await fetch(endpoint, {
|
|
4070
|
+
method: "POST",
|
|
4071
|
+
headers: { "Content-Type": "application/json" },
|
|
4072
|
+
body: JSON.stringify(input)
|
|
4073
|
+
});
|
|
4074
|
+
const body = await response.json().catch(() => null);
|
|
4075
|
+
if (!response.ok) {
|
|
4076
|
+
const detail = body && typeof body === "object" && typeof body.detail === "string" ? body.detail : "Booking failed";
|
|
4077
|
+
throw new Error(detail);
|
|
4078
|
+
}
|
|
4079
|
+
setBooking(body);
|
|
4080
|
+
setStatus("confirmed");
|
|
4081
|
+
options.onConfirmed?.(body);
|
|
4082
|
+
} catch (cause) {
|
|
4083
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
4084
|
+
setStatus("failed");
|
|
4085
|
+
}
|
|
4086
|
+
},
|
|
4087
|
+
[endpoint, options]
|
|
4088
|
+
);
|
|
4089
|
+
const reset = useCallback4(() => {
|
|
4090
|
+
setStatus("idle");
|
|
4091
|
+
setError(null);
|
|
4092
|
+
setBooking(null);
|
|
4093
|
+
}, []);
|
|
4094
|
+
return { status, error, booking, submit, reset };
|
|
4095
|
+
}
|
|
3677
4096
|
export {
|
|
3678
4097
|
AdPlatformTracking,
|
|
3679
4098
|
AranovaApiError,
|
|
4099
|
+
BOOKING_SOURCES,
|
|
4100
|
+
BOOKING_STATUSES,
|
|
4101
|
+
CALENDAR_ERROR_CODES,
|
|
4102
|
+
CALENDAR_METADATA_SCHEMA_VERSION,
|
|
4103
|
+
CALENDAR_SYNC_STATUSES,
|
|
4104
|
+
CalendarClientProvider,
|
|
3680
4105
|
ConsentBanner,
|
|
3681
4106
|
DEFAULT_DECLINE_TTL_DAYS,
|
|
3682
4107
|
DEFAULT_PHONE_COUNTRY,
|
|
3683
4108
|
EVENT_SEMANTICS,
|
|
4109
|
+
GOOGLE_SYNC_STATES,
|
|
3684
4110
|
GoogleAdsTracking,
|
|
4111
|
+
MAX_ATTENDEES,
|
|
4112
|
+
MAX_BOOKING_MINUTES,
|
|
4113
|
+
MAX_REMINDER_MINUTES,
|
|
4114
|
+
MAX_REMINDER_OVERRIDES,
|
|
3685
4115
|
NAMED_RANGES,
|
|
3686
4116
|
PhoneField,
|
|
3687
4117
|
SUPPORTED_CURRENCIES,
|
|
3688
4118
|
TRACKING_PARAM_KEYS,
|
|
3689
4119
|
TRACKING_RANGES,
|
|
4120
|
+
bookingAttendeeSchema,
|
|
4121
|
+
bookingCancelSchema,
|
|
4122
|
+
bookingCreateSchema,
|
|
4123
|
+
bookingCustomerSchema,
|
|
4124
|
+
bookingLocationNullableSchema,
|
|
4125
|
+
bookingLocationSchema,
|
|
4126
|
+
bookingMetadataSchema,
|
|
4127
|
+
bookingReminderSchema,
|
|
4128
|
+
bookingRemindersSchema,
|
|
4129
|
+
bookingRescheduleSchema,
|
|
3690
4130
|
captureTrackingParamsFromLocation,
|
|
3691
4131
|
clearStashedUserData,
|
|
4132
|
+
computeSlots,
|
|
4133
|
+
createCalendarReadClient,
|
|
3692
4134
|
createSalesClient,
|
|
3693
4135
|
createTracking,
|
|
3694
4136
|
createTrackingClientContext,
|
|
3695
4137
|
createTrackingEventCreatePayload,
|
|
3696
4138
|
createTrackingSessionUpsertPayload,
|
|
4139
|
+
durationMinutes,
|
|
4140
|
+
endOfDayInTz,
|
|
3697
4141
|
fetchServices,
|
|
3698
4142
|
formatDateInTz,
|
|
3699
4143
|
formatMoney,
|
|
3700
4144
|
formatPhone,
|
|
3701
4145
|
formatPhoneAsTyped,
|
|
4146
|
+
formatSlotRange,
|
|
3702
4147
|
fromMinor,
|
|
3703
4148
|
getConsentChoice,
|
|
3704
4149
|
getConsentState,
|
|
3705
4150
|
getEventSemantics,
|
|
3706
4151
|
getTrackingConfigRuntime,
|
|
4152
|
+
groupSlotsByDay,
|
|
4153
|
+
instantFromWallClock,
|
|
4154
|
+
isCalendarError,
|
|
4155
|
+
localDateKey,
|
|
4156
|
+
offsetMinutesAt,
|
|
3707
4157
|
onConsentChange,
|
|
3708
4158
|
optIn,
|
|
3709
4159
|
optOut,
|
|
3710
4160
|
parsePhone,
|
|
3711
4161
|
phoneField,
|
|
3712
4162
|
resetConsent,
|
|
4163
|
+
resolveBrowserTimeZone,
|
|
3713
4164
|
resolveConversionConfig,
|
|
3714
4165
|
saleCreateSchema,
|
|
3715
4166
|
saleItemSchema,
|
|
@@ -3717,15 +4168,20 @@ export {
|
|
|
3717
4168
|
saleUpdateSchema,
|
|
3718
4169
|
salesRequest,
|
|
3719
4170
|
setConsentState,
|
|
4171
|
+
startOfDayInTz,
|
|
3720
4172
|
stashUserData,
|
|
3721
4173
|
toE164,
|
|
3722
4174
|
toMinor,
|
|
4175
|
+
useBookingForm,
|
|
4176
|
+
useCalendarBusy,
|
|
4177
|
+
useCalendarClient,
|
|
3723
4178
|
useConsent,
|
|
3724
4179
|
useConsentState,
|
|
3725
4180
|
useCookiePreferences,
|
|
3726
4181
|
useGclid,
|
|
3727
4182
|
usePhoneConfig,
|
|
3728
4183
|
usePhoneField,
|
|
4184
|
+
useSlots,
|
|
3729
4185
|
useTrackingParams
|
|
3730
4186
|
};
|
|
3731
4187
|
//# sourceMappingURL=index.mjs.map
|