Skip to content

feat: market page detail + campaigns#297

Merged
antoncoding merged 5 commits intomasterfrom
feat/details-and-campaigns
Jan 13, 2026
Merged

feat: market page detail + campaigns#297
antoncoding merged 5 commits intomasterfrom
feat/details-and-campaigns

Conversation

@antoncoding
Copy link
Owner

@antoncoding antoncoding commented Jan 13, 2026

Summary by CodeRabbit

  • New Features

    • Extended timeframe options (3M, 6M) for rate and volume charts
    • Expandable Advanced Details panel in market header with smooth animations
    • Campaign names, date ranges and external "View on Merkl" links added to campaign modal
  • Improvements

    • Enhanced chart tooltips with time-of-day details
    • Refined oracle price formatting and header layout
    • Improved campaign filtering and identifier selection; reward badge and APR preserved

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Contributor

vercel bot commented Jan 13, 2026

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

Project Deployment Review Updated (UTC)
monarch Ready Ready Preview, Comment Jan 13, 2026 3:39pm

@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

Warning

Rate limit exceeded

@antoncoding has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 50 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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.

📥 Commits

Reviewing files that changed from the base of the PR and between a7ab5af and 8c9058a.

📒 Files selected for processing (2)
  • src/features/market-detail/components/campaign-modal.tsx
  • src/hooks/useMarketCampaigns.ts
📝 Walkthrough

Walkthrough

Extends chart timeframes to 3M/6M, refactors market header to an animated expandable panel, enriches Merkl campaign data with name/opportunityIdentifier and updates campaign-modal UI (filtering, simplified rows, URL identifier logic).

Changes

Cohort / File(s) Summary
Campaign data & modal
src/features/market-detail/components/campaign-modal.tsx, src/utils/merklApi.ts, src/utils/merklTypes.ts
Drops actionVerb from CAMPAIGN_TYPE_CONFIG; adds BLACKLISTED_CAMPAIGN_IDS and filters campaigns before render; getUrlIdentifier now prefers campaign.opportunityIdentifier; adds formatCampaignDate; CampaignRow UI simplified (name, date range, badge, reward token, external link); getBaseCampaignFields now includes name and opportunityIdentifier; SimplifiedCampaign gains optional name.
Charts & chart utils
src/features/market-detail/components/charts/chart-utils.tsx, src/features/market-detail/components/charts/rate-chart.tsx, src/features/market-detail/components/charts/volume-chart.tsx, src/stores/useMarketDetailChartState.ts
Adds '3m' and '6m' timeframe labels; tooltip formatting includes time (month/day hour:minute); RateChart and VolumeChart add 3M/6M options and handler types; store adds MONTH_IN_SECONDS, expands calculateTimeRange, selectedTimeframe, and setTimeframe to support '3m'/'6m' (using DAY interval for longer ranges).
Market header UI
src/features/market-detail/components/market-header.tsx
Replaces native details/summary with animated expand/collapse using Framer Motion (isExpanded state, motion.div); wraps collateral icon in rounded container; changes advanced-details layout to two-column grid with warnings, oracle info, and address identity blocks; tweaks oracle price formatting and action button sizing.

Sequence Diagram(s)

(omitted — changes are UI/formatting and incremental timeline additions that do not introduce a new multi-component sequential flow requiring visualization)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: market page detail + campaigns' accurately captures the main changes: enhanced market detail page UI and new campaign modal functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/details-and-campaigns

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 and usage tips.

@coderabbitai coderabbitai bot added feature request Specific feature ready to be implemented ui User interface labels Jan 13, 2026
Copy link

@coderabbitai coderabbitai bot left a 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

🤖 Fix all issues with AI agents
In @src/hooks/useMarketCampaigns.ts:
- Around line 45-46: Remove the debug console.log call that prints
singleTokenCampaigns in useMarketCampaigns.ts: delete the line containing
console.log('singleTokenCampaigns', singleTokenCampaigns) (or guard it behind a
dev-only check) so production consoles are not polluted; locate it inside the
hook function where the singleTokenCampaigns variable is computed and remove or
replace it with proper logging if needed.
🧹 Nitpick comments (2)
src/stores/useMarketDetailChartState.ts (1)

37-43: Consider extracting the timeframe union type.

The '1d' | '7d' | '30d' | '3m' | '6m' union is repeated 4 times in this file. Extract to a named type for maintainability.

