feat: decouple dashboard bootstrap from merchant account API#4903
feat: decouple dashboard bootstrap from merchant account API#4903kanikabansal08 wants to merge 1 commit into
Conversation
Replace the merchant-account API dependency at dashboard bootstrap with the lightweight user merchant details endpoint, so groups without the account resource can load the dashboard. - Add USER_MERCHANT_DETAILS endpoint (v1: user/merchant_details, v2: v2/users/merchant-details) and a lightweight fetch hook that only reads product_type and merchant_account_type - Bootstrap now uses the lightweight endpoint for the active product - Full merchant account is fetched lazily, only where needed, via useMerchantDetails (guarded by account access) - Gate the homepage credentials/keys card by account access and guard the fetch so non-account users no longer hit the merchant account API - Guard the homepage "Connect Processors" button with connector access Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
XyneSpaces
left a comment
There was a problem hiding this comment.
Review Summary
Classification: Feature — Decouple dashboard bootstrap from merchant account API
Risk Level: Low
Files Changed: 9 files
Verification Against Control Center Checklist
Tier 1 — High Signal:
✅ Naming Consistency — USER_MERCHANT_DETAILS follows the existing entity naming convention; useFetchUserMerchantDetails clearly distinguishes from useFetchMerchantDetails
✅ Feature Flag Gating — Proper ACL gating on "Connect Processors" button (ConnectorsView) and "Credentials and Keys" card (MerchantDetailsView + AccountView)
✅ Reuse Existing Utilities — Uses existing hasAnyGroupAccess pattern from GroupACLHooks; follows established useMerchantDetails lazy-fetch pattern
✅ Error Handling — Catch block in useFetchUserMerchantDetails properly propagates errors via Exn.raiseError
Tier 2 — Medium Signal:
✅ Blast Radius Checked — Changes to merchantDetailsValueAtom are localized; the lazy fetch pattern doesn't affect existing direct Recoil consumers
✅ Effect Dependencies — useMerchantDetails effect has proper dependency array with shouldFetch and empty array for mount-only behavior
Minor Observation
🔍 Silent error swallow in useMerchantDetails effect
In MerchantDetailsHook.res, lines 82-84:
} catch {
| _ => ()
}The error is silently discarded when fetchMerchantDetails fails. Consider logging the error for observability:
| err => {
logger.error("Failed to load merchant details", err)
}However, given this is guarded by showErrorToast=false and the caller explicitly controls error display, this is acceptable if intentional.
Positive Notes
- Clean separation between lightweight
USER_MERCHANT_DETAILSendpoint and fullMERCHANT_ACCOUNTendpoint - Proper V1/V2 API version handling in both endpoints
- The lazy fetch pattern (
useMerchantDetails) correctly avoids unnecessary API calls whenpublishable_keyis already populated
Verdict: ✅ Approve — Clean implementation following established patterns with proper ACL gating.
|
💡 Review Finding - useEffect Dependencies In Suggestion: Consider adding |
|
💡 Review Finding - Error Context In Suggested Fix: | Exn.Error(e) => {
let versionStr = switch version { | V1 => "V1" | V2 => "V2" }
let err = Exn.message(e)->Option.getOr(`Failed to fetch user merchant details (${versionStr})!`)
Exn.raiseError(err)
} |
XyneSpaces
left a comment
There was a problem hiding this comment.
Review Summary
Classification: Feature — Dashboard Bootstrap Decoupling
Risk Level: Medium (bootstrap path changes)
Files Changed: 9 files (+91/-13 lines)
Verification Against Control Center Checklist
Tier 1 — High Signal:
✅ Naming Consistency — useFetchUserMerchantDetails follows useFetchMerchantDetails pattern; USER_MERCHANT_DETAILS matches entity naming convention
✅ Reuse Existing Utilities — Uses existing ACLButton, EnableMerchantDetailsContext patterns
✅ Feature Flag Gating — No feature flag needed for this permission decoupling
✅ Blast Radius — Changes localized to bootstrap path; graceful degradation for existing flows
Tier 2 — Medium Signal:
✅ Error Handling — Proper error states in useFetchUserMerchantDetails with setScreenState(Error(...))
✅ Prop Consistency — useMerchantDetails(~shouldFetch) hook properly forwards boolean param
Findings
🔍 ~shouldFetch parameter naming
let useMerchantDetails = (~shouldFetch) => {Consider a more descriptive name like ~lazy or ~fetchImmediately to better convey intent. shouldFetch is vague — does it mean "should fetch now" or "should fetch at all"?
Fix: Rename to ~fetchImmediately or document the parameter with a comment.
💡 Redundant useEffect in HomeUtils.res
React.useEffect(() => {
if isAccessAllowedForAccount {
merchantDetails.fetchMerchantDetails()->ignore
}
None
}, [isAccessAllowedForAccount])This effect fetches when isAccessAllowedForAccount becomes true. Verify this doesn't cause double-fetching if the component re-renders while isAccessAllowedForAccount remains true.
Fix: Consider adding a fetched flag or use useMemo to ensure single fetch:
let _ = React.useMemo(() => {
if isAccessAllowedForAccount {
merchantDetails.fetchMerchantDetails()->ignore
}
}, [isAccessAllowedForAccount])productType mapping from API response
productType: Dict.get(merchantDict, "product_type")The API returns "product_type" but the variable is named productType (camelCase). Verify the API field name matches exactly — ReScript dict access is case-sensitive.
Positive Notes
- Clean separation of concerns: lightweight bootstrap vs full merchant details
- Proper ACL gating on Credentials and Keys card
- Good use of
ACLButtonfor Connectors button - Fallback to full merchant details preserved in existing consumers
Verdict: ✅ Approve — Clean decoupling of dashboard bootstrap from full merchant account API. Minor suggestions on naming and effect handling.
| @@ -30,4 +61,26 @@ let useFetchMerchantDetails = () => { | |||
| } | |||
| } | |||
There was a problem hiding this comment.
🔍 Parameter naming suggestion
~shouldFetch is ambiguous — does it mean "fetch now" or "should ever fetch"? Consider ~fetchImmediately or ~lazy for clarity.
| @@ -147,8 +148,9 @@ module ControlCenter = { | |||
| subHeading="Get a head start by connecting with 20+ gateways, payment methods, and networks." | |||
| /> | |||
There was a problem hiding this comment.
💡 Potential double-fetch on re-render
This useEffect fetches whenever isAccessAllowedForAccount is true. If the component re-renders while permission remains granted, the fetch re-executes. Consider guarding with a fetched state or using useMemo for one-time fetch.
Type of Change
Description
Replaces the merchant-account API dependency at dashboard bootstrap with the new
lightweight user merchant-details endpoint, so roles without the
accountresource can load the dashboard.
USER_MERCHANT_DETAILSendpoint (v1:user/merchant_details,v2:
v2/users/merchant-details) + a lightweight fetch hook returning onlyproduct_type/merchant_account_type.HyperSwitchApp) now uses the lightweight endpoint to set theactive product — no account permission required.
(
WebSDK,PaymentLinkThemeConfigurator, home) viauseMerchantDetails.guarded so non-account users no longer hit the merchant-account API on home.
ACLButtongated byConnectorsView.Motivation and Context
Dashboard load was coupled to Account-level permissions because bootstrap called
the full merchant-account API just to read a couple of signals. That leaked
account access into roles that didn't need it. This migrates only the bootstrap
path to the low-privilege endpoint.
How did you test it?
npx rescriptcompiles clean.roles still see keys card / SDK publishable key; non-account roles no longer
trigger the merchant-account call on home; "Connect Processors" disabled without
connector access.
Where to test it?
Backend Dependency
Backend PR URL: juspay/hyperswitch#12160
Feature Flag
Checklist
npm run re:build🤖 Generated with Claude Code