refactor: replace hardcoded hex colors with design tokens across priority components
- Add DS_COLORS.heat, DS_COLORS.futureCard, INQUIRY_STATUS_META tokens to ds.ts - Eliminate 4 duplicate RESULT_TYPE_META / TYPE_META constants (now all use ds.ts) - Eliminate 3 duplicate scoreColor / SCORE_COLOR functions (now all use matchScoreHex) - HeatBadge: use DS_COLORS.heat.VERY_HOT / HOT tokens - FutureAvailabilityCard: use DS_COLORS.futureCard.controlled / signal tokens - IntelligenceMatchCard: import RESULT_TYPE_META from ds.ts, remove primary override - MatchScoreDisplay: replace local scoreColor() with matchScoreHex() - CompareColumnHeader: remove local TYPE_META + SCORE_COLOR, use ds.ts + utils.ts - CompareTableBody: import RESULT_TYPE_META + matchScoreHex, fix #7c3aed → token - compareUtils: remove TYPE_META + SCORE_COLOR, use dataQualityHex in scoreBar - Anfragen + AnfragenInquiryItem: deduplicate STATUS_CONFIG → INQUIRY_STATUS_META - Anfragen: #1e3a5f → 'primary.main', KI alert → futureCard.controlled tokens - AnfragenMessageBubble: own-message bubble → 'primary.main', AI bubble → tokens - ScoreBreakdownPanel: #1e3a5f → 'primary.main', #6d28d9 → futureCard.controlled - ESLint: add warn rule against new hex colors in component sx props Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { useNavigate } from 'react-router'
|
||||
import { getScoreTier, SCORE_THEME } from '../shared/scoreTheme'
|
||||
import { floorLabel, SOURCE_META, ASSET_TYPE_LABELS, getOpportunityHeadline } from './futureAvailabilityCardHelpers'
|
||||
import { SignalQualityDots } from './SignalQualityDots'
|
||||
import { DS_COLORS } from '../../lib/ds'
|
||||
import type { MatchCardViewModel } from './MatchCardViewModel'
|
||||
|
||||
// ── Future Availability Card ──────────────────────────────────────────────────
|
||||
@@ -12,13 +13,12 @@ export function FutureAvailabilityCard({ vm }: { vm: MatchCardViewModel }) {
|
||||
const isControlled = vm.signalIsControlled ?? false
|
||||
const navigate = useNavigate()
|
||||
|
||||
// PRE-MARKET VERIFIED: soft purple / institutional premium
|
||||
// MARKET SIGNAL: slate blue / analytical
|
||||
const accentColor = isControlled ? '#7c3aed' : '#1d4ed8'
|
||||
const headerBg = isControlled ? '#faf5ff' : '#eff6ff'
|
||||
const borderColor = isControlled ? '#e9d5ff' : '#bfdbfe'
|
||||
const badgeBg = isControlled ? '#ede9fe' : '#dbeafe'
|
||||
const badgeColor = isControlled ? '#5b21b6' : '#1e40af'
|
||||
const cardColors = isControlled ? DS_COLORS.futureCard.controlled : DS_COLORS.futureCard.signal
|
||||
const accentColor = cardColors.accent
|
||||
const headerBg = cardColors.headerBg
|
||||
const borderColor = cardColors.border
|
||||
const badgeBg = cardColors.badgeBg
|
||||
const badgeColor = cardColors.badgeText
|
||||
|
||||
const assetLabel = vm.assetType ? (ASSET_TYPE_LABELS[vm.assetType] ?? vm.assetType) : 'Fläche'
|
||||
const headline = getOpportunityHeadline(vm.signalType, assetLabel)
|
||||
@@ -168,7 +168,7 @@ export function FutureAvailabilityCard({ vm }: { vm: MatchCardViewModel }) {
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.35 }}>
|
||||
{(vm.signalMarketIndicators ?? []).slice(0, 3).map((ind, i) => (
|
||||
<Box key={i} sx={{ display: 'flex', alignItems: 'flex-start', gap: 0.6 }}>
|
||||
<Box sx={{ width: 4, height: 4, borderRadius: '50%', bgcolor: '#1d4ed8', mt: '5px', flexShrink: 0 }} />
|
||||
<Box sx={{ width: 4, height: 4, borderRadius: '50%', bgcolor: DS_COLORS.futureCard.signal.indicator, mt: '5px', flexShrink: 0 }} />
|
||||
<Typography sx={{ fontSize: '0.72rem', color: '#475569', lineHeight: 1.3 }}>{ind}</Typography>
|
||||
</Box>
|
||||
))}
|
||||
@@ -210,7 +210,7 @@ export function FutureAvailabilityCard({ vm }: { vm: MatchCardViewModel }) {
|
||||
textTransform: 'none', fontSize: '0.7rem', py: 0.375, px: 1,
|
||||
...(a.variant === 'primary' && {
|
||||
bgcolor: accentColor,
|
||||
'&:hover': { bgcolor: isControlled ? '#6d28d9' : '#1e40af' },
|
||||
'&:hover': { bgcolor: cardColors.accentHover },
|
||||
}),
|
||||
}}
|
||||
>
|
||||
@@ -224,7 +224,9 @@ export function FutureAvailabilityCard({ vm }: { vm: MatchCardViewModel }) {
|
||||
onClick={() => navigate(`/demand/property/${vm.propertyId}${vm.unitId ? `?unit=${vm.unitId}` : ''}`)}
|
||||
sx={{
|
||||
textTransform: 'none', fontSize: '0.7rem', py: 0.375, px: 1,
|
||||
bgcolor: '#7c3aed', '&:hover': { bgcolor: '#6d28d9' }, ml: 'auto',
|
||||
bgcolor: DS_COLORS.futureCard.controlled.accent,
|
||||
'&:hover': { bgcolor: DS_COLORS.futureCard.controlled.accentHover },
|
||||
ml: 'auto',
|
||||
}}
|
||||
>
|
||||
Zur Einheit →
|
||||
|
||||
@@ -7,18 +7,10 @@ import { useSessionStore } from '../../stores/sessionStore'
|
||||
import { LocationPreview } from '../shared/LocationPreview'
|
||||
import { HeatBadge } from '../shared/HeatBadge'
|
||||
import { getScoreTier, SCORE_THEME } from '../shared/scoreTheme'
|
||||
import { RESULT_TYPE_META } from '../../lib/ds'
|
||||
import type { MatchCardViewModel } from './MatchCardViewModel'
|
||||
import { FutureAvailabilityCard } from './FutureAvailabilityCard'
|
||||
|
||||
// ── Constants ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const RESULT_TYPE_META: Record<string, { label: string; color: string }> = {
|
||||
VERIFIED_PORTFOLIO: { label: 'Plattform', color: '#1e3a5f' },
|
||||
EXTERNAL_MARKET: { label: 'Plattform', color: '#1e3a5f' },
|
||||
MAISON_WORK: { label: 'Maison Work', color: '#0369a1' },
|
||||
FUTURE_AVAILABILITY: { label: 'Future Availability', color: '#7c3aed' },
|
||||
}
|
||||
|
||||
// ── Main component ────────────────────────────────────────────────────────────
|
||||
|
||||
interface Props {
|
||||
@@ -163,10 +155,6 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel }: Pro
|
||||
disabled={a.disabled}
|
||||
sx={{
|
||||
textTransform: 'none', fontSize: '0.7rem', py: 0.375, px: 1,
|
||||
...(a.variant === 'primary' && {
|
||||
bgcolor: '#1e3a5f',
|
||||
'&:hover': { bgcolor: '#162d4a' },
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{a.label}
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
import { Box, Typography } from '@mui/material'
|
||||
import { matchScoreHex } from '../../lib/utils'
|
||||
|
||||
interface Props {
|
||||
score: number
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
}
|
||||
|
||||
function scoreColor(score: number): string {
|
||||
if (score >= 78) return '#1a7a4a'
|
||||
if (score >= 52) return '#d97706'
|
||||
return '#c0392b'
|
||||
}
|
||||
|
||||
const FONT_SIZES: Record<string, string> = { sm: '1.25rem', md: '1.75rem', lg: '2.5rem' }
|
||||
|
||||
export function MatchScoreDisplay({ score, size = 'md' }: Props) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 0.25, flexShrink: 0 }}>
|
||||
<Typography sx={{ fontWeight: 800, fontSize: FONT_SIZES[size], color: scoreColor(score), lineHeight: 1 }}>
|
||||
<Typography sx={{ fontWeight: 800, fontSize: FONT_SIZES[size], color: matchScoreHex(score), lineHeight: 1 }}>
|
||||
{score}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">/100</Typography>
|
||||
|
||||
Reference in New Issue
Block a user