'use client'; import React, { useState, useEffect } from 'react'; import { Label } from '@/components/ui/label'; import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '@/components/ui/select'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { useTheme } from '@/app/components/ThemeProvider'; import { useNavidromeConfig } from '@/app/components/NavidromeConfigContext'; import { useToast } from '@/hooks/use-toast'; import { useStandaloneLastFm } from '@/hooks/use-standalone-lastfm'; import { FaServer, FaUser, FaLock, FaCheck, FaTimes, FaLastfm, FaCog } from 'react-icons/fa'; import { Settings, ExternalLink } from 'lucide-react'; const SettingsPage = () => { const { theme, setTheme } = useTheme(); const { config, updateConfig, isConnected, testConnection, clearConfig } = useNavidromeConfig(); const { toast } = useToast(); const { isEnabled: isStandaloneLastFmEnabled, getCredentials, getAuthUrl, getSessionKey } = useStandaloneLastFm(); const [formData, setFormData] = useState({ serverUrl: config.serverUrl, username: config.username, password: config.password }); const [isTesting, setIsTesting] = useState(false); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); // Last.fm scrobbling settings (Navidrome integration) const [scrobblingEnabled, setScrobblingEnabled] = useState(() => { if (typeof window !== 'undefined') { return localStorage.getItem('lastfm-scrobbling-enabled') === 'true'; } return true; }); // Standalone Last.fm settings const [standaloneLastFmEnabled, setStandaloneLastFmEnabled] = useState(() => { if (typeof window !== 'undefined') { return localStorage.getItem('standalone-lastfm-enabled') === 'true'; } return false; }); const [lastFmCredentials, setLastFmCredentials] = useState({ apiKey: '', apiSecret: '', sessionKey: '', username: '' }); // Sidebar settings const [sidebarCollapsed, setSidebarCollapsed] = useState(() => { if (typeof window !== 'undefined') { return localStorage.getItem('sidebar-collapsed') === 'true'; } return false; }); // Load Last.fm credentials on mount useEffect(() => { const credentials = getCredentials(); if (credentials) { setLastFmCredentials({ apiKey: credentials.apiKey, apiSecret: credentials.apiSecret, sessionKey: credentials.sessionKey || '', username: credentials.username || '' }); } }, [getCredentials]); const handleInputChange = (field: string, value: string) => { setFormData(prev => ({ ...prev, [field]: value })); setHasUnsavedChanges(true); }; const handleTestConnection = async () => { if (!formData.serverUrl || !formData.username || !formData.password) { toast({ title: "Missing Information", description: "Please fill in all fields before testing the connection.", variant: "destructive" }); return; } setIsTesting(true); try { // Strip trailing slash from server URL before testing const cleanServerUrl = formData.serverUrl.replace(/\/+$/, ''); const success = await testConnection({ serverUrl: cleanServerUrl, username: formData.username, password: formData.password }); if (success) { toast({ title: "Connection Successful", description: "Successfully connected to Navidrome server.", }); } else { toast({ title: "Connection Failed", description: "Could not connect to the server. Please check your settings.", variant: "destructive" }); } } catch (error) { toast({ title: "Connection Error", description: "An error occurred while testing the connection.", variant: "destructive" }); } finally { setIsTesting(false); } }; const handleSaveConfig = async () => { if (!formData.serverUrl || !formData.username || !formData.password) { toast({ title: "Missing Information", description: "Please fill in all fields.", variant: "destructive" }); return; } // Strip trailing slash from server URL to ensure consistency const cleanServerUrl = formData.serverUrl.replace(/\/+$/, ''); updateConfig({ serverUrl: cleanServerUrl, username: formData.username, password: formData.password }); // Update form data to reflect the cleaned URL setFormData(prev => ({ ...prev, serverUrl: cleanServerUrl })); setHasUnsavedChanges(false); toast({ title: "Settings Saved", description: "Navidrome configuration has been saved.", }); }; const handleClearConfig = () => { clearConfig(); setFormData({ serverUrl: '', username: '', password: '' }); setHasUnsavedChanges(false); toast({ title: "Configuration Cleared", description: "Navidrome configuration has been cleared.", }); }; const handleScrobblingToggle = (enabled: boolean) => { setScrobblingEnabled(enabled); localStorage.setItem('lastfm-scrobbling-enabled', enabled.toString()); toast({ title: enabled ? "Scrobbling Enabled" : "Scrobbling Disabled", description: enabled ? "Tracks will now be scrobbled to Last.fm via Navidrome" : "Last.fm scrobbling has been disabled", }); }; const handleStandaloneLastFmToggle = (enabled: boolean) => { setStandaloneLastFmEnabled(enabled); localStorage.setItem('standalone-lastfm-enabled', enabled.toString()); toast({ title: enabled ? "Standalone Last.fm Enabled" : "Standalone Last.fm Disabled", description: enabled ? "Direct Last.fm integration enabled" : "Standalone Last.fm integration disabled", }); }; const handleSidebarToggle = (collapsed: boolean) => { setSidebarCollapsed(collapsed); localStorage.setItem('sidebar-collapsed', collapsed.toString()); toast({ title: collapsed ? "Sidebar Collapsed" : "Sidebar Expanded", description: collapsed ? "Sidebar will show only icons" : "Sidebar will show full labels", }); // Trigger a custom event to notify the sidebar component window.dispatchEvent(new CustomEvent('sidebar-toggle', { detail: { collapsed } })); }; const handleLastFmAuth = () => { if (!lastFmCredentials.apiKey) { toast({ title: "API Key Required", description: "Please enter your Last.fm API key first.", variant: "destructive" }); return; } const authUrl = getAuthUrl(lastFmCredentials.apiKey); window.open(authUrl, '_blank'); toast({ title: "Last.fm Authorization", description: "Please authorize the application in the opened window and return here.", }); }; const handleLastFmCredentialsSave = () => { if (!lastFmCredentials.apiKey || !lastFmCredentials.apiSecret) { toast({ title: "Missing Credentials", description: "Please enter both API key and secret.", variant: "destructive" }); return; } localStorage.setItem('lastfm-credentials', JSON.stringify(lastFmCredentials)); toast({ title: "Credentials Saved", description: "Last.fm credentials have been saved locally.", }); }; const handleLastFmSessionComplete = async (token: string) => { try { const { sessionKey, username } = await getSessionKey( token, lastFmCredentials.apiKey, lastFmCredentials.apiSecret ); const updatedCredentials = { ...lastFmCredentials, sessionKey, username }; setLastFmCredentials(updatedCredentials); localStorage.setItem('lastfm-credentials', JSON.stringify(updatedCredentials)); toast({ title: "Last.fm Authentication Complete", description: `Successfully authenticated as ${username}`, }); } catch (error) { toast({ title: "Authentication Failed", description: error instanceof Error ? error.message : "Failed to complete Last.fm authentication", variant: "destructive" }); } }; return (

