Skip to content

feat(sdk): guard the delete organization flow - #1881

Open
whoAbhishekSah wants to merge 4 commits into
mainfrom
sdk-org-delete-guards
Open

feat(sdk): guard the delete organization flow#1881
whoAbhishekSah wants to merge 4 commits into
mainfrom
sdk-org-delete-guards

Conversation

@whoAbhishekSah

@whoAbhishekSah whoAbhishekSah commented Aug 13, 2026

Copy link
Copy Markdown
Member

Part of #1837 — the client half of the delete pre-flight. The server stack (#1865#1894) is merged; this PR now contains all the SDK changes in one place.

Guards in the client SDK's General settings view:

  • The delete button asks the server whether a delete would go through. It calls the new CheckOrganizationDelete RPC (shipped in feat(deleter): serve CheckOrganizationDelete for delete eligibility #1894, @raystack/proton bumped to the proton main commit that carries it). The button greys out for every blocker the real delete would refuse with — a paid subscription to downgrade, unpaid invoices, a token debt — and the hover tooltip shows one short instruction per kind of blocker ("Downgrade the subscription to the standard plan", "Pay the 2 open invoices from the billing page", "Contact support to settle the token balance"), with a fallback to the server's own message for kinds this version does not know. While the check loads the button stays disabled; a failed check fails open, since the server refuses a blocked delete independently.
  • The delete dialog warns about remaining tokens. The balance is fetched only while the dialog is open, and the confirm button waits for the answer so a slow response cannot skip the warning. The dialog says how many tokens remain, that deleting forfeits them, and that any purchased amount can be transferred via support — matching the owner notice email, which never promises a transfer for complimentary tokens. Confirming the dialog is the user's consent — the server does not block on tokens.
  • Idempotent confirm. The dialog's delete button disables while the request is in flight, so repeated clicks cannot fire the delete twice. A failed_precondition response still surfaces the server's reasons in the error toast for anyone who reaches the server anyway.
  • The open-invoice filters used by the billing page's payment-issue banner now live in one shared helper (client/utils/invoice-queries.ts).

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview Aug 24, 2026 9:29am

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a6957bed-f917-4c4a-8559-93a85ac4a7fe

📥 Commits

Reviewing files that changed from the base of the PR and between cfd992e and 3ac4b9d.

📒 Files selected for processing (2)
  • web/sdk/client/views/general/components/delete-organization-dialog.tsx
  • web/sdk/client/views/general/general-view.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/sdk/client/views/general/general-view.tsx
  • web/sdk/client/views/general/components/delete-organization-dialog.tsx

Included review availability: Your plan provides up to 2 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved organization deletion checks for subscriptions, unpaid invoices, and negative token balances.
    • Added clearer guidance for resolving deletion blockers, including support-based transfer options for purchased tokens.
    • Improved error messages when deletion is blocked.
    • Disabled deletion while checks, token loading, or deletion processing are underway.
    • Improved invoice filtering for more reliable detection of outstanding invoices.
    • Avoided unnecessary token-balance checks while the deletion dialog is closed.

Walkthrough

Organization deletion now checks server-reported blockers and token balances. The UI disables deletion while checks load or blockers exist. The dialog displays processed precondition errors and token transfer guidance.

Changes

Organization deletion validation

Layer / File(s) Summary
Shared open-invoice filter
web/sdk/client/utils/invoice-queries.ts, web/sdk/client/views/billing/components/payment-issue.tsx
The client adds openInvoiceFilters() for open invoices with positive amounts. The payment-issue query uses the shared helper.
Organization deletion blocker check
web/sdk/client/views/general/general-view.tsx, web/sdk/package.json
GeneralView queries server-reported deletion blockers and maps subscription, invoice, and token blockers to instructions. The delete control is disabled while the check loads or reports blockers. The Proton dependency is updated.
Deletion dialog safeguards
web/sdk/client/hooks/useTokens.ts, web/sdk/client/views/general/components/delete-organization-dialog.tsx
useTokens supports conditional fetching. The dialog fetches balances only while open, updates token forfeiture guidance, displays processed precondition errors, and disables confirmation while token data loads.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🔵 Low · up to 3ac4b

The organization-delete preflight can still enable the delete control before invoice status is available, allowing users to attempt deletion without complete blocker guidance. The server continues to enforce blocked deletions, so the impact is limited and the change is mergeable with explicit owner awareness or follow-up.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
web/sdk/client/views/general/components/delete-organization-dialog.tsx (1)

48-48: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Defer the balance query until the dialog opens.

GeneralView mounts DeleteOrganizationDialog even when open is false. Calling useTokens() starts the billing query for every permitted General view. A failed request can also show the “Unable to fetch balance” toast before the user opens the dialog.

Add an enabled: open option to useTokens, or mount the dialog only while it is open.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e51689d-663d-4f44-83ac-03667d03f265

📥 Commits

Reviewing files that changed from the base of the PR and between 229ea89 and 347b39f.

📒 Files selected for processing (2)
  • web/sdk/client/views/general/components/delete-organization-dialog.tsx
  • web/sdk/client/views/general/general-view.tsx

Comment thread web/sdk/client/views/general/general-view.tsx Outdated
@coveralls

coveralls commented Aug 13, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 32711636426

Warning

No base build found for commit 45631e7 on main.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 49.131%

Details

  • Patch coverage: No coverable lines changed in this PR.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 40522
Covered Lines: 19909
Line Coverage: 49.13%
Coverage Strength: 15.7 hits per line

💛 - Coveralls

@rohilsurana rohilsurana left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review against main. The guard follows the existing payment-issue pattern well. A few correctness and UX notes; the token-transfer wording is the one I would fix before this ships.

Comment thread web/sdk/client/views/general/components/delete-organization-dialog.tsx Outdated
Comment thread web/sdk/client/views/general/general-view.tsx Outdated
Comment thread web/sdk/client/views/general/components/delete-organization-dialog.tsx Outdated
Comment thread web/sdk/client/views/general/components/delete-organization-dialog.tsx Outdated
Comment thread web/sdk/client/views/general/general-view.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f32f0d8-36f5-4f90-a760-824945d49de1

📥 Commits

Reviewing files that changed from the base of the PR and between f15c922 and 4bf5ef9.

📒 Files selected for processing (1)
  • web/sdk/client/views/general/components/delete-organization-dialog.tsx

Included review availability: Your plan provides up to 2 included reviews per hour; 0 remain after this review.

Comment on lines +48 to +50
// fetched only while the dialog is open; the confirm button waits for the
// answer so the forfeit warning cannot be skipped by a slow response
const { tokenBalance, isTokensLoading } = useTokens({ enabled: open });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

fd -HI -t f \
  'package\.json|package-lock\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.yaml' . \
  -E node_modules \
  -x sh -c 'printf "\n%s\n" "$1"; rg -n -C 2 "\"(`@tanstack/react-query`|react-query)\"" "$1" || true' sh {}

Repository: raystack/frontier

Length of output: 882


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

file="web/sdk/client/views/general/components/delete-organization-dialog.tsx"
printf '%s\n' '--- target file outline ---'
ast-grep outline "$file" --lang tsx || true
printf '%s\n' '--- target file ---'
cat -n "$file"
printf '%s\n' '--- useTokens definitions and usages ---'
rg -n -C 6 'useTokens|isTokensLoading|tokenBalance|isFetching' web/sdk/client web/sdk \
  -g '*.{ts,tsx}' -g '!node_modules' | head -n 500
printf '%s\n' '--- related tests ---'
rg -n -C 5 'delete-organization-dialog|DeleteOrganization|forfeit|token balance|tokenBalance' web/sdk/client \
  -g '*.{test,spec}.{ts,tsx}' -g '!node_modules' || true

Repository: raystack/frontier

Length of output: 41441


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- useTokens.ts ---'
cat -n web/sdk/client/hooks/useTokens.ts

printf '%s\n' '--- query option patterns ---'
rg -n -C 8 'useQuery\(' web/sdk/client/hooks web/sdk/client/views \
  -g '*.{ts,tsx}' -g '!node_modules' | head -n 800

printf '%s\n' '--- lockfile package versions ---'
rg -n -C 3 '`@tanstack/`(react-query|query-core)|connect-query' web/pnpm-lock.yaml \
  web/sdk/client/package.json web/sdk/package.json web/package.json

Repository: raystack/frontier

Length of output: 50373


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- locked query packages ---'
python3 - <<'PY'
from pathlib import Path
p = Path("web/pnpm-lock.yaml")
lines = p.read_text().splitlines()
for i, line in enumerate(lines):
    if any(x in line for x in ("`@tanstack/react-query`", "`@tanstack/query-core`", "`@connectrpc/connect-query`")):
        print("\n".join(lines[max(0, i-3):i+8]))
        print("---")
PY

printf '%s\n' '--- package declarations ---'
rg -n -C 2 '"(`@connectrpc/connect-query`|`@tanstack/react-query`)"' \
  web/package.json web/sdk/package.json web/sdk/client/package.json web/apps/admin/package.json

printf '%s\n' '--- TanStack state semantics from repository documentation/comments ---'
rg -n -C 3 'isLoading.*isFetching|isFetching.*isLoading|refetchOnMount|staleTime' \
  . -g '!node_modules' -g '!pnpm-lock.yaml' | head -n 300 || true

printf '%s\n' '--- standalone state probe ---'
node - <<'JS'
const cases = [
  { name: 'initial enabled request', hasData: false, isFetching: true },
  { name: 'cached data with background refetch', hasData: true, isFetching: true },
  { name: 'cached fresh data', hasData: true, isFetching: false },
];
for (const c of cases) {
  // TanStack Query v5 derives isLoading from pending && fetching.
  const status = c.hasData ? 'success' : 'pending';
  const isPending = status === 'pending';
  const isLoading = isPending && c.isFetching;
  console.log(JSON.stringify({ ...c, status, isPending, isLoading }));
}
JS

Repository: raystack/frontier

Length of output: 44836


Disable deletion during balance refetches.

isLoading is false when cached data exists during a background refetch. Expose isFetching from useTokens and include it in the delete-button guard. Add a regression test for reopening the dialog after the balance changes from zero to positive.

The delete button greys out while the org has open invoices with a
non-zero amount, and its hover tooltip says to pay them from the
billing page first — the server refuses the delete in that state
anyway, this stops the user before the failed call.

The delete dialog warns when tokens remain on the billing account:
deleting forfeits them, and support can transfer the amount to the
user's bank account. Confirming the dialog is the user's consent.

The confirm button also disables while the request is in flight, so
repeated clicks cannot fire the delete twice, and a failed_precondition
response shows the server's reasons instead of a generic error.
The token balance is only fetched while the delete dialog is open, so
the General page stops firing a balance request (and a failure toast on
accounts without balance support) on every visit. The delete button now
stays disabled while the unpaid-invoice answer is still loading instead
of briefly allowing a delete the server would refuse, and it trusts the
server-filtered result instead of re-checking only the state. The token
warning stops promising a bank transfer for the whole balance — only a
purchased amount is transferable, matching the notice email. The
failed_precondition toast uses err.message like the other handlers, and
the open-invoice filters live in one shared helper used by both the
delete guard (one row, no sort — existence is all it needs) and the
billing page's payment-issue banner.
The delete button now asks the server directly whether a delete would
go through (CheckOrganizationDelete, added in #1894)
instead of inferring one blocker from the invoice list. The button
greys out for every blocker the real delete would refuse with — a paid
subscription to downgrade, unpaid invoices, a token debt — and the
tooltip shows one short instruction per kind of blocker, with counts
for invoices and a fallback to the server's message for kinds this
version does not know. While the check loads the button stays disabled;
a failed check fails open since the server refuses independently.

@raystack/proton moves to the proton main commit that ships the RPC.
The shared open-invoice filters stay for the billing page's
payment-issue banner.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

3 participants