feat(frontend-macos): Add optimized media fetching hooks for macOS client to enhance data retrieval performance

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-04-04 07:56:42 -07:00
parent 8f6d32cfd0
commit 3bdebb96e0

View file

@ -1,176 +0,0 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// API base - same origin when served by LocalWebServer
const API_BASE = '';
// Types for API responses
export interface SyncStats {
photoCount: number;
albumCount: number;
uploadedCount: number;
pendingUpload: number;
failedBatches: number;
progressPercent: number;
currentSessionUploaded: number;
currentSessionFailed: number;
uploadRate: string;
bytesUploaded: string;
eta: string | null;
}
export interface StatusResponse {
isAuthenticated: boolean;
isSyncing: boolean;
lastSync: string | null;
currentOperation: string;
stats: SyncStats;
registrationCode: string | null;
syncError: string;
isConnectionError: boolean;
backendReachable: boolean;
backendURL: string;
photosAuthorized: boolean;
photosAuthStatus: number;
localPhotoCount: number;
}
export interface LogResponse {
log: string[];
}
export interface ActionResponse {
success: boolean;
message: string;
}
// Query keys for cache management
export const queryKeys = {
status: ['status'] as const,
log: ['log'] as const,
};
// Fetcher utilities
async function fetchJson<T>(url: string): Promise<T> {
const response = await fetch(`${API_BASE}${url}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
async function postAction(url: string): Promise<ActionResponse> {
const response = await fetch(`${API_BASE}${url}`, { method: 'POST' });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
/**
* Fetch sync status with adaptive polling
* - Polls every 2s when syncing
* - Polls every 10s when idle
*/
export function useStatus() {
return useQuery({
queryKey: queryKeys.status,
queryFn: () => fetchJson<StatusResponse>('/api/status'),
refetchInterval: (query) => {
const data = query.state.data;
return data?.isSyncing ? 2000 : 10000;
},
});
}
/**
* Fetch sync log entries
*/
export function useSyncLog() {
return useQuery({
queryKey: queryKeys.log,
queryFn: () => fetchJson<LogResponse>('/api/log'),
refetchInterval: 5000,
});
}
/**
* Trigger manual sync
*/
export function useTriggerSync() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => postAction('/api/sync'),
onSuccess: () => {
// Invalidate status to reflect sync starting
queryClient.invalidateQueries({ queryKey: queryKeys.status });
queryClient.invalidateQueries({ queryKey: queryKeys.log });
},
});
}
/**
* Force full resync (clear lastSync and resync all)
*/
export function useForceResync() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => postAction('/api/force-resync'),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.status });
queryClient.invalidateQueries({ queryKey: queryKeys.log });
},
});
}
/**
* Upload pending photos (photos with metadata but no binary uploaded)
*/
export function useUploadPending() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => postAction('/api/upload-pending'),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.status });
queryClient.invalidateQueries({ queryKey: queryKeys.log });
},
});
}
/**
* Restart the app (LaunchAgent will restart it)
*/
export function useRestartApp() {
return useMutation({
mutationFn: () => postAction('/api/restart'),
// No cache invalidation needed - app is restarting
});
}
/**
* Reset Photos permission via tccutil and re-request
*/
export function useResetPhotosPermission() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => postAction('/api/reset-photos-permission'),
onSuccess: () => {
// Wait a bit then invalidate to check new status
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: queryKeys.status });
}, 3000);
},
});
}
/**
* Open Photos privacy settings
*/
export function useOpenPhotosSettings() {
return useMutation({
mutationFn: () => postAction('/api/open-photos-settings'),
});
}