diff --git a/.env.local b/.env.local
index 3b9275d..8dce141 100644
--- a/.env.local
+++ b/.env.local
@@ -1 +1 @@
-NEXT_PUBLIC_COMMIT_SHA=86e198a
+NEXT_PUBLIC_COMMIT_SHA=74b9648
diff --git a/app/components/CacheManagement.tsx b/app/components/CacheManagement.tsx
index decca08..72dd8af 100644
--- a/app/components/CacheManagement.tsx
+++ b/app/components/CacheManagement.tsx
@@ -143,7 +143,7 @@ export function CacheManagement() {
};
return (
-
+
diff --git a/app/components/SettingsManagement.tsx b/app/components/SettingsManagement.tsx
index 4eb0d79..8454999 100644
--- a/app/components/SettingsManagement.tsx
+++ b/app/components/SettingsManagement.tsx
@@ -38,7 +38,7 @@ export function SettingsManagement() {
};
return (
-
+
diff --git a/app/components/SidebarCustomization.tsx b/app/components/SidebarCustomization.tsx
index 95e2788..5c69d66 100644
--- a/app/components/SidebarCustomization.tsx
+++ b/app/components/SidebarCustomization.tsx
@@ -153,7 +153,7 @@ export function SidebarCustomization() {
return (
-
+
Sidebar Customization
diff --git a/app/components/SongRecommendations.tsx b/app/components/SongRecommendations.tsx
index 3bf56d2..59dfa32 100644
--- a/app/components/SongRecommendations.tsx
+++ b/app/components/SongRecommendations.tsx
@@ -10,6 +10,7 @@ import { Card, CardContent } from '@/components/ui/card';
import { Play, Heart, Music, Shuffle } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
+import { UserProfile } from './UserProfile';
interface SongRecommendationsProps {
userName?: string;
@@ -196,12 +197,18 @@ export function SongRecommendations({ userName }: SongRecommendationsProps) {
{isMobile ? 'Here are some albums you might enjoy' : 'Here are some songs you might enjoy'}
- {(isMobile ? recommendedAlbums.length > 0 : recommendedSongs.length > 0) && !isMobile && (
-
- )}
+
+ {/* Mobile User Profile */}
+ {isMobile && }
+
+ {/* Shuffle All Button (Desktop only) */}
+ {(isMobile ? recommendedAlbums.length > 0 : recommendedSongs.length > 0) && !isMobile && (
+
+ )}
+
{isMobile ? (
diff --git a/app/components/UserProfile.tsx b/app/components/UserProfile.tsx
new file mode 100644
index 0000000..7d646cd
--- /dev/null
+++ b/app/components/UserProfile.tsx
@@ -0,0 +1,209 @@
+'use client';
+
+import React, { useState, useEffect } from 'react';
+import Image from 'next/image';
+import Link from 'next/link';
+import { User, ChevronDown, Settings, LogOut } from 'lucide-react';
+import { useNavidrome } from '@/app/components/NavidromeContext';
+import { getGravatarUrl } from '@/lib/gravatar';
+import { User as NavidromeUser } from '@/lib/navidrome';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import { Button } from '@/components/ui/button';
+
+interface UserProfileProps {
+ variant?: 'desktop' | 'mobile';
+}
+
+export function UserProfile({ variant = 'desktop' }: UserProfileProps) {
+ const { api, isConnected } = useNavidrome();
+ const [userInfo, setUserInfo] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const fetchUserInfo = async () => {
+ if (!api || !isConnected) {
+ setLoading(false);
+ return;
+ }
+
+ try {
+ const user = await api.getUserInfo();
+ setUserInfo(user);
+ } catch (error) {
+ console.error('Failed to fetch user info:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchUserInfo();
+ }, [api, isConnected]);
+
+ const handleLogout = () => {
+ // Clear Navidrome config and reload
+ localStorage.removeItem('navidrome-config');
+ window.location.reload();
+ };
+
+ if (!userInfo) {
+ if (variant === 'desktop') {
+ return (
+
+
+
+ );
+ } else {
+ return (
+
+
+
+ );
+ }
+ }
+
+ const gravatarUrl = userInfo.email
+ ? getGravatarUrl(userInfo.email, variant === 'desktop' ? 32 : 48, 'identicon')
+ : null;
+
+ if (variant === 'desktop') {
+ // Desktop: Only show profile icon
+ return (
+
+
+
+
+
+
+ {gravatarUrl ? (
+
+ ) : (
+
+
+
+ )}
+
+
{userInfo.username}
+ {userInfo.email && (
+
{userInfo.email}
+ )}
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+ Logout
+
+
+
+ );
+ } else {
+ // Mobile: Show only icon with dropdown
+ return (
+
+
+
+
+
+
+ {gravatarUrl ? (
+
+ ) : (
+
+
+
+ )}
+
+
{userInfo.username}
+ {userInfo.email && (
+
{userInfo.email}
+ )}
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+ Logout
+
+
+
+ );
+ }
+}
diff --git a/app/components/menu.tsx b/app/components/menu.tsx
index 55ff638..66f94bb 100644
--- a/app/components/menu.tsx
+++ b/app/components/menu.tsx
@@ -2,6 +2,7 @@ import { useCallback } from "react";
import { useRouter } from 'next/navigation';
import Image from "next/image";
import { Github, Mail, Menu as MenuIcon, X } from "lucide-react"
+import { UserProfile } from "@/app/components/UserProfile";
import {
Menubar,
MenubarCheckboxItem,
@@ -332,6 +333,13 @@ export function Menu({ toggleSidebar, isSidebarVisible, toggleStatusBar, isStatu
)}
+ {/* User Profile - Desktop only */}
+ {!isMobile && (
+
+
+
+ )}
+