Skip to content

Commit d469119

Browse files
committed
chore: refactored tabbed info box and seperated sections into their own components
1 parent 9a1324d commit d469119

File tree

9 files changed

+733
-572
lines changed

9 files changed

+733
-572
lines changed

src/components/LikeButton.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ export default function LikeButton({
3232
disabled ? 'cursor-not-allowed opacity-50' : ''
3333
}`}
3434
style={{
35-
backgroundColor: '#f1f5fb',
36-
color: isLiked ? colours.status.success.text : colours.text.primary
35+
backgroundColor: isLiked ? colours.status.success.background: colours.button.primary.background,
36+
color: colours.button.primary.text
3737
}}
3838
>
39-
<ThumbsUp size={iconSize} className={isLiked ? 'fill-current' : ''} />
39+
<ThumbsUp size={iconSize}/>
4040
<span>{likeCount}</span>
4141
</button>
4242
);

src/components/TabbedInfoBox.tsx

Lines changed: 61 additions & 568 deletions
Large diffs are not rendered by default.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import React from "react";
2+
import { Product } from "@/types/product";
3+
import { colours } from "@/styles/colours";
4+
5+
interface BrandStats {
6+
price: number;
7+
quality: number;
8+
nutrition: number;
9+
sustainability: number;
10+
overallScore: number;
11+
productCount: number;
12+
}
13+
14+
interface BrandTabContentProps {
15+
product: Product;
16+
animatedBrand: number;
17+
calculateBrandStats: BrandStats | null;
18+
}
19+
20+
const BrandTabContent: React.FC<BrandTabContentProps> = ({
21+
product,
22+
animatedBrand,
23+
calculateBrandStats,
24+
}) => {
25+
return (
26+
<div className="w-full flex flex-col opacity-0 animate-fade-in" style={{ animationDelay: '0.05s' }}>
27+
<h2
28+
className="text-lg font-bold mb-4 self-start"
29+
style={{ color: colours.text.primary }}
30+
>
31+
Brand Performance
32+
</h2>
33+
<div className="flex items-center gap-6">
34+
{/* Brand Score Circle - Smaller and on the left */}
35+
<div className="flex flex-col items-center">
36+
<span className="relative inline-block w-16 h-16 align-middle">
37+
<svg width="64" height="64" viewBox="0 0 64 64" className="absolute top-0 left-0" style={{ zIndex: 1 }}>
38+
<circle
39+
cx="32" cy="32" r="28"
40+
fill="none"
41+
stroke={colours.chart.primary}
42+
strokeWidth="6"
43+
strokeDasharray={Math.PI * 2 * 28}
44+
strokeDashoffset={Math.PI * 2 * 28 * (1 - (animatedBrand / 5))}
45+
strokeLinecap="round"
46+
style={{
47+
transition: 'stroke-dashoffset 0.7s cubic-bezier(0.4,0,0.2,1)',
48+
transform: 'rotate(-90deg)',
49+
transformOrigin: 'center center',
50+
}}
51+
/>
52+
</svg>
53+
<div className="absolute inset-0 flex flex-col items-center justify-center">
54+
<span className="text-2xl">🏢</span>
55+
<span
56+
className="text-lg font-bold"
57+
style={{ color: colours.text.primary }}
58+
>
59+
{animatedBrand}
60+
</span>
61+
</div>
62+
</span>
63+
<span
64+
className="text-xs font-medium mt-1 text-center"
65+
style={{ color: colours.text.secondary }}
66+
>
67+
Brand Score
68+
</span>
69+
</div>
70+
71+
{/* Brand Stats Bar Graph - On the right */}
72+
<div className="flex-1">
73+
<div
74+
className="text-sm font-medium mb-3"
75+
style={{ color: colours.text.primary }}
76+
>
77+
Average Performance
78+
</div>
79+
<div className="space-y-3">
80+
{calculateBrandStats ? [
81+
{ label: 'Price', value: calculateBrandStats.price, color: '#ECCC36' }, // colourMap.yellow for price
82+
{ label: 'Quality', value: calculateBrandStats.quality, color: '#D24330' }, // colourMap.red for quality
83+
{ label: 'Nutrition', value: calculateBrandStats.nutrition, color: '#3b82f6' }, // blue for nutrition
84+
{ label: 'Sustainability', value: calculateBrandStats.sustainability, color: '#309563' } // colourMap.green for sustainability
85+
].map((stat, index) => (
86+
<div key={stat.label} className="flex items-center gap-3">
87+
<span
88+
className="text-xs font-medium w-20 text-right"
89+
style={{ color: colours.text.secondary }}
90+
>
91+
{stat.label}
92+
</span>
93+
<div className="flex-1 max-w-24">
94+
<div
95+
className="flex items-center h-4 rounded-full overflow-hidden"
96+
style={{ backgroundColor: colours.content.surfaceSecondary }}
97+
>
98+
<div
99+
className="h-full rounded-full transition-all duration-1000 ease-out opacity-0 animate-fade-in"
100+
style={{
101+
width: `${(stat.value / 5) * 100}%`,
102+
backgroundColor: stat.color,
103+
animationDelay: `${0.3 + index * 0.1}s`
104+
}}
105+
/>
106+
</div>
107+
</div>
108+
<span
109+
className="text-xs font-medium w-8"
110+
style={{ color: colours.text.primary }}
111+
>
112+
{stat.value.toFixed(1)}
113+
</span>
114+
</div>
115+
)) : (
116+
<div
117+
className="text-sm text-center py-4"
118+
style={{ color: colours.text.secondary }}
119+
>
120+
Not enough brand data available
121+
</div>
122+
)}
123+
</div>
124+
{calculateBrandStats && (
125+
<div
126+
className="text-xs mt-3 text-center"
127+
style={{ color: colours.text.secondary }}
128+
>
129+
Based on {calculateBrandStats.productCount} products
130+
</div>
131+
)}
132+
</div>
133+
</div>
134+
</div>
135+
);
136+
};
137+
138+
export default BrandTabContent;
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import React from "react";
2+
import { Product } from "@/types/product";
3+
import { colours } from "@/styles/colours";
4+
5+
interface NutritionTabContentProps {
6+
product: Product;
7+
animatedNutrition: number;
8+
}
9+
10+
const NutritionTabContent: React.FC<NutritionTabContentProps> = ({
11+
product,
12+
animatedNutrition,
13+
}) => {
14+
return (
15+
<div className="w-full flex flex-col items-center opacity-0 animate-fade-in" style={{ animationDelay: '0.05s' }}>
16+
<h2
17+
className="text-lg font-bold mb-2 self-start"
18+
style={{ color: colours.text.primary }}
19+
>
20+
Nutrition
21+
</h2>
22+
<div className="flex flex-col items-center justify-center gap-4 w-full">
23+
{/* Nutrition Grade Circle */}
24+
<div className="flex flex-col items-center gap-2">
25+
<span className="relative inline-block w-24 h-24 align-middle">
26+
<svg width="96" height="96" viewBox="0 0 96 96" className="absolute top-0 left-0" style={{ zIndex: 1 }}>
27+
<circle
28+
cx="48" cy="48" r="40"
29+
fill="none"
30+
stroke={(() => {
31+
const score = animatedNutrition;
32+
if (score <= 2) return colours.score.low;
33+
if (score <= 3) return colours.score.medium;
34+
return colours.score.high;
35+
})()}
36+
strokeWidth="8"
37+
strokeDasharray={Math.PI * 2 * 40}
38+
strokeDashoffset={Math.PI * 2 * 40 * (1 - (animatedNutrition / 5))}
39+
strokeLinecap="round"
40+
style={{
41+
transition: 'stroke-dashoffset 0.7s cubic-bezier(0.4,0,0.2,1), stroke 0.7s cubic-bezier(0.4,0,0.2,1)',
42+
transform: 'rotate(-90deg)',
43+
transformOrigin: 'center center',
44+
}}
45+
/>
46+
</svg>
47+
<div className="absolute inset-0 flex flex-col items-center justify-center">
48+
<span
49+
className="text-4xl font-bold"
50+
style={{
51+
color: (() => {
52+
const score = animatedNutrition;
53+
if (score <= 2) return colours.score.low;
54+
if (score <= 3) return colours.score.medium;
55+
return colours.score.high;
56+
})()
57+
}}
58+
>
59+
{product.combinedNutritionGrade?.toUpperCase() || '?'}
60+
</span>
61+
</div>
62+
</span>
63+
<span
64+
className="text-sm font-medium"
65+
style={{ color: colours.text.secondary }}
66+
>
67+
Nutrition Grade
68+
</span>
69+
</div>
70+
71+
{/* Nutrition Macros per 100g */}
72+
{product.nutritionMacros && Object.values(product.nutritionMacros).some(value => value !== undefined) && (
73+
<div className="w-full mt-4">
74+
<h3
75+
className="text-md font-semibold mb-3 text-center"
76+
style={{ color: colours.text.primary }}
77+
>
78+
Nutrition Facts (per 100g)
79+
</h3>
80+
<div className="grid grid-cols-2 gap-3 w-full max-w-sm mx-auto">
81+
{product.nutritionMacros.energy !== undefined && (
82+
<div className="flex justify-between items-center p-2 rounded-lg" style={{ backgroundColor: colours.content.surfaceSecondary }}>
83+
<span className="text-sm font-medium" style={{ color: colours.text.secondary }}>Energy</span>
84+
<span className="text-sm font-bold" style={{ color: colours.text.primary }}>{product.nutritionMacros.energy} kcal</span>
85+
</div>
86+
)}
87+
{product.nutritionMacros.proteins !== undefined && (
88+
<div className="flex justify-between items-center p-2 rounded-lg" style={{ backgroundColor: colours.content.surfaceSecondary }}>
89+
<span className="text-sm font-medium" style={{ color: colours.text.secondary }}>Protein</span>
90+
<span className="text-sm font-bold" style={{ color: colours.text.primary }}>{product.nutritionMacros.proteins}g</span>
91+
</div>
92+
)}
93+
{product.nutritionMacros.carbohydrates !== undefined && (
94+
<div className="flex justify-between items-center p-2 rounded-lg" style={{ backgroundColor: colours.content.surfaceSecondary }}>
95+
<span className="text-sm font-medium" style={{ color: colours.text.secondary }}>Carbs</span>
96+
<span className="text-sm font-bold" style={{ color: colours.text.primary }}>{product.nutritionMacros.carbohydrates}g</span>
97+
</div>
98+
)}
99+
{product.nutritionMacros.sugars !== undefined && (
100+
<div className="flex justify-between items-center p-2 rounded-lg" style={{ backgroundColor: colours.content.surfaceSecondary }}>
101+
<span className="text-sm font-medium" style={{ color: colours.text.secondary }}>Sugars</span>
102+
<span className="text-sm font-bold" style={{ color: colours.text.primary }}>{product.nutritionMacros.sugars}g</span>
103+
</div>
104+
)}
105+
{product.nutritionMacros.fat !== undefined && (
106+
<div className="flex justify-between items-center p-2 rounded-lg" style={{ backgroundColor: colours.content.surfaceSecondary }}>
107+
<span className="text-sm font-medium" style={{ color: colours.text.secondary }}>Fat</span>
108+
<span className="text-sm font-bold" style={{ color: colours.text.primary }}>{product.nutritionMacros.fat}g</span>
109+
</div>
110+
)}
111+
{product.nutritionMacros.saturatedFat !== undefined && (
112+
<div className="flex justify-between items-center p-2 rounded-lg" style={{ backgroundColor: colours.content.surfaceSecondary }}>
113+
<span className="text-sm font-medium" style={{ color: colours.text.secondary }}>Sat. Fat</span>
114+
<span className="text-sm font-bold" style={{ color: colours.text.primary }}>{product.nutritionMacros.saturatedFat}g</span>
115+
</div>
116+
)}
117+
{product.nutritionMacros.fiber !== undefined && (
118+
<div className="flex justify-between items-center p-2 rounded-lg" style={{ backgroundColor: colours.content.surfaceSecondary }}>
119+
<span className="text-sm font-medium" style={{ color: colours.text.secondary }}>Fiber</span>
120+
<span className="text-sm font-bold" style={{ color: colours.text.primary }}>{product.nutritionMacros.fiber}g</span>
121+
</div>
122+
)}
123+
{product.nutritionMacros.sodium !== undefined && (
124+
<div className="flex justify-between items-center p-2 rounded-lg" style={{ backgroundColor: colours.content.surfaceSecondary }}>
125+
<span className="text-sm font-medium" style={{ color: colours.text.secondary }}>Sodium</span>
126+
<span className="text-sm font-bold" style={{ color: colours.text.primary }}>{(product.nutritionMacros.sodium * 1000).toFixed(0)}mg</span>
127+
</div>
128+
)}
129+
</div>
130+
</div>
131+
)}
132+
</div>
133+
</div>
134+
);
135+
};
136+
137+
export default NutritionTabContent;

0 commit comments

Comments
 (0)