From 0b568a3b3755ee7b5cf19a315a6624a7b8dd8088 Mon Sep 17 00:00:00 2001 From: Arunavo Ray Date: Sat, 24 May 2025 11:31:40 +0530 Subject: [PATCH] feat: implement auto-save functionality for schedule config changes and enhance UI with loading indicator --- src/components/config/ConfigTabs.tsx | 65 +++++++++++++++++++- src/components/config/ScheduleConfigForm.tsx | 29 ++++++--- 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/src/components/config/ConfigTabs.tsx b/src/components/config/ConfigTabs.tsx index 3ec3094..1edd509 100644 --- a/src/components/config/ConfigTabs.tsx +++ b/src/components/config/ConfigTabs.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback, useRef } from 'react'; import { GitHubConfigForm } from './GitHubConfigForm'; import { GiteaConfigForm } from './GiteaConfigForm'; import { ScheduleConfigForm } from './ScheduleConfigForm'; @@ -52,6 +52,8 @@ export function ConfigTabs() { const [isLoading, setIsLoading] = useState(true); const [isSyncing, setIsSyncing] = useState(false); const [isConfigSaved, setIsConfigSaved] = useState(false); + const [isAutoSaving, setIsAutoSaving] = useState(false); + const autoSaveTimeoutRef = useRef(null); const isConfigFormValid = (): boolean => { const { githubConfig, giteaConfig } = config; @@ -150,6 +152,65 @@ export function ConfigTabs() { } }; + // Auto-save function specifically for schedule config changes + const autoSaveScheduleConfig = useCallback(async (scheduleConfig: ScheduleConfig) => { + if (!user?.id || !isConfigSaved) return; // Only auto-save if config was previously saved + + // Clear any existing timeout + if (autoSaveTimeoutRef.current) { + clearTimeout(autoSaveTimeoutRef.current); + } + + // Debounce the auto-save to prevent excessive API calls + autoSaveTimeoutRef.current = setTimeout(async () => { + setIsAutoSaving(true); + + const reqPayload: SaveConfigApiRequest = { + userId: user.id!, + githubConfig: config.githubConfig, + giteaConfig: config.giteaConfig, + scheduleConfig: scheduleConfig, + }; + + try { + const response = await fetch('/api/config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(reqPayload), + }); + const result: SaveConfigApiResponse = await response.json(); + + if (result.success) { + // Silent success - no toast for auto-save + await refreshUser(); + } else { + toast.error( + `Auto-save failed: ${result.message || 'Unknown error'}`, + { duration: 3000 } + ); + } + } catch (error) { + toast.error( + `Auto-save error: ${ + error instanceof Error ? error.message : String(error) + }`, + { duration: 3000 } + ); + } finally { + setIsAutoSaving(false); + } + }, 500); // 500ms debounce + }, [user?.id, isConfigSaved, config.githubConfig, config.giteaConfig, refreshUser]); + + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (autoSaveTimeoutRef.current) { + clearTimeout(autoSaveTimeoutRef.current); + } + }; + }, []); + useEffect(() => { if (!user) return; @@ -331,6 +392,8 @@ export function ConfigTabs() { : update, })) } + onAutoSave={autoSaveScheduleConfig} + isAutoSaving={isAutoSaving} /> diff --git a/src/components/config/ScheduleConfigForm.tsx b/src/components/config/ScheduleConfigForm.tsx index 405b5b5..b06e9e9 100644 --- a/src/components/config/ScheduleConfigForm.tsx +++ b/src/components/config/ScheduleConfigForm.tsx @@ -9,33 +9,36 @@ import { SelectTrigger, SelectValue, } from "../ui/select"; +import { RefreshCw } from "lucide-react"; interface ScheduleConfigFormProps { config: ScheduleConfig; setConfig: React.Dispatch>; + onAutoSave?: (config: ScheduleConfig) => void; + isAutoSaving?: boolean; } export function ScheduleConfigForm({ config, setConfig, + onAutoSave, + isAutoSaving = false, }: ScheduleConfigFormProps) { const handleChange = ( e: React.ChangeEvent ) => { const { name, value, type } = e.target; - setConfig({ + const newConfig = { ...config, [name]: type === "checkbox" ? (e.target as HTMLInputElement).checked : value, - }); - }; + }; + setConfig(newConfig); - // Convert seconds to human-readable format - const formatInterval = (seconds: number): string => { - if (seconds < 60) return `${seconds} seconds`; - if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes`; - if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours`; - return `${Math.floor(seconds / 86400)} days`; + // Trigger auto-save for schedule config changes + if (onAutoSave) { + onAutoSave(newConfig); + } }; // Predefined intervals @@ -55,7 +58,13 @@ export function ScheduleConfigForm({ return ( - + + {isAutoSaving && ( +
+ + Auto-saving... +
+ )}