'use client'
import { useFeatureFlags } from 'common'
import { Activity, ChevronDown, ChevronUp, Flag } from 'lucide-react'
import {
type ChangeEvent,
type Dispatch,
type SetStateAction,
useCallback,
useEffect,
useState,
} from 'react'
import {
Badge,
Button,
Input_Shadcn_ as Input,
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
Switch,
Tabs_Shadcn_ as Tabs,
TabsContent_Shadcn_ as TabsContent,
TabsList_Shadcn_ as TabsList,
TabsTrigger_Shadcn_ as TabsTrigger,
cn,
} from 'ui'
import { useDevToolbar } from './DevToolbarContext'
import type { DevTelemetryEvent } from './types'
import {
CC_ORIGINALS_KEY,
PH_ORIGINALS_KEY,
deleteCookie,
getCookie,
parseOverrideValue,
readOriginals,
safeJsonParse,
setCookie,
valuesAreEqual,
writeOriginals,
} from './utils'
const IS_LOCAL_DEV = process.env.NODE_ENV === 'development'
function EventCard({ event }: { event: DevTelemetryEvent }) {
const [isExpanded, setIsExpanded] = useState(false)
const handleToggle = () => setIsExpanded((prev) => !prev)
return (
{isExpanded && (
{JSON.stringify(event.properties, null, 2)}
)}
)
}
function FlagCard({
flagName,
currentValue,
originalValue,
isOverridden,
onToggle,
}: {
flagName: string
currentValue: unknown
originalValue: unknown
isOverridden: boolean
onToggle: (value: unknown) => void
}) {
const valueType = typeof originalValue
const isNull = originalValue === null
const inputProps = {
value: String(currentValue),
onChange: (event: ChangeEvent) => onToggle(event.target.value),
className: 'w-32',
}
return (
{isOverridden && (
Original:{' '}
{JSON.stringify(originalValue)}
)}
)
}
export function DevToolbar() {
const { isEnabled, isOpen, setIsOpen, events, setEvents } = useDevToolbar()
const [activeTab, setActiveTab] = useState('events')
const [flagsSubTab, setFlagsSubTab] = useState<'posthog' | 'configcat'>('posthog')
const [eventFilter, setEventFilter] = useState('')
const { posthog: posthogFlags, configcat: configcatFlags } = useFeatureFlags()
const [phFlagOverrides, setPhFlagOverrides] = useState>({})
const [ccFlagOverrides, setCcFlagOverrides] = useState>({})
const [phFlagOriginals, setPhFlagOriginals] = useState>({})
const [ccFlagOriginals, setCcFlagOriginals] = useState>({})
const loadOverrides = useCallback(
(cookieName: string, label: string, setter: (value: Record) => void) => {
const parsed = safeJsonParse>(getCookie(cookieName), {}, label)
if (Object.keys(parsed).length > 0) {
setter(parsed)
}
},
[]
)
const saveOverrides = useCallback(
(
cookieName: string,
overrides: Record,
setter: (value: Record) => void
) => {
setter(overrides)
if (Object.keys(overrides).length > 0) {
setCookie(cookieName, JSON.stringify(overrides), '/')
} else {
deleteCookie(cookieName)
}
},
[]
)
const updateOriginals = useCallback(
(
storageKey: typeof PH_ORIGINALS_KEY | typeof CC_ORIGINALS_KEY,
setter: Dispatch>>
) =>
(updater: (prev: Record) => Record) => {
setter((prev) => {
const next = updater(prev)
writeOriginals(storageKey, next)
return next
})
},
[]
)
useEffect(() => {
loadOverrides('x-ph-flag-overrides', 'PostHog flag overrides', setPhFlagOverrides)
loadOverrides('x-cc-flag-overrides', 'ConfigCat flag overrides', setCcFlagOverrides)
}, [loadOverrides])
useEffect(() => {
setPhFlagOriginals(readOriginals(PH_ORIGINALS_KEY))
setCcFlagOriginals(readOriginals(CC_ORIGINALS_KEY))
}, [])
const updatePhOriginals = updateOriginals(PH_ORIGINALS_KEY, setPhFlagOriginals)
const updateCcOriginals = updateOriginals(CC_ORIGINALS_KEY, setCcFlagOriginals)
const togglePhFlagOverride = (flagName: string, value: unknown) => {
const originalValue = phFlagOriginals[flagName] ?? posthogFlags[flagName]
const parsedValue = parseOverrideValue(value, originalValue)
if (valuesAreEqual(parsedValue, originalValue)) {
const newOverrides = { ...phFlagOverrides }
delete newOverrides[flagName]
saveOverrides('x-ph-flag-overrides', newOverrides, setPhFlagOverrides)
return
}
updatePhOriginals((prev) =>
flagName in prev ? prev : { ...prev, [flagName]: posthogFlags[flagName] }
)
const newOverrides = { ...phFlagOverrides, [flagName]: parsedValue }
saveOverrides('x-ph-flag-overrides', newOverrides, setPhFlagOverrides)
}
const toggleCcFlagOverride = (flagName: string, value: unknown) => {
const originalValue = ccFlagOriginals[flagName] ?? configcatFlags[flagName]
const parsedValue = parseOverrideValue(value, originalValue)
if (valuesAreEqual(parsedValue, originalValue)) {
const newOverrides = { ...ccFlagOverrides }
delete newOverrides[flagName]
saveOverrides('x-cc-flag-overrides', newOverrides, setCcFlagOverrides)
return
}
updateCcOriginals((prev) =>
flagName in prev ? prev : { ...prev, [flagName]: configcatFlags[flagName] }
)
const newOverrides = { ...ccFlagOverrides, [flagName]: parsedValue }
saveOverrides('x-cc-flag-overrides', newOverrides, setCcFlagOverrides)
}
const clearAllOverrides = () => {
setPhFlagOverrides({})
setCcFlagOverrides({})
setPhFlagOriginals({})
setCcFlagOriginals({})
deleteCookie('x-ph-flag-overrides')
deleteCookie('x-cc-flag-overrides')
writeOriginals(PH_ORIGINALS_KEY, {})
writeOriginals(CC_ORIGINALS_KEY, {})
window.location.reload()
}
const normalizedFilter = eventFilter.trim().toLowerCase()
const filteredEvents = (
normalizedFilter
? events.filter(
(e) =>
e.eventName.toLowerCase().includes(normalizedFilter) ||
e.eventType.toLowerCase().includes(normalizedFilter)
)
: events
)
.slice()
.sort((a, b) => b.timestamp - a.timestamp)
const phOverrideCount = Object.keys(phFlagOverrides).length
const ccOverrideCount = Object.keys(ccFlagOverrides).length
const totalOverrideCount = phOverrideCount + ccOverrideCount
if (!IS_LOCAL_DEV || !isEnabled) return null
return (
event.preventDefault()}
>
View telemetry events and feature flags for local development
Events ({filteredEvents.length})
Flags {totalOverrideCount > 0 && `(${totalOverrideCount} overrides)`}
{activeTab === 'events' && (
setEventFilter(e.target.value)}
className="flex-1"
/>
{filteredEvents.length === 0 ? (
No events yet. Interact with the app to see telemetry events.
) : (
filteredEvents.map((event) => (
))
)}
)}
{activeTab === 'flags' && (
{totalOverrideCount > 0 && (
{totalOverrideCount} flag(s) overridden
{phOverrideCount > 0 && ccOverrideCount > 0
? ` (${phOverrideCount} PostHog, ${ccOverrideCount} ConfigCat)`
: ''}
)}
setFlagsSubTab(v as 'posthog' | 'configcat')}
className="flex-1 flex flex-col min-h-0 overflow-hidden"
>
PostHog {phOverrideCount > 0 && `(${phOverrideCount})`}
ConfigCat {ccOverrideCount > 0 && `(${ccOverrideCount})`}
{flagsSubTab === 'posthog' && (
{Object.keys(posthogFlags).length === 0 ? (
No PostHog feature flags loaded yet.
) : (
Object.entries(posthogFlags).map(([flagName, flagValue]) => (
togglePhFlagOverride(flagName, value)}
/>
))
)}
)}
{flagsSubTab === 'configcat' && (
{Object.keys(configcatFlags).length === 0 ? (
No ConfigCat feature flags loaded yet.
) : (
Object.entries(configcatFlags).map(([flagName, flagValue]) => (
toggleCcFlagOverride(flagName, value)}
/>
))
)}
)}
)}
)
}