Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import { GoShield, GoShieldCheck } from 'react-icons/go';
import { Button } from '@/components/ui/button';
import { IconSwitch } from '@/components/ui/icon-switch';
import { PaletteSelector } from '@/components/ui/palette-preview';
import Header from '@/components/layout/header/Header';
import { AdvancedRpcSettings } from '@/modals/settings/custom-rpc-settings';
import { VaultIdentity } from '@/features/autovault/components/vault-identity';
Expand All @@ -12,11 +13,13 @@ import { useModal } from '@/hooks/useModal';
import { useTrustedVaults } from '@/stores/useTrustedVaults';
import { useAppSettings } from '@/stores/useAppSettings';
import { useMarketPreferences } from '@/stores/useMarketPreferences';
import { useChartPalette } from '@/stores/useChartPalette';

export default function SettingsPage() {
const { usePermit2, setUsePermit2, showUnwhitelistedMarkets, setShowUnwhitelistedMarkets, isAprDisplay, setIsAprDisplay } =
useAppSettings();
const { includeUnknownTokens, setIncludeUnknownTokens, showUnknownOracle, setShowUnknownOracle } = useMarketPreferences();
const { palette: selectedPalette, setPalette } = useChartPalette();

const { vaults: userTrustedVaults } = useTrustedVaults();

Expand Down Expand Up @@ -101,6 +104,27 @@ export default function SettingsPage() {
</div>
</div>

<div className="flex flex-col gap-4 pt-4">
<h2 className="text font-monospace text-secondary">Chart Appearance</h2>

<div className="bg-surface rounded p-6">
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<h3 className="text-lg font-medium text-primary">Color Palette</h3>
<p className="text-sm text-secondary">
Choose a color scheme for charts and graphs. Changes apply to all chart visualizations across the application.
</p>
</div>
{mounted && (
<PaletteSelector
selectedPalette={selectedPalette}
onSelect={setPalette}
/>
)}
</div>
</div>
</div>

<div className="flex flex-col gap-4 pt-4">
<h2 className="text font-monospace text-secondary">Filter Settings</h2>

Expand Down
79 changes: 79 additions & 0 deletions src/components/ui/palette-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client';

import { cn } from '@/utils/components';
import { CHART_PALETTES, PALETTE_META } from '@/constants/chartColors';
import { CHART_PALETTE_NAMES, type ChartPaletteName } from '@/stores/useChartPalette';

type PaletteOptionProps = {
paletteId: ChartPaletteName;
selected: boolean;
onSelect: () => void;
};

function ColorSwatches({ paletteId }: { paletteId: ChartPaletteName }) {
const palette = CHART_PALETTES[paletteId];
const colors = [
palette.supply.stroke,
palette.borrow.stroke,
palette.apyAtTarget.stroke,
palette.risk.stroke,
];

return (
<div className="flex gap-1.5">
{colors.map((color, index) => (
<div
key={index}
className="h-4 w-4 rounded-full"
style={{ backgroundColor: color }}
/>
))}
</div>
);
}

export function PaletteOption({ paletteId, selected, onSelect }: PaletteOptionProps) {
const meta = PALETTE_META[paletteId];
if (!meta) return null;

return (
<button
type="button"
onClick={onSelect}
className={cn(
'flex flex-col gap-2 rounded border-2 p-4 text-left transition-all duration-200',
selected
? 'border-primary bg-surface'
: 'border-border/50 bg-surface/50 hover:border-border hover:bg-surface',
)}
>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-primary">{meta.name}</span>
{selected && <div className="h-2 w-2 rounded-full bg-primary" />}
</div>
<span className="text-xs text-secondary">{meta.description}</span>
<ColorSwatches paletteId={paletteId} />
</button>
);
}

export function PaletteSelector({
selectedPalette,
onSelect,
}: {
selectedPalette: ChartPaletteName;
onSelect: (palette: ChartPaletteName) => void;
}) {
return (
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
{CHART_PALETTE_NAMES.map((id) => (
<PaletteOption
key={id}
paletteId={id}
selected={selectedPalette === id}
onSelect={() => onSelect(id)}
/>
))}
</div>
);
}
172 changes: 130 additions & 42 deletions src/constants/chartColors.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,138 @@
import { useChartPalette, type ChartPaletteName } from '@/stores/useChartPalette';

export const MONARCH_PRIMARY = '#f45f2d';

export const CHART_COLORS = {
supply: {
stroke: '#3B82F6',
gradient: {
start: '#3B82F6',
startOpacity: 0.3,
endOpacity: 0,
},
},
borrow: {
stroke: '#10B981',
gradient: {
start: '#10B981',
startOpacity: 0.3,
endOpacity: 0,
},
},
apyAtTarget: {
stroke: '#F59E0B',
gradient: {
start: '#F59E0B',
startOpacity: 0.3,
endOpacity: 0,
},
},
} as const;
type ChartColorConfig = {
stroke: string;
gradient: {
start: string;
startOpacity: number;
endOpacity: number;
};
};

type ChartPaletteConfig = {
supply: ChartColorConfig;
borrow: ChartColorConfig;
apyAtTarget: ChartColorConfig;
risk: ChartColorConfig;
pie: readonly string[];
};

export const PIE_COLORS = [
'#3B82F6', // Blue
'#10B981', // Green
'#F59E0B', // Amber
'#8B5CF6', // Violet
'#EC4899', // Pink
'#06B6D4', // Cyan
'#84CC16', // Lime
'#F97316', // Orange
'#6366F1', // Indigo
'#64748B', // Slate (for "Other")
] as const;

export const RISK_COLORS = {
stroke: '#EF4444',
const createColorConfig = (color: string): ChartColorConfig => ({
stroke: color,
gradient: {
start: '#EF4444',
start: color,
startOpacity: 0.3,
endOpacity: 0,
},
});

export const CHART_PALETTES: Record<ChartPaletteName, ChartPaletteConfig> = {
// Classic: Industry standard (Tableau 10)
classic: {
supply: createColorConfig('#4E79A7'), // Blue
borrow: createColorConfig('#59A14F'), // Green
apyAtTarget: createColorConfig('#EDC948'), // Yellow
risk: createColorConfig('#E15759'), // Red
pie: [
'#4E79A7', // Blue
'#59A14F', // Green
'#EDC948', // Yellow
'#B07AA1', // Purple
'#76B7B2', // Teal
'#FF9DA7', // Pink
'#F28E2B', // Orange
'#9C755F', // Brown
'#BAB0AC', // Gray
'#64748B', // Slate (for "Other")
],
},

// Earth: Warm terracotta and earth tones
earth: {
supply: createColorConfig('#B26333'), // Burnt sienna
borrow: createColorConfig('#89392D'), // Rust/terracotta
apyAtTarget: createColorConfig('#A48A7A'), // Taupe
risk: createColorConfig('#411E1D'), // Dark maroon
pie: [
'#B26333', // Burnt sienna
'#89392D', // Rust
'#A48A7A', // Taupe
'#411E1D', // Dark maroon
'#EEEBEA', // Light cream
'#8B5A2B', // Saddle brown
'#CD853F', // Peru
'#D2691E', // Chocolate
'#A0522D', // Sienna
'#64748B', // Slate (for "Other")
],
},

// Forest: Sage and olive tones
forest: {
supply: createColorConfig('#223A30'), // Dark forest green
borrow: createColorConfig('#8DA99D'), // Sage green
apyAtTarget: createColorConfig('#727472'), // Medium gray
risk: createColorConfig('#7A7C7B'), // Gray
pie: [
'#223A30', // Dark forest
'#8DA99D', // Sage
'#727472', // Medium gray
'#7A7C7B', // Gray
'#DFDDDA', // Light cream
'#2F4F4F', // Dark slate gray
'#556B2F', // Dark olive
'#6B8E23', // Olive drab
'#4A5D23', // Army green
'#64748B', // Slate (for "Other")
],
},

// Simple: Pure primary colors
simple: {
supply: createColorConfig('#2563EB'), // Blue-600
borrow: createColorConfig('#16A34A'), // Green-600
apyAtTarget: createColorConfig('#EAB308'), // Yellow-500
risk: createColorConfig('#DC2626'), // Red-600
pie: [
'#2563EB', // Blue
'#16A34A', // Green
'#EAB308', // Yellow
'#DC2626', // Red
'#8B5CF6', // Purple
'#EC4899', // Pink
'#F97316', // Orange
'#6B7280', // Gray
'#14B8A6', // Teal
'#64748B', // Slate (for "Other")
],
},
} as const;

export const PALETTE_META: Record<ChartPaletteName, { name: string; description: string }> = {
classic: { name: 'Classic', description: 'Industry standard (Tableau)' },
earth: { name: 'Earth', description: 'Warm terracotta tones' },
forest: { name: 'Forest', description: 'Sage and olive tones' },
simple: { name: 'Simple', description: 'Pure primary colors' },
};

// Backwards compatibility exports
export const CHART_COLORS = CHART_PALETTES.classic;
export const PIE_COLORS = CHART_PALETTES.classic.pie;
export const RISK_COLORS = CHART_PALETTES.classic.risk;

/**
* Hook to get chart colors based on user's palette preference.
*/
export function useChartColors(): ChartPaletteConfig {
const { palette } = useChartPalette();
return CHART_PALETTES[palette];
}

/**
* Get chart colors for a specific palette (non-reactive).
*/
export function getChartColors(palette: ChartPaletteName): ChartPaletteConfig {
return CHART_PALETTES[palette];
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend } from 'recha
import { Card } from '@/components/ui/card';
import { Spinner } from '@/components/ui/spinner';
import { TokenIcon } from '@/components/shared/token-icon';
import { PIE_COLORS } from '@/constants/chartColors';
import { useChartColors } from '@/constants/chartColors';
import { useVaultRegistry } from '@/contexts/VaultRegistryContext';
import { useAllMarketBorrowers } from '@/hooks/useAllMarketPositions';
import { formatSimple } from '@/utils/balance';
Expand Down Expand Up @@ -39,6 +39,7 @@ export function BorrowersPieChart({ chainId, market, oraclePrice }: BorrowersPie
const { data: borrowers, isLoading, totalCount } = useAllMarketBorrowers(market.uniqueKey, chainId);
const { getVaultByAddress } = useVaultRegistry();
const [expandedOther, setExpandedOther] = useState(false);
const chartColors = useChartColors();

// Helper to get display name for an address (vault name or shortened address)
const getDisplayName = (address: string): string => {
Expand Down Expand Up @@ -246,7 +247,7 @@ export function BorrowersPieChart({ chainId, market, oraclePrice }: BorrowersPie
{pieData.map((entry, index) => (
<Cell
key={`cell-${entry.address}`}
fill={entry.isOther ? OTHER_COLOR : PIE_COLORS[index % PIE_COLORS.length]}
fill={entry.isOther ? OTHER_COLOR : chartColors.pie[index % chartColors.pie.length]}
stroke="var(--color-border)"
strokeWidth={1}
/>
Expand Down
28 changes: 27 additions & 1 deletion src/features/market-detail/components/charts/chart-utils.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Dispatch, SetStateAction } from 'react';
import { CHART_COLORS } from '@/constants/chartColors';
import { CHART_COLORS, useChartColors } from '@/constants/chartColors';

export const TIMEFRAME_LABELS: Record<string, string> = {
'1d': '1D',
Expand Down Expand Up @@ -42,6 +42,7 @@ export function ChartGradients({ prefix, gradients }: { prefix: string; gradient
);
}

// Static gradient configs for backwards compatibility
export const RATE_CHART_GRADIENTS: GradientConfig[] = [
{ id: 'supplyGradient', color: CHART_COLORS.supply.stroke },
{ id: 'borrowGradient', color: CHART_COLORS.borrow.stroke },
Expand All @@ -54,6 +55,31 @@ export const VOLUME_CHART_GRADIENTS: GradientConfig[] = [
{ id: 'liquidityGradient', color: CHART_COLORS.apyAtTarget.stroke },
];

// Dynamic gradient config builders
export function createRateChartGradients(colors: ReturnType<typeof useChartColors>): GradientConfig[] {
return [
{ id: 'supplyGradient', color: colors.supply.stroke },
{ id: 'borrowGradient', color: colors.borrow.stroke },
{ id: 'targetGradient', color: colors.apyAtTarget.stroke },
];
}

export function createVolumeChartGradients(colors: ReturnType<typeof useChartColors>): GradientConfig[] {
return [
{ id: 'supplyGradient', color: colors.supply.stroke },
{ id: 'borrowGradient', color: colors.borrow.stroke },
{ id: 'liquidityGradient', color: colors.apyAtTarget.stroke },
];
}

export function createRiskChartGradients(colors: ReturnType<typeof useChartColors>): GradientConfig[] {
return [{ id: 'riskGradient', color: colors.apyAtTarget.stroke }];
}

export function createConcentrationGradient(color: string): GradientConfig[] {
return [{ id: 'concentrationGradient', color }];
}

type ChartTooltipContentProps = {
active?: boolean;
payload?: any[];
Expand Down
Loading