refactor: improve loading state management and add live active indicator in RepositoryTable

This commit is contained in:
Arunavo Ray
2025-05-24 21:00:27 +05:30
parent 3d8bdff9af
commit 3ff86de67d
2 changed files with 57 additions and 20 deletions

View File

@@ -34,10 +34,10 @@ import { useNavigation } from "@/components/layout/MainLayout";
export default function Repository() {
const [repositories, setRepositories] = useState<Repository[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isInitialLoading, setIsInitialLoading] = useState(true);
const { user } = useAuth();
const { registerRefreshCallback } = useLiveRefresh();
const { isGitHubConfigured } = useConfigStatus();
const { registerRefreshCallback, isLiveEnabled } = useLiveRefresh();
const { isGitHubConfigured, isFullyConfigured } = useConfigStatus();
const { navigationKey } = useNavigation();
const { filter, setFilter } = useFilterParams({
searchTerm: "",
@@ -80,17 +80,20 @@ export default function Repository() {
onMessage: handleNewMessage,
});
const fetchRepositories = useCallback(async () => {
const fetchRepositories = useCallback(async (isLiveRefresh = false) => {
if (!user?.id) return;
// Don't fetch repositories if GitHub is not configured or still loading config
if (!isGitHubConfigured) {
setIsLoading(false);
setIsInitialLoading(false);
return false;
}
try {
setIsLoading(true);
// Set appropriate loading state based on refresh type
if (!isLiveRefresh) {
setIsInitialLoading(true);
}
const response = await apiRequest<RepositoryApiResponse>(
`/github/repositories?userId=${user.id}`,
@@ -103,23 +106,31 @@ export default function Repository() {
setRepositories(response.repositories);
return true;
} else {
toast.error(response.error || "Error fetching repositories");
// Only show error toast for manual refreshes to avoid spam during live updates
if (!isLiveRefresh) {
toast.error(response.error || "Error fetching repositories");
}
return false;
}
} catch (error) {
toast.error(
error instanceof Error ? error.message : "Error fetching repositories"
);
// Only show error toast for manual refreshes to avoid spam during live updates
if (!isLiveRefresh) {
toast.error(
error instanceof Error ? error.message : "Error fetching repositories"
);
}
return false;
} finally {
setIsLoading(false);
if (!isLiveRefresh) {
setIsInitialLoading(false);
}
}
}, [user?.id, isGitHubConfigured]); // Only depend on user.id, not entire user object
useEffect(() => {
// Reset loading state when component becomes active
setIsLoading(true);
fetchRepositories();
setIsInitialLoading(true);
fetchRepositories(false); // Manual refresh, not live
}, [fetchRepositories, navigationKey]); // Include navigationKey to trigger on navigation
// Register with global live refresh system
@@ -130,14 +141,14 @@ export default function Repository() {
}
const unregister = registerRefreshCallback(() => {
fetchRepositories();
fetchRepositories(true); // Live refresh
});
return unregister;
}, [registerRefreshCallback, fetchRepositories, isGitHubConfigured]);
const handleRefresh = async () => {
const success = await fetchRepositories();
const success = await fetchRepositories(false); // Manual refresh, show loading skeleton
if (success) {
toast.success("Repositories refreshed successfully.");
}
@@ -363,7 +374,7 @@ export default function Repository() {
toast.success(`Repository added successfully`);
setRepositories((prevRepos) => [...prevRepos, response.repository]);
await fetchRepositories();
await fetchRepositories(false); // Manual refresh after adding repository
setFilter((prev) => ({
...prev,
@@ -463,7 +474,7 @@ export default function Repository() {
<Button
variant="default"
onClick={handleMirrorAllRepos}
disabled={isLoading || loadingRepoIds.size > 0}
disabled={isInitialLoading || loadingRepoIds.size > 0}
>
<FlipHorizontal className="h-4 w-4 mr-2" />
Mirror All
@@ -490,7 +501,8 @@ export default function Repository() {
) : (
<RepositoryTable
repositories={repositories}
isLoading={isLoading || !connected}
isLoading={isInitialLoading || !connected}
isLiveActive={isLiveEnabled && isFullyConfigured}
filter={filter}
setFilter={setFilter}
onMirror={handleMirrorRepo}

View File

@@ -13,6 +13,7 @@ import { useGiteaConfig } from "@/hooks/useGiteaConfig";
interface RepositoryTableProps {
repositories: Repository[];
isLoading: boolean;
isLiveActive?: boolean;
filter: FilterParams;
setFilter: (filter: FilterParams) => void;
onMirror: ({ repoId }: { repoId: string }) => Promise<void>;
@@ -24,6 +25,7 @@ interface RepositoryTableProps {
export default function RepositoryTable({
repositories,
isLoading,
isLiveActive = false,
filter,
setFilter,
onMirror,
@@ -345,15 +347,38 @@ export default function RepositoryTable({
</div>
{/* Status Bar */}
<div className="h-[40px] flex items-center justify-between border-t bg-muted/30 px-3">
<div className="h-[40px] flex items-center justify-between border-t bg-muted/30 px-3 relative">
<div className="flex items-center gap-2">
<div className="h-1.5 w-1.5 rounded-full bg-primary" />
<div className={`h-1.5 w-1.5 rounded-full ${isLiveActive ? 'bg-emerald-500' : 'bg-primary'}`} />
<span className="text-sm font-medium text-foreground">
{hasAnyFilter
? `Showing ${filteredRepositories.length} of ${repositories.length} repositories`
: `${repositories.length} ${repositories.length === 1 ? 'repository' : 'repositories'} total`}
</span>
</div>
{/* Center - Live active indicator */}
{isLiveActive && (
<div className="flex items-center gap-1.5 absolute left-1/2 transform -translate-x-1/2">
<div
className="h-1 w-1 rounded-full bg-emerald-500"
style={{
animation: 'pulse 2s ease-in-out infinite'
}}
/>
<span className="text-xs text-emerald-600 dark:text-emerald-400 font-medium">
Live active
</span>
<div
className="h-1 w-1 rounded-full bg-emerald-500"
style={{
animation: 'pulse 2s ease-in-out infinite',
animationDelay: '1s'
}}
/>
</div>
)}
{hasAnyFilter && (
<span className="text-xs text-muted-foreground">
Filters applied