diff --git a/backend/app/api/users.py b/backend/app/api/users.py index 65f562e..7542d36 100644 --- a/backend/app/api/users.py +++ b/backend/app/api/users.py @@ -49,6 +49,7 @@ def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depend "id": user.id, "username": user.username, "email": user.email, + "avatar": user.avatar, "is_admin": user.is_admin } } @@ -73,6 +74,7 @@ def register_user(user: UserCreate, background_tasks: BackgroundTasks, db: Sessi db_user = User( username=user.username, email=user.email, + avatar=user.avatar, hashed_password=hashed_password, is_active=is_active, is_admin=is_admin @@ -178,6 +180,7 @@ def create_user(user: UserCreate, db: Session = Depends(get_db)): db_user = User( username=user.username, email=user.email, + avatar=user.avatar, hashed_password=get_password_hash(user.password), is_active=user.is_active, is_admin=user.is_admin diff --git a/backend/app/models/user.py b/backend/app/models/user.py index be26bae..5192ba5 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -10,6 +10,7 @@ class User(Base): username = Column(String, unique=True, index=True, nullable=False) email = Column(String, unique=True, index=True, nullable=False) hashed_password = Column(String, nullable=False) + avatar = Column(String, nullable=True) # Store avatar identifier or URL is_active = Column(Boolean, default=True) is_admin = Column(Boolean, default=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py index 2af1c30..3354fe7 100644 --- a/backend/app/schemas/user.py +++ b/backend/app/schemas/user.py @@ -5,6 +5,7 @@ from datetime import datetime class UserBase(BaseModel): username: str email: str + avatar: Optional[str] = None is_active: Optional[bool] = True is_admin: Optional[bool] = False @@ -14,6 +15,7 @@ class UserCreate(UserBase): class UserUpdate(BaseModel): username: Optional[str] = None email: Optional[str] = None + avatar: Optional[str] = None is_active: Optional[bool] = None is_admin: Optional[bool] = None password: Optional[str] = None diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index e98051b..4f493c5 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -885,8 +885,12 @@ function SidebarBody() { } }} > -
- +
+ {user?.avatar ? ( + avatar + ) : ( + + )}
{user?.username || t('defaultUser')} diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 09f3ce0..604dc32 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -4,14 +4,30 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"; -import { Save, Loader2 } from "lucide-react"; +import { Save, Loader2, Check } from "lucide-react"; import { api } from "@/lib/api"; import { useAuthStore } from "@/store/authStore"; +const BUILTIN_AVATARS = [ + "https://api.dicebear.com/7.x/bottts/svg?seed=Felix", + "https://api.dicebear.com/7.x/bottts/svg?seed=Aneka", + "https://api.dicebear.com/7.x/bottts/svg?seed=Tinkerbell", + "https://api.dicebear.com/7.x/bottts/svg?seed=Bella", + "https://api.dicebear.com/7.x/bottts/svg?seed=Buster", + "https://api.dicebear.com/7.x/bottts/svg?seed=Max", + "https://api.dicebear.com/7.x/adventurer/svg?seed=Leo", + "https://api.dicebear.com/7.x/adventurer/svg?seed=Oliver", + "https://api.dicebear.com/7.x/adventurer/svg?seed=Mia", + "https://api.dicebear.com/7.x/adventurer/svg?seed=Lily", + "https://api.dicebear.com/7.x/adventurer/svg?seed=Chloe", + "https://api.dicebear.com/7.x/adventurer/svg?seed=Simba" +]; + export function Settings() { const { t } = useTranslation(); const { user, updateUser } = useAuthStore(); const [email, setEmail] = useState(''); + const [avatar, setAvatar] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [isSaving, setIsSaving] = useState(false); @@ -21,6 +37,7 @@ export function Settings() { useEffect(() => { if (user) { setEmail(user.email || ''); + setAvatar(user.avatar || ''); } }, [user]); @@ -38,7 +55,8 @@ export function Settings() { setIsSaving(true); try { const updateData: any = { - email: email + email: email, + avatar: avatar || null }; if (password) { @@ -55,8 +73,8 @@ export function Settings() { setPassword(''); setConfirmPassword(''); - // Update global state with new email - updateUser({ email: response.email }); + // Update global state with new email and avatar + updateUser({ email: response.email, avatar: response.avatar }); } } catch (error: any) { console.error("Failed to save settings", error); @@ -87,6 +105,28 @@ export function Settings() {
+ +
+ {BUILTIN_AVATARS.map((url) => ( +
setAvatar(url)} + > + avatar + {avatar === url && ( +
+ +
+ )} +
+ ))} +
+
+ +