Skip to content

Commit 7454799

Browse files
UI/UX Improvements and Bug Fixes (#138)
* Improve navigation layout: reduce spacing and move help icons inside nav buttons - Reduced horizontal spacing between nav items from sm:space-x-2 lg:space-x-8 to sm:space-x-1 - Moved ContextualHelpIcon components inside Button components for better UX - Removed unnecessary wrapper divs to create more compact layout - Help symbols are now part of the clickable nav area * Enhance stop/destroy button hover effects and colors - Added scaling and shadow hover effects to stop/start and destroy buttons - Enhanced color scheme with high-contrast colors: - Stop button: vibrant red (bg-red-600) with red glow shadow - Start button: vibrant green (bg-green-600) with green glow shadow - Destroy button: dark red (bg-red-800) with intense red glow shadow - Added colored borders and smooth transitions for better visual feedback - Improved button visibility and user experience with color-coded actions * Fix input field alignment in editing mode - Improved vertical alignment of script name and container ID input fields - Removed script path line during editing for cleaner interface - Added consistent min-height and flex centering for both input fields - Enhanced input styling with better padding and focus states - Input fields now align perfectly at the same height level * Improve button styling on available scripts page - Made download button smaller and less colorful with subtle blue theme - Updated Clear Selection and Select All Visible buttons to match Settings button styling - Changed buttons to outline variant with default size for consistency - Removed custom border styling to use standard outline appearance - Improved visual hierarchy and button cohesion across the interface * Reduce footer size by approximately 50% - Changed vertical padding from py-6 to py-3 (3rem to 1.5rem) - Reduced gaps between elements from gap-4 to gap-2 throughout - Made footer more compact while maintaining functionality - Improved screen space utilization for main content * Add tab persistence with localStorage - Implement localStorage persistence for active tab selection - Tab selection now survives page reloads and browser sessions - Added lazy initialization to read saved tab from localStorage - Added useEffect to automatically save tab changes to localStorage - Includes SSR safety checks for Next.js compatibility - Defaults to 'scripts' tab if no saved tab found * Clean up code: remove unused comments and variables - Remove unnecessary comments in InstalledScriptsTab - Clean up unused error variable in installedScripts router - Minor code cleanup for better readability
1 parent 892b3ae commit 7454799

File tree

5 files changed

+95
-85
lines changed

5 files changed

+95
-85
lines changed

src/app/_components/Footer.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ export function Footer({ onOpenReleaseNotes }: FooterProps) {
1212
const { data: versionData } = api.version.getCurrentVersion.useQuery();
1313

1414
return (
15-
<footer className="sticky bottom-0 mt-auto border-t border-border bg-muted/30 py-6 backdrop-blur-sm">
15+
<footer className="sticky bottom-0 mt-auto border-t border-border bg-muted/30 py-3 backdrop-blur-sm">
1616
<div className="container mx-auto px-4">
17-
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 text-sm text-muted-foreground">
18-
<div className="flex items-center gap-4">
17+
<div className="flex flex-col sm:flex-row items-center justify-between gap-2 text-sm text-muted-foreground">
18+
<div className="flex items-center gap-2">
1919
<span>© 2024 PVE Scripts Local</span>
2020
{versionData?.success && versionData.version && (
2121
<Button
@@ -29,7 +29,7 @@ export function Footer({ onOpenReleaseNotes }: FooterProps) {
2929
)}
3030
</div>
3131

32-
<div className="flex items-center gap-4">
32+
<div className="flex items-center gap-2">
3333
<Button
3434
variant="ghost"
3535
size="sm"

src/app/_components/InstalledScriptsTab.tsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ export function InstalledScriptsTab() {
322322
if (serverIds.length > 0) {
323323
containerStatusMutation.mutate({ serverIds });
324324
}
325-
}, []); // Empty dependency array to prevent infinite loops
325+
}, []);
326326

327327
// Run cleanup when component mounts and scripts are loaded (only once)
328328
useEffect(() => {
@@ -333,17 +333,12 @@ export function InstalledScriptsTab() {
333333
}, [scripts.length, serversData?.servers, cleanupMutation]);
334334

335335

336-
337-
// Note: Individual status fetching removed - using bulk fetchContainerStatuses instead
338-
339-
// Trigger status check when tab becomes active (component mounts)
340336
useEffect(() => {
341337
if (scripts.length > 0) {
342338
fetchContainerStatuses();
343339
}
344340
}, [scripts.length]); // Only depend on scripts.length to prevent infinite loops
345341

346-
// Update scripts with container statuses
347342
const scriptsWithStatus = scripts.map(script => ({
348343
...script,
349344
container_status: script.container_id ? containerStatuses.get(script.id) ?? 'unknown' : undefined
@@ -1067,15 +1062,14 @@ export function InstalledScriptsTab() {
10671062
>
10681063
<td className="px-6 py-4 whitespace-nowrap">
10691064
{editingScriptId === script.id ? (
1070-
<div className="space-y-2">
1065+
<div className="flex items-center min-h-[2.5rem]">
10711066
<input
10721067
type="text"
10731068
value={editFormData.script_name}
10741069
onChange={(e) => handleInputChange('script_name', e.target.value)}
1075-
className="w-full px-2 py-1 text-sm border border-border rounded bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
1070+
className="w-full px-3 py-2 text-sm font-medium border border-input rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring"
10761071
placeholder="Script name"
10771072
/>
1078-
<div className="text-xs text-muted-foreground">{script.script_path}</div>
10791073
</div>
10801074
) : (
10811075
<div>
@@ -1086,13 +1080,15 @@ export function InstalledScriptsTab() {
10861080
</td>
10871081
<td className="px-6 py-4 whitespace-nowrap">
10881082
{editingScriptId === script.id ? (
1089-
<input
1090-
type="text"
1091-
value={editFormData.container_id}
1092-
onChange={(e) => handleInputChange('container_id', e.target.value)}
1093-
className="w-full px-2 py-1 text-sm font-mono border border-border rounded bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
1094-
placeholder="Container ID"
1095-
/>
1083+
<div className="flex items-center min-h-[2.5rem]">
1084+
<input
1085+
type="text"
1086+
value={editFormData.container_id}
1087+
onChange={(e) => handleInputChange('container_id', e.target.value)}
1088+
className="w-full px-3 py-2 text-sm font-mono border border-input rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring"
1089+
placeholder="Container ID"
1090+
/>
1091+
</div>
10961092
) : (
10971093
script.container_id ? (
10981094
<div className="flex items-center space-x-2">
@@ -1187,6 +1183,10 @@ export function InstalledScriptsTab() {
11871183
disabled={controllingScriptId === script.id || (containerStatuses.get(script.id) ?? 'unknown') === 'unknown'}
11881184
variant={(containerStatuses.get(script.id) ?? 'unknown') === 'running' ? 'destructive' : 'default'}
11891185
size="sm"
1186+
className={(containerStatuses.get(script.id) ?? 'unknown') === 'running'
1187+
? "bg-red-600 hover:bg-red-700 text-white border border-red-500 hover:border-red-400 hover:scale-105 hover:shadow-lg hover:shadow-red-500/25 transition-all duration-200 disabled:hover:scale-100"
1188+
: "bg-green-600 hover:bg-green-700 text-white border border-green-500 hover:border-green-400 hover:scale-105 hover:shadow-lg hover:shadow-green-500/25 transition-all duration-200 disabled:hover:scale-100"
1189+
}
11901190
>
11911191
{controllingScriptId === script.id ? 'Working...' : (containerStatuses.get(script.id) ?? 'unknown') === 'running' ? 'Stop' : 'Start'}
11921192
</Button>
@@ -1195,6 +1195,7 @@ export function InstalledScriptsTab() {
11951195
disabled={controllingScriptId === script.id}
11961196
variant="destructive"
11971197
size="sm"
1198+
className="bg-red-800 hover:bg-red-900 text-white border border-red-600 hover:border-red-500 hover:scale-105 hover:shadow-lg hover:shadow-red-600/30 transition-all duration-200 disabled:hover:scale-100"
11981199
>
11991200
{controllingScriptId === script.id ? 'Working...' : 'Destroy'}
12001201
</Button>

src/app/_components/ScriptsGrid.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -626,11 +626,13 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
626626
<Button
627627
onClick={handleBatchDownload}
628628
disabled={loadSingleScriptMutation.isPending}
629-
className="bg-blue-600 hover:bg-blue-700 text-white"
629+
variant="outline"
630+
size="sm"
631+
className="bg-blue-500/10 hover:bg-blue-500/20 border-blue-500/30 text-blue-300 hover:text-blue-200 hover:border-blue-400/50"
630632
>
631633
{loadSingleScriptMutation.isPending ? (
632634
<>
633-
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
635+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-current mr-2"></div>
634636
Downloading...
635637
</>
636638
) : (
@@ -642,6 +644,7 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
642644
onClick={handleDownloadAllFiltered}
643645
disabled={filteredScripts.length === 0 || loadSingleScriptMutation.isPending}
644646
variant="outline"
647+
size="sm"
645648
>
646649
{loadSingleScriptMutation.isPending ? (
647650
<>
@@ -657,8 +660,8 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
657660
{selectedSlugs.size > 0 && (
658661
<Button
659662
onClick={clearSelection}
660-
variant="ghost"
661-
size="sm"
663+
variant="outline"
664+
size="default"
662665
>
663666
Clear Selection
664667
</Button>
@@ -667,8 +670,8 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
667670
{filteredScripts.length > 0 && (
668671
<Button
669672
onClick={selectAllVisible}
670-
variant="ghost"
671-
size="sm"
673+
variant="outline"
674+
size="default"
672675
>
673676
Select All Visible
674677
</Button>

src/app/page.tsx

Lines changed: 63 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ import { api } from '~/trpc/react';
2020

2121
export default function Home() {
2222
const [runningScript, setRunningScript] = useState<{ path: string; name: string; mode?: 'local' | 'ssh'; server?: any } | null>(null);
23-
const [activeTab, setActiveTab] = useState<'scripts' | 'downloaded' | 'installed'>('scripts');
23+
const [activeTab, setActiveTab] = useState<'scripts' | 'downloaded' | 'installed'>(() => {
24+
if (typeof window !== 'undefined') {
25+
const savedTab = localStorage.getItem('activeTab') as 'scripts' | 'downloaded' | 'installed';
26+
return savedTab || 'scripts';
27+
}
28+
return 'scripts';
29+
});
2430
const [releaseNotesOpen, setReleaseNotesOpen] = useState(false);
2531
const [highlightVersion, setHighlightVersion] = useState<string | undefined>(undefined);
2632
const terminalRef = useRef<HTMLDivElement>(null);
@@ -31,6 +37,13 @@ export default function Home() {
3137
const { data: installedScriptsData } = api.installedScripts.getAllInstalledScripts.useQuery();
3238
const { data: versionData } = api.version.getCurrentVersion.useQuery();
3339

40+
// Save active tab to localStorage whenever it changes
41+
useEffect(() => {
42+
if (typeof window !== 'undefined') {
43+
localStorage.setItem('activeTab', activeTab);
44+
}
45+
}, [activeTab]);
46+
3447
// Auto-show release notes modal after update
3548
useEffect(() => {
3649
if (versionData?.success && versionData.version) {
@@ -131,64 +144,58 @@ export default function Home() {
131144
{/* Tab Navigation */}
132145
<div className="mb-6 sm:mb-8">
133146
<div className="border-b border-border">
134-
<nav className="-mb-px flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 lg:space-x-8">
135-
<div className="flex items-center gap-2">
136-
<Button
137-
variant="ghost"
138-
size="null"
139-
onClick={() => setActiveTab('scripts')}
140-
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
141-
activeTab === 'scripts'
142-
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
143-
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
144-
}`}>
145-
<Package className="h-4 w-4" />
146-
<span className="hidden sm:inline">Available Scripts</span>
147-
<span className="sm:hidden">Available</span>
148-
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
149-
{scriptCounts.available}
150-
</span>
151-
</Button>
147+
<nav className="-mb-px flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-1">
148+
<Button
149+
variant="ghost"
150+
size="null"
151+
onClick={() => setActiveTab('scripts')}
152+
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
153+
activeTab === 'scripts'
154+
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
155+
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
156+
}`}>
157+
<Package className="h-4 w-4" />
158+
<span className="hidden sm:inline">Available Scripts</span>
159+
<span className="sm:hidden">Available</span>
160+
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
161+
{scriptCounts.available}
162+
</span>
152163
<ContextualHelpIcon section="available-scripts" tooltip="Help with Available Scripts" />
153-
</div>
154-
<div className="flex items-center gap-2">
155-
<Button
156-
variant="ghost"
157-
size="null"
158-
onClick={() => setActiveTab('downloaded')}
159-
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
160-
activeTab === 'downloaded'
161-
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
162-
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
163-
}`}>
164-
<HardDrive className="h-4 w-4" />
165-
<span className="hidden sm:inline">Downloaded Scripts</span>
166-
<span className="sm:hidden">Downloaded</span>
167-
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
168-
{scriptCounts.downloaded}
169-
</span>
170-
</Button>
164+
</Button>
165+
<Button
166+
variant="ghost"
167+
size="null"
168+
onClick={() => setActiveTab('downloaded')}
169+
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
170+
activeTab === 'downloaded'
171+
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
172+
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
173+
}`}>
174+
<HardDrive className="h-4 w-4" />
175+
<span className="hidden sm:inline">Downloaded Scripts</span>
176+
<span className="sm:hidden">Downloaded</span>
177+
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
178+
{scriptCounts.downloaded}
179+
</span>
171180
<ContextualHelpIcon section="downloaded-scripts" tooltip="Help with Downloaded Scripts" />
172-
</div>
173-
<div className="flex items-center gap-2">
174-
<Button
175-
variant="ghost"
176-
size="null"
177-
onClick={() => setActiveTab('installed')}
178-
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
179-
activeTab === 'installed'
180-
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
181-
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
182-
}`}>
183-
<FolderOpen className="h-4 w-4" />
184-
<span className="hidden sm:inline">Installed Scripts</span>
185-
<span className="sm:hidden">Installed</span>
186-
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
187-
{scriptCounts.installed}
188-
</span>
189-
</Button>
181+
</Button>
182+
<Button
183+
variant="ghost"
184+
size="null"
185+
onClick={() => setActiveTab('installed')}
186+
className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
187+
activeTab === 'installed'
188+
? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
189+
: 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
190+
}`}>
191+
<FolderOpen className="h-4 w-4" />
192+
<span className="hidden sm:inline">Installed Scripts</span>
193+
<span className="sm:hidden">Installed</span>
194+
<span className="ml-1 px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded-full">
195+
{scriptCounts.installed}
196+
</span>
190197
<ContextualHelpIcon section="installed-scripts" tooltip="Help with Installed Scripts" />
191-
</div>
198+
</Button>
192199
</nav>
193200
</div>
194201
</div>

src/server/api/routers/installedScripts.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -906,9 +906,8 @@ export const installedScriptsRouter = createTRPCRouter({
906906
);
907907
});
908908
}
909-
} catch (_error) {
910-
// If status check fails, continue with destroy attempt
911-
// The destroy command will handle the error appropriately
909+
} catch {
910+
912911
}
913912

914913
// Execute destroy command

0 commit comments

Comments
 (0)