license leanup and billing - #2287
Conversation
|
Capy auto-review is paused for this organization because the usage-cycle auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews. |
| const handleOpenPortal = async () => { | ||
| setIsLoading(true); | ||
| try { | ||
| await openCustomerPortal(); | ||
| } catch { | ||
| toast.error("Failed to open billing portal"); | ||
| setIsLoading(false); | ||
| } |
There was a problem hiding this comment.
isLoading never resets on success
setIsLoading(false) is only called in the catch block. If openCustomerPortal() resolves normally (e.g. opens a portal URL in a new tab), the caller returns to this page with the button permanently stuck in its loading/spinner state until the component unmounts.
Prompt To Fix With AI
This is a comment left during a code review.
Path: vite/src/views/settings/sections/SubscriptionSection.tsx
Line: 11-18
Comment:
**`isLoading` never resets on success**
`setIsLoading(false)` is only called in the `catch` block. If `openCustomerPortal()` resolves normally (e.g. opens a portal URL in a new tab), the caller returns to this page with the button permanently stuck in its loading/spinner state until the component unmounts.
How can I resolve this? If you propose a fix, please make it concise.| const handleOpenPortal = async () => { | ||
| setIsLoading(true); | ||
| try { | ||
| await openCustomerPortal(); | ||
| } catch { | ||
| toast.error("Failed to open billing portal"); | ||
| setIsLoading(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Add a
finally block to reset the loading state after both success and failure so the button is never permanently stuck.
| const handleOpenPortal = async () => { | |
| setIsLoading(true); | |
| try { | |
| await openCustomerPortal(); | |
| } catch { | |
| toast.error("Failed to open billing portal"); | |
| setIsLoading(false); | |
| } | |
| }; | |
| const handleOpenPortal = async () => { | |
| setIsLoading(true); | |
| try { | |
| await openCustomerPortal(); | |
| } catch { | |
| toast.error("Failed to open billing portal"); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: vite/src/views/settings/sections/SubscriptionSection.tsx
Line: 11-19
Comment:
Add a `finally` block to reset the loading state after both success and failure so the button is never permanently stuck.
```suggestion
const handleOpenPortal = async () => {
setIsLoading(true);
try {
await openCustomerPortal();
} catch {
toast.error("Failed to open billing portal");
} finally {
setIsLoading(false);
}
};
```
How can I resolve this? If you propose a fix, please make it concise.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!
| const linkedProducts = await planLicenseRepo.listProductsByInternalIds({ | ||
| db, | ||
| internalProductIds: linkedInternalIds, | ||
| }); | ||
| const linkedExternalIds = new Set( | ||
| linkedProducts.map((product) => product.id), | ||
| ); |
There was a problem hiding this comment.
Missing org/env scope in
listProductsByInternalIds
The underlying query (inArray(products.internal_id, internalProductIds)) contains no orgId / env filter. While the input IDs are already org-scoped (they come from listCatalogByOrgEnv), an explicit scope guard would prevent cross-org data leakage if, for any reason, a license_internal_product_id from another org's plan ended up in the set — e.g., due to a future refactor or a shared-product scenario.
Prompt To Fix With AI
This is a comment left during a code review.
Path: server/src/internal/products/internalHandlers/handleGetProducts.ts
Line: 116-122
Comment:
**Missing org/env scope in `listProductsByInternalIds`**
The underlying query (`inArray(products.internal_id, internalProductIds)`) contains no `orgId` / `env` filter. While the input IDs are already org-scoped (they come from `listCatalogByOrgEnv`), an explicit scope guard would prevent cross-org data leakage if, for any reason, a `license_internal_product_id` from another org's plan ended up in the set — e.g., due to a future refactor or a shared-product scenario.
How can I resolve this? If you propose a fix, please make it concise.|
slop |
Summary by cubic
Adds a Subscription settings page with a billing portal launcher, and fixes license product linking across versions so license plans match the latest products. Also simplifies license UI and centers button spinners while loading.
New Features
autumn-js/react(useCustomer.openCustomerPortal).Bug Fixes
handleGetLicenseProducts, so older links still match the latest product list.Written for commit b484878. Summary will update on new commits.
Greptile Summary
This PR deprecates the entity feature ID concept in favor of licenses, fixes a bug where versioned license plan links failed to resolve to the current product version, and adds a new Subscription settings section for billing portal access.
handleGetLicenseProductsnow performs a two-step resolution: it maps stalelicense_internal_product_idvalues (pointing at older versions) to their current public product IDs via a newlistProductsByInternalIdslookup, so versioned license plans are correctly surfaced in the UI.AdvancedSettingsis soft-deprecated — it remains visible only for items that already haveentity_feature_idset, preventing new usage without breaking existing plans.Buttoncomponent also gets a loading-state UX fix: content is hidden (invisible) during loading and the spinner is absolutely centered, so button width no longer collapses.Confidence Score: 3/5
Two active defects in the changed paths: the billing portal button gets permanently stuck in its loading state after a successful open, and the new product-resolution query fetches across all orgs without an org/env guard.
The SubscriptionSection missing finally block means the first time any real user clicks 'Open billing portal' and the call succeeds, they are left with an unresponsive, forever-spinning button. The listProductsByInternalIds call added to handleGetLicenseProducts omits the orgId/env filter that every other repo function in that file applies, which is a latent cross-org data concern even if the current input path reduces the practical risk.
vite/src/views/settings/sections/SubscriptionSection.tsx (loading state reset) and server/src/internal/products/internalHandlers/handleGetProducts.ts / server/src/internal/licenses/repos/planLicenseRepo.ts (missing org/env scope in the new DB query).
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant UI as handleGetLicenseProducts participant Repo as planLicenseRepo participant PS as ProductService UI->>Repo: listCatalogByOrgEnv(orgId, env) Repo-->>UI: links[] UI->>Repo: listProductsByInternalIds(linkedInternalIds) Note over Repo: Resolves old internal_ids<br/>to current public ids<br/>(no org/env filter) Repo-->>UI: linkedProducts[] UI->>PS: listFull(orgId, env, all_versions) PS-->>UI: products[] UI->>UI: filter products by linkedExternalIds UI-->>UI: licenseProducts[]%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant UI as handleGetLicenseProducts participant Repo as planLicenseRepo participant PS as ProductService UI->>Repo: listCatalogByOrgEnv(orgId, env) Repo-->>UI: links[] UI->>Repo: listProductsByInternalIds(linkedInternalIds) Note over Repo: Resolves old internal_ids<br/>to current public ids<br/>(no org/env filter) Repo-->>UI: linkedProducts[] UI->>PS: listFull(orgId, env, all_versions) PS-->>UI: products[] UI->>UI: filter products by linkedExternalIds UI-->>UI: licenseProducts[]Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "license leanup and billing" | Re-trigger Greptile