Files
takerofnotes-website/composables/useKlaviyo.js
2026-05-29 11:22:56 -04:00

108 lines
3.1 KiB
JavaScript

import { ref } from 'vue'
// Wraps Klaviyo's legacy client-side list-subscribe endpoint.
// Klaviyo requires any non-standard property names to be declared in `$fields`
// for them to be persisted on the profile — this composable handles that for you.
const ENDPOINT = 'https://manage.kmail-lists.com/ajax/subscriptions/subscribe'
export default () => {
const loading = ref(false)
const success = ref(false)
const error = ref('')
const reset = () => {
loading.value = false
success.value = false
error.value = ''
}
const subscribe = async ({
listId,
email,
firstName,
lastName,
name,
source,
properties = {},
} = {}) => {
if (!listId) {
error.value = 'Missing listId'
return
}
if (!email) {
error.value = 'Missing email'
return
}
loading.value = true
error.value = ''
// `name` is a convenience: first token → first_name, rest → last_name
let fName = firstName
let lName = lastName
if (name && !fName && !lName) {
const parts = name.trim().split(/\s+/)
fName = parts.shift()
lName = parts.join(' ') || undefined
}
const body = { g: listId, email }
if (fName) body.first_name = fName
if (lName) body.last_name = lName
const extraFields = []
if (source) {
body.$source = source
extraFields.push('$source')
}
for (const [key, value] of Object.entries(properties)) {
if (value === undefined || value === null || value === '') continue
body[key] = value
extraFields.push(key)
}
if (extraFields.length) body.$fields = extraFields.join(',')
try {
await $fetch(ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cache-Control': 'no-cache',
},
body: new URLSearchParams(body),
})
success.value = true
} catch (err) {
console.error('useKlaviyo subscribe error', err)
error.value =
err?.toString().split('Error: ')[1] || 'Subscription failed'
}
loading.value = false
}
// Fires a Klaviyo event (metric) — append-only history, good for things
// segments will slice on like "Submitted RSVP" with per-event properties.
// Throws on failure; doesn't touch the shared loading/success/error refs,
// so callers can pair it with subscribe() without state collisions.
const track = ({ email, metric, properties = {} }) => {
if (!email || !metric) {
throw new Error('useKlaviyo.track: email and metric are required')
}
return $fetch('/api/klaviyo-track', {
method: 'POST',
body: { email, metric, properties },
})
}
return {
loading,
success,
error,
subscribe,
track,
reset,
}
}