refactor(affiliate-dashboard): migrate to Chakra UI + React Query#12287
refactor(affiliate-dashboard): migrate to Chakra UI + React Query#12287kaladinlight merged 4 commits intodevelopfrom
Conversation
- Split 1270-line App.tsx into components, hooks, and lib folders - Migrate inline styles to Chakra UI with shapeshift theme palette - Replace custom useState/useEffect fetchers with React Query (useQuery + useMutation) - Add Zod validation for all API responses with shared parseResponse helper - Add stale-while-revalidate via keepPreviousData to eliminate period-change flicker - Make dashboard responsive with mobile-card fallback for swaps table - Strip workspace-shared deps from package.json (hoisted from root) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 25 minutes and 23 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (16)
📝 WalkthroughWalkthroughComprehensive refactoring of the affiliate-dashboard from a monolithic React component to a modular, component-driven architecture. Removed runtime dependencies while introducing React Query for data fetching, Chakra UI for styling, Zod for validation, and creating 40+ new UI components and utilities to support tab-based settings, overview, and swaps pages. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Actionable comments posted: 7
🧹 Nitpick comments (7)
packages/affiliate-dashboard/src/lib/api.ts (1)
15-27: Minor: JSON parse failure is swallowed into a generic “Invalid response from server”.When
response.json()rejects (e.g., empty/HTML body on a 200), thenullflows into Zod and surfaces as a schema-shape error, losing the actual cause. Consider logging/throwing a distinct message for decode failures to make debugging easier.♻️ Suggested tweak
- const json = await response.json().catch(() => null) - - const parsed = schema.safeParse(json) - if (!parsed.success) { - console.error('Invalid API response shape:', parsed.error.errors) - throw new Error('Invalid response from server') - } + let json: unknown + try { + json = await response.json() + } catch (err) { + console.error('Failed to decode JSON response:', err) + throw new Error('Invalid response from server (decode failed)') + } + + const parsed = schema.safeParse(json) + if (!parsed.success) { + console.error('Invalid API response shape:', parsed.error.errors) + throw new Error('Invalid response from server') + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/affiliate-dashboard/src/lib/api.ts` around lines 15 - 27, The parseResponse function currently swallows JSON decode failures by mapping them to Zod shape errors; update parseResponse to catch JSON parsing errors separately (when calling response.json()) and throw or log a distinct, informative error before running schema.safeParse — include the original JSON parsing error message (and optionally the raw response text) in the thrown/logged message so callers can distinguish decode failures from schema validation failures; keep existing behavior of calling throwFromResponse when !response.ok and continue to validate with schema.safeParse only when JSON decoding succeeded.packages/affiliate-dashboard/src/lib/periods.ts (1)
10-54: Minor: year-crossing label uses only the end year, andperiodsis computed once at module load.Two small things worth considering for the follow-up period-selector work:
formatPeriodLabelonly rendersend.getUTCFullYear(), so a window like Dec 5 → Jan 5, 2026 reads as “Dec 5 - Jan 5, 2026” and hides that the start was in 2025. Including the start year when it differs would avoid ambiguity.export const periods: Period[] = generatePeriods()runs once when the module is first imported. If a user keeps the dashboard open across a period boundary (midnight on the 5th UTC), the list will silently drift stale until reload. Recomputing on mount / via a memoized hook would fix it — likely fine to defer to the period-selector polish PR.♻️ Optional label tweak
const formatPeriodLabel = (start: Date, end: Date): string => { const monthShort = new Intl.DateTimeFormat('en-US', { month: 'short' }) - const startLabel = `${monthShort.format(start)} ${start.getUTCDate()}` + const startYear = start.getUTCFullYear() + const endYear = end.getUTCFullYear() + const startLabel = + startYear === endYear + ? `${monthShort.format(start)} ${start.getUTCDate()}` + : `${monthShort.format(start)} ${start.getUTCDate()}, ${startYear}` const endLabel = `${monthShort.format(end)} ${end.getUTCDate()}` - const year = end.getUTCFullYear() - return `${startLabel} - ${endLabel}, ${year}` + return `${startLabel} - ${endLabel}, ${endYear}` }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/affiliate-dashboard/src/lib/periods.ts` around lines 10 - 54, formatPeriodLabel currently uses only end.getUTCFullYear(), causing misleading labels for ranges that cross years; update formatPeriodLabel to include the start year when start.getUTCFullYear() !== end.getUTCFullYear() (e.g., "Dec 5, 2025 - Jan 5, 2026") so the label disambiguates year-crossing periods. Also avoid computing periods once at module load: replace the module-level export const periods = generatePeriods() pattern by exporting the generatePeriods function (or provide a getPeriods helper) and call it from the period selector component (or on mount/useMemo) so the list is recalculated when the component mounts or when current time changes rather than staying stale.packages/affiliate-dashboard/src/main.tsx (1)
28-34: Add a root error boundary around the dashboard.This is the best place to catch render-time failures from the refactored component tree and show a safe fallback instead of blanking the dashboard.
🛡️ Proposed shape
<ChakraProvider theme={theme}> <WagmiProvider config={wagmiConfig}> <QueryClientProvider client={queryClient}> - <App /> + <ErrorBoundary> + <App /> + </ErrorBoundary> </QueryClientProvider> </WagmiProvider> </ChakraProvider>As per coding guidelines,
**/*.{tsx,jsx}should “ALWAYS wrap React components in error boundaries and provide user-friendly fallback components with error logging”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/affiliate-dashboard/src/main.tsx` around lines 28 - 34, Add a root error boundary around the dashboard by creating a React ErrorBoundary (e.g., class RootErrorBoundary with componentDidCatch to log errors) and wrap the existing top-level tree in main.tsx (wrap ChakraProvider, WagmiProvider, QueryClientProvider and <App />) with <RootErrorBoundary>; the boundary should render a simple user-friendly fallback UI (retry/refresh button or message) and call your logging mechanism (console.error or app logger) with the error and info inside componentDidCatch to capture render-time failures.packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts (1)
9-9: Consider centralizing this URL inlib/constantsalongsideAFFILIATE_URL.Other hooks (
useAffiliateConfig,useAffiliateActions) importAFFILIATE_URLfrom../lib/constants, but this file builds the swaps URL inline fromimport.meta.env.VITE_API_URL. IfVITE_API_URLis missing at build time this silently becomes"undefined/v1/affiliate/swaps". Colocating with the other endpoints would keep env validation in one place.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts` at line 9, AFFILIATE_SWAPS_URL is being built inline in useAffiliateSwaps.ts from import.meta.env which can produce "undefined/..." if VITE_API_URL is missing; move the swaps endpoint constant into the shared ../lib/constants (next to AFFILIATE_URL), perform the same env validation/assembly there, export the new AFFILIATE_SWAPS_URL, and replace the inline const in useAffiliateSwaps.ts with an import of AFFILIATE_SWAPS_URL so all affiliate endpoints are centralized and validated in one place.packages/affiliate-dashboard/src/hooks/useAffiliateActions.ts (2)
103-124: Returned action wrappers are recreated each render and swallow errors invisibly.Two concerns with this return shape:
The returned object and its method wrappers are allocated on every render. Since the whole
actionsobject is threaded throughApp.tsx→SettingsTab→ each card, nothing downstream can meaningfully benefit frommemo/useCallback. Consider wrapping each wrapper inuseCallbackand the return value inuseMemo.
claimCode/updateBps/updateReceiveAddressresolve successfully even on failure (.catch(() => undefined)).onErroralready sets the message, but callers can't distinguish success from failure — which is exactly whyAffiliateBpsCard(flagged separately) clears its input on a failed update. Returning a boolean (or re-throwing) from these wrappers would let cards clear their state only on success.♻️ Sketch
- claimCode: async code => { - setMessage(null) - await claimCodeMutation.mutateAsync(code).catch(() => undefined) - }, + claimCode: useCallback(async (code: string): Promise<boolean> => { + setMessage(null) + try { + await claimCodeMutation.mutateAsync(code) + return true + } catch { + return false + } + }, [claimCodeMutation]),…and wrap the full return in
useMemokeyed on the mutations +message.As per coding guidelines: "ALWAYS use
useCallbackfor event handlers and functions passed as props" and "ALWAYS useuseMemofor expensive computations, object/array creations".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/affiliate-dashboard/src/hooks/useAffiliateActions.ts` around lines 103 - 124, The returned actions object recreates wrappers each render and swallows errors; wrap each handler (register, claimCode, updateBps, updateReceiveAddress) in useCallback (dependencies: their respective mutations and setMessage) and wrap the final returned object in useMemo (deps: the callbacks and message/isLoading) to avoid reallocation, and change the async wrappers (claimCodeMutation.mutateAsync, updateBpsMutation.mutateAsync, updateReceiveAddressMutation.mutateAsync) to either re-throw errors or return a boolean success value instead of .catch(() => undefined) so callers can detect failure (keep onError for messaging via the existing mutation onError handlers); ensure register still calls registerMutation.mutate inside its useCallback.
29-50: Minor: typeauthHeadersat the call boundary and validate on mount.
authHeaders: Record<string, string>comes fromuseSiweAuth. If the user isn't signed in yet and triggers a mutation (shouldn't happen becauseSettingsTabgates UI onisAuthenticated), the POST will fire without auth and the server response will surface as a generic error message. Nothing to change in this PR, but worth a short-circuit (if (!authHeaders.Authorization) return) in eachmutationFnas a defense-in-depth measure against future regressions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/affiliate-dashboard/src/hooks/useAffiliateActions.ts` around lines 29 - 50, The hook useAffiliateActions should assert the authHeaders shape and defend against missing Authorization: add a stronger type at the call boundary for authHeaders (Record<string,string> with Authorization optional/required) and on mount validate authHeaders.Authorization (e.g., early-return or set an error message) and, crucially, add a short-circuit inside each mutationFn (e.g., in registerMutation's mutationFn) that returns/rejects immediately if !authHeaders?.Authorization to avoid firing POSTs without auth; reference authHeaders, useAffiliateActions, registerMutation, and mutationFn when applying these checks.packages/affiliate-dashboard/src/App.tsx (1)
44-93: Wrap prop-bound handlers inuseCallback.
handleSelectPeriod,setSwapPage/setActiveTab(already stable),() => void signIn(), andmessage => actions.setMessage(message)are passed as props toOverviewTab,SwapsTab, andSettingsTab. The inline lambdas allocate on every render and defeat any downstreammemoon those tabs. The last one can just forwardactions.setMessagedirectly.♻️ Suggested tweak
- const handleSelectPeriod = (index: number): void => { - setSelectedPeriod(index) - setSwapPage(0) - } + const handleSelectPeriod = useCallback((index: number): void => { + setSelectedPeriod(index) + setSwapPage(0) + }, []) + + const handleSignIn = useCallback(() => void signIn(), [signIn]) @@ - onSignIn={() => void signIn()} + onSignIn={handleSignIn} onSignOut={signOut} - onValidationError={message => actions.setMessage(message)} + onValidationError={actions.setMessage}As per coding guidelines: "ALWAYS use
useCallbackfor event handlers and functions passed as props".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/affiliate-dashboard/src/App.tsx` around lines 44 - 93, The handlers passed as props (handleSelectPeriod, setSwapPage, setActiveTab, the inline () => void signIn() and message => actions.setMessage(message)) are re-created each render and can break memoization in OverviewTab, SwapsTab and SettingsTab; wrap handleSelectPeriod in useCallback (dependent on setSelectedPeriod and setSwapPage), wrap the setters passed as props with useCallback or pass the stable setters directly (setSwapPage and setActiveTab are already stable so ensure you pass them unchanged), replace the inline sign-in lambda with a useCallback that calls signIn (or pass signIn directly if stable), and forward actions.setMessage directly instead of the inline message => actions.setMessage(message) to ensure stable references for child components.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/affiliate-dashboard/src/components/settings/AffiliateBpsCard.tsx`:
- Around line 25-33: The input is being cleared unconditionally in handleUpdate
by calling setValue('') after awaiting onUpdate, which causes the field to be
reset even when the mutation fails; modify the flow so the input is cleared only
on a successful update: either update onUpdate (useAffiliateActions.updateBps)
to return a success boolean or rethrow errors, then in handleUpdate await the
result and only call setValue('') when the mutation indicates success (or move
setValue('') into the mutation's onSuccess callback); reference handleUpdate,
onUpdate (useAffiliateActions.updateBps), and setValue to locate the changes.
In `@packages/affiliate-dashboard/src/components/settings/ReceiveAddressCard.tsx`:
- Around line 28-35: The handleUpdate function awaits onUpdate(trimmed) without
catching rejections which can produce unhandled promise errors; wrap the await
in a try/catch inside handleUpdate, call onValidationError({ type: 'error',
text: <friendly message or error.message> }) when onUpdate rejects, and only
call setAddress('') on success so failures are surfaced in the component message
flow; update the handleUpdate implementation (references: handleUpdate,
onUpdate, onValidationError, ADDRESS_REGEX) accordingly.
In `@packages/affiliate-dashboard/src/components/settings/RegisterCard.tsx`:
- Around line 13-55: The BPS input isn't validated before submit—parseInt
accepts partial/scientific strings and HTML min/max don't stop invalid values—so
update parseBps and RegisterCard: make parseBps strictly parse only whole-number
decimal strings (no exponentals), return NaN for invalid input, and clamp valid
numbers to the allowed range (e.g., 0–1000); in RegisterCard use the strict
parser before calling onRegister, show/track a validation error state (e.g., set
a local error when parseBps returns NaN or out-of-range) and prevent onRegister
from being invoked (early return / disable Button) until the value is valid;
reference parseBps, bps, setBps, onRegister, and the Button/validation state so
you can locate and update the logic and UI.
In `@packages/affiliate-dashboard/src/components/swaps/AssetPill.tsx`:
- Around line 3-8: The AssetPill component computes `symbol` incorrectly by
ignoring `asset.name`; update the logic in AssetPill (where `symbol` is derived)
to use asset.symbol if present, otherwise fall back to asset.name, and only use
the literal 'Unknown' if neither exists so objects like { name: 'Ethereum' }
render that name instead of "Unknown".
In `@packages/affiliate-dashboard/src/components/swaps/SwapsTable.tsx`:
- Around line 24-26: The current parse helpers silently coerce invalid/malformed
financial strings to 0 (parseNum and parseBpsDisplay); change parseNum to
perform strict parsing (e.g., use Number.isFinite on parsed value) and return
null (or undefined) for absent/invalid inputs instead of 0, and update
parseBpsDisplay to validate parseInt results and return null for invalid BPS
(also ensure you don't subtract 10 from invalid values). Then update the UI to
stop relying on implicit zeros and call formatUsdValue(swap.sellAmountUsd) and
formatUsdValue(swap.affiliateFeeUsd) (or equivalent) in the table/card cells so
the formatter can render an explicit fallback for null/invalid values.
In `@packages/affiliate-dashboard/src/hooks/useAffiliateStats.ts`:
- Around line 16-35: The ApiResponseSchema currently accepts any string for
totalVolumeUsd and totalFeesEarnedUsd and then parseFloat(...) || 0 masks
invalid data; update ApiResponseSchema to coerce and validate those fields as
numbers (use z.preprocess to parse the string into a number and then
z.number().refine or .nonnegative() as appropriate) so parseResponse returns
properly typed numeric values, then update fetchStats to use the validated
numeric fields (data.totalVolumeUsd and data.totalFeesEarnedUsd) directly
instead of calling parseFloat(...) || 0 and ensure parseResponse errors early on
validation failures; reference ApiResponseSchema and fetchStats to locate and
change the schema and the mapping logic.
In `@packages/affiliate-dashboard/src/types/appkit.d.ts`:
- Around line 3-9: The current augmentation wrongly targets the removed global
JSX namespace; instead declare a module augmentation for "react/jsx-runtime" and
put the IntrinsicElements entry for 'appkit-button' there. Replace the declare
global { namespace JSX { interface IntrinsicElements { 'appkit-button':
React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> } } }
with a module "react/jsx-runtime" augmentation that exports/augments the JSX
namespace and adds the same IntrinsicElements entry so the custom element type
is recognized under the React 19 / "react-jsx" runtime.
---
Nitpick comments:
In `@packages/affiliate-dashboard/src/App.tsx`:
- Around line 44-93: The handlers passed as props (handleSelectPeriod,
setSwapPage, setActiveTab, the inline () => void signIn() and message =>
actions.setMessage(message)) are re-created each render and can break
memoization in OverviewTab, SwapsTab and SettingsTab; wrap handleSelectPeriod in
useCallback (dependent on setSelectedPeriod and setSwapPage), wrap the setters
passed as props with useCallback or pass the stable setters directly
(setSwapPage and setActiveTab are already stable so ensure you pass them
unchanged), replace the inline sign-in lambda with a useCallback that calls
signIn (or pass signIn directly if stable), and forward actions.setMessage
directly instead of the inline message => actions.setMessage(message) to ensure
stable references for child components.
In `@packages/affiliate-dashboard/src/hooks/useAffiliateActions.ts`:
- Around line 103-124: The returned actions object recreates wrappers each
render and swallows errors; wrap each handler (register, claimCode, updateBps,
updateReceiveAddress) in useCallback (dependencies: their respective mutations
and setMessage) and wrap the final returned object in useMemo (deps: the
callbacks and message/isLoading) to avoid reallocation, and change the async
wrappers (claimCodeMutation.mutateAsync, updateBpsMutation.mutateAsync,
updateReceiveAddressMutation.mutateAsync) to either re-throw errors or return a
boolean success value instead of .catch(() => undefined) so callers can detect
failure (keep onError for messaging via the existing mutation onError handlers);
ensure register still calls registerMutation.mutate inside its useCallback.
- Around line 29-50: The hook useAffiliateActions should assert the authHeaders
shape and defend against missing Authorization: add a stronger type at the call
boundary for authHeaders (Record<string,string> with Authorization
optional/required) and on mount validate authHeaders.Authorization (e.g.,
early-return or set an error message) and, crucially, add a short-circuit inside
each mutationFn (e.g., in registerMutation's mutationFn) that returns/rejects
immediately if !authHeaders?.Authorization to avoid firing POSTs without auth;
reference authHeaders, useAffiliateActions, registerMutation, and mutationFn
when applying these checks.
In `@packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts`:
- Line 9: AFFILIATE_SWAPS_URL is being built inline in useAffiliateSwaps.ts from
import.meta.env which can produce "undefined/..." if VITE_API_URL is missing;
move the swaps endpoint constant into the shared ../lib/constants (next to
AFFILIATE_URL), perform the same env validation/assembly there, export the new
AFFILIATE_SWAPS_URL, and replace the inline const in useAffiliateSwaps.ts with
an import of AFFILIATE_SWAPS_URL so all affiliate endpoints are centralized and
validated in one place.
In `@packages/affiliate-dashboard/src/lib/api.ts`:
- Around line 15-27: The parseResponse function currently swallows JSON decode
failures by mapping them to Zod shape errors; update parseResponse to catch JSON
parsing errors separately (when calling response.json()) and throw or log a
distinct, informative error before running schema.safeParse — include the
original JSON parsing error message (and optionally the raw response text) in
the thrown/logged message so callers can distinguish decode failures from schema
validation failures; keep existing behavior of calling throwFromResponse when
!response.ok and continue to validate with schema.safeParse only when JSON
decoding succeeded.
In `@packages/affiliate-dashboard/src/lib/periods.ts`:
- Around line 10-54: formatPeriodLabel currently uses only end.getUTCFullYear(),
causing misleading labels for ranges that cross years; update formatPeriodLabel
to include the start year when start.getUTCFullYear() !== end.getUTCFullYear()
(e.g., "Dec 5, 2025 - Jan 5, 2026") so the label disambiguates year-crossing
periods. Also avoid computing periods once at module load: replace the
module-level export const periods = generatePeriods() pattern by exporting the
generatePeriods function (or provide a getPeriods helper) and call it from the
period selector component (or on mount/useMemo) so the list is recalculated when
the component mounts or when current time changes rather than staying stale.
In `@packages/affiliate-dashboard/src/main.tsx`:
- Around line 28-34: Add a root error boundary around the dashboard by creating
a React ErrorBoundary (e.g., class RootErrorBoundary with componentDidCatch to
log errors) and wrap the existing top-level tree in main.tsx (wrap
ChakraProvider, WagmiProvider, QueryClientProvider and <App />) with
<RootErrorBoundary>; the boundary should render a simple user-friendly fallback
UI (retry/refresh button or message) and call your logging mechanism
(console.error or app logger) with the error and info inside componentDidCatch
to capture render-time failures.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6dca79a9-b49d-47e8-8116-8f9aa323f333
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (39)
packages/affiliate-dashboard/package.jsonpackages/affiliate-dashboard/src/App.tsxpackages/affiliate-dashboard/src/components/ConfigBar.tsxpackages/affiliate-dashboard/src/components/EmptyState.tsxpackages/affiliate-dashboard/src/components/ErrorBanner.tsxpackages/affiliate-dashboard/src/components/Header.tsxpackages/affiliate-dashboard/src/components/Layout.tsxpackages/affiliate-dashboard/src/components/PeriodSelector.tsxpackages/affiliate-dashboard/src/components/ShapeShiftLogo.tsxpackages/affiliate-dashboard/src/components/TabBar.tsxpackages/affiliate-dashboard/src/components/overview/OverviewTab.tsxpackages/affiliate-dashboard/src/components/overview/StatCard.tsxpackages/affiliate-dashboard/src/components/settings/ActionMessage.tsxpackages/affiliate-dashboard/src/components/settings/AffiliateBpsCard.tsxpackages/affiliate-dashboard/src/components/settings/AuthBanner.tsxpackages/affiliate-dashboard/src/components/settings/AuthStatusBar.tsxpackages/affiliate-dashboard/src/components/settings/ClaimCodeCard.tsxpackages/affiliate-dashboard/src/components/settings/ConfigSummaryCard.tsxpackages/affiliate-dashboard/src/components/settings/ReceiveAddressCard.tsxpackages/affiliate-dashboard/src/components/settings/RegisterCard.tsxpackages/affiliate-dashboard/src/components/settings/SettingsCard.tsxpackages/affiliate-dashboard/src/components/settings/SettingsTab.tsxpackages/affiliate-dashboard/src/components/swaps/AssetPill.tsxpackages/affiliate-dashboard/src/components/swaps/Pagination.tsxpackages/affiliate-dashboard/src/components/swaps/StatusBadge.tsxpackages/affiliate-dashboard/src/components/swaps/SwapsTab.tsxpackages/affiliate-dashboard/src/components/swaps/SwapsTable.tsxpackages/affiliate-dashboard/src/hooks/useAffiliateActions.tspackages/affiliate-dashboard/src/hooks/useAffiliateConfig.tspackages/affiliate-dashboard/src/hooks/useAffiliateStats.tspackages/affiliate-dashboard/src/hooks/useAffiliateSwaps.tspackages/affiliate-dashboard/src/hooks/useSiweAuth.tspackages/affiliate-dashboard/src/lib/api.tspackages/affiliate-dashboard/src/lib/constants.tspackages/affiliate-dashboard/src/lib/format.tspackages/affiliate-dashboard/src/lib/periods.tspackages/affiliate-dashboard/src/main.tsxpackages/affiliate-dashboard/src/theme/index.tspackages/affiliate-dashboard/src/types/appkit.d.ts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…flow - appkit.d.ts: migrate to react/jsx-runtime module augmentation (React 19 drops global JSX) - AssetSchema: require symbol, drop defensive string-union and optional fallbacks; AssetPill simplifies accordingly - settings cards (bps/receive/claim): preserve input on mutation failure via try/catch; useAffiliateActions stops swallowing errors - register/bps cards: share MIN_BPS/MAX_BPS/DEFAULT_BPS constants and parseBps/bpsToPercent helpers - parseResponse: generalize to accept any ZodType and infer output (supports .transform()) - NumericString / NullableNumericString in lib/api.ts: strict stats parsing (loud fail) vs per-row soft-fail for swaps (malformed USD -> null -> "—" without killing the page) - API_BASE_URL in constants; affiliate and auth URLs derive from it Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Description
Refactor of the affiliate dashboard — foundational cleanup ahead of the new affiliate swaps API shape, period selector, and polish that follow on separate branches.
App.tsxintocomponents/,hooks/, andlib/foldersuseState/useEffectfetchers with React Query (useQuery+useMutation)parseResponsehelperkeepPreviousData)package.json(hoisted from root)No backend changes — zero DB migrations, zero API changes. Dashboard is a standalone package under
packages/affiliate-dashboard.Issue (if applicable)
closes #
Risk
Low. Isolated to
packages/affiliate-dashboard; no changes to web app, chain adapters, wallets, or on-chain tx paths. Worst case is a regression in the affiliate dashboard UI, which is an internal-facing tool.Testing
Engineering
pnpm --filter @shapeshiftoss/affiliate-dashboard devboots the dashboard without errorsOperations
Internal affiliate dashboard only — not user-facing.
Screenshots (if applicable)
Summary by CodeRabbit
New Features
Styling