added about / redesigned main page

This commit is contained in:
nvrl committed 2026-02-17 02:53:12 +01:00
1 parent e7193c70c2
commit 63753c65fd
5 files changed
+514 -270

No files matched your search

+36 -12
View File
@@ -41,33 +41,36 @@ const FlavorDetail = lazy(() => import('./pages/flavors/FlavorDetail'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const PublicProfile = lazy(() => import('./pages/PublicProfile'));
const CommunityFeed = lazy(() => import('./pages/CommunityFeed'));
const About = lazy(() => import('./pages/About'));
const Login = lazy(() => import('./pages/Login'));
const Settings = lazy(() => import('./pages/Settings'));
interface SearchFlavor {
interface SearchResult {
id: number;
name: string;
type: 'flavor' | 'category' | 'user';
subtitle: string;
image_url: string | null;
category_name: string;
slug: string | null;
}
const GlobalSearch = () => {
const [query, setQuery] = useState('');
const [options, setOptions] = useState<SearchFlavor[]>([]);
const [options, setOptions] = useState<SearchResult[]>([]);
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
const fetchFlavors = async () => {
const fetchSearchOptions = async () => {
try {
// Fetch from unpaginated search endpoint for full catalog coverage
// Fetch unified results: flavors, categories, and users
const res = await api.get('flavors/search/');
setOptions(res.data);
} catch (err) {
console.error('Failed to fetch search options');
}
};
fetchFlavors();
fetchSearchOptions();
}, []); // Only fetch once to avoid excessive API calls
useEffect(() => {
@@ -93,13 +96,15 @@ const GlobalSearch = () => {
options={options}
getOptionLabel={(option) => {
if (typeof option === 'string') return option;
return option.category_name ? `${option.name} (${option.category_name})` : (option.name || '');
return option.name || '';
}}
inputValue={query}
onInputChange={(_, newValue) => setQuery(typeof newValue === 'string' ? newValue : '')}
onChange={(_, newValue) => {
if (newValue && typeof newValue !== 'string') {
navigate(`/flavor/${newValue.id}`);
if (newValue.type === 'flavor') navigate(`/flavor/${newValue.id}`);
if (newValue.type === 'category') navigate(`/category/${newValue.slug}`);
if (newValue.type === 'user') navigate(`/profile/${newValue.slug}`);
}
}}
onKeyDown={(e) => {
@@ -111,7 +116,7 @@ const GlobalSearch = () => {
<TextField
{...params}
fullWidth
placeholder="Search flavors..."
placeholder="Search..."
sx={{
bgcolor: 'action.hover',
borderRadius: 2,
@@ -134,12 +139,16 @@ const GlobalSearch = () => {
const { key, ...optionProps } = props as any;
return (
<Box component="li" key={key} {...optionProps} sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
{option.image_url && (
<Box component="img" src={option.image_url} sx={{ width: 32, height: 32, objectFit: 'contain' }} />
{option.image_url ? (
<Avatar src={option.image_url} sx={{ width: 32, height: 32, borderRadius: 1 }} />
) : (
<Avatar sx={{ width: 32, height: 32, borderRadius: 1, bgcolor: 'primary.main', fontSize: '0.8rem' }}>
{option.name.charAt(0).toUpperCase()}
</Avatar>
)}
<Box>
<Typography variant="body2">{option.name}</Typography>
<Typography variant="caption" color="text.secondary">{option.category_name}</Typography>
<Typography variant="caption" color="text.secondary">{option.subtitle}</Typography>
</Box>
</Box>
);
@@ -243,6 +252,12 @@ const App: React.FC = () => {
<ListItemText primary="Community" />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton component={Link} to="/about" onClick={() => setDrawerOpen(false)}>
<ListItemText primary="About" />
</ListItemButton>
</ListItem>
<ListItemButton onClick={() => setCatOpen(!catOpen)}>
<ListItemText primary="Categories" />
@@ -343,6 +358,14 @@ const App: React.FC = () => {
>
Community
</Button>
<Button
color="inherit"
component={Link}
to="/about"
sx={{ fontWeight: 'bold', textTransform: 'none', fontSize: '1rem', mr: 2 }}
>
About
</Button>
<Button
color="inherit"
endIcon={<ArrowDropDownIcon />}
@@ -433,6 +456,7 @@ const App: React.FC = () => {
<Route path="/flavor/:id" element={<FlavorDetail />} />
<Route path="/profile/:username" element={<PublicProfile />} />
<Route path="/community" element={<CommunityFeed />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings themeName={themeName} onThemeChange={handleThemeChange} />} />
<Route path="/login" element={<Login />} />
+91
View File
@@ -0,0 +1,91 @@
import React from 'react';
import { Typography, Box, Container, Paper, Divider, Button } from '@mui/material';
import { Link } from 'react-router-dom';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { useTitle } from '../hooks/useTitle';
const About: React.FC = () => {
useTitle('About & Impressum');
return (
<Container maxWidth="md" sx={{ py: 8 }}>
<Button
variant="outlined"
component={Link}
to="/"
startIcon={<ArrowBackIcon />}
sx={{ mb: 4, textTransform: 'none', borderRadius: 2 }}
>
Back to Archive
</Button>
<Paper sx={{ p: { xs: 3, md: 6 }, borderRadius: 4 }}>
<Typography variant="h3" gutterBottom sx={{ fontWeight: 'bold' }}>About This Project</Typography>
<Typography variant="body1" paragraph sx={{ fontSize: '1.1rem', lineHeight: 1.8 }}>
<strong>Holy Flavors Archive</strong> is a community-driven project dedicated to documenting and rating
the vast world of Holy Energy products. Our goal is to create a definitive, high-density catalog of every
flavor ever released—including legacy and limited editions.
</Typography>
<Box sx={{ my: 4 }}>
<Typography variant="h5" gutterBottom sx={{ fontWeight: 'bold' }}>What you can do here:</Typography>
<Typography component="ul" sx={{ lineHeight: 2 }}>
<li><strong>Explore:</strong> Find details on any Holy Energy, Iced Tea, or Milkshake product.</li>
<li><strong>Rate:</strong> Share your honest opinion and score flavors from 1 to 10.</li>
<li><strong>Community:</strong> Follow other fans, see their latest tests, and discover new favorites together.</li>
<li><strong>Taste Profile:</strong> Build your own personal tiered leaderboard to share with the world.</li>
</Typography>
</Box>
<Typography variant="body2" color="text.secondary" paragraph>
This website is a fan project and is not officially affiliated with, authorized, maintained, sponsored,
or endorsed by HOLY Energy GmbH. All product names, logos, and brands are property of their respective owners.
</Typography>
<Divider sx={{ my: 6 }} />
<Typography variant="h4" gutterBottom sx={{ fontWeight: 'bold' }}>Impressum</Typography>
<Typography variant="body2" sx={{ lineHeight: 2 }}>
<strong>Angaben gemäß § 5 TMG:</strong><br />
[Your Name or Project Name]<br />
[Your Street Address]<br />
[Postal Code and City]<br />
Germany
</Typography>
<Box sx={{ mt: 3 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>Kontakt:</Typography>
<Typography variant="body2">
E-Mail: [Your Support Email Address]<br />
Internet: holy.narl.io
</Typography>
</Box>
<Box sx={{ mt: 3 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV:</Typography>
<Typography variant="body2">
[Your Name]<br />
[Your Street Address]<br />
[Postal Code and City]
</Typography>
</Box>
<Box sx={{ mt: 4 }}>
<Typography variant="h6" gutterBottom sx={{ fontWeight: 'bold' }}>Haftungsausschluss (Disclaimer)</Typography>
<Typography variant="caption" color="text.secondary" component="p" sx={{ mb: 2 }}>
<strong>Haftung für Inhalte:</strong> Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte
auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter
jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen.
</Typography>
<Typography variant="caption" color="text.secondary" component="p">
<strong>Urheberrecht:</strong> Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten
unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der
Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors.
</Typography>
</Box>
</Paper>
</Container>
);
};
export default About;
+116 -107
View File
@@ -15,7 +15,9 @@ import {
ListItem,
ListItemAvatar,
ListItemText,
ListItemButton
ListItemButton,
Pagination,
Paper as MuiPaper
} from '@mui/material';
import StarIcon from '@mui/icons-material/Star';
import { Link } from 'react-router-dom';
@@ -48,53 +50,52 @@ const CommunityFeed: React.FC = () => {
const [ratings, setRatings] = useState<FeedRating[]>([]);
const [following, setFollowing] = useState<FollowedUser[]>([]);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [nextPage, setNextPage] = useState<string | null>(null);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const fetchInitialData = async () => {
const fetchFeedData = async (pageNum: number) => {
setLoading(true);
try {
const [feedRes, followingRes] = await Promise.all([
api.get('ratings/feed/'),
api.get(`ratings/feed/?page=${pageNum}`),
api.get('users/following_list/')
]);
const feedData = feedRes.data.results || feedRes.data;
setRatings(Array.isArray(feedData) ? feedData : []);
setNextPage(feedRes.data.next || null);
const feedData = feedRes.data.results || [];
setRatings(feedData);
// Calculate total pages (DRF returns total count)
const count = feedRes.data.count || 0;
setTotalPages(Math.ceil(count / 10)); // 10 is our page_size
setFollowing(followingRes.data);
} catch (err) {
console.error('Failed to fetch initial feed data', err);
console.error('Failed to fetch feed data', err);
} finally {
setLoading(false);
}
};
const handleLoadMore = async () => {
if (!nextPage || loadingMore) return;
setLoadingMore(true);
try {
// nextPage is a full URL from DRF, we can use axios directly or parse it
const res = await api.get(nextPage);
const newData = res.data.results || res.data;
setRatings(prev => [...prev, ...(Array.isArray(newData) ? newData : [])]);
setNextPage(res.data.next || null);
} catch (err) {
console.error('Failed to load more feed items', err);
} finally {
setLoadingMore(false);
}
useEffect(() => {
fetchFeedData(page);
}, [page]);
const handlePageChange = (_: React.ChangeEvent<unknown>, value: number) => {
setPage(value);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
useEffect(() => {
fetchInitialData();
}, []);
if (loading) return <Box sx={{ display: 'flex', justifyContent: 'center', mt: 8 }}><CircularProgress /></Box>;
if (loading && ratings.length === 0) return <Box sx={{ display: 'flex', justifyContent: 'center', mt: 8 }}><CircularProgress /></Box>;
return (
<Container maxWidth="lg" sx={{ py: 4 }}>
<Typography variant="h4" sx={{ mb: 4, fontWeight: 'bold' }}>Community Feed</Typography>
<Box sx={{ mb: 6 }}>
<Typography variant="h3" sx={{ mb: 1, fontWeight: 'bold' }}>Community Activity</Typography>
<Typography variant="h6" color="text.secondary" sx={{ maxWidth: 700, lineHeight: 1.6 }}>
Discover which Holy Energy flavors your circle is currently testing.
See their latest ratings, read their reviews, and find your next favorite together.
</Typography>
</Box>
<Grid container spacing={4}>
{/* Main Feed */}
@@ -111,87 +112,95 @@ const CommunityFeed: React.FC = () => {
</Box>
) : (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{ratings.map(rating => (
<Card key={rating.id} sx={{ borderRadius: 3, transition: 'transform 0.2s', '&:hover': { transform: 'translateY(-2px)' } }}>
<CardContent sx={{ p: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Link to={`/profile/${rating.user}`} style={{ textDecoration: 'none', color: 'inherit', display: 'flex', alignItems: 'center' }}>
<Avatar src={rating.user_avatar || undefined} sx={{ width: 40, height: 40, mr: 2 }}>
{!rating.user_avatar && rating.user.charAt(0).toUpperCase()}
</Avatar>
<Box>
<Typography variant="subtitle1" sx={{ fontWeight: 'bold', '&:hover': { color: 'primary.main' } }}>
{rating.user}
</Typography>
<Typography variant="caption" color="text.secondary">
{new Date(rating.created_at).toLocaleDateString()}
</Typography>
</Box>
</Link>
</Box>
<Divider sx={{ my: 2 }} />
<Box sx={{ display: 'flex', gap: 3, flexDirection: { xs: 'column', sm: 'row' } }}>
<Link to={`/flavor/${rating.flavor}`} style={{ textDecoration: 'none' }}>
<Box sx={{ width: { xs: '100%', sm: 100 }, aspectRatio: '1/1', borderRadius: 2, overflow: 'hidden', border: '1px solid', borderColor: 'divider' }}>
<Box
component="img"
src={rating.flavor_image || undefined}
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</Box>
</Link>
<Box sx={{ flex: 1 }}>
<Link to={`/flavor/${rating.flavor}`} style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography variant="h6" sx={{ fontSize: '1.1rem', mb: 0.5, '&:hover': { color: 'primary.main' } }}>
{rating.flavor_name}
</Typography>
</Link>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1.5 }}>
{rating.category_name}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1.5 }}>
{/* Compact rating for mobile, stars for desktop */}
<Box sx={{ display: { xs: 'flex', sm: 'none' }, alignItems: 'center', gap: 0.5 }}>
<StarIcon sx={{ fontSize: '1rem', color: 'primary.main' }} />
<Typography variant="body2" sx={{ fontWeight: 'bold' }}>{rating.score}/10</Typography>
{loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}><CircularProgress /></Box>
) : (
<>
{ratings.map(rating => (
<Card key={rating.id} sx={{ borderRadius: 3, transition: 'transform 0.2s', '&:hover': { transform: 'translateY(-2px)' } }}>
<CardContent sx={{ p: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Link to={`/profile/${rating.user}`} style={{ textDecoration: 'none', color: 'inherit', display: 'flex', alignItems: 'center' }}>
<Avatar src={rating.user_avatar || undefined} sx={{ width: 40, height: 40, mr: 2 }}>
{!rating.user_avatar && rating.user.charAt(0).toUpperCase()}
</Avatar>
<Box>
<Typography variant="subtitle1" sx={{ fontWeight: 'bold', '&:hover': { color: 'primary.main' } }}>
{rating.user}
</Typography>
<Typography variant="caption" color="text.secondary">
{new Date(rating.created_at).toLocaleDateString()}
</Typography>
</Box>
</Link>
</Box>
<Box sx={{ display: { xs: 'none', sm: 'flex' }, alignItems: 'center' }}>
<MuiRating value={rating.score} readOnly max={10} size="small" />
<Typography variant="body2" sx={{ ml: 1, fontWeight: 'bold' }}>{rating.score}/10</Typography>
<Divider sx={{ my: 2 }} />
<Box sx={{ display: 'flex', gap: 3, flexDirection: { xs: 'column', sm: 'row' } }}>
<Link to={`/flavor/${rating.flavor}`} style={{ textDecoration: 'none' }}>
<Box sx={{ width: { xs: '100%', sm: 100 }, aspectRatio: '1/1', borderRadius: 2, overflow: 'hidden', border: '1px solid', borderColor: 'divider' }}>
<Box
component="img"
src={rating.flavor_image || undefined}
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</Box>
</Link>
<Box sx={{ flex: 1 }}>
<Link to={`/flavor/${rating.flavor}`} style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography variant="h6" sx={{ fontSize: '1.1rem', mb: 0.5, '&:hover': { color: 'primary.main' } }}>
{rating.flavor_name}
</Typography>
</Link>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1.5 }}>
{rating.category_name}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1.5 }}>
{/* Compact rating for mobile, stars for desktop */}
<Box sx={{ display: { xs: 'flex', sm: 'none' }, alignItems: 'center', gap: 0.5 }}>
<StarIcon sx={{ fontSize: '1rem', color: 'primary.main' }} />
<Typography variant="body2" sx={{ fontWeight: 'bold' }}>{rating.score}/10</Typography>
</Box>
<Box sx={{ display: { xs: 'none', sm: 'flex' }, alignItems: 'center' }}>
<MuiRating value={rating.score} readOnly max={10} size="small" />
<Typography variant="body2" sx={{ ml: 1, fontWeight: 'bold' }}>{rating.score}/10</Typography>
</Box>
</Box>
{rating.comment && (
<Typography variant="body2" sx={{
fontStyle: 'italic',
color: 'text.secondary',
borderLeft: '3px solid',
borderColor: 'divider',
pl: 2,
py: 0.5
}}>
"{rating.comment}"
</Typography>
)}
</Box>
</Box>
</Box>
</CardContent>
</Card>
))}
</>
)}
{rating.comment && (
<Typography variant="body2" sx={{
fontStyle: 'italic',
color: 'text.secondary',
borderLeft: '3px solid',
borderColor: 'divider',
pl: 2,
py: 0.5
}}>
"{rating.comment}"
</Typography>
)}
</Box>
</Box>
</CardContent>
</Card>
))}
{nextPage && (
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
<Button
variant="outlined"
onClick={handleLoadMore}
disabled={loadingMore}
sx={{ borderRadius: 2, px: 4 }}
>
{loadingMore ? <CircularProgress size={24} /> : 'Load More Activity'}
</Button>
{totalPages > 1 && (
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
<Pagination
count={totalPages}
page={page}
onChange={handlePageChange}
color="primary"
size="large"
sx={{
'& .MuiPaginationItem-root': { borderRadius: 2, fontWeight: 'bold' }
}}
/>
</Box>
)}
</Box>
+235 -143
View File
@@ -210,153 +210,245 @@ const MainPage: React.FC = () => {
const currentTop = topFlavors[activeIndex];
const featuredReview = currentTop?.ratings?.find(r => r.comment) || null;
return (
<Box sx={{ width: '100%' }}>
{/* Hero Section / Activity Feed */}
{isLoggedIn ? (
<Box sx={{
py: { xs: 4, md: 8 },
px: { xs: 2, sm: 4, md: 6 },
background: (theme) => theme.palette.mode === 'dark'
? 'linear-gradient(180deg, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0) 100%)'
: 'linear-gradient(180deg, rgba(0,0,0,0.05) 0%, rgba(0,0,0,0) 100%)',
borderBottom: '1px solid',
borderColor: 'divider',
mb: 4
}}>
<Container maxWidth="lg">
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', mb: 4, flexWrap: 'wrap', gap: 2 }}>
<Box>
<Typography variant="h3" sx={{ fontWeight: 'bold', mb: 1, fontSize: { xs: '2rem', md: '3rem' } }}>Community Activity</Typography>
<Typography variant="body1" color="text.secondary">Latest reviews from people you follow</Typography>
</Box>
<Button component={Link} to="/community" variant="outlined" sx={{ borderRadius: 2, textTransform: 'none' }}>
View Full Feed
</Button>
</Box>
return (
<Box sx={{ width: '100%' }}>
{/* 1. Hero Section / Activity Feed */}
{isLoggedIn ? (
<Box sx={{
py: { xs: 4, md: 8 },
px: { xs: 2, sm: 4, md: 6 },
{feedRatings.length === 0 ? (
<Paper sx={{ p: 4, textAlign: 'center', borderRadius: 4, bgcolor: 'action.hover', border: '2px dashed', borderColor: 'divider', elevation: 0 }}>
<Typography variant="h6">Your feed is a bit quiet</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Follow more users to see their activity here!</Typography>
<Button variant="contained" component={Link} to="/" onClick={() => document.getElementById('categories')?.scrollIntoView({ behavior: 'smooth' })} size="small" sx={{ borderRadius: 2 }}>
Discover Reviewers
</Button>
</Paper>
) : (
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', lg: 'repeat(3, 1fr)' }, gap: 3 }}>
{feedRatings.map(rating => (
<Card key={rating.id} sx={{ borderRadius: 3, transition: 'transform 0.2s', '&:hover': { transform: 'translateY(-4px)' } }}>
<CardContent sx={{ p: { xs: 1.5, sm: 2 } }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1.5 }}>
<Link to={`/profile/${rating.user}`} style={{ textDecoration: 'none', color: 'inherit', display: 'flex', alignItems: 'center' }}>
<Avatar src={rating.user_avatar || undefined} sx={{ width: 24, height: 24, mr: 1 }}>
{!rating.user_avatar && rating.user.charAt(0).toUpperCase()}
</Avatar>
<Typography variant="subtitle2" sx={{ fontWeight: 'bold', fontSize: '0.8rem' }}>{rating.user}</Typography>
</Link>
<Typography variant="caption" color="text.secondary" sx={{ ml: 0.5 }}>rated</Typography>
<Box sx={{ flexGrow: 1 }} />
<Typography variant="caption" color="text.secondary">
{new Date(rating.created_at).toLocaleDateString()}
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 1.5, alignItems: 'center' }}>
<Box
component="img"
src={rating.flavor_image || undefined}
sx={{ width: 50, height: 50, borderRadius: 1, objectFit: 'cover', border: '1px solid', borderColor: 'divider' }}
/>
<Box sx={{ minWidth: 0, flex: 1 }}>
<Link to={`/flavor/${rating.flavor}`} style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography variant="body2" sx={{ fontWeight: 'bold', display: '-webkit-box', WebkitLineClamp: 1, WebkitBoxOrient: 'vertical', overflow: 'hidden', fontSize: '0.85rem', mb: 0.5 }}>
{rating.flavor_name}
</Typography>
</Link>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{/* Compact rating for anything below Large desktop to prevent cutoffs */}
<Box sx={{ display: { xs: 'flex', lg: 'none' }, alignItems: 'center', gap: 0.5 }}>
<StarIcon sx={{ fontSize: '1rem', color: 'primary.main' }} />
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>{rating.score}/10</Typography>
</Box>
<Box sx={{ display: { xs: 'none', lg: 'flex' }, alignItems: 'center' }}>
<MuiRating value={rating.score} readOnly size="small" max={10} />
<Typography variant="caption" sx={{ ml: 1, fontWeight: 'bold' }}>{rating.score}/10</Typography>
</Box>
</Box>
</Box>
</Box>
</CardContent>
</Card>
))}
</Box>
)}
</Container>
</Box>
) : (
<Paper
elevation={0}
sx={{
p: { xs: 4, md: 10 },
mb: 4,
borderRadius: 0,
background: (theme) => theme.palette.mode === 'dark'
? 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)'
: 'linear-gradient(135deg, #fdf6f7 0%, #f8e1e5 100%)',
textAlign: 'center',
borderBottom: '1px solid rgba(255,255,255,0.1)',
width: '100%',
boxSizing: 'border-box',
overflow: 'hidden'
}}
>
<Typography variant="h2" component="h1" gutterBottom sx={{ fontWeight: 'bold', fontSize: { xs: '2.2rem', sm: '3rem', md: '4.5rem' }, overflowWrap: 'break-word', px: 2 }}>
The Ultimate Holy Archive
</Typography>
<Typography variant="h5" color="text.secondary" sx={{ mb: 4, maxWidth: 800, mx: 'auto', px: 2 }}>
Browse every flavor ever released, discover new favorites, and share your ratings with the community.
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2, flexWrap: 'wrap', px: 2 }}>
<Button variant="contained" size="large" component={Link} to="/login" sx={{ px: 4, py: 1.5, borderRadius: 2 }}>
Join Community
</Button>
<Button variant="outlined" size="large" onClick={() => document.getElementById('categories')?.scrollIntoView({ behavior: 'smooth' })} sx={{ px: 4, py: 1.5, borderRadius: 2 }}>
Explore Categories
</Button>
</Box>
</Paper>
)}
<Container maxWidth={false} sx={{ px: { xs: 2, sm: 4, md: 6 }, pb: 8 }}>
{/* Categories Grid - MOVED UP */}
<Box id="categories" sx={{ mb: 8 }}>
<Typography variant="h4" sx={{ mb: 4, fontWeight: 'bold' }}>Browse by Category</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 3 }}>
{categories.map(category => (
<Box key={category.id} sx={{ flex: { xs: '1 1 100%', sm: '1 1 45%', lg: '1 1 23%' }, minWidth: 280 }}>
<Link to={`/category/${category.slug}`} style={{ textDecoration: 'none' }}>
<Card sx={{
height: 100,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'all 0.3s',
borderRadius: 3,
'&:hover': { transform: 'scale(1.02)', bgcolor: 'primary.main', '& .cat-name': { color: 'white' } }
}}>
<CardContent>
<Typography variant="h5" className="cat-name" color="primary" sx={{ fontWeight: 'bold' }}>{category.name}</Typography>
</CardContent>
</Card>
</Link>
</Box>
))}
? 'linear-gradient(180deg, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0) 100%)'
: 'linear-gradient(180deg, rgba(0,0,0,0.05) 0%, rgba(0,0,0,0) 100%)',
borderBottom: '1px solid',
borderColor: 'divider',
mb: 4
}}>
<Container maxWidth="lg">
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', mb: 4, flexWrap: 'wrap', gap: 2 }}>
<Box>
<Typography variant="h3" sx={{ fontWeight: 'bold', mb: 1, fontSize: { xs: '2rem', md: '3rem' } }}>Community Activity</Typography>
<Typography variant="body1" color="text.secondary">Latest reviews from people you follow</Typography>
</Box>
<Button component={Link} to="/community" variant="outlined" sx={{ borderRadius: 2, textTransform: 'none' }}>
View Full Feed
</Button>
</Box>
{feedRatings.length === 0 ? (
<Paper sx={{ p: 4, textAlign: 'center', borderRadius: 4, bgcolor: 'action.hover', border: '2px dashed', borderColor: 'divider', elevation: 0 }}>
<Typography variant="h6">Your feed is a bit quiet</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Follow more users to see their activity here!</Typography>
<Button variant="contained" component={Link} to="/" size="small" sx={{ borderRadius: 2 }}>Explore Flavors</Button>
</Paper>
) : (
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', lg: 'repeat(3, 1fr)' }, gap: 3 }}>
{feedRatings.map(rating => (
<Card key={rating.id} sx={{ borderRadius: 3, transition: 'transform 0.2s', '&:hover': { transform: 'translateY(-4px)' } }}>
<CardContent sx={{ p: { xs: 1.5, sm: 2 } }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1.5 }}>
<Link to={`/profile/${rating.user}`} style={{ textDecoration: 'none', color: 'inherit', display: 'flex', alignItems: 'center' }}>
<Avatar src={rating.user_avatar || undefined} sx={{ width: 24, height: 24, mr: 1 }}>
{!rating.user_avatar && rating.user.charAt(0).toUpperCase()}
</Avatar>
<Typography variant="subtitle2" sx={{ fontWeight: 'bold', fontSize: '0.8rem' }}>{rating.user}</Typography>
</Link>
<Typography variant="caption" color="text.secondary" sx={{ ml: 0.5 }}>rated</Typography>
<Box sx={{ flexGrow: 1 }} />
<Typography variant="caption" color="text.secondary">
{new Date(rating.created_at).toLocaleDateString()}
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 1.5, alignItems: 'center' }}>
<Box
component="img"
src={rating.flavor_image || undefined}
sx={{ width: 50, height: 50, borderRadius: 1, objectFit: 'cover', border: '1px solid', borderColor: 'divider' }}
/>
<Box sx={{ minWidth: 0, flex: 1 }}>
<Link to={`/flavor/${rating.flavor}`} style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography variant="body2" sx={{ fontWeight: 'bold', display: '-webkit-box', WebkitLineClamp: 1, WebkitBoxOrient: 'vertical', overflow: 'hidden', fontSize: '0.85rem', mb: 0.5 }}>
{rating.flavor_name}
</Typography>
</Link>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{/* Compact rating for anything below Large desktop to prevent cutoffs */}
<Box sx={{ display: { xs: 'flex', lg: 'none' }, alignItems: 'center', gap: 0.5 }}>
<StarIcon sx={{ fontSize: '1rem', color: 'primary.main' }} />
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>{rating.score}/10</Typography>
</Box>
<Box sx={{ display: { xs: 'none', lg: 'flex' }, alignItems: 'center' }}>
<MuiRating value={rating.score} readOnly size="small" max={10} />
<Typography variant="caption" sx={{ ml: 1, fontWeight: 'bold' }}>{rating.score}/10</Typography>
</Box>
</Box>
</Box>
</Box>
</CardContent>
</Card>
))}
</Box>
)}
</Container>
</Box>
</Box>
) : (
<Paper
elevation={0}
sx={{
p: { xs: 4, md: 10 },
mb: 4,
borderRadius: 0,
background: (theme) => theme.palette.mode === 'dark'
? 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)'
: 'linear-gradient(135deg, #fdf6f7 0%, #f8e1e5 100%)',
textAlign: 'center',
borderBottom: '1px solid rgba(255,255,255,0.1)',
width: '100%',
boxSizing: 'border-box',
overflow: 'hidden'
}}
>
<Typography variant="h2" component="h1" gutterBottom sx={{ fontWeight: 'bold', fontSize: { xs: '2.2rem', sm: '3rem', md: '4.5rem' }, overflowWrap: 'break-word', px: 2 }}>
The Ultimate Holy Archive
</Typography>
<Typography variant="h5" color="text.secondary" sx={{ mb: 4, maxWidth: 800, mx: 'auto', px: 2 }}>
Browse every flavor ever released, discover new favorites, and share your ratings with the community.
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2, flexWrap: 'wrap', px: 2 }}>
<Button variant="contained" size="large" component={Link} to="/login" sx={{ px: 4, py: 1.5, borderRadius: 2 }}>
Join Community
</Button>
<Button variant="outlined" size="large" component={Link} to="/about" sx={{ px: 4, py: 1.5, borderRadius: 2 }}>
What is this for?
</Button>
</Box>
</Paper>
)}
<Container maxWidth={false} sx={{ px: { xs: 2, sm: 4, md: 6 }, pb: 8 }}>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', xl: 'row' }, gap: 6, mb: 10 }}>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', xl: 'row' }, gap: 6, mb: 10 }}>
{/* Hall of Fame Carousel - SIDE BY SIDE */}