Skip to content

feat(invoices): accept button + status filter#474

Merged
ralyodio merged 1 commit into
masterfrom
feat/invoice-accept
Jun 14, 2026
Merged

feat(invoices): accept button + status filter#474
ralyodio merged 1 commit into
masterfrom
feat/invoice-accept

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

What

Adds an Accept action on received invoices plus a status filter so a payer can triage and pay quickly.

  • Accept button on each received invoice (awaiting payment). Sets metadata.accepted_at and notifies the worker that the invoice was accepted and will be paid soon. Accepting doesn't move money — it queues the invoice to be paid. Once accepted it shows a durable "Accepted — will be paid soon" indicator.
  • New route POST /api/gigs/[id]/invoice/[invoiceId]/accept (mirrors the existing reject route; only the payer/poster can accept; guards paid/rejected/cancelled; idempotent).
  • Status filter pills (All / Pending / Accepted / Paid) on both the Received and Sent tabs, with live counts. Pending = billed & awaiting payment, not yet accepted; Accepted = accepted but unpaid; Paid = settled.
  • "Accepted — ready to pay" stat card + "Accepted · will be paid soon" chip on cards.

DB

No migration — reuses the gig_invoices.metadata JSONB column (same soft-flag pattern as replacement_requested_at).

Tests

InvoicePaymentActions.test.tsx extended (accept click → endpoint → indicator, and already-accepted state). type-check + targeted tests green on top of current master.

🤖 Generated with Claude Code

Add an Accept action on received invoices so the payer can confirm
intent to pay: sets metadata.accepted_at and notifies the worker the
invoice was accepted and will be paid soon. Accepting doesn't move
money — it queues the invoice to be paid.

Replace the extra "Accepted" tab with a status filter (All / Pending /
Accepted / Paid) on both the Received and Sent tabs, so accepted-but-
unpaid invoices can be triaged and paid quickly. No DB migration —
reuses the gig_invoices.metadata JSONB column.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ralyodio ralyodio merged commit 8f1af45 into master Jun 14, 2026
4 checks passed
@ralyodio ralyodio deleted the feat/invoice-accept branch June 14, 2026 07:19
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds an Accept action for received invoices and a client-side status filter (All / Pending / Accepted / Paid) on the invoices dashboard. Acceptance is stored as metadata.accepted_at (no migration required) and is idempotent; a notification is sent to the worker on the first acceptance.

  • New POST /api/gigs/[id]/invoice/[invoiceId]/accept route that guards poster-only access, rejects terminal-state invoices, and updates the JSONB metadata column; mirrors the existing reject route pattern.
  • InvoicePaymentActions gains an acceptInvoice handler and an acceptControl element that transitions from an Accept button to a durable "Accepted — will be paid soon" indicator once confirmed.
  • page.tsx adds isAcceptedUnpaid helper, filter pill links with live counts, an "Accepted — ready to pay" stat card, and a chip on each accepted invoice card.

Confidence Score: 3/5

Safe to merge after fixing the notification type; the wrong label could mislead workers into believing payment was received when it has not.

The accept route sends a notification with type payment_received even though no money has moved. Workers could misread this as payment confirmation and stop following up. The rest of the change — idempotency logic, auth guards, client state, and tests — is well-structured.

src/app/api/gigs/[id]/invoice/[invoiceId]/accept/route.ts — notification type and missing error handling need attention before merging.

Important Files Changed

Filename Overview
src/app/api/gigs/[id]/invoice/[invoiceId]/accept/route.ts New POST route that marks an invoice as accepted via metadata.accepted_at; has a wrong notification type ("payment_received" instead of "invoice_accepted") and silently swallows notification insert errors.
src/app/dashboard/invoices/InvoicePaymentActions.tsx Adds acceptInvoice handler and acceptControl UI element; logic is correct, idempotency is handled client-side via acceptedAt state, and button disabled states are wired properly across all async operations.
src/app/dashboard/invoices/InvoicePaymentActions.test.tsx Two new test cases cover the accept-click-to-indicator flow and the already-accepted initial state; assertions are correctly scoped and test the right UI text.
src/app/dashboard/invoices/page.tsx Adds status filter pills, accepted stat card, and isAcceptedUnpaid helper; totalAccepted and totalOwed overlap without visual indication that one is a subset of the other, which may confuse payers.

Sequence Diagram

sequenceDiagram
    participant Payer as Payer (Browser)
    participant API as POST /accept
    participant DB as gig_invoices
    participant Notif as notifications
    participant Worker as Worker

    Payer->>API: POST accept
    API->>DB: SELECT invoice
    DB-->>API: invoice row
    API->>API: Guard poster_id + status
    alt already accepted
        API-->>Payer: 200 accepted_at existing
    else first acceptance
        API->>DB: UPDATE metadata.accepted_at
        API->>Notif: INSERT type payment_received
        Note over Notif: error silently dropped
        API-->>Payer: 200 accepted_at new
    end
    Payer->>Payer: show Accepted indicator
    Worker->>Worker: sees mislabelled notification
Loading

Fix All in Codex Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat(invoices): accept invoices + status..." | Re-trigger Greptile

const serviceSupabase = createServiceClient();
await (serviceSupabase.from("notifications") as any).insert({
user_id: invoice.worker_id,
type: "payment_received",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Wrong notification type for acceptance event

type: "payment_received" misrepresents an acceptance as a completed payment. If the notification UI, analytics, or any downstream handler branch on type, the worker would see a "payment received" signal when no money has moved yet. This could cause workers to treat an acceptance as full confirmation of payment, leading to disputes. A value like "invoice_accepted" (matching the surrounding title/body copy) is semantically correct.

Fix in Codex Fix in Claude Code

Comment on lines +86 to +96
const serviceSupabase = createServiceClient();
await (serviceSupabase.from("notifications") as any).insert({
user_id: invoice.worker_id,
type: "payment_received",
title: "Invoice accepted",
body: `The client accepted your invoice for "${title}" and will pay it soon.`,
data: {
gig_id: invoice.gig_id,
invoice_id: invoice.id,
},
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Notification insert errors are silently discarded

Supabase .insert() never throws — it returns { data, error }. The returned value is not destructured here, so a failed insert (DB error, missing table, RLS rejection) is completely invisible. At minimum the error should be logged with console.error so on-call engineers can detect notification delivery failures without running queries against the table.

Fix in Codex Fix in Claude Code

Comment on lines 222 to 239
@@ -194,7 +239,7 @@ export default async function InvoicesDashboardPage({
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 "Accepted — ready to pay" overlaps with "You owe" without making the relationship clear

totalOwed is computed over all awaiting-payment received invoices (including accepted ones), and totalAccepted is a strict subset of that same group. Displaying both as separate stat cards without any visual cue that one is contained in the other could cause a payer to mentally add the two figures and believe they owe more than they do. Consider labelling totalOwed as "Total owed (incl. accepted)" or subtracting totalAccepted from totalOwed to show a "Not yet committed" figure instead.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant