diff --git a/frontend/app/onboarding/_components/animated-provider-steps.tsx b/frontend/app/onboarding/_components/animated-provider-steps.tsx index 27ddde543..da9bc5e20 100644 --- a/frontend/app/onboarding/_components/animated-provider-steps.tsx +++ b/frontend/app/onboarding/_components/animated-provider-steps.tsx @@ -27,16 +27,17 @@ export function AnimatedProviderSteps({ processingStartTime?: number | null; hasError?: boolean; }) { - const [startTime, setStartTime] = useState(null); + const [prevIsCompleted, setPrevIsCompleted] = useState(false); const [elapsedTime, setElapsedTime] = useState(0); - // Initialize start time from prop - useEffect(() => { + if (isCompleted && !prevIsCompleted) { + setPrevIsCompleted(true); if (processingStartTime) { - // Use the start time passed from parent (when user clicked Complete) - setStartTime(processingStartTime); + setElapsedTime(Date.now() - processingStartTime); } - }, [processingStartTime]); + } else if (!isCompleted && prevIsCompleted) { + setPrevIsCompleted(false); + } // Progress through steps useEffect(() => { @@ -48,14 +49,6 @@ export function AnimatedProviderSteps({ } }, [currentStep, setCurrentStep, steps, isCompleted]); - // Calculate elapsed time when completed - useEffect(() => { - if (isCompleted && startTime) { - const elapsed = Date.now() - startTime; - setElapsedTime(elapsed); - } - }, [isCompleted, startTime]); - const isDone = currentStep >= steps.length && !isCompleted && !hasError; return ( diff --git a/frontend/app/onboarding/_components/onboarding-step.tsx b/frontend/app/onboarding/_components/onboarding-step.tsx index 36bf9900a..b2941f6b4 100644 --- a/frontend/app/onboarding/_components/onboarding-step.tsx +++ b/frontend/app/onboarding/_components/onboarding-step.tsx @@ -35,26 +35,35 @@ export function OnboardingStep({ isMarkdown = false, hideIcon = false, }: OnboardingStepProps) { - const [displayedText, setDisplayedText] = useState(""); - const [showChildren, setShowChildren] = useState(false); + const completedOnMount = isVisible && isCompleted && !isMarkdown; + const [displayedText, setDisplayedText] = useState( + completedOnMount ? text : "", + ); + const [showChildren, setShowChildren] = useState(completedOnMount); + const [prev, setPrev] = useState({ text, isVisible, isCompleted }); - useEffect(() => { + if ( + text !== prev.text || + isVisible !== prev.isVisible || + isCompleted !== prev.isCompleted + ) { + setPrev({ text, isVisible, isCompleted }); if (!isVisible) { setDisplayedText(""); setShowChildren(false); - return; - } - - if (isCompleted) { + } else if (isCompleted) { setDisplayedText(text); setShowChildren(true); - return; + } else { + setDisplayedText(""); + setShowChildren(false); } + } - let currentIndex = 0; - setDisplayedText(""); - setShowChildren(false); + useEffect(() => { + if (!isVisible || isCompleted) return; + let currentIndex = 0; const interval = setInterval(() => { if (currentIndex < text.length) { setDisplayedText(text.slice(0, currentIndex + 1)); @@ -63,7 +72,7 @@ export function OnboardingStep({ clearInterval(interval); setShowChildren(true); } - }, 20); // 20ms per character + }, 20); return () => clearInterval(interval); }, [text, isVisible, isCompleted]); diff --git a/frontend/components/cloud-picker/unified-cloud-picker.tsx b/frontend/components/cloud-picker/unified-cloud-picker.tsx index c87d64a72..27a3faafd 100644 --- a/frontend/components/cloud-picker/unified-cloud-picker.tsx +++ b/frontend/components/cloud-picker/unified-cloud-picker.tsx @@ -28,7 +28,6 @@ export const UnifiedCloudPicker = ({ const [isPickerLoaded, setIsPickerLoaded] = useState(false); const [isPickerOpen, setIsPickerOpen] = useState(false); const [isIngestSettingsOpen, setIsIngestSettingsOpen] = useState(false); - const [isLoadingBaseUrl, setIsLoadingBaseUrl] = useState(false); const [autoBaseUrl, setAutoBaseUrl] = useState(undefined); const isControlled = @@ -58,25 +57,9 @@ export const UnifiedCloudPicker = ({ const effectiveBaseUrl = baseUrl || autoBaseUrl; - // Auto-detect base URL for OneDrive personal accounts - // For SharePoint, we require the baseUrl to be provided from the connector config - useEffect(() => { - if (provider === "onedrive" && !baseUrl && accessToken && !autoBaseUrl) { - // Only auto-set for OneDrive, not SharePoint - const getBaseUrl = async () => { - setIsLoadingBaseUrl(true); - try { - setAutoBaseUrl("https://onedrive.live.com/picker"); - } catch (error) { - console.error("Auto-detect baseUrl failed:", error); - } finally { - setIsLoadingBaseUrl(false); - } - }; - - getBaseUrl(); - } - }, [accessToken, baseUrl, autoBaseUrl, provider]); + if (provider === "onedrive" && !baseUrl && accessToken && !autoBaseUrl) { + setAutoBaseUrl("https://onedrive.live.com/picker"); + } // Load picker API useEffect(() => { @@ -155,14 +138,6 @@ export const UnifiedCloudPicker = ({ onFileSelected([]); }; - if (isLoadingBaseUrl) { - return ( -
- Loading... -
- ); - } - if ( (provider === "onedrive" || provider === "sharepoint") && !clientId && diff --git a/frontend/components/task-dialog/file-list.tsx b/frontend/components/task-dialog/file-list.tsx index dc818a7d6..0aacfbd8a 100644 --- a/frontend/components/task-dialog/file-list.tsx +++ b/frontend/components/task-dialog/file-list.tsx @@ -1,7 +1,7 @@ "use client"; import { ArrowUpAZ, ChevronDown, FileText } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import type { TaskFileEntry } from "@/app/api/queries/useGetTasksQuery"; import { useIsCloudBrand } from "@/contexts/brand-context"; import type { Task } from "@/contexts/task-context"; @@ -69,11 +69,9 @@ export function TaskDialogFileList({ const showRetryIngestionsTab = retryIngestionCount > 0; - useEffect(() => { - if (!showRetryIngestionsTab && activeTab === "retry-ingestions") { - setActiveTab("task-ingestions"); - } - }, [showRetryIngestionsTab, activeTab]); + if (!showRetryIngestionsTab && activeTab === "retry-ingestions") { + setActiveTab("task-ingestions"); + } const analysisByPath = useMemo(() => { const map = new Map< diff --git a/frontend/components/task-dialog/use-task-dialog.ts b/frontend/components/task-dialog/use-task-dialog.ts index 0d87275de..70f942cf2 100644 --- a/frontend/components/task-dialog/use-task-dialog.ts +++ b/frontend/components/task-dialog/use-task-dialog.ts @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { toast } from "sonner"; import { type RetryTaskResponse, @@ -110,11 +110,13 @@ export function useTaskDialog(open: boolean, taskId: string) { const [expandedPath, setExpandedPath] = useState(null); const [nameSort, setNameSort] = useState("asc"); - useEffect(() => { + const [prevOpen, setPrevOpen] = useState(open); + if (open !== prevOpen) { + setPrevOpen(open); if (!open) { setSelectedPaths(new Set()); } - }, [open]); + } const fileEntries = useMemo( () => (task ? getTaskFileEntries(task) : []), @@ -140,16 +142,13 @@ export function useTaskDialog(open: boolean, taskId: string) { return countTaskFileEntriesByCategory(scopedEntries); }, [task, fileEntries, search, activeFileType]); - useEffect(() => { - if ( - !categoryCounts || - statusCategory === ALL_TASK_STATUS_CATEGORIES || - (categoryCounts[statusCategory as TaskFileStatusCategory] ?? 0) > 0 - ) { - return; - } + if ( + categoryCounts && + statusCategory !== ALL_TASK_STATUS_CATEGORIES && + (categoryCounts[statusCategory as TaskFileStatusCategory] ?? 0) === 0 + ) { setStatusCategory(ALL_TASK_STATUS_CATEGORIES); - }, [categoryCounts, statusCategory]); + } const filteredEntries = useMemo( () =>