Suggested change
+export type ChartTimeframe = '1d' | '7d' | '30d' | '3m' | '6m';
+
 // Helper to calculate time range based on timeframe string
-const calculateTimeRange = (timeframe: '1d' | '7d' | '30d' | '3m' | '6m'): TimeseriesOptions => {
+const calculateTimeRange = (timeframe: ChartTimeframe): TimeseriesOptions => {

Then use ChartTimeframe in the type definitions and function signatures.

src/features/market-detail/components/campaign-modal.tsx (1)

109-110: Consider memoizing the filter.

If campaigns array is large, filtering on every render may be wasteful. Wrap in useMemo with campaigns dependency.

Suggested change
+import { useMemo } from 'react';
+
 export function CampaignModal({ isOpen, onClose, campaigns }: CampaignModalProps) {
+  // Filter out blacklisted campaigns
+  const filteredCampaigns = useMemo(
+    () => campaigns.filter((c) => !BLACKLISTED_CAMPAIGN_IDS.includes(c.campaignId)),
+    [campaigns]
+  );
+
   if (!isOpen) return null;
-
-  // Filter out blacklisted campaigns
-  const filteredCampaigns = campaigns.filter((c) => !BLACKLISTED_CAMPAIGN_IDS.includes(c.campaignId));
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f6621d and eb6edba.

📒 Files selected for processing (9)
  • src/features/market-detail/components/campaign-modal.tsx
  • src/features/market-detail/components/charts/chart-utils.tsx
  • src/features/market-detail/components/charts/rate-chart.tsx
  • src/features/market-detail/components/charts/volume-chart.tsx
  • src/features/market-detail/components/market-header.tsx
  • src/hooks/useMarketCampaigns.ts
  • src/stores/useMarketDetailChartState.ts
  • src/utils/merklApi.ts
  • src/utils/merklTypes.ts
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-09T10:06:39.848Z
Learnt from: antoncoding
Repo: antoncoding/monarch PR: 231
File: src/hooks/useDeployMorphoMarketV1Adapter.ts:3-3
Timestamp: 2025-12-09T10:06:39.848Z
Learning: In Wagmi v3, useConnection is the correct hook to obtain the connected wallet address, chainId, and connection status (isConnected). This replaces the useAccount hook from Wagmi v2. In your code under src/hooks, use: const { address, chainId, isConnected } = useConnection() from 'wagmi' to access the connected wallet information.

Applied to files:

  • src/hooks/useMarketCampaigns.ts
📚 Learning: 2024-10-12T09:23:16.495Z
Learnt from: antoncoding
Repo: antoncoding/monarch PR: 63
File: app/markets/components/MarketRowDetail.tsx:49-52
Timestamp: 2024-10-12T09:23:16.495Z
Learning: When rendering oracle feeds in `ExpandedMarketDetail` (`app/markets/components/MarketRowDetail.tsx`), prefer explicit rendering over iterating keys when dealing with a small number of feeds.

Applied to files:

  • src/features/market-detail/components/market-header.tsx
📚 Learning: 2025-11-13T20:32:48.908Z
Learnt from: antoncoding
Repo: antoncoding/monarch PR: 183
File: app/positions/components/RebalanceModal.tsx:301-308
Timestamp: 2025-11-13T20:32:48.908Z
Learning: The TokenIcon component in the codebase accepts a `symbol` prop as part of its valid interface, in addition to `address`, `chainId`, `width`, `height`, and other props.

Applied to files:

  • src/features/market-detail/components/market-header.tsx
🧬 Code graph analysis (6)
src/features/market-detail/components/charts/volume-chart.tsx (1)
src/components/ui/select.tsx (1)
  • SelectItem (136-136)
src/features/market-detail/components/market-header.tsx (4)
src/components/shared/token-icon.tsx (1)
  • TokenIcon (23-95)
src/components/shared/address-identity.tsx (1)
  • AddressIdentity (20-49)
src/utils/morpho.ts (1)
  • getIRMTitle (51-70)
src/features/markets/components/oracle/MarketOracle/OracleTypeInfo.tsx (1)
  • OracleTypeInfo (18-70)
src/features/market-detail/components/charts/rate-chart.tsx (1)
src/components/ui/select.tsx (1)
  • SelectItem (136-136)
src/utils/merklApi.ts (1)
src/utils/merklTypes.ts (1)
  • SimplifiedCampaign (133-159)
src/features/market-detail/components/campaign-modal.tsx (2)
src/utils/merklTypes.ts (1)
  • SimplifiedCampaign (133-159)
src/utils/external.ts (1)
  • getMerklCampaignURL (57-60)
src/stores/useMarketDetailChartState.ts (1)
src/utils/types.ts (1)
  • TimeseriesOptions (341-345)
🔇 Additional comments (17)
src/stores/useMarketDetailChartState.ts (2)

6-6: LGTM on the constant.

Using 30 days as a month approximation is fine for chart purposes.


9-34: Timeframe logic looks correct.

The '3m' and '6m' cases are handled consistently with 'DAY' intervals which makes sense for longer ranges.

src/utils/merklTypes.ts (1)

157-158: LGTM.

Optional field addition is backward compatible.

src/features/market-detail/components/charts/rate-chart.tsx (1)

47-49: LGTM.

Timeframe extension is consistent with the store and other chart components.

Also applies to: 127-127, 136-137

src/features/market-detail/components/charts/chart-utils.tsx (2)

4-10: LGTM.

Labels added correctly.


68-75: Good improvement on tooltip formatting.

Adding time precision helps distinguish data points at shorter timeframes.

src/features/market-detail/components/charts/volume-chart.tsx (1)

47-49: LGTM.

Consistent with rate-chart.tsx changes.

Also applies to: 183-183, 192-193

src/features/market-detail/components/campaign-modal.tsx (3)

18-23: Hardcoded blacklist works for now.

Consider moving to config if this list grows. The comment explaining the reason is helpful.


31-43: LGTM on identifier logic.

Preferring opportunityIdentifier with fallback is clean.


45-51: LGTM.

Clean date formatter.

src/utils/merklApi.ts (1)

78-109: LGTM!

Clean addition of name and opportunityIdentifier fields. Optional chaining handles missing Opportunity data correctly, and the return type aligns with SimplifiedCampaign.

src/features/market-detail/components/market-header.tsx (6)

38-67: LGTM!

WarningBlock is well-structured with proper conditional styling and null handling.


76-97: LGTM!

Clean badge component. The pluralization logic works for the current usage patterns.


100-111: LGTM!

Simple, clear icon mapping.


376-395: Good accessibility implementation.

Proper keyboard handling (Enter/Space), ARIA attributes, and focus management for the expandable section.


397-484: LGTM!

Clean framer-motion animation setup. The overflow-hidden class correctly prevents content overflow during height transition.


314-320: size="xs" is valid. The Button component explicitly supports this size variant.

Copy link

@coderabbitai coderabbitai bot left a 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)
src/features/market-detail/components/campaign-modal.tsx (2)

106-138: No empty state when all campaigns are filtered.

If all campaigns are blacklisted, the modal body will be empty. Consider adding a fallback message.

Suggested fix
       <ModalBody className="space-y-4">
-        {filteredCampaigns.map((campaign) => (
+        {filteredCampaigns.length === 0 ? (
+          <p className="text-center text-gray-500">No active campaigns available.</p>
+        ) : (
+          filteredCampaigns.map((campaign) => (
           <CampaignRow
             key={`${campaign.campaignId}-${campaign.marketId}`}
             campaign={campaign}
           />
-        ))}
+          ))
+        )}
       </ModalBody>

11-16: Remove unused actionType field from CAMPAIGN_TYPE_CONFIG.

The actionType property is defined but never accessed anywhere in the codebase. Simplify the config by removing it.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb6edba and a20aff2.

📒 Files selected for processing (2)
  • src/features/market-detail/components/campaign-modal.tsx
  • src/features/market-detail/components/market-header.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-10-12T09:23:16.495Z
Learnt from: antoncoding
Repo: antoncoding/monarch PR: 63
File: app/markets/components/MarketRowDetail.tsx:49-52
Timestamp: 2024-10-12T09:23:16.495Z
Learning: When rendering oracle feeds in `ExpandedMarketDetail` (`app/markets/components/MarketRowDetail.tsx`), prefer explicit rendering over iterating keys when dealing with a small number of feeds.

Applied to files:

  • src/features/market-detail/components/market-header.tsx
📚 Learning: 2025-11-13T20:32:48.908Z
Learnt from: antoncoding
Repo: antoncoding/monarch PR: 183
File: app/positions/components/RebalanceModal.tsx:301-308
Timestamp: 2025-11-13T20:32:48.908Z
Learning: The TokenIcon component in the codebase accepts a `symbol` prop as part of its valid interface, in addition to `address`, `chainId`, `width`, `height`, and other props.

Applied to files:

  • src/features/market-detail/components/market-header.tsx
🧬 Code graph analysis (2)
src/features/market-detail/components/market-header.tsx (4)
src/components/shared/token-icon.tsx (1)
  • TokenIcon (23-95)
src/components/shared/address-identity.tsx (1)
  • AddressIdentity (20-49)
src/utils/morpho.ts (1)
  • getIRMTitle (51-70)
src/features/markets/components/oracle/MarketOracle/OracleTypeInfo.tsx (1)
  • OracleTypeInfo (18-70)
src/features/market-detail/components/campaign-modal.tsx (2)
src/utils/merklTypes.ts (2)
  • MerklCampaignType (1-1)
  • SimplifiedCampaign (133-159)
src/utils/external.ts (1)
  • getMerklCampaignURL (57-60)
🔇 Additional comments (14)
src/features/market-detail/components/market-header.tsx (10)

1-30: Imports look good.

Clean import organization with appropriate dependencies for the animated expandable panel.


38-67: Clean implementation.

Early return for empty array, conditional styling based on risk level, proper key usage.


76-97: LGTM.

Pluralization handles the expected cases. The count=0 edge case won't occur given current usage.


100-111: LGTM.

Simple switch with null fallback for unknown levels.


227-235: Good visual fix for overlapping icons.

The bg-surface wrapper creates a clean border effect between the overlapping token icons.


291-297: Good precision handling.

toPrecision(6) in tooltip for accuracy across price magnitudes, toFixed(2) for compact display.


314-321: Size change is reasonable.

Icon-only dropdown trigger doesn't need larger touch target here.


376-388: Good accessible expand/collapse button.

type="button", aria-expanded, and chevron rotation all handled correctly.


390-398: Animation implementation is clean.

AnimatePresence with height/opacity animation and overflow-hidden prevents layout jumps.


407-473: Two-column layout with warning blocks works well.

Responsive grid, clear section headers with risk icons, proper use of AddressIdentity and OracleTypeInfo components.

src/features/market-detail/components/campaign-modal.tsx (4)

18-23: LGTM!

Clear documentation on why the campaign is blacklisted. If this list grows significantly, consider using a Set for O(1) lookups.


31-43: LGTM!

Clean fallback chain: opportunityIdentifier → type-specific logic → marketId address extraction.


45-51: LGTM!

Clean timestamp formatting using locale-aware date display.


53-104: LGTM!

Clean component structure. The fallback to MORPHOSUPPLY config (line 57) handles unknown campaign types gracefully. External image domains are already covered by the wildcard pattern in your next.config.js.

Copy link

@coderabbitai coderabbitai bot left a 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)
src/features/market-detail/components/charts/rate-chart.tsx (1)

47-49: Consider extracting the timeframe type to avoid duplication.

The literal union '1d' | '7d' | '30d' | '3m' | '6m' appears in both the handler signature (line 47) and the cast (line 127). If you add more timeframes later, you'll need to update multiple places.

♻️ Suggested refactor

Define a shared type (or import from the store if it already exists there):

type Timeframe = '1d' | '7d' | '30d' | '3m' | '6m';

const handleTimeframeChange = (timeframe: Timeframe) => {
  setTimeframe(timeframe);
};

// ...

onValueChange={(value) => handleTimeframeChange(value as Timeframe)}

Also applies to: 127-127

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a20aff2 and a7ab5af.

📒 Files selected for processing (1)
  • src/features/market-detail/components/charts/rate-chart.tsx
🔇 Additional comments (2)
src/features/market-detail/components/charts/rate-chart.tsx (2)

136-137: New timeframe options look good.

UI now matches the extended timeframe support.


199-208: New Area for "Rate at Util Target" integrates correctly.

All required definitions verified: CHART_COLORS.apyAtTarget is defined, rateChart-targetGradient is properly rendered via the ChartGradients component (which prefixes gradient IDs), and visibleLines.apyAtTarget is initialized in state. Data prep from getChartData includes the apyAtTarget field. Feature is complete.

@antoncoding antoncoding merged commit 228d762 into master Jan 13, 2026
4 checks passed
@antoncoding antoncoding deleted the feat/details-and-campaigns branch January 13, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request Specific feature ready to be implemented ui User interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant