Skip to content

Commit aac7371

Browse files
committed
feat: implement feature flag for refund metrics display
- Check showWidget field from API - Hide component when disabled - Default to showing for backward compatibility
1 parent be29d75 commit aac7371

File tree

3 files changed

+236
-2
lines changed

3 files changed

+236
-2
lines changed

MEV_METRICS_IMPLEMENTATION.md

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
# Adding MEV Metrics Tickers to Docusaurus Navbar
2+
3+
This guide explains how to add MEV metrics tickers to a Docusaurus website navbar using the ComponentTypes pattern.
4+
5+
## Overview
6+
7+
Add a compact metrics ticker to the navbar displaying: `Refund | MEV: 380.290 ETH | Gas: 444.240 ETH`
8+
9+
## Implementation Steps
10+
11+
### 1. Install Dependencies
12+
13+
```bash
14+
npm install
15+
```
16+
17+
Create `.env` file if needed:
18+
```
19+
BASE_URL=/
20+
TARGET_URL=http://localhost:3000
21+
```
22+
23+
### 2. Create MEV Metrics Component
24+
25+
Create `src/components/MevMetrics.tsx`:
26+
27+
```tsx
28+
import React, { useEffect, useState } from 'react';
29+
import styles from './MevMetrics.module.css';
30+
31+
interface MetricData {
32+
value: number;
33+
unit: string;
34+
}
35+
36+
interface MetricsResponse {
37+
mevRefund: MetricData;
38+
gasRefund: MetricData;
39+
}
40+
41+
export default function MevMetrics(): JSX.Element {
42+
const [data, setData] = useState<MetricsResponse | null>(null);
43+
const [loading, setLoading] = useState(true);
44+
45+
useEffect(() => {
46+
const fetchMetrics = async () => {
47+
try {
48+
const response = await fetch('/api/mev/metrics');
49+
const metrics: MetricsResponse = await response.json();
50+
setData(metrics);
51+
} catch (error) {
52+
console.error('Error fetching MEV metrics:', error);
53+
// Mock data as fallback
54+
setData({
55+
mevRefund: { value: 380.29, unit: 'ETH' },
56+
gasRefund: { value: 444.24, unit: 'ETH' }
57+
});
58+
} finally {
59+
setLoading(false);
60+
}
61+
};
62+
63+
fetchMetrics();
64+
}, []);
65+
66+
const formatValue = (metric: MetricData): string => {
67+
if (metric.unit === '$') {
68+
if (metric.value >= 1e9) return `$${(metric.value / 1e9).toFixed(1)}B`;
69+
if (metric.value >= 1e6) return `$${(metric.value / 1e6).toFixed(1)}M`;
70+
if (metric.value >= 1e3) return `$${(metric.value / 1e3).toFixed(1)}K`;
71+
return `$${metric.value.toFixed(2)}`;
72+
}
73+
return `${metric.value.toFixed(3)} ${metric.unit}`;
74+
};
75+
76+
return (
77+
<div className={styles.container}>
78+
<span className={styles.label}>Refund</span>
79+
<span className={styles.separator}>|</span>
80+
<div className={styles.metric}>
81+
<span className={styles.label}>MEV:</span>
82+
<span className={`${styles.value} ${loading ? styles.loading : ''}`}>
83+
{loading ? '...' : data && formatValue(data.mevRefund)}
84+
</span>
85+
</div>
86+
<span className={styles.separator}>|</span>
87+
<div className={styles.metric}>
88+
<span className={styles.label}>Gas:</span>
89+
<span className={`${styles.value} ${loading ? styles.loading : ''}`}>
90+
{loading ? '...' : data && formatValue(data.gasRefund)}
91+
</span>
92+
</div>
93+
</div>
94+
);
95+
}
96+
```
97+
98+
### 3. Create CSS Module
99+
100+
Create `src/components/MevMetrics.module.css`:
101+
102+
```css
103+
.container {
104+
display: flex;
105+
align-items: center;
106+
gap: 0.75rem;
107+
font-size: 0.875rem;
108+
color: var(--ifm-navbar-link-color);
109+
margin-right: 0.75rem;
110+
}
111+
112+
.metric {
113+
display: flex;
114+
align-items: center;
115+
gap: 0.25rem;
116+
}
117+
118+
.label {
119+
font-weight: 400;
120+
}
121+
122+
.value {
123+
font-family: monospace;
124+
font-weight: 600;
125+
transition: opacity 0.3s ease;
126+
}
127+
128+
.loading {
129+
opacity: 0.5;
130+
}
131+
132+
.separator {
133+
color: var(--ifm-navbar-link-color);
134+
opacity: 0.3;
135+
}
136+
137+
@media (max-width: 996px) {
138+
.container {
139+
display: none !important;
140+
}
141+
}
142+
```
143+
144+
### 4. Swizzle ComponentTypes
145+
146+
```bash
147+
npm run swizzle @docusaurus/theme-classic NavbarItem/ComponentTypes -- --eject --typescript
148+
```
149+
150+
### 5. Register Component
151+
152+
Update `src/theme/NavbarItem/ComponentTypes.tsx`:
153+
154+
```tsx
155+
// Add import at top
156+
import MevMetrics from '@site/src/components/MevMetrics';
157+
158+
// Add to ComponentTypes object
159+
const ComponentTypes: ComponentTypesObject = {
160+
// ... existing types
161+
'custom-mevMetrics': MevMetrics,
162+
};
163+
```
164+
165+
### 6. Configure Navbar
166+
167+
Update `docusaurus.config.js`:
168+
169+
```javascript
170+
navbar: {
171+
items: [
172+
{ to: '/docs', label: 'Docs', position: 'left' },
173+
{ to: '/blog', label: 'Blog', position: 'left' },
174+
{
175+
type: 'custom-mevMetrics',
176+
position: 'right',
177+
},
178+
{
179+
href: 'https://your-forum-link.com',
180+
label: 'Forum',
181+
position: 'right',
182+
},
183+
],
184+
},
185+
```
186+
187+
## API Specification
188+
189+
Endpoint: `/api/mev/metrics`
190+
191+
Expected response:
192+
```json
193+
{
194+
"mevRefund": {
195+
"value": 380.29,
196+
"unit": "ETH"
197+
},
198+
"gasRefund": {
199+
"value": 444.24,
200+
"unit": "ETH"
201+
}
202+
}
203+
```
204+
205+
## Features
206+
207+
- **Single API call** for both metrics
208+
- **CSS Modules** for better performance and maintainability
209+
- **Responsive design** - hidden on mobile devices
210+
- **Loading states** with visual feedback
211+
- **Error handling** with mock data fallback
212+
- **Number formatting** for large values (K/M/B)
213+
214+
## Notes
215+
216+
- Mock data is included for development
217+
- Metrics update once on page load (suitable for daily updates)
218+
- Uses Docusaurus theme variables for consistent styling

