From ec20e0322a773594494f57c53f277d33ea6321ad Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:35:53 +0200 Subject: [PATCH] Add dark mode support across UI (#33) * Add dark mode support across UI Introduces DarkModeProvider and DarkModeToggle components for theme management. Updates all major UI components and pages to support dark mode styling using Tailwind CSS dark variants, improving accessibility and user experience for users preferring dark themes. * Improve dark mode initialization and modal UI (#32) Adds a script to layout.tsx to set dark mode before hydration, preventing UI flicker. Refactors DarkModeProvider to initialize theme and dark state after mount. Updates ScriptDetailModal for improved readability, consistent styling, and better handling of script status, install methods, and notes. --- src/app/_components/DarkModeProvider.tsx | 83 +++ src/app/_components/DarkModeToggle.tsx | 66 +++ src/app/_components/InstalledScriptsTab.tsx | 59 +- src/app/_components/ResyncButton.tsx | 10 +- src/app/_components/ScriptCard.tsx | 22 +- src/app/_components/ScriptDetailModal.tsx | 605 +++++++++++++------- src/app/_components/ScriptsGrid.tsx | 6 +- src/app/_components/ServerForm.tsx | 36 +- src/app/_components/SettingsButton.tsx | 2 +- src/app/_components/SettingsModal.tsx | 32 +- src/app/layout.tsx | 35 +- src/app/page.tsx | 16 +- src/styles/globals.css | 2 + 13 files changed, 678 insertions(+), 296 deletions(-) create mode 100644 src/app/_components/DarkModeProvider.tsx create mode 100644 src/app/_components/DarkModeToggle.tsx diff --git a/src/app/_components/DarkModeProvider.tsx b/src/app/_components/DarkModeProvider.tsx new file mode 100644 index 0000000..2991bf7 --- /dev/null +++ b/src/app/_components/DarkModeProvider.tsx @@ -0,0 +1,83 @@ +'use client'; + +import { createContext, useContext, useEffect, useState } from 'react'; + +type Theme = 'light' | 'dark' | 'system'; + +interface DarkModeContextType { + theme: Theme; + setTheme: (theme: Theme) => void; + isDark: boolean; +} + +const DarkModeContext = createContext(undefined); + +export function DarkModeProvider({ children }: { children: React.ReactNode }) { + const [theme, setThemeState] = useState('system'); + const [isDark, setIsDark] = useState(false); + const [mounted, setMounted] = useState(false); + + // Initialize theme from localStorage after mount + useEffect(() => { + setMounted(true); + const stored = localStorage.getItem('theme') as Theme; + if (stored && ['light', 'dark', 'system'].includes(stored)) { + setThemeState(stored); + } + + // Set initial isDark state based on current DOM state + const currentlyDark = document.documentElement.classList.contains('dark'); + setIsDark(currentlyDark); + }, []); + + // Update dark mode state and DOM when theme changes + useEffect(() => { + if (!mounted) return; + + const updateDarkMode = () => { + const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + const shouldBeDark = theme === 'dark' || (theme === 'system' && systemDark); + + setIsDark(shouldBeDark); + + // Apply to document + if (shouldBeDark) { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + }; + + updateDarkMode(); + + // Listen for system theme changes + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handleChange = () => { + if (theme === 'system') { + updateDarkMode(); + } + }; + + mediaQuery.addEventListener('change', handleChange); + return () => mediaQuery.removeEventListener('change', handleChange); + }, [theme, mounted]); + + const setTheme = (newTheme: Theme) => { + setThemeState(newTheme); + localStorage.setItem('theme', newTheme); + }; + + return ( + + {children} + + ); +} + +export function useDarkMode() { + const context = useContext(DarkModeContext); + if (context === undefined) { + throw new Error('useDarkMode must be used within a DarkModeProvider'); + } + return context; +} \ No newline at end of file diff --git a/src/app/_components/DarkModeToggle.tsx b/src/app/_components/DarkModeToggle.tsx new file mode 100644 index 0000000..5cccd61 --- /dev/null +++ b/src/app/_components/DarkModeToggle.tsx @@ -0,0 +1,66 @@ +'use client'; + +import { useDarkMode } from './DarkModeProvider'; + +export function DarkModeToggle() { + const { theme, setTheme, isDark } = useDarkMode(); + + const toggleTheme = () => { + if (theme === 'light') { + setTheme('dark'); + } else if (theme === 'dark') { + setTheme('system'); + } else { + setTheme('light'); + } + }; + + const getIcon = () => { + if (theme === 'light') { + return ( + + + + ); + } else if (theme === 'dark') { + return ( + + + + ); + } else { + // System theme icon + return ( + + + + ); + } + }; + + const getLabel = () => { + if (theme === 'light') return 'Light mode'; + if (theme === 'dark') return 'Dark mode'; + return 'System theme'; + }; + + return ( + + ); +} \ No newline at end of file diff --git a/src/app/_components/InstalledScriptsTab.tsx b/src/app/_components/InstalledScriptsTab.tsx index 17f2f4a..2db0ea9 100644 --- a/src/app/_components/InstalledScriptsTab.tsx +++ b/src/app/_components/InstalledScriptsTab.tsx @@ -119,7 +119,7 @@ export function InstalledScriptsTab() { case 'in_progress': return `${baseClasses} bg-yellow-100 text-yellow-800`; default: - return `${baseClasses} bg-gray-100 text-gray-800`; + return `${baseClasses} bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200`; } }; @@ -131,14 +131,14 @@ export function InstalledScriptsTab() { case 'ssh': return `${baseClasses} bg-purple-100 text-purple-800`; default: - return `${baseClasses} bg-gray-100 text-gray-800`; + return `${baseClasses} bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200`; } }; if (isLoading) { return (
-
Loading installed scripts...
+
Loading installed scripts...
); } @@ -160,8 +160,8 @@ export function InstalledScriptsTab() { )} {/* Header with Stats */} -
-

