Add duplicate pattern check when adding budget rules
Client side catches it immediately (case-insensitive match against existing rules) and shows an inline error. API also rejects with 409 as the authoritative guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,11 @@ export async function POST(req: Request) {
|
||||
const budget = await prisma.budget.findFirst({ where: { id: budgetId, userId: session.user.id } })
|
||||
if (!budget) return NextResponse.json({ error: 'Budget not found' }, { status: 404 })
|
||||
|
||||
const existing = await prisma.budgetRule.findFirst({
|
||||
where: { budgetId, pattern: { equals: pattern, mode: 'insensitive' } },
|
||||
})
|
||||
if (existing) return NextResponse.json({ error: 'Duplicate rule pattern' }, { status: 409 })
|
||||
|
||||
const rule = await prisma.budgetRule.create({
|
||||
data: { userId: session.user.id, budgetId, pattern },
|
||||
select: { id: true, budgetId: true, pattern: true },
|
||||
|
||||
@@ -24,10 +24,14 @@ export function BudgetRulesDialog({ open, onOpenChange, budgetId, budgetName, ru
|
||||
const router = useRouter()
|
||||
const [pattern, setPattern] = useState('')
|
||||
const [adding, setAdding] = useState(false)
|
||||
const [dupError, setDupError] = useState(false)
|
||||
|
||||
async function handleAdd(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
if (!pattern.trim()) return
|
||||
const isDup = rules.some((r) => r.pattern.toLowerCase() === pattern.trim().toLowerCase())
|
||||
if (isDup) { setDupError(true); return }
|
||||
setDupError(false)
|
||||
setAdding(true)
|
||||
await fetch('/api/budget-rules', {
|
||||
method: 'POST',
|
||||
@@ -36,6 +40,7 @@ export function BudgetRulesDialog({ open, onOpenChange, budgetId, budgetName, ru
|
||||
})
|
||||
setPattern('')
|
||||
setAdding(false)
|
||||
setDupError(false)
|
||||
router.refresh()
|
||||
}
|
||||
|
||||
@@ -78,17 +83,22 @@ export function BudgetRulesDialog({ open, onOpenChange, budgetId, budgetName, ru
|
||||
</ul>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleAdd} className="flex gap-2">
|
||||
<Input
|
||||
placeholder="e.g. Amazon, Netflix, Starbucks"
|
||||
value={pattern}
|
||||
onChange={(e) => setPattern(e.target.value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button type="submit" disabled={adding || !pattern.trim()} size="sm">
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
Add
|
||||
</Button>
|
||||
<form onSubmit={handleAdd} className="space-y-1.5">
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
placeholder="e.g. Amazon, Netflix, Starbucks"
|
||||
value={pattern}
|
||||
onChange={(e) => { setPattern(e.target.value); setDupError(false) }}
|
||||
className={dupError ? 'flex-1 border-destructive focus-visible:ring-destructive' : 'flex-1'}
|
||||
/>
|
||||
<Button type="submit" disabled={adding || !pattern.trim()} size="sm">
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
{dupError && (
|
||||
<p className="text-xs text-destructive">That pattern already exists for this budget.</p>
|
||||
)}
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user