diff --git a/.env.local b/.env.local index 2ae4e13..fd84c2e 100644 --- a/.env.local +++ b/.env.local @@ -1 +1 @@ -NEXT_PUBLIC_COMMIT_SHA=f721213 +NEXT_PUBLIC_COMMIT_SHA=f0f3d5a diff --git a/app/components/RootLayoutClient.tsx b/app/components/RootLayoutClient.tsx index 69ae609..72a5cf9 100644 --- a/app/components/RootLayoutClient.tsx +++ b/app/components/RootLayoutClient.tsx @@ -14,19 +14,56 @@ import Image from "next/image"; function NavidromeErrorBoundary({ children }: { children: React.ReactNode }) { const { error } = useNavidrome(); - if (error) { + + // Check if this is a first-time user + const hasCompletedOnboarding = typeof window !== 'undefined' + ? localStorage.getItem('onboarding-completed') + : false; + + // Simple check: has config in localStorage or environment + const hasAnyConfig = React.useMemo(() => { + if (typeof window === 'undefined') return false; + + // Check localStorage config + const savedConfig = localStorage.getItem('navidrome-config'); + if (savedConfig) { + try { + const config = JSON.parse(savedConfig); + if (config.serverUrl && config.username && config.password) { + return true; + } + } catch (e) { + // Invalid config, continue to env check + } + } + + // Check environment variables (visible on client side with NEXT_PUBLIC_) + if (process.env.NEXT_PUBLIC_NAVIDROME_URL && + process.env.NEXT_PUBLIC_NAVIDROME_USERNAME && + process.env.NEXT_PUBLIC_NAVIDROME_PASSWORD) { + return true; + } + + return false; + }, []); + + // Show start screen ONLY if: + // 1. First-time user (no onboarding completed), OR + // 2. User has completed onboarding BUT there's an error AND no config exists + const shouldShowStartScreen = !hasCompletedOnboarding || (hasCompletedOnboarding && error && !hasAnyConfig); + + if (shouldShowStartScreen) { return (
diff --git a/app/components/start-screen.tsx b/app/components/start-screen.tsx
index ed71031..6350b32 100644
--- a/app/components/start-screen.tsx
+++ b/app/components/start-screen.tsx
@@ -1,6 +1,6 @@
'use client';
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
@@ -13,16 +13,18 @@ import {
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '@/components/ui/select';
+import { Badge } from '@/components/ui/badge';
import { useNavidromeConfig } from '@/app/components/NavidromeConfigContext';
import { useTheme } from '@/app/components/ThemeProvider';
import { useToast } from '@/hooks/use-toast';
-import { FaServer, FaUser, FaLock, FaCheck, FaTimes, FaPalette, FaLastfm } from 'react-icons/fa';
+import { FaServer, FaUser, FaLock, FaCheck, FaTimes, FaPalette, FaLastfm, FaBars } from 'react-icons/fa';
export function LoginForm({
className,
...props
}: React.ComponentProps<"div">) {
const [step, setStep] = useState<'login' | 'settings'>('login');
+ const [canSkipNavidrome, setCanSkipNavidrome] = useState(false);
const { config, updateConfig, testConnection } = useNavidromeConfig();
const { theme, setTheme } = useTheme();
const { toast } = useToast();
@@ -43,6 +45,85 @@ export function LoginForm({
return true;
});
+ // New settings
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
+ if (typeof window !== 'undefined') {
+ return localStorage.getItem('sidebar-collapsed') === 'true';
+ }
+ return false;
+ });
+
+ const [standaloneLastfmEnabled, setStandaloneLastfmEnabled] = useState(() => {
+ if (typeof window !== 'undefined') {
+ return localStorage.getItem('standalone-lastfm-enabled') === 'true';
+ }
+ return false;
+ });
+
+ // Check if Navidrome is configured via environment variables
+ const hasEnvConfig = React.useMemo(() => {
+ return !!(process.env.NEXT_PUBLIC_NAVIDROME_URL &&
+ process.env.NEXT_PUBLIC_NAVIDROME_USERNAME &&
+ process.env.NEXT_PUBLIC_NAVIDROME_PASSWORD);
+ }, []);
+
+ // Check if Navidrome is already working on component mount
+ useEffect(() => {
+ checkNavidromeConnection();
+ }, []);
+
+ const checkNavidromeConnection = async () => {
+ try {
+ // First check if there's a working API instance
+ const { getNavidromeAPI } = await import('@/lib/navidrome');
+ const api = getNavidromeAPI();
+
+ if (api) {
+ // Test the existing API
+ const success = await api.ping();
+ if (success) {
+ setCanSkipNavidrome(true);
+
+ // Get the current config to populate form
+ if (config.serverUrl && config.username && config.password) {
+ setFormData({
+ serverUrl: config.serverUrl,
+ username: config.username,
+ password: config.password
+ });
+ }
+
+ // If this is first-time setup and Navidrome is working, skip to settings
+ const hasCompletedOnboarding = localStorage.getItem('onboarding-completed');
+ if (!hasCompletedOnboarding) {
+ setStep('settings');
+ }
+ return;
+ }
+ }
+
+ // If no working API, check if we have config that just needs testing
+ if (config.serverUrl && config.username && config.password) {
+ const success = await testConnection(config);
+ if (success) {
+ setCanSkipNavidrome(true);
+ setFormData({
+ serverUrl: config.serverUrl,
+ username: config.username,
+ password: config.password
+ });
+
+ const hasCompletedOnboarding = localStorage.getItem('onboarding-completed');
+ if (!hasCompletedOnboarding) {
+ setStep('settings');
+ }
+ }
+ }
+ } catch (error) {
+ console.log('Navidrome connection check failed, will show config step');
+ }
+ };
+
const handleInputChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
@@ -104,8 +185,13 @@ export function LoginForm({
};
const handleFinishSetup = () => {
- // Save scrobbling preference
+ // Save all settings
localStorage.setItem('lastfm-scrobbling-enabled', scrobblingEnabled.toString());
+ localStorage.setItem('sidebar-collapsed', sidebarCollapsed.toString());
+ localStorage.setItem('standalone-lastfm-enabled', standaloneLastfmEnabled.toString());
+
+ // Mark onboarding as complete
+ localStorage.setItem('onboarding-completed', '1.1.0');
toast({
title: "Setup Complete",
@@ -126,7 +212,9 @@ export function LoginForm({
+ You can always toggle this later using the button in the sidebar +
++ {standaloneLastfmEnabled + ? "Direct Last.fm API integration (configure in Settings later)" + : "Use only Navidrome's Last.fm integration"} +
+Customize your music experience
-Configured via Environment Variables
+Server: {process.env.NEXT_PUBLIC_NAVIDROME_URL}
+Username: {process.env.NEXT_PUBLIC_NAVIDROME_USERNAME}
++ Your Navidrome connection is configured through environment variables. + Contact your administrator to change these settings. +
++ Re-run the initial setup wizard to configure your preferences from scratch +
+