@se-studio/core-ui 1.34.0 → 1.35.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/ANALYTICS.md +24 -2
- package/CHANGELOG.md +6 -0
- package/dist/analytics/form/createFormAnalyticsSession.d.ts +36 -0
- package/dist/analytics/form/createFormAnalyticsSession.d.ts.map +1 -0
- package/dist/analytics/form/createFormAnalyticsSession.js +62 -0
- package/dist/analytics/form/createFormAnalyticsSession.js.map +1 -0
- package/dist/analytics/form/formAnalyticsTypes.d.ts +43 -0
- package/dist/analytics/form/formAnalyticsTypes.d.ts.map +1 -0
- package/dist/analytics/form/formAnalyticsTypes.js +40 -0
- package/dist/analytics/form/formAnalyticsTypes.js.map +1 -0
- package/dist/analytics/form/index.d.ts +5 -0
- package/dist/analytics/form/index.d.ts.map +1 -0
- package/dist/analytics/form/index.js +5 -0
- package/dist/analytics/form/index.js.map +1 -0
- package/dist/analytics/form/sanitizeFormAnalyticsProperties.d.ts +9 -0
- package/dist/analytics/form/sanitizeFormAnalyticsProperties.d.ts.map +1 -0
- package/dist/analytics/form/sanitizeFormAnalyticsProperties.js +62 -0
- package/dist/analytics/form/sanitizeFormAnalyticsProperties.js.map +1 -0
- package/dist/analytics/form/useFormAnalytics.d.ts +13 -0
- package/dist/analytics/form/useFormAnalytics.d.ts.map +1 -0
- package/dist/analytics/form/useFormAnalytics.js +67 -0
- package/dist/analytics/form/useFormAnalytics.js.map +1 -0
- package/dist/analytics/formAnalyticsEntry.d.ts +10 -0
- package/dist/analytics/formAnalyticsEntry.d.ts.map +1 -0
- package/dist/analytics/formAnalyticsEntry.js +9 -0
- package/dist/analytics/formAnalyticsEntry.js.map +1 -0
- package/docs/PACKAGE_EXPORT_BOUNDARIES.md +1 -0
- package/docs/llms.md +6 -0
- package/package.json +5 -1
package/ANALYTICS.md
CHANGED
|
@@ -81,14 +81,36 @@ Target names (same property names where the site already uses them):
|
|
|
81
81
|
|-------|------|
|
|
82
82
|
| `page_view` | Full and soft navigations |
|
|
83
83
|
| `click` | Tracked UI clicks (`trackClick`) |
|
|
84
|
-
| `form_view` | Form
|
|
84
|
+
| `form_view` | Form component mount (not viewport impression; see below) |
|
|
85
85
|
| `form_start` | First interaction with a form field |
|
|
86
|
-
| `form_submit` | Successful submit |
|
|
86
|
+
| `form_submit` | Successful submit (HubSpot/API accept) |
|
|
87
87
|
| `form_error` | Submit failed (validation or HubSpot/API error shown to the user) |
|
|
88
88
|
| `video_play` | Video starts |
|
|
89
89
|
|
|
90
90
|
Sites may add extra events. Do not rename or drop an existing site event only to converge. Policy: `docs/PHILOSOPHY.md` R7.5. Form failure handling: `@se-studio/hubspot` `docs/llms.md`.
|
|
91
91
|
|
|
92
|
+
### Form analytics (non-PII)
|
|
93
|
+
|
|
94
|
+
`useFormAnalytics` / `createFormAnalyticsSession` emit the four events through `useAnalytics().trackEvent`. Import them from `@se-studio/core-ui/form-analytics` (not the root `@se-studio/core-ui` barrel). `@se-studio/hubspot` `useHubspotSubmit` and `HubspotDynamicForm` emit only when you pass `formAnalytics={true}` (off by default so upgrades are a no-op).
|
|
95
|
+
|
|
96
|
+
`form_view` fires **on mount**, not when the form enters the viewport. A form below the fold still counts as a view, so view-to-submit will sit close to page-view rates. That is v1; do not treat it as an impression rate. A visibility option can come later.
|
|
97
|
+
|
|
98
|
+
Allowed parameters only:
|
|
99
|
+
|
|
100
|
+
| Parameter | Meaning |
|
|
101
|
+
|-----------|---------|
|
|
102
|
+
| `form_id` | HubSpot form GUID or site form id. Identifier token (`SAFE_TOKEN`); digits-only ids pass validation. |
|
|
103
|
+
| `form_name` | Form name (not a person name) |
|
|
104
|
+
| `form_type` | e.g. `hubspot` |
|
|
105
|
+
| `cms_entry_id` | Contentful entry id when the renderer passes it |
|
|
106
|
+
| `step` | Multi-step index or id |
|
|
107
|
+
| `error_code` | Token such as `hubspot_reject`, `too_fast`, `constraint` |
|
|
108
|
+
| `error_category` | `validation` \| `api` \| `too_fast` \| `network` \| `redirect` |
|
|
109
|
+
|
|
110
|
+
Never send field values, emails, names, phone numbers, or raw error messages. Payloads are allowlisted in `sanitizeFormAnalyticsProperties`. Custom site forms should import `useFormAnalytics` from `@se-studio/core-ui/form-analytics` instead of inventing their own event names.
|
|
111
|
+
|
|
112
|
+
HubSpot / form packages must import from `@se-studio/core-ui/form-analytics` (slim entry: provider + form events, no Visual/Lottie). Do not re-export that module from the root `@se-studio/core-ui` barrel.
|
|
113
|
+
|
|
92
114
|
## Google Tag Manager (GTM)
|
|
93
115
|
|
|
94
116
|
The `GoogleTagManagerAdapter` supports both GTM Container ID and GA4 Measurement ID.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @se-studio/core-ui
|
|
2
2
|
|
|
3
|
+
## 1.35.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e746884: Add generic non-PII form analytics (`form_view`, `form_start`, `form_submit`, `form_error`) via `useAnalytics`. HubSpot forms do **not** emit these on upgrade; opt in with `formAnalytics={true}` on `HubspotDynamicForm` / `useHubspotSubmit` / `createHubspotFormExternalRenderer`. Import from `@se-studio/core-ui/form-analytics` only (not the root barrel). Payloads are allowlisted and never include field values or raw error text.
|
|
8
|
+
|
|
3
9
|
## 1.34.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { FormAnalyticsErrorInfo, FormAnalyticsTrackFn } from './formAnalyticsTypes';
|
|
2
|
+
import { type FormAnalyticsIdentity } from './formAnalyticsTypes';
|
|
3
|
+
export interface CreateFormAnalyticsSessionOptions {
|
|
4
|
+
identity: FormAnalyticsIdentity;
|
|
5
|
+
track: FormAnalyticsTrackFn;
|
|
6
|
+
}
|
|
7
|
+
export interface FormAnalyticsSession {
|
|
8
|
+
/** Fire `form_view` once for this form instance. */
|
|
9
|
+
markView: () => void;
|
|
10
|
+
/** Fire `form_start` once on first interaction. */
|
|
11
|
+
markStart: () => void;
|
|
12
|
+
/** Fire `form_submit` on successful accept (each success). */
|
|
13
|
+
markSubmit: () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Fire `form_error`. Validation errors are deduped per attempt (multiple
|
|
16
|
+
* invalid fields on one submit → one event). API/network errors fire each time.
|
|
17
|
+
*/
|
|
18
|
+
markError: (info?: FormAnalyticsErrorInfo) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Snapshot for unit tests / debugging only.
|
|
21
|
+
*/
|
|
22
|
+
getState: () => Readonly<FormAnalyticsSessionState>;
|
|
23
|
+
}
|
|
24
|
+
export interface FormAnalyticsSessionState {
|
|
25
|
+
viewed: boolean;
|
|
26
|
+
started: boolean;
|
|
27
|
+
submitCount: number;
|
|
28
|
+
errorCount: number;
|
|
29
|
+
validationErrorThisAttempt: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pure form analytics session. Callers (hooks / HubSpot submit) translate DOM
|
|
33
|
+
* events into these methods. Never pass field values into `track`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createFormAnalyticsSession(options: CreateFormAnalyticsSessionOptions): FormAnalyticsSession;
|
|
36
|
+
//# sourceMappingURL=createFormAnalyticsSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createFormAnalyticsSession.d.ts","sourceRoot":"","sources":["../../../src/analytics/form/createFormAnalyticsSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAyB,KAAK,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGzF,MAAM,WAAW,iCAAiC;IAChD,QAAQ,EAAE,qBAAqB,CAAC;IAChC,KAAK,EAAE,oBAAoB,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,mDAAmD;IACnD,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB;;;OAGG;IACH,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACnD;;OAEG;IACH,QAAQ,EAAE,MAAM,QAAQ,CAAC,yBAAyB,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,0BAA0B,EAAE,OAAO,CAAC;CACrC;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iCAAiC,GACzC,oBAAoB,CAyDtB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { FORM_ANALYTICS_EVENTS } from './formAnalyticsTypes';
|
|
2
|
+
import { sanitizeFormAnalyticsProperties } from './sanitizeFormAnalyticsProperties';
|
|
3
|
+
/**
|
|
4
|
+
* Pure form analytics session. Callers (hooks / HubSpot submit) translate DOM
|
|
5
|
+
* events into these methods. Never pass field values into `track`.
|
|
6
|
+
*/
|
|
7
|
+
export function createFormAnalyticsSession(options) {
|
|
8
|
+
const { identity, track } = options;
|
|
9
|
+
const state = {
|
|
10
|
+
viewed: false,
|
|
11
|
+
started: false,
|
|
12
|
+
submitCount: 0,
|
|
13
|
+
errorCount: 0,
|
|
14
|
+
validationErrorThisAttempt: false,
|
|
15
|
+
};
|
|
16
|
+
const emit = (event, extra) => {
|
|
17
|
+
const properties = sanitizeFormAnalyticsProperties({
|
|
18
|
+
...identity,
|
|
19
|
+
...extra,
|
|
20
|
+
});
|
|
21
|
+
track(event, properties);
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
markView: () => {
|
|
25
|
+
if (state.viewed) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
state.viewed = true;
|
|
29
|
+
emit(FORM_ANALYTICS_EVENTS.view);
|
|
30
|
+
},
|
|
31
|
+
markStart: () => {
|
|
32
|
+
if (state.started) {
|
|
33
|
+
state.validationErrorThisAttempt = false;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
state.started = true;
|
|
37
|
+
emit(FORM_ANALYTICS_EVENTS.start);
|
|
38
|
+
},
|
|
39
|
+
markSubmit: () => {
|
|
40
|
+
state.validationErrorThisAttempt = false;
|
|
41
|
+
state.submitCount += 1;
|
|
42
|
+
emit(FORM_ANALYTICS_EVENTS.submit);
|
|
43
|
+
},
|
|
44
|
+
markError: (info) => {
|
|
45
|
+
const category = info?.error_category;
|
|
46
|
+
if (category === 'validation') {
|
|
47
|
+
if (!state.started) {
|
|
48
|
+
state.started = true;
|
|
49
|
+
emit(FORM_ANALYTICS_EVENTS.start);
|
|
50
|
+
}
|
|
51
|
+
if (state.validationErrorThisAttempt) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
state.validationErrorThisAttempt = true;
|
|
55
|
+
}
|
|
56
|
+
state.errorCount += 1;
|
|
57
|
+
emit(FORM_ANALYTICS_EVENTS.error, info);
|
|
58
|
+
},
|
|
59
|
+
getState: () => state,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=createFormAnalyticsSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createFormAnalyticsSession.js","sourceRoot":"","sources":["../../../src/analytics/form/createFormAnalyticsSession.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAA8B,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AAiCpF;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAA0C;IAE1C,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAEpC,MAAM,KAAK,GAA8B;QACvC,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,KAAK;QACd,WAAW,EAAE,CAAC;QACd,UAAU,EAAE,CAAC;QACb,0BAA0B,EAAE,KAAK;KAClC,CAAC;IAEF,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,KAA8B,EAAE,EAAE;QAC7D,MAAM,UAAU,GAAG,+BAA+B,CAAC;YACjD,GAAG,QAAQ;YACX,GAAG,KAAK;SACT,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IAC3B,CAAC,CAAC;IAEF,OAAO;QACL,QAAQ,EAAE,GAAG,EAAE;YACb,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjB,OAAO;YACT,CAAC;YACD,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;QACD,SAAS,EAAE,GAAG,EAAE;YACd,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBAClB,KAAK,CAAC,0BAA0B,GAAG,KAAK,CAAC;gBACzC,OAAO;YACT,CAAC;YACD,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;QACD,UAAU,EAAE,GAAG,EAAE;YACf,KAAK,CAAC,0BAA0B,GAAG,KAAK,CAAC;YACzC,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC;YACvB,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC;QACD,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YAClB,MAAM,QAAQ,GAAG,IAAI,EAAE,cAAc,CAAC;YACtC,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;gBAC9B,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;oBACnB,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;oBACrB,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;gBACpC,CAAC;gBACD,IAAI,KAAK,CAAC,0BAA0B,EAAE,CAAC;oBACrC,OAAO;gBACT,CAAC;gBACD,KAAK,CAAC,0BAA0B,GAAG,IAAI,CAAC;YAC1C,CAAC;YACD,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;YACtB,IAAI,CAAC,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1C,CAAC;QACD,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK;KACtB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-party form lifecycle events fired via `trackEvent`.
|
|
3
|
+
* Sites map these names to GTM tags or HubSpot behavioural events.
|
|
4
|
+
*
|
|
5
|
+
* Payloads are non-PII: never field values, emails, names, phones, or free text.
|
|
6
|
+
*/
|
|
7
|
+
export declare const FORM_ANALYTICS_EVENTS: {
|
|
8
|
+
readonly view: 'form_view';
|
|
9
|
+
readonly start: 'form_start';
|
|
10
|
+
readonly submit: 'form_submit';
|
|
11
|
+
readonly error: 'form_error';
|
|
12
|
+
};
|
|
13
|
+
export type FormAnalyticsEventName = (typeof FORM_ANALYTICS_EVENTS)[keyof typeof FORM_ANALYTICS_EVENTS];
|
|
14
|
+
export declare const FORM_ANALYTICS_EVENT_NAMES: readonly FormAnalyticsEventName[];
|
|
15
|
+
/** Allowed payload keys — anything else is dropped before `trackEvent`. */
|
|
16
|
+
export declare const FORM_ANALYTICS_ALLOWED_KEYS: readonly ['form_id', 'form_name', 'form_type', 'cms_entry_id', 'step', 'error_code', 'error_category'];
|
|
17
|
+
export type FormAnalyticsAllowedKey = (typeof FORM_ANALYTICS_ALLOWED_KEYS)[number];
|
|
18
|
+
/**
|
|
19
|
+
* Closed set of error categories. Do not pass visitor-facing or API error messages —
|
|
20
|
+
* those can echo field values.
|
|
21
|
+
*/
|
|
22
|
+
export declare const FORM_ERROR_CATEGORIES: {
|
|
23
|
+
readonly validation: 'validation';
|
|
24
|
+
readonly api: 'api';
|
|
25
|
+
readonly too_fast: 'too_fast';
|
|
26
|
+
readonly network: 'network';
|
|
27
|
+
readonly redirect: 'redirect';
|
|
28
|
+
};
|
|
29
|
+
export type FormErrorCategory = (typeof FORM_ERROR_CATEGORIES)[keyof typeof FORM_ERROR_CATEGORIES];
|
|
30
|
+
export interface FormAnalyticsIdentity {
|
|
31
|
+
form_id?: string;
|
|
32
|
+
form_name?: string;
|
|
33
|
+
form_type?: string;
|
|
34
|
+
cms_entry_id?: string;
|
|
35
|
+
step?: string | number;
|
|
36
|
+
}
|
|
37
|
+
export interface FormAnalyticsErrorInfo {
|
|
38
|
+
error_code?: string;
|
|
39
|
+
error_category?: FormErrorCategory | string;
|
|
40
|
+
}
|
|
41
|
+
export type FormAnalyticsProperties = FormAnalyticsIdentity & FormAnalyticsErrorInfo;
|
|
42
|
+
export type FormAnalyticsTrackFn = (event: string, properties: Record<string, unknown>) => void;
|
|
43
|
+
//# sourceMappingURL=formAnalyticsTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formAnalyticsTypes.d.ts","sourceRoot":"","sources":["../../../src/analytics/form/formAnalyticsTypes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;aAChC,IAAI,EAAE,WAAW;aACjB,KAAK,EAAE,YAAY;aACnB,MAAM,EAAE,aAAa;aACrB,KAAK,EAAE,YAAY;CACX,CAAC;AAEX,MAAM,MAAM,sBAAsB,GAChC,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,OAAO,qBAAqB,CAAC,CAAC;AAErE,eAAO,MAAM,0BAA0B,EAAE,SAAS,sBAAsB,EAKvE,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B,YACtC,SAAS,EACT,WAAW,EACX,WAAW,EACX,cAAc,EACd,MAAM,EACN,YAAY,EACZ,gBAAgB,CACR,CAAC;AAEX,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnF;;;GAGG;AACH,eAAO,MAAM,qBAAqB;aAChC,UAAU,EAAE,YAAY;aACxB,GAAG,EAAE,KAAK;aACV,QAAQ,EAAE,UAAU;aACpB,OAAO,EAAE,SAAS;aAClB,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,OAAO,qBAAqB,CAAC,CAAC;AAEnG,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC;CAC7C;AAED,MAAM,MAAM,uBAAuB,GAAG,qBAAqB,GAAG,sBAAsB,CAAC;AAErF,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-party form lifecycle events fired via `trackEvent`.
|
|
3
|
+
* Sites map these names to GTM tags or HubSpot behavioural events.
|
|
4
|
+
*
|
|
5
|
+
* Payloads are non-PII: never field values, emails, names, phones, or free text.
|
|
6
|
+
*/
|
|
7
|
+
export const FORM_ANALYTICS_EVENTS = {
|
|
8
|
+
view: 'form_view',
|
|
9
|
+
start: 'form_start',
|
|
10
|
+
submit: 'form_submit',
|
|
11
|
+
error: 'form_error',
|
|
12
|
+
};
|
|
13
|
+
export const FORM_ANALYTICS_EVENT_NAMES = [
|
|
14
|
+
FORM_ANALYTICS_EVENTS.view,
|
|
15
|
+
FORM_ANALYTICS_EVENTS.start,
|
|
16
|
+
FORM_ANALYTICS_EVENTS.submit,
|
|
17
|
+
FORM_ANALYTICS_EVENTS.error,
|
|
18
|
+
];
|
|
19
|
+
/** Allowed payload keys — anything else is dropped before `trackEvent`. */
|
|
20
|
+
export const FORM_ANALYTICS_ALLOWED_KEYS = [
|
|
21
|
+
'form_id',
|
|
22
|
+
'form_name',
|
|
23
|
+
'form_type',
|
|
24
|
+
'cms_entry_id',
|
|
25
|
+
'step',
|
|
26
|
+
'error_code',
|
|
27
|
+
'error_category',
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Closed set of error categories. Do not pass visitor-facing or API error messages —
|
|
31
|
+
* those can echo field values.
|
|
32
|
+
*/
|
|
33
|
+
export const FORM_ERROR_CATEGORIES = {
|
|
34
|
+
validation: 'validation',
|
|
35
|
+
api: 'api',
|
|
36
|
+
too_fast: 'too_fast',
|
|
37
|
+
network: 'network',
|
|
38
|
+
redirect: 'redirect',
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=formAnalyticsTypes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formAnalyticsTypes.js","sourceRoot":"","sources":["../../../src/analytics/form/formAnalyticsTypes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,WAAW;IACjB,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,YAAY;CACX,CAAC;AAKX,MAAM,CAAC,MAAM,0BAA0B,GAAsC;IAC3E,qBAAqB,CAAC,IAAI;IAC1B,qBAAqB,CAAC,KAAK;IAC3B,qBAAqB,CAAC,MAAM;IAC5B,qBAAqB,CAAC,KAAK;CAC5B,CAAC;AAEF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,SAAS;IACT,WAAW;IACX,WAAW;IACX,cAAc;IACd,MAAM;IACN,YAAY;IACZ,gBAAgB;CACR,CAAC;AAIX;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,UAAU,EAAE,YAAY;IACxB,GAAG,EAAE,KAAK;IACV,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,UAAU;CACZ,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { type CreateFormAnalyticsSessionOptions, createFormAnalyticsSession, type FormAnalyticsSession, type FormAnalyticsSessionState, } from './createFormAnalyticsSession';
|
|
2
|
+
export { FORM_ANALYTICS_ALLOWED_KEYS, FORM_ANALYTICS_EVENT_NAMES, FORM_ANALYTICS_EVENTS, FORM_ERROR_CATEGORIES, type FormAnalyticsAllowedKey, type FormAnalyticsErrorInfo, type FormAnalyticsEventName, type FormAnalyticsIdentity, type FormAnalyticsProperties, type FormAnalyticsTrackFn, type FormErrorCategory, } from './formAnalyticsTypes';
|
|
3
|
+
export { sanitizeFormAnalyticsProperties } from './sanitizeFormAnalyticsProperties';
|
|
4
|
+
export { type UseFormAnalyticsOptions, useFormAnalytics } from './useFormAnalytics';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analytics/form/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iCAAiC,EACtC,0BAA0B,EAC1B,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,GAC/B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,KAAK,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createFormAnalyticsSession, } from './createFormAnalyticsSession';
|
|
2
|
+
export { FORM_ANALYTICS_ALLOWED_KEYS, FORM_ANALYTICS_EVENT_NAMES, FORM_ANALYTICS_EVENTS, FORM_ERROR_CATEGORIES, } from './formAnalyticsTypes';
|
|
3
|
+
export { sanitizeFormAnalyticsProperties } from './sanitizeFormAnalyticsProperties';
|
|
4
|
+
export { useFormAnalytics } from './useFormAnalytics';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analytics/form/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,0BAA0B,GAG3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,GAQtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAgC,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type FormAnalyticsProperties } from './formAnalyticsTypes';
|
|
2
|
+
/**
|
|
3
|
+
* Whitelist form analytics payloads. Drops unknown keys, nested objects, arrays,
|
|
4
|
+
* and unconstrained free text. Email/phone heuristics apply to `form_name` only;
|
|
5
|
+
* `form_id`, `cms_entry_id`, and `error_code` use the identifier token pattern
|
|
6
|
+
* so HubSpot UUIDs and digits-only ids are not dropped.
|
|
7
|
+
*/
|
|
8
|
+
export declare function sanitizeFormAnalyticsProperties(properties: FormAnalyticsProperties | Record<string, unknown>): Record<string, string | number>;
|
|
9
|
+
//# sourceMappingURL=sanitizeFormAnalyticsProperties.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitizeFormAnalyticsProperties.d.ts","sourceRoot":"","sources":["../../../src/analytics/form/sanitizeFormAnalyticsProperties.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AA4C9B;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,uBAAuB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5D,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAqBjC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { FORM_ANALYTICS_ALLOWED_KEYS, FORM_ERROR_CATEGORIES, } from './formAnalyticsTypes';
|
|
2
|
+
const ERROR_CATEGORY_SET = new Set(Object.values(FORM_ERROR_CATEGORIES));
|
|
3
|
+
/** Identifier-like tokens: HubSpot GUIDs, Contentful ids, snake_case codes. */
|
|
4
|
+
const SAFE_TOKEN = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,99}$/;
|
|
5
|
+
const MAX_STRING_LENGTH = 100;
|
|
6
|
+
function looksLikeEmail(value) {
|
|
7
|
+
return value.includes('@');
|
|
8
|
+
}
|
|
9
|
+
function looksLikePhone(value) {
|
|
10
|
+
const digits = value.replace(/\D/g, '');
|
|
11
|
+
return digits.length >= 7 && digits.length / value.replace(/\s/g, '').length >= 0.6;
|
|
12
|
+
}
|
|
13
|
+
function sanitizeString(key, raw) {
|
|
14
|
+
const trimmed = raw.trim().slice(0, MAX_STRING_LENGTH);
|
|
15
|
+
if (!trimmed) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (key === 'form_name') {
|
|
19
|
+
if (looksLikeEmail(trimmed) || looksLikePhone(trimmed)) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return trimmed;
|
|
23
|
+
}
|
|
24
|
+
if (key === 'error_category') {
|
|
25
|
+
return ERROR_CATEGORY_SET.has(trimmed) ? trimmed : undefined;
|
|
26
|
+
}
|
|
27
|
+
if (key === 'error_code' ||
|
|
28
|
+
key === 'form_id' ||
|
|
29
|
+
key === 'form_type' ||
|
|
30
|
+
key === 'cms_entry_id' ||
|
|
31
|
+
key === 'step') {
|
|
32
|
+
return SAFE_TOKEN.test(trimmed) ? trimmed : undefined;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Whitelist form analytics payloads. Drops unknown keys, nested objects, arrays,
|
|
38
|
+
* and unconstrained free text. Email/phone heuristics apply to `form_name` only;
|
|
39
|
+
* `form_id`, `cms_entry_id`, and `error_code` use the identifier token pattern
|
|
40
|
+
* so HubSpot UUIDs and digits-only ids are not dropped.
|
|
41
|
+
*/
|
|
42
|
+
export function sanitizeFormAnalyticsProperties(properties) {
|
|
43
|
+
const out = {};
|
|
44
|
+
for (const key of FORM_ANALYTICS_ALLOWED_KEYS) {
|
|
45
|
+
const value = properties[key];
|
|
46
|
+
if (value === undefined || value === null) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (typeof value === 'number' && Number.isFinite(value) && key === 'step') {
|
|
50
|
+
out[key] = value;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (typeof value === 'string') {
|
|
54
|
+
const sanitized = sanitizeString(key, value);
|
|
55
|
+
if (sanitized !== undefined) {
|
|
56
|
+
out[key] = sanitized;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=sanitizeFormAnalyticsProperties.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitizeFormAnalyticsProperties.js","sourceRoot":"","sources":["../../../src/analytics/form/sanitizeFormAnalyticsProperties.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,GAGtB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAS,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC;AAEjF,+EAA+E;AAC/E,MAAM,UAAU,GAAG,oCAAoC,CAAC;AAExD,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,SAAS,cAAc,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACnC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC;AACtF,CAAC;AAED,SAAS,cAAc,CAAC,GAA4B,EAAE,GAAW;IAC/D,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC;IACvD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;QACxB,IAAI,cAAc,CAAC,OAAO,CAAC,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;YACvD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,GAAG,KAAK,gBAAgB,EAAE,CAAC;QAC7B,OAAO,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/D,CAAC;IACD,IACE,GAAG,KAAK,YAAY;QACpB,GAAG,KAAK,SAAS;QACjB,GAAG,KAAK,WAAW;QACnB,GAAG,KAAK,cAAc;QACtB,GAAG,KAAK,MAAM,EACd,CAAC;QACD,OAAO,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACxD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,+BAA+B,CAC7C,UAA6D;IAE7D,MAAM,GAAG,GAAoC,EAAE,CAAC;IAEhD,KAAK,MAAM,GAAG,IAAI,2BAA2B,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAC1C,SAAS;QACX,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YAC1E,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACjB,SAAS;QACX,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC7C,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC5B,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type FormAnalyticsSession } from './createFormAnalyticsSession';
|
|
2
|
+
import type { FormAnalyticsIdentity } from './formAnalyticsTypes';
|
|
3
|
+
export interface UseFormAnalyticsOptions {
|
|
4
|
+
identity: FormAnalyticsIdentity;
|
|
5
|
+
/** When false, no events are emitted. Default true. */
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Binds a form analytics session to `useAnalytics().trackEvent`.
|
|
10
|
+
* Fires `form_view` once on mount when enabled (not on viewport impression).
|
|
11
|
+
*/
|
|
12
|
+
export declare function useFormAnalytics({ identity, enabled, }: UseFormAnalyticsOptions): FormAnalyticsSession;
|
|
13
|
+
//# sourceMappingURL=useFormAnalytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFormAnalytics.d.ts","sourceRoot":"","sources":["../../../src/analytics/form/useFormAnalytics.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAA0B,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE1F,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,qBAAqB,CAAC;IAChC,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,OAAc,GACf,EAAE,uBAAuB,GAAG,oBAAoB,CAsEhD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
3
|
+
import { useAnalytics } from '../useAnalytics';
|
|
4
|
+
import { createFormAnalyticsSession, } from './createFormAnalyticsSession';
|
|
5
|
+
/**
|
|
6
|
+
* Binds a form analytics session to `useAnalytics().trackEvent`.
|
|
7
|
+
* Fires `form_view` once on mount when enabled (not on viewport impression).
|
|
8
|
+
*/
|
|
9
|
+
export function useFormAnalytics({ identity, enabled = true, }) {
|
|
10
|
+
const { trackEvent } = useAnalytics();
|
|
11
|
+
const trackEventRef = useRef(trackEvent);
|
|
12
|
+
trackEventRef.current = trackEvent;
|
|
13
|
+
const sessionRef = useRef(null);
|
|
14
|
+
const formId = identity.form_id ?? '';
|
|
15
|
+
const formName = identity.form_name ?? '';
|
|
16
|
+
const formType = identity.form_type ?? '';
|
|
17
|
+
const cmsEntryId = identity.cms_entry_id ?? '';
|
|
18
|
+
const step = identity.step ?? '';
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!enabled) {
|
|
21
|
+
sessionRef.current = null;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const session = createFormAnalyticsSession({
|
|
25
|
+
identity: {
|
|
26
|
+
form_id: formId || undefined,
|
|
27
|
+
form_name: formName || undefined,
|
|
28
|
+
form_type: formType || undefined,
|
|
29
|
+
cms_entry_id: cmsEntryId || undefined,
|
|
30
|
+
step: step === '' ? undefined : step,
|
|
31
|
+
},
|
|
32
|
+
track: (event, properties) => {
|
|
33
|
+
trackEventRef.current(event, properties);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
sessionRef.current = session;
|
|
37
|
+
session.markView();
|
|
38
|
+
return () => {
|
|
39
|
+
if (sessionRef.current === session) {
|
|
40
|
+
sessionRef.current = null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}, [enabled, formId, formName, formType, cmsEntryId, step]);
|
|
44
|
+
const markView = useCallback(() => {
|
|
45
|
+
sessionRef.current?.markView();
|
|
46
|
+
}, []);
|
|
47
|
+
const markStart = useCallback(() => {
|
|
48
|
+
sessionRef.current?.markStart();
|
|
49
|
+
}, []);
|
|
50
|
+
const markSubmit = useCallback(() => {
|
|
51
|
+
sessionRef.current?.markSubmit();
|
|
52
|
+
}, []);
|
|
53
|
+
const markError = useCallback((info) => {
|
|
54
|
+
sessionRef.current?.markError(info);
|
|
55
|
+
}, []);
|
|
56
|
+
const getState = useCallback(() => {
|
|
57
|
+
return (sessionRef.current?.getState() ?? {
|
|
58
|
+
viewed: false,
|
|
59
|
+
started: false,
|
|
60
|
+
submitCount: 0,
|
|
61
|
+
errorCount: 0,
|
|
62
|
+
validationErrorThisAttempt: false,
|
|
63
|
+
});
|
|
64
|
+
}, []);
|
|
65
|
+
return { markView, markStart, markSubmit, markError, getState };
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=useFormAnalytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFormAnalytics.js","sourceRoot":"","sources":["../../../src/analytics/form/useFormAnalytics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slim client-safe entry for form lifecycle analytics.
|
|
3
|
+
* Import from `@se-studio/core-ui/form-analytics` in HubSpot/forms so the
|
|
4
|
+
* main `core-ui` barrel (Visual / Lottie) is not pulled into the form graph.
|
|
5
|
+
*/
|
|
6
|
+
export { AnalyticsProvider, useAnalyticsContext } from './AnalyticsProvider';
|
|
7
|
+
export { type CreateFormAnalyticsSessionOptions, createFormAnalyticsSession, FORM_ANALYTICS_ALLOWED_KEYS, FORM_ANALYTICS_EVENT_NAMES, FORM_ANALYTICS_EVENTS, FORM_ERROR_CATEGORIES, type FormAnalyticsAllowedKey, type FormAnalyticsErrorInfo, type FormAnalyticsEventName, type FormAnalyticsIdentity, type FormAnalyticsProperties, type FormAnalyticsSession, type FormAnalyticsSessionState, type FormAnalyticsTrackFn, type FormErrorCategory, sanitizeFormAnalyticsProperties, type UseFormAnalyticsOptions, useFormAnalytics, } from './form';
|
|
8
|
+
export type { AnalyticsAdapter, ClickTrackingProperties, PageTrackingProperties } from './types';
|
|
9
|
+
export { useAnalytics } from './useAnalytics';
|
|
10
|
+
//# sourceMappingURL=formAnalyticsEntry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formAnalyticsEntry.d.ts","sourceRoot":"","sources":["../../src/analytics/formAnalyticsEntry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EACL,KAAK,iCAAiC,EACtC,0BAA0B,EAC1B,2BAA2B,EAC3B,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,+BAA+B,EAC/B,KAAK,uBAAuB,EAC5B,gBAAgB,GACjB,MAAM,QAAQ,CAAC;AAChB,YAAY,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AACjG,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slim client-safe entry for form lifecycle analytics.
|
|
3
|
+
* Import from `@se-studio/core-ui/form-analytics` in HubSpot/forms so the
|
|
4
|
+
* main `core-ui` barrel (Visual / Lottie) is not pulled into the form graph.
|
|
5
|
+
*/
|
|
6
|
+
export { AnalyticsProvider, useAnalyticsContext } from './AnalyticsProvider';
|
|
7
|
+
export { createFormAnalyticsSession, FORM_ANALYTICS_ALLOWED_KEYS, FORM_ANALYTICS_EVENT_NAMES, FORM_ANALYTICS_EVENTS, FORM_ERROR_CATEGORIES, sanitizeFormAnalyticsProperties, useFormAnalytics, } from './form';
|
|
8
|
+
export { useAnalytics } from './useAnalytics';
|
|
9
|
+
//# sourceMappingURL=formAnalyticsEntry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formAnalyticsEntry.js","sourceRoot":"","sources":["../../src/analytics/formAnalyticsEntry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAEL,0BAA0B,EAC1B,2BAA2B,EAC3B,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EAUrB,+BAA+B,EAE/B,gBAAgB,GACjB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -11,6 +11,7 @@ Next.js App Router mixes **Server Components** and **Client Components** (`'use
|
|
|
11
11
|
| Entry | Who may import | Contents |
|
|
12
12
|
|-------|----------------|----------|
|
|
13
13
|
| `@se-studio/core-ui` | **Everyone** — `'use client'` files, server components, registrations | React UI, hooks, `cn`, CMS renderers, registration helpers, shared types |
|
|
14
|
+
| `@se-studio/core-ui/form-analytics` | **Client-safe** — HubSpot forms and custom forms | `useFormAnalytics`, `FORM_ANALYTICS_EVENTS`, `AnalyticsProvider` (no Visual/Lottie) |
|
|
14
15
|
| `@se-studio/core-ui/server` | **Server only** — `import 'server-only'` at top | `createAppHelpers`, `createRouteHandlers`, `getRelated*`, `filterRelatedArticles`, `articleLinkToListCard`, listing structured-data helpers, etc. |
|
|
15
16
|
| `@se-studio/core-ui/route-handlers` | **App route modules** (Node `fs`) | Discovery/guidelines/cms.txt/OG image handlers |
|
|
16
17
|
|
package/docs/llms.md
CHANGED
|
@@ -185,6 +185,12 @@ Interactive (non-autoplay) Vimeo and YouTube videos fire `trackEvent` with:
|
|
|
185
185
|
|
|
186
186
|
Export `VIDEO_ENGAGEMENT_EVENTS` / `VIDEO_ENGAGEMENT_EVENT_NAMES` for GTM or HubSpot `eventNameMap`. Requires `analyticsContext` on the visual chain. Autoplay/background videos are not tracked.
|
|
187
187
|
|
|
188
|
+
### Form analytics events
|
|
189
|
+
|
|
190
|
+
`form_view` (once on **mount**, not viewport impression) · `form_start` (first interaction) · `form_submit` (success) · `form_error` (validation or API failure).
|
|
191
|
+
|
|
192
|
+
Export `FORM_ANALYTICS_EVENTS` / `useFormAnalytics` / `sanitizeFormAnalyticsProperties` from `@se-studio/core-ui/form-analytics` only (not the root barrel). Payload keys: `form_id`, `form_name`, `form_type`, `cms_entry_id`, `step`, `error_code`, `error_category`. Never field values or raw error text. HubSpot forms emit these from `useHubspotSubmit` only when `formAnalytics={true}` (default off).
|
|
193
|
+
|
|
188
194
|
### Play button (YouTube / Vimeo)
|
|
189
195
|
|
|
190
196
|
- Site-wide brand: wrap the app in `ExternalVideoPlayButtonProvider` with a custom icon (see Brightline / SE website / HopSkipDrive).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@se-studio/core-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"description": "Shared React UI component library with Tailwind CSS v4 for SE Studio applications",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"./agent-ready": {
|
|
28
28
|
"types": "./dist/utils/agent-ready.d.ts",
|
|
29
29
|
"import": "./dist/utils/agent-ready.js"
|
|
30
|
+
},
|
|
31
|
+
"./form-analytics": {
|
|
32
|
+
"types": "./dist/analytics/formAnalyticsEntry.d.ts",
|
|
33
|
+
"import": "./dist/analytics/formAnalyticsEntry.js"
|
|
30
34
|
}
|
|
31
35
|
},
|
|
32
36
|
"files": [
|