From a92968e89c7f52121b6908a3e71517b7bf7e1990 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 15:16:02 +0200 Subject: [PATCH 1/2] feat: Add settings modal with GitHub PAT and filter toggle - Add GeneralSettingsModal with General and GitHub tabs - Create GitHub PAT input field that saves to .env as GITHUB_TOKEN - Add animated toggle component for SAVE_FILTER setting - Create API endpoints for settings management - Add Input and Toggle UI components - Implement smooth animations for toggle interactions - Add proper error handling and user feedback --- src/app/_components/GeneralSettingsModal.tsx | 241 +++++++++++++++++++ src/app/_components/ServerSettingsButton.tsx | 50 ++++ src/app/_components/SettingsButton.tsx | 33 +-- src/app/_components/SettingsModal.tsx | 77 ++---- src/app/_components/ui/input.tsx | 24 ++ src/app/_components/ui/toggle.tsx | 41 ++++ src/app/api/settings/github-token/route.ts | 72 ++++++ src/app/api/settings/save-filter/route.ts | 72 ++++++ src/app/page.tsx | 12 +- 9 files changed, 531 insertions(+), 91 deletions(-) create mode 100644 src/app/_components/GeneralSettingsModal.tsx create mode 100644 src/app/_components/ServerSettingsButton.tsx create mode 100644 src/app/_components/ui/input.tsx create mode 100644 src/app/_components/ui/toggle.tsx create mode 100644 src/app/api/settings/github-token/route.ts create mode 100644 src/app/api/settings/save-filter/route.ts diff --git a/src/app/_components/GeneralSettingsModal.tsx b/src/app/_components/GeneralSettingsModal.tsx new file mode 100644 index 0000000..4235544 --- /dev/null +++ b/src/app/_components/GeneralSettingsModal.tsx @@ -0,0 +1,241 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Button } from './ui/button'; +import { Input } from './ui/input'; +import { Toggle } from './ui/toggle'; + +interface GeneralSettingsModalProps { + isOpen: boolean; + onClose: () => void; +} + +export function GeneralSettingsModal({ isOpen, onClose }: GeneralSettingsModalProps) { + const [activeTab, setActiveTab] = useState<'general' | 'github'>('general'); + const [githubToken, setGithubToken] = useState(''); + const [saveFilter, setSaveFilter] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); + + // Load existing settings when modal opens + useEffect(() => { + if (isOpen) { + loadGithubToken(); + loadSaveFilter(); + } + }, [isOpen]); + + const loadGithubToken = async () => { + setIsLoading(true); + try { + const response = await fetch('/api/settings/github-token'); + if (response.ok) { + const data = await response.json(); + setGithubToken(data.token || ''); + } + } catch (error) { + console.error('Error loading GitHub token:', error); + } finally { + setIsLoading(false); + } + }; + + const loadSaveFilter = async () => { + try { + const response = await fetch('/api/settings/save-filter'); + if (response.ok) { + const data = await response.json(); + setSaveFilter(data.enabled || false); + } + } catch (error) { + console.error('Error loading save filter setting:', error); + } + }; + + const saveSaveFilter = async (enabled: boolean) => { + try { + const response = await fetch('/api/settings/save-filter', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ enabled }), + }); + + if (response.ok) { + setSaveFilter(enabled); + setMessage({ type: 'success', text: 'Save filter setting updated!' }); + } else { + const errorData = await response.json(); + setMessage({ type: 'error', text: errorData.error || 'Failed to save setting' }); + } + } catch (error) { + setMessage({ type: 'error', text: 'Failed to save setting' }); + } + }; + + const saveGithubToken = async () => { + setIsSaving(true); + setMessage(null); + + try { + const response = await fetch('/api/settings/github-token', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ token: githubToken }), + }); + + if (response.ok) { + setMessage({ type: 'success', text: 'GitHub token saved successfully!' }); + } else { + const errorData = await response.json(); + setMessage({ type: 'error', text: errorData.error || 'Failed to save token' }); + } + } catch (error) { + setMessage({ type: 'error', text: 'Failed to save token' }); + } finally { + setIsSaving(false); + } + }; + + if (!isOpen) return null; + + return ( +
+
+ {/* Header */} +
+

Settings

