mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-08 20:46:44 +03:00
feat: implement auto-save functionality for schedule config changes and enhance UI with loading indicator
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useCallback, useRef } from 'react';
|
||||||
import { GitHubConfigForm } from './GitHubConfigForm';
|
import { GitHubConfigForm } from './GitHubConfigForm';
|
||||||
import { GiteaConfigForm } from './GiteaConfigForm';
|
import { GiteaConfigForm } from './GiteaConfigForm';
|
||||||
import { ScheduleConfigForm } from './ScheduleConfigForm';
|
import { ScheduleConfigForm } from './ScheduleConfigForm';
|
||||||
@@ -52,6 +52,8 @@ export function ConfigTabs() {
|
|||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isSyncing, setIsSyncing] = useState<boolean>(false);
|
const [isSyncing, setIsSyncing] = useState<boolean>(false);
|
||||||
const [isConfigSaved, setIsConfigSaved] = useState<boolean>(false);
|
const [isConfigSaved, setIsConfigSaved] = useState<boolean>(false);
|
||||||
|
const [isAutoSaving, setIsAutoSaving] = useState<boolean>(false);
|
||||||
|
const autoSaveTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const isConfigFormValid = (): boolean => {
|
const isConfigFormValid = (): boolean => {
|
||||||
const { githubConfig, giteaConfig } = config;
|
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(() => {
|
useEffect(() => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
@@ -331,6 +392,8 @@ export function ConfigTabs() {
|
|||||||
: update,
|
: update,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
onAutoSave={autoSaveScheduleConfig}
|
||||||
|
isAutoSaving={isAutoSaving}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,33 +9,36 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "../ui/select";
|
} from "../ui/select";
|
||||||
|
import { RefreshCw } from "lucide-react";
|
||||||
|
|
||||||
interface ScheduleConfigFormProps {
|
interface ScheduleConfigFormProps {
|
||||||
config: ScheduleConfig;
|
config: ScheduleConfig;
|
||||||
setConfig: React.Dispatch<React.SetStateAction<ScheduleConfig>>;
|
setConfig: React.Dispatch<React.SetStateAction<ScheduleConfig>>;
|
||||||
|
onAutoSave?: (config: ScheduleConfig) => void;
|
||||||
|
isAutoSaving?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ScheduleConfigForm({
|
export function ScheduleConfigForm({
|
||||||
config,
|
config,
|
||||||
setConfig,
|
setConfig,
|
||||||
|
onAutoSave,
|
||||||
|
isAutoSaving = false,
|
||||||
}: ScheduleConfigFormProps) {
|
}: ScheduleConfigFormProps) {
|
||||||
const handleChange = (
|
const handleChange = (
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||||||
) => {
|
) => {
|
||||||
const { name, value, type } = e.target;
|
const { name, value, type } = e.target;
|
||||||
setConfig({
|
const newConfig = {
|
||||||
...config,
|
...config,
|
||||||
[name]:
|
[name]:
|
||||||
type === "checkbox" ? (e.target as HTMLInputElement).checked : value,
|
type === "checkbox" ? (e.target as HTMLInputElement).checked : value,
|
||||||
});
|
};
|
||||||
};
|
setConfig(newConfig);
|
||||||
|
|
||||||
// Convert seconds to human-readable format
|
// Trigger auto-save for schedule config changes
|
||||||
const formatInterval = (seconds: number): string => {
|
if (onAutoSave) {
|
||||||
if (seconds < 60) return `${seconds} seconds`;
|
onAutoSave(newConfig);
|
||||||
if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes`;
|
}
|
||||||
if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours`;
|
|
||||||
return `${Math.floor(seconds / 86400)} days`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Predefined intervals
|
// Predefined intervals
|
||||||
@@ -55,7 +58,13 @@ export function ScheduleConfigForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6 relative">
|
||||||
|
{isAutoSaving && (
|
||||||
|
<div className="absolute top-4 right-4 flex items-center text-sm text-muted-foreground">
|
||||||
|
<RefreshCw className="h-3 w-3 animate-spin mr-1" />
|
||||||
|
<span className="text-xs">Auto-saving...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex flex-col gap-y-4">
|
<div className="flex flex-col gap-y-4">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
Reference in New Issue
Block a user