diff --git a/backend/api/serializers.py b/backend/api/serializers.py index 23ca690..a8d7534 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -34,11 +34,13 @@ class RatingSerializer(serializers.ModelSerializer): user_avatar = serializers.SerializerMethodField() flavor_name = serializers.CharField(source='flavor.name', read_only=True) flavor_image = serializers.SerializerMethodField() + category_name = serializers.CharField(source='flavor.category.name', read_only=True) + category_slug = serializers.CharField(source='flavor.category.slug', read_only=True) replies = ReplySerializer(many=True, read_only=True) class Meta: model = Rating - fields = ['id', 'user', 'user_avatar', 'flavor', 'flavor_name', 'flavor_image', 'score', 'comment', 'created_at', 'replies'] + fields = ['id', 'user', 'user_avatar', 'flavor', 'flavor_name', 'flavor_image', 'category_name', 'category_slug', 'score', 'comment', 'created_at', 'replies'] def get_user_avatar(self, obj): if obj.user.avatar: diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2ae7d52..183e57c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,7 +18,6 @@ import { ListItem, ListItemButton, ListItemText, - useMediaQuery, TextField, InputAdornment, Autocomplete @@ -75,7 +74,7 @@ const GlobalSearch = () => { }; return ( - + { renderInput={(params) => ( { const [user, setUser] = useState<{username: string, avatar: string | null} | null>(null); const [loadingUser, setLoadingUser] = useState(true); const [drawerOpen, setDrawerOpen] = useState(false); - const isMobile = useMediaQuery('(max-width:900px)'); useEffect(() => { const fetchUser = async () => { @@ -184,33 +182,42 @@ const App: React.FC = () => { window.location.href = '/'; }; - const navItems = user ? [ - { label: 'Catalog', path: '/' }, - { label: 'Dashboard', path: '/dashboard' }, - { label: 'Settings', path: '/settings' }, - ] : [ - { label: 'Catalog', path: '/' }, - { label: 'Login', path: '/login' }, - ]; - const drawer = ( setDrawerOpen(false)} sx={{ textAlign: 'center' }}> Holy Flavors - {navItems.map((item) => ( - - - + + + - - ))} + {user && ( - - - - - + <> + + + + + + + + + + + + + + + + + + )} + {!user && !loadingUser && ( + + + + + )} @@ -220,19 +227,16 @@ const App: React.FC = () => { - - + + setDrawerOpen(true)} sx={{ mr: { xs: 0, sm: 2 }, display: { md: 'none' } }} @@ -240,75 +244,41 @@ const App: React.FC = () => { - + Holy Flavors - + - {!isMobile && !loadingUser && ( - - - - - - - - )} - - - - {!loadingUser && ( - - user ? ( - - <> - - setAnchorEl(e.currentTarget)} sx={{ ml: 1 }}> - - - - {!user.avatar && user.username.charAt(0).toUpperCase()} - - - - - - setAnchorEl(null)} - - > - - setAnchorEl(null)}>Dashboard - - setAnchorEl(null)}>Settings - - - - Logout - - - - - - ) : ( - - - - ) - - )} - - - - + + {!loadingUser && ( + user ? ( + <> + setAnchorEl(e.currentTarget)}> + + {!user.avatar && user.username.charAt(0).toUpperCase()} + + + setAnchorEl(null)} + elevation={3} + sx={{ mt: 1 }} + > + setAnchorEl(null)}>Dashboard + setAnchorEl(null)}>Settings + + Logout + + + ) : ( + + ) + )} + @@ -319,10 +289,9 @@ const App: React.FC = () => { } /> } /> } /> - } /> - } /> - } /> - + } /> + } /> + } /> diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 895acd3..f7c792d 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -11,7 +11,8 @@ import { CircularProgress, Avatar, Tabs, - Tab + Tab, + IconButton } from '@mui/material'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import { Link } from 'react-router-dom'; @@ -56,10 +57,11 @@ const Dashboard: React.FC = () => { if (loading) return ; if (!data) return Please login to view dashboard.; - // Helper to group by category + // Robust grouping helper const groupBy = (array: any[]) => { return array.reduce((result, currentValue) => { - const groupKey = currentValue.flavor?.category_name || currentValue.category_name; + // Use category_name from either the rating or the flavor object + const groupKey = currentValue.category_name || currentValue.flavor?.category_name || 'Other'; (result[groupKey] = result[groupKey] || []).push(currentValue); return result; }, {}); @@ -70,47 +72,58 @@ const Dashboard: React.FC = () => { return ( - + - + {!data.user.avatar && data.user.username.charAt(0).toUpperCase()} - Dashboard + + Dashboard + Welcome back, {data.user.username}! + - + + Share Profile: - + + + - - setActiveTab(v)}> - - + + setActiveTab(v)} textColor="primary" indicatorColor="primary"> + + {activeTab === 0 ? ( {Object.keys(ratedGrouped).length === 0 ? ( - You haven't rated any flavors yet. + + You haven't rated any flavors yet. + + ) : ( - Object.entries(ratedGrouped).map(([category, ratings]: [string, any]) => ( - - {category} - - {ratings.map((rating: any) => ( - + Object.entries(ratedGrouped).map(([category, items]: [string, any]) => ( + + + {category} + + + + {items.map((rating: any) => ( + {rating.flavor_image && ( - + )} @@ -120,10 +133,16 @@ const Dashboard: React.FC = () => { - {rating.score}/10 + {rating.score}/10 {rating.comment && ( - + "{rating.comment}" )} @@ -139,17 +158,22 @@ const Dashboard: React.FC = () => { ) : ( {Object.keys(missingGrouped).length === 0 ? ( - Amazing! You've rated everything! + + Amazing! You've rated everything! 🏆 + ) : ( Object.entries(missingGrouped).map(([category, flavors]: [string, any]) => ( - - {category} - + + + {category} + + + {flavors.map((flavor: any) => ( - + {flavor.image_url && ( - + )} @@ -157,11 +181,17 @@ const Dashboard: React.FC = () => { {flavor.name} - - {flavor.description.substring(0, 60)}... + + {flavor.description} {flavor.shop_url && ( - )}