'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 (
Customize your music experience
Note: Your credentials are stored locally in your browser
Security: Always use HTTPS for your Navidrome server
How it works:
Note: Last.fm credentials must be configured in Navidrome, not here.
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.
Setup Instructions:
Features:
Theme: Choose between blue and violet color schemes
Dark Mode: Automatically follows your system preferences
Sample Song Title
Sample Artist