"use client"; import { useEffect, useState } from "react"; import { AppShell } from "../../components/app-shell"; type ApiResponse = { data: T; meta: { timestamp: string; version: "v1" }; error: null | { message: string; code?: string }; }; type RuleRow = { id: string; name: string; priority: number; isActive: boolean; conditions: Record; actions: Record; }; type Suggestion = { id: string; name: string; conditions: Record; actions: Record; confidence: number; }; export default function RulesPage() { const [rules, setRules] = useState([]); const [suggestions, setSuggestions] = useState([]); const [status, setStatus] = useState("Loading rules..."); const [showNew, setShowNew] = useState(false); const [form, setForm] = useState({ name: "", priority: "", textContains: "", amountGreater: "", amountLess: "", setCategory: "", setHidden: false, isActive: true }); const load = async () => { const userId = localStorage.getItem("ledgerone_user_id"); const query = userId ? `?user_id=${encodeURIComponent(userId)}` : ""; try { const [rulesRes, suggestionsRes] = await Promise.all([ fetch(`/api/rules${query}`), fetch(`/api/rules/suggestions${query}`) ]); const rulesPayload = (await rulesRes.json()) as ApiResponse; const suggestionsPayload = (await suggestionsRes.json()) as ApiResponse; if (!rulesRes.ok || rulesPayload.error) { setStatus(rulesPayload.error?.message ?? "Unable to load rules."); return; } setRules(rulesPayload.data); setSuggestions(suggestionsPayload.data ?? []); setStatus(rulesPayload.data.length ? "" : "No rules yet."); } catch { setStatus("Unable to load rules."); } }; useEffect(() => { load(); }, []); const onCreate = async () => { const userId = localStorage.getItem("ledgerone_user_id"); if (!userId) { setStatus("Missing user id."); return; } const payload = { userId, name: form.name || "Untitled rule", priority: form.priority ? Number(form.priority) : undefined, isActive: form.isActive, conditions: { textContains: form.textContains || undefined, amountGreaterThan: form.amountGreater ? Number(form.amountGreater) : undefined, amountLessThan: form.amountLess ? Number(form.amountLess) : undefined }, actions: { setCategory: form.setCategory || undefined, setHidden: form.setHidden } }; try { const res = await fetch("/api/rules", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); const data = (await res.json()) as ApiResponse; if (!res.ok || data.error) { setStatus(data.error?.message ?? "Unable to create rule."); return; } setShowNew(false); setForm({ name: "", priority: "", textContains: "", amountGreater: "", amountLess: "", setCategory: "", setHidden: false, isActive: true }); setRules((prev) => [data.data, ...prev]); setStatus(""); } catch { setStatus("Unable to create rule."); } }; return (
Active rules Priority ordered Auto applied
{showNew ? (

Create a rule

setForm((prev) => ({ ...prev, name: event.target.value }))} placeholder="Rule name" className="rounded-xl border border-border bg-background px-3 py-2 text-xs text-foreground focus:border-primary focus:ring-primary" /> setForm((prev) => ({ ...prev, priority: event.target.value })) } placeholder="Priority (optional)" className="rounded-xl border border-border bg-background px-3 py-2 text-xs text-foreground focus:border-primary focus:ring-primary" /> setForm((prev) => ({ ...prev, textContains: event.target.value })) } placeholder="Description contains" className="rounded-xl border border-border bg-background px-3 py-2 text-xs text-foreground focus:border-primary focus:ring-primary" /> setForm((prev) => ({ ...prev, setCategory: event.target.value })) } placeholder="Set category" className="rounded-xl border border-border bg-background px-3 py-2 text-xs text-foreground focus:border-primary focus:ring-primary" /> setForm((prev) => ({ ...prev, amountGreater: event.target.value })) } placeholder="Amount greater than" className="rounded-xl border border-border bg-background px-3 py-2 text-xs text-foreground focus:border-primary focus:ring-primary" /> setForm((prev) => ({ ...prev, amountLess: event.target.value })) } placeholder="Amount less than" className="rounded-xl border border-border bg-background px-3 py-2 text-xs text-foreground focus:border-primary focus:ring-primary" />
) : null} {status ?

{status}

: null} {rules.length ? (
{rules.map((rule) => (

{rule.name}

Priority {rule.priority} - {rule.isActive ? "Active" : "Paused"}

{rule.isActive ? "Live" : "Paused"}
Conditions: {JSON.stringify(rule.conditions)}
Actions: {JSON.stringify(rule.actions)}
))}
) : null}

AI Suggestions

Pattern-based rule ideas

{suggestions.length ? ( suggestions.map((item) => (

{item.name}

Conditions: {JSON.stringify(item.conditions)}

Actions: {JSON.stringify(item.actions)}

Confidence: {(item.confidence * 100).toFixed(0)}%

)) ) : (

No suggestions yet.

)}
); }