diff --git a/frontend/src/components/RatingBadge.tsx b/frontend/src/components/RatingBadge.tsx index b1ec522..54c4bd0 100644 --- a/frontend/src/components/RatingBadge.tsx +++ b/frontend/src/components/RatingBadge.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Box, Typography, useTheme } from '@mui/material'; +import { Box, Typography, alpha, useTheme } from '@mui/material'; interface RatingBadgeProps { score: number; @@ -7,46 +7,74 @@ interface RatingBadgeProps { sx?: any; } +const sizeMap = { + small: { + px: 1, + py: 0.25, + score: '0.75rem', + suffix: '0.55rem', + gap: 0.25, + }, + medium: { + px: 1.25, + py: 0.4, + score: '0.95rem', + suffix: '0.625rem', + gap: 0.35, + }, + large: { + px: 1.75, + py: 0.6, + score: '1.4rem', + suffix: '0.7rem', + gap: 0.5, + }, +} as const; + const RatingBadge: React.FC = ({ score, size = 'medium', sx }) => { const theme = useTheme(); + const dims = sizeMap[size]; const isLarge = size === 'large'; - const isSmall = size === 'small'; + const numeric = typeof score === 'number'; + const display = numeric ? (isLarge ? score.toFixed(1) : score.toFixed(0)) : score; return ( - {typeof score === 'number' ? (isLarge ? score.toFixed(1) : score.toFixed(0)) : score} + {display} / 10 diff --git a/frontend/src/components/StatusBadge.tsx b/frontend/src/components/StatusBadge.tsx index 9d3dcbc..8840863 100644 --- a/frontend/src/components/StatusBadge.tsx +++ b/frontend/src/components/StatusBadge.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Box, Typography, alpha, useTheme } from '@mui/material'; -import HistoryIcon from '@mui/icons-material/History'; -import BlockIcon from '@mui/icons-material/Block'; +import { useTranslation } from 'react-i18next'; interface StatusBadgeProps { isLegacy?: boolean; @@ -10,6 +9,11 @@ interface StatusBadgeProps { sx?: any; } +const sizeMap = { + small: { px: 0.85, py: 0.3, dot: 5, font: '0.55rem', gap: 0.5 }, + medium: { px: 1.1, py: 0.45, dot: 6, font: '0.65rem', gap: 0.6 }, +} as const; + const StatusBadge: React.FC = ({ isLegacy, isAvailable, @@ -17,41 +21,57 @@ const StatusBadge: React.FC = ({ sx, }) => { const theme = useTheme(); - const isSmall = size === 'small'; + const { t } = useTranslation(); if (isAvailable && !isLegacy) return null; - const label = isLegacy ? 'Limited' : 'Out of Stock'; - const color = isLegacy ? theme.palette.warning.main : theme.palette.error.main; - const Icon = isLegacy ? HistoryIcon : BlockIcon; + const dims = sizeMap[size]; + const accent = isLegacy ? theme.palette.warning.main : theme.palette.error.main; + const label = isLegacy + ? t('status.limited', { defaultValue: 'Limited' }) + : t('status.outOfStock', { defaultValue: 'Out of Stock' }); + + const isLight = theme.palette.mode === 'light'; + const surface = alpha(theme.palette.background.paper, isLight ? 0.85 : 0.55); return ( - - + {label} diff --git a/frontend/src/components/ui/FlavorCard.tsx b/frontend/src/components/ui/FlavorCard.tsx index 9ad71b0..9d037f4 100644 --- a/frontend/src/components/ui/FlavorCard.tsx +++ b/frontend/src/components/ui/FlavorCard.tsx @@ -2,6 +2,7 @@ import { Link } from 'react-router-dom'; import Box from '@mui/material/Box'; import CardContent from '@mui/material/CardContent'; import Typography from '@mui/material/Typography'; +import { alpha, useTheme } from '@mui/material/styles'; import { GlassCard } from './Glass'; import RatingBadge from '../RatingBadge'; import StatusBadge from '../StatusBadge'; @@ -28,92 +29,193 @@ export const FlavorCard = ({ caption, showCategory = true, compact = false, -}: FlavorCardProps) => ( - - theme.tokens.elevation.md, - }, - }} - > - - - +}: FlavorCardProps) => { + const theme = useTheme(); + const hasRating = typeof flavor.average_rating === 'number' && flavor.average_rating > 0; - {flavor.image_url && ( + return ( + + + {flavor.image_url ? ( + + ) : ( + + + {flavor.name.charAt(0).toUpperCase()} + + + )} + + {/* Bottom fade for legibility of overlays */} - - )} - - - {flavor.name} - + {/* Status badge — top-right */} + + + - {showCategory && flavor.category_name && ( - - {flavor.category_name} - - )} - - - - {caption && ( - - {caption} - + {/* Category chip — bottom-left */} + {showCategory && flavor.category_name && ( + + + {flavor.category_name} + + )} - - - -); + + + + {flavor.name} + + + + {hasRating ? ( + + ) : ( + + — + + )} + {caption && ( + + {caption} + + )} + + + + + ); +}; diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 7eb82fe..4597e25 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -135,6 +135,10 @@ const resources = { save: 'Save', cancel: 'Cancel', }, + status: { + limited: 'Limited', + outOfStock: 'Out of Stock', + }, flavorDetail: { rateThisFlavor: 'Rate this flavor', scoreLabel: 'Your score, 1 to 10', @@ -433,6 +437,10 @@ const resources = { save: 'Speichern', cancel: 'Abbrechen', }, + status: { + limited: 'Limitiert', + outOfStock: 'Ausverkauft', + }, flavorDetail: { rateThisFlavor: 'Diese Sorte bewerten', scoreLabel: 'Deine Bewertung, 1 bis 10',