'use client' import { useCallback, useRef } from 'react' import { HONEYPOT_FIELD, ELAPSED_MS_FIELD, type HumanitySignals, } from '../utils/humanity-signals' /** * useHumanitySignals — client primitive backing the invisible bot-protection * layer. Owns the honeypot ref + a mount timestamp, and exposes the * props to render the field plus a getter that produces the wire object to * merge into a form's POST body. Reused by every public form (contact, * waitlist, ticket, review) so the signal shape lives in exactly one place. * * Usage: * const { honeypotInputProps, getSignals, resetSignals } = useHumanitySignals() * // render: * // submit: fetch(url, { body: JSON.stringify({ ...formData, ...getSignals() }) }) * // after a successful submit: resetSignals() * * The two signals are origin-independent (they ride in the body), so they keep * working when the form is embedded behind a reverse-proxy / prefixed URL. */ export function useHumanitySignals() { const ref = useRef(null) // performance.now() is monotonic (immune to wall-clock skew). SSR-guarded // even though the hook only executes on the client. const mountedAt = useRef(typeof performance !== 'undefined' ? performance.now() : 0) const getSignals = useCallback( (): HumanitySignals => ({ [HONEYPOT_FIELD]: ref.current?.value ?? '', [ELAPSED_MS_FIELD]: typeof performance !== 'undefined' ? Math.round(performance.now() - mountedAt.current) : 0, }), [], ) const resetSignals = useCallback(() => { if (ref.current) ref.current.value = '' if (typeof performance !== 'undefined') mountedAt.current = performance.now() }, []) return { honeypotInputProps: { ref, name: HONEYPOT_FIELD }, getSignals, resetSignals } as const }