src/components/MevMetrics.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ interface MetricsResponse {
77
totalGasRefund: number;
88
fetchedAt: string;
99
stale: boolean;
10+
showWidget?: boolean;
1011
}
1112

12-
export default function MevMetrics(): JSX.Element {
13+
export default function MevMetrics(): JSX.Element | null {
1314
const { siteConfig } = useDocusaurusContext();
1415
const [data, setData] = useState<MetricsResponse | null>(null);
16+
const [showWidget, setShowWidget] = useState(true);
1517
const [loading, setLoading] = useState(true);
1618

1719
useEffect(() => {
@@ -23,6 +25,14 @@ export default function MevMetrics(): JSX.Element {
2325
throw new Error(`HTTP error! status: ${response.status}`);
2426
}
2527
const metrics: MetricsResponse = await response.json();
28+
29+
// Check feature flag
30+
if (metrics.showWidget === false) {
31+
setShowWidget(false);
32+
setLoading(false);
33+
return;
34+
}
35+
2636
setData(metrics);
2737
} catch (error) {
2838
console.error('Error fetching MEV metrics:', error);
@@ -50,6 +60,11 @@ export default function MevMetrics(): JSX.Element {
5060
window.open(redirectUrl, '_blank', 'noopener,noreferrer');
5161
};
5262

63+
// Hide widget if flag says so
64+
if (!showWidget) {
65+
return null;
66+
}
67+
5368
return (
5469
<div className={styles.container}>
5570
<span className={styles.label}>Refund</span>

src/css/custom.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
--ifm-color-primary: #04F;
2828
--ifm-color-content: var(--ifm-color-emphasis-800);
2929
--ifm-code-font-size: 95%;
30+
--ifm-toc-padding-horizontal: 4px;
3031
}
3132

3233
[data-theme='dark'] {
@@ -80,4 +81,4 @@ h2 {
8081
.menu__link--sublist::after {
8182
background: var(--ifm-menu-link-sublist-icon) 50%/1.5rem 1.5rem;
8283
opacity: .75;
83-
}
84+
}

0 commit comments

Comments
 (0)