'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 (
{flagName} {isOverridden && ( Overridden )} {isNull && ( null )}
{isNull ? ( ) : valueType === 'boolean' ? ( onToggle(checked)} /> ) : valueType === 'number' ? ( ) : ( )}
{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()} >
Dev Telemetry Local Only
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)} /> )) )}
)}
)}
) }