Installed Scripts

+
+

Installed Scripts

{stats && (
@@ -192,14 +192,14 @@ export function InstalledScriptsTab() { placeholder="Search scripts, container IDs, or servers..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400" />
setServerFilter(e.target.value)} - className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400" > @@ -222,60 +222,57 @@ export function InstalledScriptsTab() {
{/* Scripts Table */} -
+
{filteredScripts.length === 0 ? ( -
+
{scripts.length === 0 ? 'No installed scripts found.' : 'No scripts match your filters.'}
) : (
- - +
+ - - - - - - - - + {filteredScripts.map((script) => ( @@ -289,7 +286,7 @@ export function InstalledScriptsTab() { {String(script.status).replace('_', ' ').toUpperCase()} -
+ Script Name + Container ID + Server - Mode - + Status - Date + + Installation Date + Actions
-
{script.script_name}
-
{script.script_path}
+
{script.script_name}
+
{script.script_path}
{script.container_id ? ( - {String(script.container_id)} + {String(script.container_id)} ) : ( - - + - )} {script.execution_mode === 'local' ? ( - Local + Local ) : (
-
{script.server_name}
-
{script.server_ip}
+
{script.server_name}
+
{script.server_ip}
)}
+ {formatDate(String(script.installation_date))} diff --git a/src/app/_components/ResyncButton.tsx b/src/app/_components/ResyncButton.tsx index 7ceb67a..fb02882 100644 --- a/src/app/_components/ResyncButton.tsx +++ b/src/app/_components/ResyncButton.tsx @@ -44,8 +44,8 @@ export function ResyncButton() { disabled={isResyncing} className={`flex items-center space-x-2 px-4 py-2 rounded-lg font-medium transition-colors ${ isResyncing - ? 'bg-gray-400 text-white cursor-not-allowed' - : 'bg-blue-600 text-white hover:bg-blue-700' + ? 'bg-gray-400 dark:bg-gray-600 text-white cursor-not-allowed' + : 'bg-blue-600 dark:bg-blue-700 text-white hover:bg-blue-700 dark:hover:bg-blue-600' }`} > {isResyncing ? ( @@ -64,7 +64,7 @@ export function ResyncButton() { {lastSync && ( -
+
Last sync: {lastSync.toLocaleTimeString()}
)} @@ -72,8 +72,8 @@ export function ResyncButton() { {syncMessage && (
{syncMessage}
diff --git a/src/app/_components/ScriptCard.tsx b/src/app/_components/ScriptCard.tsx index c3bb58f..85ad8c7 100644 --- a/src/app/_components/ScriptCard.tsx +++ b/src/app/_components/ScriptCard.tsx @@ -18,7 +18,7 @@ export function ScriptCard({ script, onClick }: ScriptCardProps) { return (
onClick(script)} >
@@ -35,15 +35,15 @@ export function ScriptCard({ script, onClick }: ScriptCardProps) { onError={handleImageError} /> ) : ( -
- +
+ {script.name?.charAt(0)?.toUpperCase() || '?'}
)}
-

+

{script.name || 'Unnamed Script'}

@@ -51,15 +51,15 @@ export function ScriptCard({ script, onClick }: ScriptCardProps) {
{script.type?.toUpperCase() || 'UNKNOWN'} {script.updateable && ( - + Updateable )} @@ -71,7 +71,7 @@ export function ScriptCard({ script, onClick }: ScriptCardProps) { script.isDownloaded ? 'bg-green-500' : 'bg-red-500' }`}>
{script.isDownloaded ? 'Downloaded' : 'Not Downloaded'} @@ -81,7 +81,7 @@ export function ScriptCard({ script, onClick }: ScriptCardProps) {
{/* Description */} -

+

{script.description || 'No description available'}

@@ -92,7 +92,7 @@ export function ScriptCard({ script, onClick }: ScriptCardProps) { href={script.website} target="_blank" rel="noopener noreferrer" - className="text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center space-x-1" + className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 text-sm font-medium flex items-center space-x-1" onClick={(e) => e.stopPropagation()} > Website diff --git a/src/app/_components/ScriptDetailModal.tsx b/src/app/_components/ScriptDetailModal.tsx index 07fae89..89ccbf8 100644 --- a/src/app/_components/ScriptDetailModal.tsx +++ b/src/app/_components/ScriptDetailModal.tsx @@ -1,21 +1,31 @@ -'use client'; +"use client"; -import { useState } from 'react'; -import Image from 'next/image'; -import { api } from '~/trpc/react'; -import type { Script } from '~/types/script'; -import { DiffViewer } from './DiffViewer'; -import { TextViewer } from './TextViewer'; -import { ExecutionModeModal } from './ExecutionModeModal'; +import { useState } from "react"; +import Image from "next/image"; +import { api } from "~/trpc/react"; +import type { Script } from "~/types/script"; +import { DiffViewer } from "./DiffViewer"; +import { TextViewer } from "./TextViewer"; +import { ExecutionModeModal } from "./ExecutionModeModal"; interface ScriptDetailModalProps { script: Script | null; isOpen: boolean; onClose: () => void; - onInstallScript?: (scriptPath: string, scriptName: string, mode?: 'local' | 'ssh', server?: any) => void; + onInstallScript?: ( + scriptPath: string, + scriptName: string, + mode?: "local" | "ssh", + server?: any, + ) => void; } -export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }: ScriptDetailModalProps) { +export function ScriptDetailModal({ + script, + isOpen, + onClose, + onInstallScript, +}: ScriptDetailModalProps) { const [imageError, setImageError] = useState(false); const [isLoading, setIsLoading] = useState(false); const [loadMessage, setLoadMessage] = useState(null); @@ -25,15 +35,23 @@ export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }: const [executionModeOpen, setExecutionModeOpen] = useState(false); // Check if script files exist locally - const { data: scriptFilesData, refetch: refetchScriptFiles, isLoading: scriptFilesLoading } = api.scripts.checkScriptFiles.useQuery( - { slug: script?.slug ?? '' }, - { enabled: !!script && isOpen } + const { + data: scriptFilesData, + refetch: refetchScriptFiles, + isLoading: scriptFilesLoading, + } = api.scripts.checkScriptFiles.useQuery( + { slug: script?.slug ?? "" }, + { enabled: !!script && isOpen }, ); // Compare local and remote script content (run in parallel, not dependent on scriptFilesData) - const { data: comparisonData, refetch: refetchComparison, isLoading: comparisonLoading } = api.scripts.compareScriptContent.useQuery( - { slug: script?.slug ?? '' }, - { enabled: !!script && isOpen } + const { + data: comparisonData, + refetch: refetchComparison, + isLoading: comparisonLoading, + } = api.scripts.compareScriptContent.useQuery( + { slug: script?.slug ?? "" }, + { enabled: !!script && isOpen }, ); // Load script mutation @@ -41,13 +59,14 @@ export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }: onSuccess: (data) => { setIsLoading(false); if (data.success) { - const message = 'message' in data ? data.message : 'Script loaded successfully'; + const message = + "message" in data ? data.message : "Script loaded successfully"; setLoadMessage(`✅ ${message}`); // Refetch script files status and comparison data to update the UI void refetchScriptFiles(); void refetchComparison(); } else { - const error = 'error' in data ? data.error : 'Failed to load script'; + const error = "error" in data ? data.error : "Failed to load script"; setLoadMessage(`❌ ${error}`); } // Clear message after 5 seconds @@ -74,7 +93,7 @@ export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }: const handleLoadScript = async () => { if (!script) return; - + setIsLoading(true); setLoadMessage(null); loadScriptMutation.mutate({ slug: script.slug }); @@ -85,38 +104,39 @@ export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }: setExecutionModeOpen(true); }; - const handleExecuteScript = (mode: 'local' | 'ssh', server?: any) => { + const handleExecuteScript = (mode: "local" | "ssh", server?: any) => { if (!script || !onInstallScript) return; - + // Find the script path (CT or tools) - const scriptMethod = script.install_methods?.find(method => method.script); + const scriptMethod = script.install_methods?.find( + (method) => method.script, + ); if (scriptMethod?.script) { const scriptPath = `scripts/${scriptMethod.script}`; const scriptName = script.name; - + // Pass execution mode and server info to the parent onInstallScript(scriptPath, scriptName, mode, server); - + // Scroll to top of the page to see the terminal - window.scrollTo({ top: 0, behavior: 'smooth' }); - + window.scrollTo({ top: 0, behavior: "smooth" }); + onClose(); // Close the modal when starting installation } }; - const handleViewScript = () => { setTextViewerOpen(true); }; return (
-
+
{/* Header */} -
+
{script.logo && !imageError ? ( {`${script.name} ) : ( -
- +
+ {script.name.charAt(0).toUpperCase()}
)}
-

{script.name}

-
- +

+ {script.name} +

+
+ {script.type.toUpperCase()} {script.updateable && ( - + Updateable )} {script.privileged && ( - + Privileged )} @@ -159,59 +183,100 @@ export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }:
{/* Install Button - only show if script files exist */} - {scriptFilesData?.success && scriptFilesData.ctExists && onInstallScript && ( - - )} + {scriptFilesData?.success && + scriptFilesData.ctExists && + onInstallScript && ( + + )} {/* View Button - only show if script files exist */} - {scriptFilesData?.success && (scriptFilesData.ctExists || scriptFilesData.installExists) && ( - - )} - + {scriptFilesData?.success && + (scriptFilesData.ctExists || scriptFilesData.installExists) && ( + + )} + {/* Load/Update Script Button */} {(() => { - const hasLocalFiles = scriptFilesData?.success && (scriptFilesData.ctExists || scriptFilesData.installExists); - const hasDifferences = comparisonData?.success && comparisonData.hasDifferences; + const hasLocalFiles = + scriptFilesData?.success && + (scriptFilesData.ctExists || scriptFilesData.installExists); + const hasDifferences = + comparisonData?.success && comparisonData.hasDifferences; const isUpToDate = hasLocalFiles && !hasDifferences; - + if (!hasLocalFiles) { // No local files - show Load Script button return ( @@ -237,21 +312,31 @@ export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }:
@@ -273,114 +368,169 @@ export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }: {/* Load Message */} {loadMessage && ( -
+
{loadMessage}
)} {/* Script Files Status */} {(scriptFilesLoading || comparisonLoading) && ( -
+
-
+
Loading script status...
)} - - {scriptFilesData?.success && !scriptFilesLoading && (() => { - // Determine script type from the first install method - const firstScript = script?.install_methods?.[0]?.script; - let scriptType = 'Script'; - if (firstScript?.startsWith('ct/')) { - scriptType = 'CT Script'; - } else if (firstScript?.startsWith('tools/')) { - scriptType = 'Tools Script'; - } else if (firstScript?.startsWith('vm/')) { - scriptType = 'VM Script'; - } else if (firstScript?.startsWith('vw/')) { - scriptType = 'VW Script'; - } - return ( -
-
-
-
- {scriptType}: {scriptFilesData.ctExists ? 'Available' : 'Not loaded'} -
-
-
- Install Script: {scriptFilesData.installExists ? 'Available' : 'Not loaded'} -
- {scriptFilesData?.success && (scriptFilesData.ctExists || scriptFilesData.installExists) && comparisonData?.success && !comparisonLoading && ( + {scriptFilesData?.success && + !scriptFilesLoading && + (() => { + // Determine script type from the first install method + const firstScript = script?.install_methods?.[0]?.script; + let scriptType = "Script"; + if (firstScript?.startsWith("ct/")) { + scriptType = "CT Script"; + } else if (firstScript?.startsWith("tools/")) { + scriptType = "Tools Script"; + } else if (firstScript?.startsWith("vm/")) { + scriptType = "VM Script"; + } else if (firstScript?.startsWith("vw/")) { + scriptType = "VW Script"; + } + + return ( +
+
-
- Status: {comparisonData.hasDifferences ? 'Update available' : 'Up to date'} +
+ + {scriptType}:{" "} + {scriptFilesData.ctExists ? "Available" : "Not loaded"} + +
+
+
+ + Install Script:{" "} + {scriptFilesData.installExists + ? "Available" + : "Not loaded"} + +
+ {scriptFilesData?.success && + (scriptFilesData.ctExists || + scriptFilesData.installExists) && + comparisonData?.success && + !comparisonLoading && ( +
+
+ + Status:{" "} + {comparisonData.hasDifferences + ? "Update available" + : "Up to date"} + +
+ )} +
+ {scriptFilesData.files.length > 0 && ( +
+ Files: {scriptFilesData.files.join(", ")}
)}
- {scriptFilesData.files.length > 0 && ( -
- Files: {scriptFilesData.files.join(', ')} -
- )} -
- ); - })()} + ); + })()} {/* Content */} -
+
{/* Description */}
-

Description

-

{script.description}

+

+ Description +

+

+ {script.description} +

{/* Basic Information */} -
+
-

Basic Information

+

+ Basic Information +

-
Slug
-
{script.slug}
+
+ Slug +
+
+ {script.slug} +
-
Date Created
-
{script.date_created}
+
+ Date Created +
+
+ {script.date_created} +
-
Categories
-
{script.categories.join(', ')}
+
+ Categories +
+
+ {script.categories.join(", ")} +
{script.interface_port && (
-
Interface Port
-
{script.interface_port}
+
+ Interface Port +
+
+ {script.interface_port} +
)} {script.config_path && (
-
Config Path
-
{script.config_path}
+
+ Config Path +
+
+ {script.config_path} +
)}
-

Links

+

+ Links +

{script.website && (
-
Website
+
+ Website +
{script.website} @@ -389,13 +539,15 @@ export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }: )} {script.documentation && (
-
Documentation
+
+ Documentation +
{script.documentation} @@ -406,56 +558,94 @@ export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }:
- {/* Install Methods */} - {script.install_methods.length > 0 && ( -
-

Install Methods

-
- {script.install_methods.map((method, index) => ( -
-
-

{method.type}

- {method.script} -
-
-
-
CPU
-
{method.resources.cpu} cores
+ {/* Install Methods - Hide for PVE and ADDON types as they typically don't have install methods */} + {script.install_methods.length > 0 && + script.type !== "pve" && + script.type !== "addon" && ( +
+

+ Install Methods +

+
+ {script.install_methods.map((method, index) => ( +
+
+

+ {method.type} +

+ + {method.script} +
-
-
RAM
-
{method.resources.ram} MB
-
-
-
HDD
-
{method.resources.hdd} GB
-
-
-
OS
-
{method.resources.os} {method.resources.version}
+
+
+
+ CPU +
+
+ {method.resources.cpu} cores +
+
+
+
+ RAM +
+
+ {method.resources.ram} MB +
+
+
+
+ HDD +
+
+ {method.resources.hdd} GB +
+
+
+
+ OS +
+
+ {method.resources.os} {method.resources.version} +
+
-
- ))} + ))} +
-
- )} + )} {/* Default Credentials */} - {(script.default_credentials.username ?? script.default_credentials.password) && ( + {(script.default_credentials.username ?? + script.default_credentials.password) && (
-

Default Credentials

+

+ Default Credentials +

{script.default_credentials.username && (
-
Username
-
{script.default_credentials.username}
+
+ Username +
+
+ {script.default_credentials.username} +
)} {script.default_credentials.password && (
-
Password
-
{script.default_credentials.password}
+
+ Password +
+
+ {script.default_credentials.password} +
)}
@@ -465,29 +655,37 @@ export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }: {/* Notes */} {script.notes.length > 0 && (
-

Notes

+

+ Notes +

    {script.notes.map((note, index) => { // Handle both object and string note formats - const noteText = typeof note === 'string' ? note : note.text; - const noteType = typeof note === 'string' ? 'info' : note.type; - + const noteText = typeof note === "string" ? note : note.text; + const noteType = + typeof note === "string" ? "info" : note.type; + return ( -
  • +
  • - + {noteType} {noteText} @@ -517,7 +715,12 @@ export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript }: {/* Text Viewer Modal */} {script && ( method.script?.startsWith('ct/'))?.script?.split('/').pop() ?? `${script.slug}.sh`} + scriptName={ + script.install_methods + ?.find((method) => method.script?.startsWith("ct/")) + ?.script?.split("/") + .pop() ?? `${script.slug}.sh` + } isOpen={textViewerOpen} onClose={() => setTextViewerOpen(false)} /> diff --git a/src/app/_components/ScriptsGrid.tsx b/src/app/_components/ScriptsGrid.tsx index 6d7dae4..30a56b2 100644 --- a/src/app/_components/ScriptsGrid.tsx +++ b/src/app/_components/ScriptsGrid.tsx @@ -155,7 +155,7 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
    - +
    @@ -164,12 +164,12 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) { placeholder="Search scripts by name..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} - className="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-lg leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm" + className="block w-full pl-10 pr-3 py-3 border border-gray-300 dark:border-gray-600 rounded-lg leading-5 bg-white dark:bg-gray-800 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-gray-100 focus:outline-none focus:placeholder-gray-400 dark:focus:placeholder-gray-300 focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-blue-500 dark:focus:border-blue-400 text-sm" /> {searchQuery && ( )} diff --git a/src/app/_components/SettingsButton.tsx b/src/app/_components/SettingsButton.tsx index 0d26422..9b16cb5 100644 --- a/src/app/_components/SettingsButton.tsx +++ b/src/app/_components/SettingsButton.tsx @@ -10,7 +10,7 @@ export function SettingsButton() { <>