admin/lib/trafficAttribution.ts
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

96 lines
2.6 KiB
TypeScript

import { SEO_ROUTES } from '@/constants/routes'
import { createClientId } from '@/lib/createClientId'
export type TrafficSource = 'instagram' | 'telegram'
const VISITOR_KEY = 'ghabilee:visitor-id'
const ATTRIBUTION_PREFIX = 'ghabilee:attribution:'
const ATTRIBUTION_TTL_MS = 30 * 24 * 60 * 60 * 1000
const SOURCES = new Set<TrafficSource>(['instagram', 'telegram'])
interface StoredAttribution {
source: TrafficSource
capturedAt: number
}
const isBrowser = () => typeof window !== 'undefined'
export const parseTrafficSource = (raw: string | null | undefined): TrafficSource | null => {
if (!raw) return null
const value = raw.trim().toLowerCase()
return SOURCES.has(value as TrafficSource) ? (value as TrafficSource) : null
}
export const getOrCreateVisitorId = (): string | null => {
if (!isBrowser()) return null
try {
const existing = window.localStorage.getItem(VISITOR_KEY)
if (existing && /^[0-9a-f-]{36}$/i.test(existing)) return existing
const created = createClientId()
window.localStorage.setItem(VISITOR_KEY, created)
return created
} catch {
return null
}
}
export const rememberEventAttribution = (eventId: string, source: TrafficSource): void => {
if (!isBrowser() || !eventId) return
try {
const key = `${ATTRIBUTION_PREFIX}${eventId}`
const existingRaw = window.localStorage.getItem(key)
if (existingRaw) {
const existing = JSON.parse(existingRaw) as StoredAttribution
if (existing?.source && Date.now() - existing.capturedAt < ATTRIBUTION_TTL_MS) {
return
}
}
const payload: StoredAttribution = { source, capturedAt: Date.now() }
window.localStorage.setItem(key, JSON.stringify(payload))
} catch {
// Privacy modes / full storage — attribution is best-effort.
}
}
export const getEventAttribution = (eventId: string): TrafficSource | null => {
if (!isBrowser() || !eventId) return null
try {
const raw = window.localStorage.getItem(`${ATTRIBUTION_PREFIX}${eventId}`)
if (!raw) return null
const stored = JSON.parse(raw) as StoredAttribution
if (!stored?.source || !SOURCES.has(stored.source)) return null
if (Date.now() - stored.capturedAt > ATTRIBUTION_TTL_MS) return null
return stored.source
} catch {
return null
}
}
export const buildEventShareUrl = (slug: string, source?: TrafficSource): string => {
const path = SEO_ROUTES.EVENT_LANDING(slug)
if (!isBrowser()) {
return source ? `${path}?src=${source}` : path
}
const url = new URL(path, window.location.origin)
if (source) url.searchParams.set('src', source)
return url.toString()
}