Merged in fix/SW-2679-tracking-signup-details-forms (pull request #2236)
feat(SW-2679): Added form tracking for checkout and signup * feat(SW-2679): Added form tracking for checkout and signup * fix(SW-2679): fixes from review Approved-by: Michael Zetterberg
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
"use client"
|
||||
|
||||
import isEqual from "fast-deep-equal"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { startTransition, useEffect, useRef, useState } from "react"
|
||||
import {
|
||||
type Control,
|
||||
type FieldErrors,
|
||||
type FieldValues,
|
||||
useFormState,
|
||||
type UseFromSubscribe,
|
||||
} from "react-hook-form"
|
||||
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
import useRouterTransitionStore from "@/stores/router-transition"
|
||||
@@ -11,6 +18,12 @@ import useLang from "@/hooks/useLang"
|
||||
import { useSessionId } from "@/hooks/useSessionId"
|
||||
import { promiseWithTimeout } from "@/utils/promiseWithTimeout"
|
||||
import { createSDKPageObject, trackPageView } from "@/utils/tracking"
|
||||
import {
|
||||
type FormType,
|
||||
trackFormAbandonment,
|
||||
trackFormInputStarted,
|
||||
trackFormValidationError,
|
||||
} from "@/utils/tracking/form"
|
||||
|
||||
import type {
|
||||
TrackingSDKProps,
|
||||
@@ -261,3 +274,83 @@ const getPageLoadTimeEntry = () => {
|
||||
observer.observe({ type: "navigation", buffered: true })
|
||||
})
|
||||
}
|
||||
|
||||
export function useFormTracking<T extends FieldValues>(
|
||||
formType: FormType,
|
||||
subscribe: UseFromSubscribe<T>,
|
||||
control: Control<T>,
|
||||
nameSuffix: string = ""
|
||||
) {
|
||||
const [formStarted, setFormStarted] = useState(false)
|
||||
const lastAccessedField = useRef<string | undefined>(undefined)
|
||||
const formState = useFormState({ control })
|
||||
const previousErrors = useRef<FieldErrors<T>>({})
|
||||
|
||||
useEffect(() => {
|
||||
const errors = formState.errors
|
||||
const prevErrors = previousErrors.current
|
||||
|
||||
const isNewErrors = !isEqual(errors, prevErrors)
|
||||
if (Object.keys(errors).length && isNewErrors) {
|
||||
const errorString = Object.values(errors)
|
||||
.map((err) => {
|
||||
if (err && "message" in err) {
|
||||
return err.message
|
||||
}
|
||||
const nested = Object.values(err ?? {}).find(
|
||||
(val) => val && typeof val === "object" && "message" in val
|
||||
)
|
||||
if (nested) {
|
||||
return nested.message
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("|")
|
||||
|
||||
trackFormValidationError(formType, errorString, nameSuffix)
|
||||
previousErrors.current = { ...errors }
|
||||
}
|
||||
}, [formType, formState, nameSuffix])
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = subscribe({
|
||||
formState: { touchedFields: true },
|
||||
callback: (data) => {
|
||||
if ("name" in data) {
|
||||
lastAccessedField.current = data.name as string
|
||||
}
|
||||
|
||||
if (!formStarted) {
|
||||
trackFormInputStarted(formType, nameSuffix)
|
||||
setFormStarted(true)
|
||||
}
|
||||
},
|
||||
})
|
||||
return () => unsubscribe()
|
||||
}, [subscribe, formType, nameSuffix, formStarted])
|
||||
|
||||
useEffect(() => {
|
||||
if (!formStarted || !lastAccessedField.current) return
|
||||
|
||||
const lastField = lastAccessedField.current
|
||||
|
||||
function handleBeforeUnload() {
|
||||
trackFormAbandonment(formType, lastField, nameSuffix)
|
||||
}
|
||||
|
||||
function handleVisibilityChange() {
|
||||
if (document.visibilityState === "hidden") {
|
||||
trackFormAbandonment(formType, lastField, nameSuffix)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("beforeunload", handleBeforeUnload)
|
||||
window.addEventListener("visibilitychange", handleVisibilityChange)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", handleBeforeUnload)
|
||||
window.removeEventListener("visibilitychange", handleVisibilityChange)
|
||||
}
|
||||
}, [formStarted, formType, nameSuffix])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user