+ +
+ + {/* Tabs */} +
+ +
+ + {/* Content */} +
+ {activeTab === 'general' && ( +
+ +

General Settings

+

+ Configure general application preferences and behavior. +

+
+
+

Save Filters

+

Save your configured script filters.

+ +
+
+
+ )} + + {activeTab === 'github' && ( +
+
+

GitHub Integration

+

+ Configure GitHub integration for script management and updates. +

+
+
+

GitHub Personal Access Token

+

Save a GitHub Personal Access Token to circumvent GitHub API rate limits.

+ +
+
+ + ) => setGithubToken(e.target.value)} + disabled={isLoading || isSaving} + className="w-full" + /> +
+ + {message && ( +
+ {message.text} +
+ )} + +
+ + +
+
+
+
+
+
+ )} +
+
+
+ ); +} diff --git a/src/app/_components/ServerSettingsButton.tsx b/src/app/_components/ServerSettingsButton.tsx new file mode 100644 index 0000000..ccbafd3 --- /dev/null +++ b/src/app/_components/ServerSettingsButton.tsx @@ -0,0 +1,50 @@ +'use client'; + +import { useState } from 'react'; +import { SettingsModal } from './SettingsModal'; +import { Button } from './ui/button'; + +export function ServerSettingsButton() { + const [isOpen, setIsOpen] = useState(false); + + return ( + <> +
+
+ Add and manage PVE Servers: +
+ +
+ + setIsOpen(false)} /> + + ); +} diff --git a/src/app/_components/SettingsButton.tsx b/src/app/_components/SettingsButton.tsx index 2b57125..88a21f2 100644 --- a/src/app/_components/SettingsButton.tsx +++ b/src/app/_components/SettingsButton.tsx @@ -1,8 +1,9 @@ 'use client'; import { useState } from 'react'; -import { SettingsModal } from './SettingsModal'; +import { GeneralSettingsModal } from './GeneralSettingsModal'; import { Button } from './ui/button'; +import { Settings } from 'lucide-react'; export function SettingsButton() { const [isOpen, setIsOpen] = useState(false); @@ -11,41 +12,21 @@ export function SettingsButton() { <>
- Add and manage PVE Servers: + Application Settings:
- setIsOpen(false)} /> + setIsOpen(false)} /> ); } - diff --git a/src/app/_components/SettingsModal.tsx b/src/app/_components/SettingsModal.tsx index 5a9edf6..f94a789 100644 --- a/src/app/_components/SettingsModal.tsx +++ b/src/app/_components/SettingsModal.tsx @@ -15,7 +15,6 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { const [servers, setServers] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [activeTab, setActiveTab] = useState<'servers' | 'general'>('servers'); useEffect(() => { if (isOpen) { @@ -116,35 +115,6 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { - {/* Tabs */} -
- -
{/* Content */}
@@ -164,37 +134,28 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
)} - {activeTab === 'servers' && ( -
-
-

Server Configurations

- -
- -
-

Saved Servers

- {loading ? ( -
-
-

Loading servers...

-
- ) : ( - - )} -
+
+
+

Server Configurations

+
- )} - - {activeTab === 'general' && ( +
-

General Settings

-

General settings will be available in a future update.

+

Saved Servers

+ {loading ? ( +
+
+

Loading servers...

+
+ ) : ( + + )}
- )} +
diff --git a/src/app/_components/ui/input.tsx b/src/app/_components/ui/input.tsx new file mode 100644 index 0000000..0900e9d --- /dev/null +++ b/src/app/_components/ui/input.tsx @@ -0,0 +1,24 @@ +import * as React from "react" +import { cn } from "../../../lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/src/app/_components/ui/toggle.tsx b/src/app/_components/ui/toggle.tsx new file mode 100644 index 0000000..bd8d3f2 --- /dev/null +++ b/src/app/_components/ui/toggle.tsx @@ -0,0 +1,41 @@ +import * as React from "react" +import { cn } from "../../../lib/utils" + +export interface ToggleProps + extends Omit, 'type'> { + checked?: boolean; + onCheckedChange?: (checked: boolean) => void; + label?: string; +} + +const Toggle = React.forwardRef( + ({ className, checked, onCheckedChange, label, ...props }, ref) => { + return ( +
+