mirror of
https://github.com/narrrl/holy-flavor-rater.git
synced 2026-11-03 16:41:27 +01:00
fixed spacing of front page community activity
This commit is contained in:
1 parent
763788a9bb
commit
ee8e51d891
4 files changed
+107
-58
No files matched your search
+42
-2
@@ -94,7 +94,27 @@ const resources = {
|
||||
"buyNow": "Buy Now",
|
||||
"reviews": "reviews",
|
||||
"rank": "Rank",
|
||||
"loading": "Loading..."
|
||||
"loading": "Loading...",
|
||||
"replies": "Replies",
|
||||
"reply": "Reply",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"community": {
|
||||
"title": "Community Activity",
|
||||
"subtitle": "Stay updated with your circle's latest ratings and discussions.",
|
||||
"quietFeed": "Your feed is a bit empty...",
|
||||
"followMore": "Follow more people to see their flavor ratings here!",
|
||||
"topRated": "Circle's Top Rated",
|
||||
"notifications": "Recent Notifications",
|
||||
"notifReply": "replied",
|
||||
"notifMention": "mentioned you",
|
||||
"noNotifications": "No notifications.",
|
||||
"searchFriends": "Search friends...",
|
||||
"writeReply": "Add a reply...",
|
||||
"rated": "rated"
|
||||
},
|
||||
"about": {
|
||||
"title": "About This Project",
|
||||
@@ -204,7 +224,27 @@ const resources = {
|
||||
"buyNow": "Jetzt kaufen",
|
||||
"reviews": "Bewertungen",
|
||||
"rank": "Rang",
|
||||
"loading": "Wird geladen..."
|
||||
"loading": "Wird geladen...",
|
||||
"replies": "Antworten",
|
||||
"reply": "Antworten",
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen"
|
||||
},
|
||||
"community": {
|
||||
"title": "Community-Aktivität",
|
||||
"subtitle": "Bleib auf dem Laufenden mit den neuesten Bewertungen und Diskussionen deines Kreises.",
|
||||
"quietFeed": "Dein Feed ist noch etwas leer...",
|
||||
"followMore": "Folge mehr Personen, um deren Bewertungen hier zu sehen!",
|
||||
"topRated": "Top-Bewertungen deines Kreises",
|
||||
"notifications": "Neueste Benachrichtigungen",
|
||||
"notifReply": "hat geantwortet",
|
||||
"notifMention": "hat dich erwähnt",
|
||||
"noNotifications": "Keine Benachrichtigungen.",
|
||||
"searchFriends": "Freunde suchen...",
|
||||
"writeReply": "Eine Antwort hinzufügen...",
|
||||
"rated": "bewertete"
|
||||
},
|
||||
"about": {
|
||||
"title": "Über dieses Projekt",
|
||||
|
||||
@@ -32,6 +32,7 @@ import api from '../api';
|
||||
import { useTitle } from '../hooks/useTitle';
|
||||
import RichText from '../components/RichText';
|
||||
import { formatDate } from '../utils/date';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Reply {
|
||||
id: number;
|
||||
@@ -76,7 +77,8 @@ interface Notification {
|
||||
}
|
||||
|
||||
const CommunityFeed: React.FC = () => {
|
||||
useTitle('Community Feed');
|
||||
const { t } = useTranslation();
|
||||
useTitle(t('community.title'));
|
||||
const navigate = useNavigate();
|
||||
const [ratings, setRatings] = useState<FeedRating[]>([]);
|
||||
const [following, setFollowing] = useState<FollowedUser[]>([]);
|
||||
@@ -175,9 +177,9 @@ const CommunityFeed: React.FC = () => {
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||
<Box sx={{ mb: 6 }}>
|
||||
<Typography variant="h3" sx={{ fontWeight: '800', mb: 1 }}>Community Activity</Typography>
|
||||
<Typography variant="h3" sx={{ fontWeight: '800', mb: 1 }}>{t('community.title')}</Typography>
|
||||
<Typography variant="h6" color="text.secondary">
|
||||
Stay updated with your circle's latest ratings and discussions.
|
||||
{t('community.subtitle')}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
@@ -194,12 +196,12 @@ const CommunityFeed: React.FC = () => {
|
||||
border: '1px dashed',
|
||||
borderColor: 'divider'
|
||||
}}>
|
||||
<Typography variant="h6" gutterBottom color="text.secondary">Your feed is a bit empty...</Typography>
|
||||
<Typography variant="h6" gutterBottom color="text.secondary">{t('community.quietFeed')}</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||
Follow more people to see their flavor ratings here!
|
||||
{t('community.followMore')}
|
||||
</Typography>
|
||||
<Button variant="contained" component={Link} to="/" sx={{ borderRadius: 2 }}>
|
||||
Explore Flavors
|
||||
{t('home.exploreFlavors')}
|
||||
</Button>
|
||||
</Box>
|
||||
) : (
|
||||
@@ -254,14 +256,14 @@ const CommunityFeed: React.FC = () => {
|
||||
<Box sx={{ display: 'flex', gap: 3 }}>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||
Rated <MuiLink component={Link} to={`/flavor/${rating.flavor}`} sx={{ color: 'primary.main', fontWeight: 'bold', textDecoration: 'none' }}>{rating.flavor_name}</MuiLink>
|
||||
{t('community.rated')} <MuiLink component={Link} to={`/flavor/${rating.flavor}`} sx={{ color: 'primary.main', fontWeight: 'bold', textDecoration: 'none' }}>{rating.flavor_name}</MuiLink>
|
||||
</Typography>
|
||||
{rating.comment ? (
|
||||
<Typography variant="body1" sx={{ lineHeight: 1.6, fontStyle: 'italic', color: 'text.primary' }}>
|
||||
<RichText text={rating.comment} />
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>No review text.</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>{t('dashboard.noRatings')}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Link to={`/flavor/${rating.flavor}`} style={{ flexShrink: 0 }}>
|
||||
@@ -280,7 +282,7 @@ const CommunityFeed: React.FC = () => {
|
||||
onClick={() => handleReplyToggle(rating.id)}
|
||||
sx={{ textTransform: 'none', color: 'text.secondary' }}
|
||||
>
|
||||
{rating.replies.length > 0 ? `${rating.replies.length} Replies` : 'Reply'}
|
||||
{rating.replies.length > 0 ? `${rating.replies.length} ${t('common.replies')}` : t('common.reply')}
|
||||
</Button>
|
||||
|
||||
<Collapse in={expandedReplies[rating.id]}>
|
||||
@@ -296,7 +298,7 @@ const CommunityFeed: React.FC = () => {
|
||||
))}
|
||||
<Box sx={{ mt: 2, display: 'flex', gap: 1 }}>
|
||||
<TextField
|
||||
fullWidth size="small" placeholder="Add a reply..."
|
||||
fullWidth size="small" placeholder={t('community.writeReply')}
|
||||
value={replyInputs[rating.id] || ''}
|
||||
onChange={(e) => setReplyInputs(prev => ({ ...prev, [rating.id]: e.target.value }))}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleReplySubmit(rating.id)}
|
||||
@@ -327,11 +329,11 @@ const CommunityFeed: React.FC = () => {
|
||||
<Card elevation={0} sx={{ borderRadius: 4, border: '1px solid', borderColor: 'divider' }}>
|
||||
<Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider', bgcolor: alpha('#000', 0.02), display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<NotificationsIcon color="primary" fontSize="small" />
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>Recent Notifications</Typography>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>{t('community.notifications')}</Typography>
|
||||
</Box>
|
||||
<List disablePadding>
|
||||
{notifications.length === 0 ? (
|
||||
<Box sx={{ p: 3, textAlign: 'center' }}><Typography variant="body2" color="text.secondary">No notifications.</Typography></Box>
|
||||
<Box sx={{ p: 3, textAlign: 'center' }}><Typography variant="body2" color="text.secondary">{t('community.noNotifications')}</Typography></Box>
|
||||
) : (
|
||||
notifications.map(n => (
|
||||
<ListItemButton key={n.id} onClick={() => n.flavor_id && navigate(`/flavor/${n.flavor_id}`)} sx={{ py: 1.5 }}>
|
||||
@@ -341,7 +343,7 @@ const CommunityFeed: React.FC = () => {
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography variant="body2" sx={{ fontSize: '0.8rem' }}>
|
||||
<strong>{n.actor_username}</strong> {n.notification_type === 'reply' ? 'replied' : 'mentioned you'}
|
||||
<strong>{n.actor_username}</strong> {n.notification_type === 'reply' ? t('community.notifReply') : t('community.notifMention')}
|
||||
</Typography>
|
||||
}
|
||||
secondary={formatTimestamp(n.created_at)}
|
||||
@@ -357,11 +359,11 @@ const CommunityFeed: React.FC = () => {
|
||||
<Card elevation={0} sx={{ borderRadius: 4, border: '1px solid', borderColor: 'divider' }}>
|
||||
<Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider', bgcolor: alpha('#000', 0.02), display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<PeopleIcon color="primary" fontSize="small" />
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>Following</Typography>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>{t('nav.following')}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ p: 1.5 }}>
|
||||
<TextField
|
||||
fullWidth size="small" placeholder="Search friends..."
|
||||
fullWidth size="small" placeholder={t('community.searchFriends')}
|
||||
value={followingSearch}
|
||||
onChange={(e) => setFollowingSearch(e.target.value)}
|
||||
InputProps={{ startAdornment: <SearchIcon fontSize="small" sx={{ mr: 1, color: 'text.secondary' }} /> }}
|
||||
@@ -381,7 +383,7 @@ const CommunityFeed: React.FC = () => {
|
||||
<Card elevation={0} sx={{ borderRadius: 4, border: '1px solid', borderColor: 'divider' }}>
|
||||
<Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider', bgcolor: alpha('#000', 0.02), display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<WhatshotIcon color="error" fontSize="small" />
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>Circle's Top Rated</Typography>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>{t('community.topRated')}</Typography>
|
||||
</Box>
|
||||
<List disablePadding>
|
||||
{topFollowed.map((flavor, idx) => (
|
||||
|
||||
@@ -251,56 +251,63 @@ const MainPage: React.FC = () => {
|
||||
<Button variant="contained" component={Link} to="/" size="small" sx={{ borderRadius: 2 }}>{t('home.exploreFlavors')}</Button>
|
||||
</Paper>
|
||||
) : (
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', lg: 'repeat(3, 1fr)' }, gap: 3 }}>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', lg: 'repeat(3, 1fr)' }, gap: 2 }}>
|
||||
{feedRatings.map(rating => (
|
||||
<Card
|
||||
key={rating.id}
|
||||
component={Link}
|
||||
to={`/flavor/${rating.flavor}`}
|
||||
sx={{
|
||||
borderRadius: 3,
|
||||
transition: 'all 0.3s ease',
|
||||
'&:hover': { transform: 'translateY(-4px)', boxShadow: '0 12px 24px rgba(0,0,0,0.15)' },
|
||||
textDecoration: 'none',
|
||||
transition: 'all 0.2s ease',
|
||||
'&:hover': { transform: 'translateY(-2px)', borderColor: 'primary.main', boxShadow: '0 8px 16px rgba(0,0,0,0.1)' },
|
||||
bgcolor: (theme) => alpha(theme.palette.background.paper, 0.4),
|
||||
backdropFilter: 'blur(10px)',
|
||||
border: '1px solid',
|
||||
borderColor: (theme) => alpha(theme.palette.text.primary, 0.05)
|
||||
borderColor: 'divider',
|
||||
color: 'inherit',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<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 }}>
|
||||
<CardContent sx={{ p: 2, flex: 1, '&:last-child': { pb: 2 } }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 1.5 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', minWidth: 0, flex: 1 }}>
|
||||
<Avatar src={rating.user_avatar || undefined} sx={{ width: 22, height: 22, 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">
|
||||
{formatDate(rating.created_at)}
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 'bold', fontSize: '0.8rem', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{rating.user}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.65rem', ml: 1, whiteSpace: 'nowrap' }}>
|
||||
{t('home.rated')} {formatDate(rating.created_at)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1.5, alignItems: 'center' }}>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
||||
<Box
|
||||
component="img"
|
||||
src={rating.flavor_image || undefined}
|
||||
sx={{ width: 50, height: 50, borderRadius: 1, objectFit: 'cover', border: '1px solid', borderColor: 'divider' }}
|
||||
sx={{ width: 44, height: 44, borderRadius: 1.5, objectFit: 'contain', bgcolor: 'action.hover', p: 0.5, 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 variant="body2" sx={{ fontWeight: 'bold', lineHeight: 1.2, mb: 0.5, fontSize: '0.85rem', display: '-webkit-box', WebkitLineClamp: 1, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
|
||||
{rating.flavor_name}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||
<Typography variant="caption" sx={{
|
||||
fontWeight: '900',
|
||||
color: 'primary.main',
|
||||
bgcolor: (theme) => alpha(theme.palette.primary.main, 0.1),
|
||||
px: 1,
|
||||
py: 0.2,
|
||||
borderRadius: 1,
|
||||
fontSize: '0.75rem'
|
||||
}}>
|
||||
{rating.score} / 10
|
||||
</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>
|
||||
|
||||
@@ -388,8 +388,8 @@ const FlavorDetail: React.FC = () => {
|
||||
onChange={(e) => setEditComment(e.target.value)}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Button variant="contained" onClick={() => handleUpdateRating(rating.id)}>Save</Button>
|
||||
<Button onClick={() => setEditMode(null)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={() => handleUpdateRating(rating.id)}>{t('common.save')}</Button>
|
||||
<Button onClick={() => setEditMode(null)}>{t('common.cancel')}</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
@@ -437,8 +437,8 @@ const FlavorDetail: React.FC = () => {
|
||||
<Box />
|
||||
{currentUser === rating.user && (
|
||||
<Box>
|
||||
<Button size="small" onClick={() => startEdit(rating)} sx={{ minWidth: 0, mr: 1 }}>Edit</Button>
|
||||
<Button size="small" color="error" onClick={() => handleDeleteRating(rating.id)} sx={{ minWidth: 0 }}>Delete</Button>
|
||||
<Button size="small" onClick={() => startEdit(rating)} sx={{ minWidth: 0, mr: 1 }}>{t('common.edit')}</Button>
|
||||
<Button size="small" color="error" onClick={() => handleDeleteRating(rating.id)} sx={{ minWidth: 0 }}>{t('common.delete')}</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
@@ -471,8 +471,8 @@ const FlavorDetail: React.FC = () => {
|
||||
</Typography>
|
||||
{currentUser === reply.user && editingReplyId !== reply.id && (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<Button size="small" sx={{ minWidth: 0, p: 0.5, fontSize: '0.7rem' }} onClick={() => { setEditingReplyId(reply.id); setEditReplyText(reply.text); }}>Edit</Button>
|
||||
<Button size="small" color="error" sx={{ minWidth: 0, p: 0.5, fontSize: '0.7rem' }} onClick={() => handleDeleteReply(reply.id)}>Delete</Button>
|
||||
<Button size="small" sx={{ minWidth: 0, p: 0.5, fontSize: '0.7rem' }} onClick={() => { setEditingReplyId(reply.id); setEditReplyText(reply.text); }}>{t('common.edit')}</Button>
|
||||
<Button size="small" color="error" sx={{ minWidth: 0, p: 0.5, fontSize: '0.7rem' }} onClick={() => handleDeleteReply(reply.id)}>{t('common.delete')}</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
@@ -487,8 +487,8 @@ const FlavorDetail: React.FC = () => {
|
||||
onChange={(e) => setEditReplyText(e.target.value)}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Button variant="contained" size="small" onClick={() => handleUpdateReply(reply.id)}>Save</Button>
|
||||
<Button size="small" onClick={() => setEditingReplyId(null)}>Cancel</Button>
|
||||
<Button variant="contained" size="small" onClick={() => handleUpdateReply(reply.id)}>{t('common.save')}</Button>
|
||||
<Button size="small" onClick={() => setEditingReplyId(null)}>{t('common.cancel')}</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
@@ -505,13 +505,13 @@ const FlavorDetail: React.FC = () => {
|
||||
<Box sx={{ mt: 2, display: 'flex', gap: 1 }}>
|
||||
<TextField
|
||||
size="small"
|
||||
placeholder="Write a reply..."
|
||||
placeholder={t('community.writeReply')}
|
||||
fullWidth
|
||||
value={replyInputs[rating.id] || ''}
|
||||
onChange={(e) => setReplyInputs({ ...replyInputs, [rating.id]: e.target.value })}
|
||||
sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'background.paper' } }}
|
||||
/>
|
||||
<Button variant="contained" size="small" disabled={!replyInputs[rating.id]} onClick={() => handleReplySubmit(rating.id)}>Reply</Button>
|
||||
<Button variant="contained" size="small" disabled={!replyInputs[rating.id]} onClick={() => handleReplySubmit(rating.id)}>{t('common.reply')}</Button>
|
||||
</Box>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
Reference in new issue
Block a user