-
Notifications
You must be signed in to change notification settings - Fork 576
UN-2781 [FEAT] Add admin custom Stripe product creation support #1651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This commit adds comprehensive support for admins to create custom Stripe subscription products for both Unstract and LLM Whisperer platforms. ## Frontend Changes ### Router Updates (Router.jsx) - Added dynamic import for LlmWhispererCustomCheckoutPage plugin component - Added public route `/llm-whisperer/custom-checkout` for LLM Whisperer custom plan checkout flow - Follows existing pattern with try/catch for Cloud-only features ### Main App Routes (useMainAppRoutes.js) - Added Unstract administration route at `/administration` - Protected with RequireAdmin wrapper (admin-only access) - Imported UnstractAdministrationPage component ### New Administration Page (UnstractAdministrationPage.jsx) - Plugin-loader pattern with try/catch for Cloud-only component - Falls back to "feature not available" message in OSS builds - Dynamically imports UnstractAdministration from Cloud plugins ## Backend Changes (Already Committed) The backend infrastructure was already added in previous commits: ### Subscription Routes (cloud_urls_v2.py) - Unstract admin endpoint: `/api/v1/unstract/admin/stripe/products/dynamic/` - LLM Whisperer admin endpoint: `/api/v1/llmwhisperer/admin/stripe/products/dynamic/portal/` ### Portal Proxy (subscription_v2/views.py) - `create_dynamic_stripe_product_portal_proxy()` method forwards LLM Whisperer admin requests to Portal service - Follows existing LLMWhispererService pattern for multi-backend architecture - Includes proper error handling and logging ### URL Patterns (subscription_v2/urls.py) - Added `admin_stripe_product_portal_proxy` view and URL pattern - Both endpoints protected with IsAuthenticated + IsAdmin permissions ## Architecture This implementation follows Unstract's plugin-based architecture: - OSS repo contains base routing and plugin-loader patterns - Cloud repo contains actual admin UI components (subscription-admin plugin) - Multi-backend support: Unstract uses local backend, LLM Whisperer proxies to Portal service - Graceful degradation for OSS builds (shows "Cloud-only feature" message) ## Related - Supersedes PR #1087 (monolithic admin UI) - Integrates checkout functionality from PR #1016 - Consolidates router changes from PR #1558 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Summary by CodeRabbit
Summary by CodeRabbit
WalkthroughAdds UnstractAdministrationPage (with OSS fallback) and CSS, registers a conditional /admin/custom-plans route for staff in non-OSS contexts, and introduces an optional dynamic import and route for LlmWhisperer custom checkout in Router.jsx. Changes
Sequence DiagramsequenceDiagram
participant User
participant Router as Router.jsx
participant Session as useSessionStore
participant Page as UnstractAdministrationPage
User->>Router: Navigate to /admin/custom-plans
Router->>Session: read sessionDetails (isStaff, isOpenSource, orgName)
Session-->>Router: returns session info
alt isStaff AND NOT isOpenSource
Router->>Page: instantiate UnstractAdministrationPage
Page->>Page: try require('UnstractAdministration') (CJS)
alt require succeeds
Page-->>User: render UnstractAdministration component
else require fails
Page-->>User: render cloud-only placeholder
end
else
Router-->>User: route not registered / access prevented
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro Cache: Disabled due to Reviews > Disable Cache setting Knowledge base: Disabled due to 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
frontend/src/routes/Router.jsx (1)
89-96: Optional: keep dynamic import style consistent or consider lazy() later.The try/catch require matches existing patterns. If you move toward React.lazy in the future, this page can follow suit; not required now.
frontend/src/pages/UnstractAdministrationPage.jsx (1)
10-20: Clear Cloud‑only fallback.Simple, accessible fallback; no runtime risk. Optionally factor a shared “CloudOnly” notice if reused elsewhere.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to Reviews > Disable Cache setting
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (3)
frontend/src/pages/UnstractAdministrationPage.jsx(1 hunks)frontend/src/routes/Router.jsx(2 hunks)frontend/src/routes/useMainAppRoutes.js(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (4)
frontend/src/routes/useMainAppRoutes.js (2)
25-25: Import looks correct and scoped.Static import is fine since the page handles its own dynamic fallback. No concerns.
121-123: Admin‑only route registered correctly.Path nests under :orgName and is gated by RequireAdmin, matching the PR intent.
Please sanity‑check that “/orgXYZ/administration” renders the Cloud‑only placeholder in OSS builds and the full admin widget in Cloud builds.
frontend/src/routes/Router.jsx (1)
158-163: Public checkout route added correctly.Absolute path and placement outside RequireAuth satisfy the “public route” requirement.
Confirm the page shows when the plugin exists and cleanly 404s (no route) when absent.
frontend/src/pages/UnstractAdministrationPage.jsx (1)
3-8: Graceful dynamic load.try/catch keeps OSS builds clean without bundling errors. Good.
Suppress SonarQube warnings for intentional empty catch blocks used in plugin loading architecture. These catch blocks are part of the graceful degradation pattern for Cloud-only features - when plugins are not available, the component remains undefined and React Router triggers NotFound page. This pattern is used throughout the codebase for optional plugin loading. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Move NOSONAR comments to separate lines to comply with Prettier formatting rules. Format multi-line Route component in useMainAppRoutes.js. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Place NOSONAR comment inline with catch statement for proper recognition. SonarQube requires the suppression comment on the same line as the issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Add minimal NOSONAR comments to suppress SonarQube warnings for intentional empty catch blocks in plugin loading pattern. These catch blocks are part of the graceful degradation architecture for Cloud-only features. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
frontend/src/routes/Router.jsx (1)
89-96: Consider moving the NOSONAR comment to follow standard convention.The
// NOSONARcomment on line 94 is placed inside the catch block. Standard practice is to place such suppression comments above the try block or on the same line as the construct being suppressed.Apply this diff to align with convention:
let LlmWhispererCustomCheckoutPage; +// NOSONAR try { LlmWhispererCustomCheckoutPage = require("../plugins/llm-whisperer/pages/LlmWhispererCustomCheckoutPage.jsx").LlmWhispererCustomCheckoutPage; } catch (err) { - // NOSONAR // Do nothing, Not-found Page will be triggered. }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to Reviews > Disable Cache setting
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (2)
frontend/src/pages/UnstractAdministrationPage.jsx(1 hunks)frontend/src/routes/Router.jsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/pages/UnstractAdministrationPage.jsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
frontend/src/routes/Router.jsx (1)
159-164: LGTM! Route implementation follows existing patterns correctly.The conditional route registration for the LLM Whisperer custom checkout page is well-implemented:
- Follows the same pattern as other dynamic routes in the file
- Correctly placed as a public route (outside
RequireAuth) to support the checkout flow- Gracefully handles missing component with conditional rendering
jaseemjaskp
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
…ation Add platform staff-only access control for custom Unstract subscription plans: - Add RequireStaff component to enforce platform staff access - Update custom plans route to use RequireStaff instead of RequireAdmin - Block access for open-source deployments (mock_org) Frontend Changes: - New RequireStaff.js component with isStaff and orgName validation - Update useMainAppRoutes.js to use RequireStaff for /admin/custom-plans route - Shows Unauthorized for non-staff users, NotFound for OSS deployments This complements the backend IsStaff permission class to ensure only platform staff can create custom Stripe products with dual pricing structure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
frontend/src/components/helpers/auth/RequireStaff.js (1)
11-11: Consider extracting magic string to a constant.The hardcoded string
"mock_org"for open source detection would be more maintainable as a named constant, especially if used in multiple places.Example refactor:
+const OPEN_SOURCE_ORG_NAME = "mock_org"; + const RequireStaff = () => { const { sessionDetails } = useSessionStore(); const isStaff = sessionDetails?.isStaff || sessionDetails?.is_staff; const orgName = sessionDetails?.orgName; - const isOpenSource = orgName === "mock_org"; + const isOpenSource = orgName === OPEN_SOURCE_ORG_NAME;Alternatively, if this identifier is used across the codebase, consider moving it to a shared constants file.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to Reviews > Disable Cache setting
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (2)
frontend/src/components/helpers/auth/RequireStaff.js(1 hunks)frontend/src/routes/useMainAppRoutes.js(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/routes/useMainAppRoutes.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (4)
frontend/src/components/helpers/auth/RequireStaff.js (4)
1-5: LGTM! Imports are appropriate.All necessary dependencies are imported correctly for implementing the route guard.
7-21: LGTM! Guard logic correctly implements two-level access control.The component correctly enforces:
- Staff-only access (returns Unauthorized if not staff)
- Cloud-only feature availability (returns NotFound for open source deployments)
- Nested route rendering via Outlet for authorized users
The guard order is appropriate for protecting admin routes that are only available in cloud deployments.
8-11: Verify session loading behavior for appropriate UX.When
sessionDetailsisundefined(e.g., during initial load), the component will immediately render<Unauthorized />since!isStaffevaluates totrue.Please verify:
- Does the session store load synchronously before routes render?
- Or should there be a loading state while
sessionDetailsis being fetched?If the session loads asynchronously, consider adding a loading state:
const RequireStaff = () => { const { sessionDetails } = useSessionStore(); + + if (!sessionDetails) { + return <div>Loading...</div>; // or a proper loading component + } + const isStaff = sessionDetails?.isStaff || sessionDetails?.is_staff;
9-9: Simplify redundant field check; backend normalization already ensures camelCase format.The dual field check is unnecessary. GetSessionData.js normalizes backend's snake_case
is_staffto camelCaseisStaffbefore storing in sessionDetails via setSessionDetails. Since all session updates flow through this normalization, sessionDetails will always containisStaffin camelCase, neveris_staff.Simplify line 9 to:
const isStaff = sessionDetails?.isStaff;
Remove RequireStaff component as it's been replaced with inline staff checking pattern in route files. This avoids component dependency issues and follows the established Verticals pattern. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Replace RequireStaff component wrapper with inline staff check using useSessionStore in an IIFE. This matches the pattern used in Cloud plugin routes and fixes the build error where RequireStaff component was deleted but still referenced. The inline check ensures: - Staff-only access to admin custom plans route - Hidden in open-source deployments (orgName === "mock_org") - No component import dependencies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
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 details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to Reviews > Disable Cache setting
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (1)
frontend/src/routes/useMainAppRoutes.js(3 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
frontend/src/routes/useMainAppRoutes.js
[error] 123-123: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
frontend/src/routes/useMainAppRoutes.js (1)
9-9: LGTM on imports.The new imports are necessary for the admin route functionality and follow the existing patterns in the file.
Also applies to: 26-26
- Create UnstractAdministrationPage.css for fallback UI styling - Replace inline styles with className in UnstractAdministrationPage.jsx - Improves code maintainability and follows project's CSS patterns
- Move useSessionStore hook call to top level of function - Remove IIFE pattern that violated hooks rules - Simplify conditional rendering logic Fixes CodeRabbit review comment about hooks being called inside nested function.
|



What
Why
How
Frontend Routes:
Router.jsx:
/llm-whisperer/custom-checkoutfor custom plan checkout flowuseMainAppRoutes.js:
/administrationrouteUnstractAdministrationPage.jsx (new):
Backend (Already Committed):
Subscription Routes:
/api/v1/unstract/admin/stripe/products/dynamic//api/v1/llmwhisperer/admin/stripe/products/dynamic/portal/Portal Proxy:
Can this PR break any existing features. If yes, please list possible items. If no, please explain why.
No, this PR will not break existing features:
Database Migrations
Env Config
Relevant Docs
/:orgName/administration/llm-whisperer/custom-checkout?product_id=XRelated Issues or PRs
Dependencies Versions
Notes on Testing
/:orgName/administrationas admin user - should see administration page/llm-whisperer/custom-checkout?product_id=X- should load checkout page (public route)Screenshots
N/A - Backend routing infrastructure
Checklist
I have read and understood the Contribution Guidelines.