"use client" import { useCallback, useEffect, useRef, useState } from "react" import { type Control, type FieldValues, useFormState, type UseFormSubscribe, } from "react-hook-form" import { type FormType, trackFormAbandonment, trackFormCompletion, trackFormInputStarted, trackFormValidationError, } from "./form" import type { FieldErrors } from "react-hook-form" export function useFormTracking( formType: FormType, subscribe: UseFormSubscribe, control: Control, nameSuffix: string = "" ) { const [formStarted, setFormStarted] = useState(false) const lastAccessedField = useRef(undefined) const formState = useFormState({ control }) useEffect(() => { const unsubscribe = subscribe({ formState: { dirtyFields: true }, callback: (data) => { if ("name" in data) { lastAccessedField.current = data.name as string } if (!formStarted) { trackFormInputStarted(formType, nameSuffix, lastAccessedField.current) setFormStarted(true) } }, }) return () => unsubscribe() }, [subscribe, formType, nameSuffix, formStarted]) useEffect(() => { if (!formStarted || !lastAccessedField.current || formState.isValid) 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, formState.isValid]) useEffect(() => { if (formState.submitCount === 0) return if (formState.isValid) return trackErrors(formState.errors, formType, nameSuffix) }, [formState.submitCount, formState.errors, formType, nameSuffix]) const trackFormSubmit = useCallback(() => { if (formState.isValid) { trackFormCompletion(formType, nameSuffix) } }, [formType, nameSuffix, formState.isValid]) return { trackFormSubmit, } } function trackErrors( errors: FieldErrors, formType: FormType, nameSuffix: string ) { const errorKeys = Object.getOwnPropertyNames(errors) errorKeys.forEach((key) => { const msg = errors[key]?.message if (!msg) { // Handle nested errors trackErrors(errors[key] as FieldErrors, formType, nameSuffix) return } if (!msg || typeof msg !== "string") return trackFormValidationError(formType, nameSuffix, msg) }) }