Settings

Customize your music experience

Navidrome Server Configure connection to your Navidrome music server
handleInputChange('serverUrl', e.target.value)} />
handleInputChange('username', e.target.value)} />
handleInputChange('password', e.target.value)} />
{isConnected ? ( <> Connected to server ) : ( <> Not connected )}

Note: Your credentials are stored locally in your browser

Security: Always use HTTPS for your Navidrome server

Last.fm Integration Configure Last.fm scrobbling through your Navidrome server

How it works:

  • Tracks are scrobbled to Last.fm through your Navidrome server
  • Configure Last.fm credentials in your Navidrome admin panel
  • Scrobbling occurs when you listen to at least 30 seconds or half the track
  • "Now Playing" updates are sent when tracks start

Note: Last.fm credentials must be configured in Navidrome, not here.

{!isConnected && (
Connect to Navidrome first to enable scrobbling
)}
Sidebar Settings Customize sidebar appearance and behavior

Expanded: Shows full navigation labels

Collapsed: Shows only icons with tooltips

Note: You can also toggle the sidebar using the collapse button in the sidebar.

Standalone Last.fm Integration Direct Last.fm scrobbling without Navidrome configuration
{standaloneLastFmEnabled && ( <>
setLastFmCredentials(prev => ({ ...prev, apiKey: e.target.value }))} />
setLastFmCredentials(prev => ({ ...prev, apiSecret: e.target.value }))} />
{lastFmCredentials.sessionKey ? (
Authenticated as {lastFmCredentials.username}
) : (
Not authenticated
)}

Setup Instructions:

  1. Create a Last.fm API account at last.fm/api
  2. Enter your API key and secret above
  3. Save credentials and click "Authorize with Last.fm"
  4. Complete the authorization process

Features:

  • Direct scrobbling to Last.fm (independent of Navidrome)
  • "Now Playing" updates
  • Follows Last.fm scrobbling rules (30s minimum or 50% played)
)}
Appearance Customize how the application looks and feels

Theme: Choose between blue and violet color schemes

Dark Mode: Automatically follows your system preferences

{/* Theme Preview */} Preview See how your theme looks

Sample Song Title

Sample Artist

3:42
); }; export default SettingsPage;