mirror of
https://github.com/narrrl/holy-flavor-rater.git
synced 2026-11-04 00:47:02 +01:00
added avatars
This commit is contained in:
1 parent
481bff7603
commit
606c3bc2a7
9 files changed
+134
-14
No files matched your search
+17
-4
@@ -9,6 +9,7 @@ import {
|
||||
Button,
|
||||
Box,
|
||||
IconButton,
|
||||
Avatar,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Drawer,
|
||||
@@ -138,7 +139,7 @@ const GlobalSearch = () => {
|
||||
const App: React.FC = () => {
|
||||
const [themeName, setThemeName] = useState<CatppuccinTheme>((localStorage.getItem('theme') as CatppuccinTheme) || 'mocha');
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [user, setUser] = useState<{username: string} | null>(null);
|
||||
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)');
|
||||
@@ -259,9 +260,21 @@ const App: React.FC = () => {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<IconButton color="inherit" onClick={(e) => setAnchorEl(e.currentTarget)} sx={{ ml: 1 }}>
|
||||
<PaletteIcon />
|
||||
</IconButton>
|
||||
<IconButton color="inherit" onClick={(e) => setAnchorEl(e.currentTarget)} sx={{ ml: 1 }}>
|
||||
|
||||
{user?.avatar ? (
|
||||
|
||||
<Avatar src={user.avatar} sx={{ width: 32, height: 32 }} />
|
||||
|
||||
) : (
|
||||
|
||||
<PaletteIcon />
|
||||
|
||||
)}
|
||||
|
||||
</IconButton>
|
||||
|
||||
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
|
||||
@@ -37,6 +37,7 @@ interface Flavor {
|
||||
interface Review {
|
||||
id: number;
|
||||
user: string;
|
||||
user_avatar: string | null;
|
||||
flavor_name: string;
|
||||
flavor_image: string | null;
|
||||
score: number;
|
||||
@@ -268,7 +269,9 @@ const CategoryList: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Avatar sx={{ width: 32, height: 32, mr: 1 }}>{review.user.charAt(0).toUpperCase()}</Avatar>
|
||||
<Avatar src={review.user_avatar || undefined} sx={{ width: 32, height: 32, mr: 1 }}>
|
||||
{!review.user_avatar && review.user.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 'bold' }}>{review.user}</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Typography, Card, CardContent, Box, Rating as MuiRating, Button, TextField, Container, CircularProgress } from '@mui/material';
|
||||
import { Typography, Card, CardContent, Box, Rating as MuiRating, Button, TextField, Container, CircularProgress, Avatar } from '@mui/material';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import api from '../api';
|
||||
import { useTitle } from '../hooks/useTitle';
|
||||
|
||||
interface DashboardData {
|
||||
user: { username: string };
|
||||
user: { username: string, avatar: string | null };
|
||||
rated_count: number;
|
||||
missing_count: number;
|
||||
missing_flavors: any[];
|
||||
@@ -44,7 +44,12 @@ const Dashboard: React.FC = () => {
|
||||
return (
|
||||
<Container maxWidth={false} sx={{ px: { xs: 2, sm: 4, md: 6 }, py: 4 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4, flexWrap: 'wrap', gap: 2 }}>
|
||||
<Typography variant="h4">Personal Dashboard</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Avatar src={data.user.avatar || undefined} sx={{ width: 48, height: 48 }}>
|
||||
{!data.user.avatar && data.user.username.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Typography variant="h4">Personal Dashboard</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<TextField
|
||||
size="small"
|
||||
|
||||
@@ -26,6 +26,7 @@ interface Rating {
|
||||
interface ProfileData {
|
||||
username: string;
|
||||
theme: string;
|
||||
avatar: string | null;
|
||||
ratings: Rating[];
|
||||
}
|
||||
|
||||
@@ -59,8 +60,8 @@ const PublicProfile: React.FC = () => {
|
||||
return (
|
||||
<Container maxWidth={false} sx={{ px: { xs: 2, sm: 4, md: 6 }, py: 4 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 6, gap: 3 }}>
|
||||
<Avatar sx={{ width: 80, height: 80, fontSize: '2rem', bgcolor: 'primary.main' }}>
|
||||
{data.username.charAt(0).toUpperCase()}
|
||||
<Avatar src={data.avatar || undefined} sx={{ width: 80, height: 80, fontSize: '2rem', bgcolor: 'primary.main' }}>
|
||||
{!data.avatar && data.username.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Box>
|
||||
<Typography variant="h3" sx={{ fontWeight: 'bold' }}>{data.username}</Typography>
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
Button,
|
||||
Paper,
|
||||
Alert,
|
||||
Container
|
||||
Container,
|
||||
Avatar
|
||||
} from '@mui/material';
|
||||
import api from '../api';
|
||||
import { useTitle } from '../hooks/useTitle';
|
||||
@@ -16,6 +17,7 @@ const Settings: React.FC = () => {
|
||||
const [username, setUsername] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [currentEmail, setCurrentEmail] = useState('');
|
||||
const [avatar, setAvatar] = useState<string | null>(null);
|
||||
const [confirmationCode, setConfirmationCode] = useState('');
|
||||
const [pendingConfirmation, setPendingConfirmation] = useState(false);
|
||||
const [oldPassword, setOldPassword] = useState('');
|
||||
@@ -29,6 +31,7 @@ const Settings: React.FC = () => {
|
||||
setUsername(res.data.username);
|
||||
setEmail(res.data.email);
|
||||
setCurrentEmail(res.data.email);
|
||||
setAvatar(res.data.avatar);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
@@ -36,6 +39,29 @@ const Settings: React.FC = () => {
|
||||
fetchUser();
|
||||
}, []);
|
||||
|
||||
const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
if (file.size > 2 * 1024 * 1024) {
|
||||
setMessage({ type: 'error', text: 'File size exceeds 2MB limit' });
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('avatar', file);
|
||||
|
||||
try {
|
||||
const res = await api.post('users/update_avatar/', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
setAvatar(res.data.avatar);
|
||||
setMessage({ type: 'success', text: 'Avatar updated successfully!' });
|
||||
} catch (err: any) {
|
||||
setMessage({ type: 'error', text: err.response?.data?.error || 'Failed to upload avatar' });
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateProfile = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
@@ -87,6 +113,24 @@ const Settings: React.FC = () => {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Paper sx={{ p: 3, mb: 4 }}>
|
||||
<Typography variant="h6" gutterBottom>Profile Picture</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 2 }}>
|
||||
<Avatar src={avatar || undefined} sx={{ width: 100, height: 100 }}>
|
||||
{!avatar && username.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Box>
|
||||
<Button variant="outlined" component="label">
|
||||
Upload New Avatar
|
||||
<input type="file" hidden accept="image/*" onChange={handleAvatarUpload} />
|
||||
</Button>
|
||||
<Typography variant="caption" display="block" sx={{ mt: 1 }} color="text.secondary">
|
||||
Max size: 2MB. JPG, PNG or WEBP.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
<Paper sx={{ p: 3, mb: 4 }}>
|
||||
<Typography variant="h6" gutterBottom>Profile Information</Typography>
|
||||
<form onSubmit={handleUpdateProfile}>
|
||||
|
||||
@@ -27,6 +27,7 @@ interface Reply {
|
||||
interface Rating {
|
||||
id: number;
|
||||
user: string;
|
||||
user_avatar: string | null;
|
||||
score: number;
|
||||
comment: string;
|
||||
created_at: string;
|
||||
@@ -224,7 +225,9 @@ const FlavorDetail: React.FC = () => {
|
||||
) : (
|
||||
<>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Avatar sx={{ width: 32, height: 32, mr: 1.5 }}>{rating.user.charAt(0).toUpperCase()}</Avatar>
|
||||
<Avatar src={rating.user_avatar || undefined} sx={{ width: 32, height: 32, mr: 1.5 }}>
|
||||
{!rating.user_avatar && rating.user.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>{rating.user}</Typography>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<MuiRating value={rating.score} readOnly size="small" max={10} />
|
||||
|
||||
Reference in new issue
Block a user