Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion app/contracts/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import MetadataSection from '@/components/MetadataSection'

export default function ContractDetailPage({ params }: { params: { id: string } }) {
const router = useRouter()
const { trackEvent } = useAnalytics()
const [contract, setContract] = useState<WatchedContract | null>(null)
const [alerts, setAlerts] = useState<AlertPayload[]>([])
const [mounted, setMounted] = useState(false)
Expand Down Expand Up @@ -46,6 +47,7 @@ export default function ContractDetailPage({ params }: { params: { id: string }
setEditedRules(contract!.rules)
setRulesError(null)
setShowEditRules(true)
trackEvent('rule_edit_opened', { contractId: params.id, ruleCount: contract!.rules.length })
}

function saveRules() {
Expand All @@ -54,6 +56,7 @@ export default function ContractDetailPage({ params }: { params: { id: string }
saveContract(updated)
setContract(updated)
setShowEditRules(false)
trackEvent('rule_edit_saved', { contractId: params.id, ruleCount: editedRules.length })
}

function hasUnsavedChanges(): boolean {
Expand Down Expand Up @@ -237,7 +240,16 @@ export default function ContractDetailPage({ params }: { params: { id: string }
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm px-4">
<div className="bg-zinc-900 border border-zinc-700 rounded-xl p-6 max-w-lg w-full space-y-4 max-h-[90vh] overflow-y-auto">
<h3 className="text-lg font-semibold text-zinc-100">Edit Alert Rules</h3>
<RuleBuilder rules={editedRules} onChange={setEditedRules} />
<RuleBuilder
rules={editedRules}
onChange={setEditedRules}
onRulesChanged={(rules, action) =>
trackEvent(action === 'remove' ? 'rule_removed' : 'rule_added', {
contractId: params.id,
ruleCount: rules.length,
})
}
/>
{rulesError && <p className="text-xs text-red-400">{rulesError}</p>}
<div className="flex gap-3 pt-2">
<button
Expand Down
19 changes: 17 additions & 2 deletions components/RuleBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const RULE_EXAMPLES: Record<AlertRuleType, string> = {
interface RuleBuilderProps {
rules: AlertRule[]
onChange: (rules: AlertRule[]) => void
/** Optional callback fired whenever a rule is added, updated, or removed.
* Intended as an analytics hook point — no behavior depends on it. */
onRulesChanged?: (rules: AlertRule[], action: 'add' | 'update' | 'remove') => void
}

const emptyRule = (): AlertRule => ({ type: 'AnyTransaction' })
Expand All @@ -38,7 +41,7 @@ function rulesEqual(a: AlertRule, b: AlertRule): boolean {
a.function_names.every((f, i) => f === b.function_names![i])
}

export default function RuleBuilder({ rules, onChange }: RuleBuilderProps) {
export default function RuleBuilder({ rules, onChange, onRulesChanged }: RuleBuilderProps) {
const [draft, setDraft] = useState<AlertRule>(emptyRule())
const [editingIndex, setEditingIndex] = useState<number | null>(null)
const [error, setError] = useState<string | null>(null)
Expand Down Expand Up @@ -115,6 +118,16 @@ export default function RuleBuilder({ rules, onChange }: RuleBuilderProps) {
}
onChange([...rules, draft])
}
if (editingIndex !== null) {
const updated = rules.map((r, i) => (i === editingIndex ? draft : r))
onChange(updated)
onRulesChanged?.(updated, 'update')
setEditingIndex(null)
} else {
const updated = [...rules, draft]
onChange(updated)
onRulesChanged?.(updated, 'add')
}
setDraft(emptyRule())
setError(null)
setWarning(null)
Expand All @@ -133,7 +146,9 @@ export default function RuleBuilder({ rules, onChange }: RuleBuilderProps) {
}

function removeRule(index: number) {
onChange(rules.filter((_, i) => i !== index))
const updated = rules.filter((_, i) => i !== index)
onChange(updated)
onRulesChanged?.(updated, 'remove')
}

return (
Expand Down
37 changes: 37 additions & 0 deletions lib/useAnalytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* useAnalytics
*
* A no-op analytics hook that provides a typed `trackEvent` callback point.
* Wire this to a real analytics provider (e.g. Segment, PostHog, Mixpanel)
* by replacing the stub implementation below — no call sites need to change.
*
* Usage:
* const { trackEvent } = useAnalytics()
* trackEvent('rule_edit_opened', { contractId: '...' })
*/

export type AnalyticsEventName =
| 'rule_edit_opened'
| 'rule_edit_saved'
| 'rule_edit_cancelled'
| 'rule_added'
| 'rule_removed'

export interface AnalyticsEventProperties {
contractId?: string
ruleCount?: number
ruleType?: string
[key: string]: unknown
}

// Stub: replace this function body to integrate a real analytics provider.
function trackEventStub(
_event: AnalyticsEventName,
_properties?: AnalyticsEventProperties,
): void {
// no-op — intentionally empty until a provider is configured
}

export function useAnalytics() {
return { trackEvent: trackEventStub }
}