import { useEffect, useState, useCallback, useRef } from 'react'; import { GitHubConfigForm } from './GitHubConfigForm'; import { GiteaConfigForm } from './GiteaConfigForm'; import { ScheduleConfigForm } from './ScheduleConfigForm'; import type { ConfigApiResponse, GiteaConfig, GitHubConfig, SaveConfigApiRequest, SaveConfigApiResponse, ScheduleConfig, } from '@/types/config'; import { Button } from '../ui/button'; import { useAuth } from '@/hooks/useAuth'; import { apiRequest } from '@/lib/utils'; import { RefreshCw } from 'lucide-react'; import { toast } from 'sonner'; import { Skeleton } from '@/components/ui/skeleton'; type ConfigState = { githubConfig: GitHubConfig; giteaConfig: GiteaConfig; scheduleConfig: ScheduleConfig; }; export function ConfigTabs() { const [config, setConfig] = useState({ githubConfig: { username: '', token: '', skipForks: false, privateRepositories: false, mirrorIssues: false, mirrorStarred: false, preserveOrgStructure: false, skipStarredIssues: false, }, giteaConfig: { url: '', username: '', token: '', organization: 'github-mirrors', visibility: 'public', starredReposOrg: 'github', }, scheduleConfig: { enabled: false, interval: 3600, }, }); const { user, refreshUser } = useAuth(); 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; const isGitHubValid = !!( githubConfig.username.trim() && githubConfig.token.trim() ); const isGiteaValid = !!( giteaConfig.url.trim() && giteaConfig.username.trim() && giteaConfig.token.trim() ); return isGitHubValid && isGiteaValid; }; // Removed the problematic useEffect that was causing circular dependencies // The lastRun and nextRun should be managed by the backend and fetched via API const handleImportGitHubData = async () => { if (!user?.id) return; setIsSyncing(true); try { const result = await apiRequest<{ success: boolean; message?: string }>( `/sync?userId=${user.id}`, { method: 'POST' }, ); result.success ? toast.success( 'GitHub data imported successfully! Head to the Dashboard to start mirroring repositories.', ) : toast.error( `Failed to import GitHub data: ${ result.message || 'Unknown error' }`, ); } catch (error) { toast.error( `Error importing GitHub data: ${ error instanceof Error ? error.message : String(error) }`, ); } finally { setIsSyncing(false); } }; const handleSaveConfig = async () => { if (!user?.id) return; const reqPayload: SaveConfigApiRequest = { userId: user.id, githubConfig: config.githubConfig, giteaConfig: config.giteaConfig, scheduleConfig: config.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) { await refreshUser(); setIsConfigSaved(true); toast.success( 'Configuration saved successfully! Now import your GitHub data to begin.', ); } else { toast.error( `Failed to save configuration: ${result.message || 'Unknown error'}`, ); } } catch (error) { toast.error( `An error occurred while saving the configuration: ${ error instanceof Error ? error.message : String(error) }`, ); } }; // 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 // Removed refreshUser() call to prevent page reload } 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]); // Cleanup timeout on unmount useEffect(() => { return () => { if (autoSaveTimeoutRef.current) { clearTimeout(autoSaveTimeoutRef.current); } }; }, []); useEffect(() => { if (!user?.id) return; const fetchConfig = async () => { setIsLoading(true); try { const response = await apiRequest( `/config?userId=${user.id}`, { method: 'GET' }, ); if (response && !response.error) { setConfig({ githubConfig: response.githubConfig || config.githubConfig, giteaConfig: response.giteaConfig || config.giteaConfig, scheduleConfig: response.scheduleConfig || config.scheduleConfig, }); if (response.id) setIsConfigSaved(true); } } catch (error) { console.warn( 'Could not fetch configuration, using defaults:', error, ); } setIsLoading(false); }; fetchConfig(); }, [user?.id]); // Only depend on user.id, not the entire user object function ConfigCardSkeleton() { return (
{/* Header section */}
{/* Content section */}
); } return isLoading ? (
) : (
{/* Header section */}

Configuration Settings

Configure your GitHub and Gitea connections, and set up automatic mirroring.

{/* Content section */}
setConfig(prev => ({ ...prev, githubConfig: typeof update === 'function' ? update(prev.githubConfig) : update, })) } /> setConfig(prev => ({ ...prev, giteaConfig: typeof update === 'function' ? update(prev.giteaConfig) : update, })) } />
setConfig(prev => ({ ...prev, scheduleConfig: typeof update === 'function' ? update(prev.scheduleConfig) : update, })) } onAutoSave={autoSaveScheduleConfig} isAutoSaving={isAutoSaving} />
); }