diff --git a/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireActions.ts b/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireActions.ts index 4bb7ff38d..3ac27bb25 100644 --- a/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireActions.ts +++ b/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireActions.ts @@ -27,25 +27,27 @@ interface UseQuestionnaireActionsProps { setQuestionStatuses: React.Dispatch< React.SetStateAction> >; - uploadFileAction: { - execute: (payload: any) => void; - status: 'idle' | 'executing' | 'hasSucceeded' | 'hasErrored' | 'transitioning' | 'hasNavigated'; - }; - parseAction: { - execute: (payload: any) => void; - status: 'idle' | 'executing' | 'hasSucceeded' | 'hasErrored' | 'transitioning' | 'hasNavigated'; - }; - triggerAutoAnswer: (payload: { - vendorId: string; - organizationId: string; - questionsAndAnswers: QuestionAnswer[]; - }) => void; - triggerSingleAnswer: (payload: { - question: string; - organizationId: string; - questionIndex: number; - totalQuestions: number; - }) => void; + setParseTaskId: (id: string | null) => void; + setParseToken: (token: string | null) => void; + uploadFileAction: { + execute: (payload: any) => void; + status: 'idle' | 'executing' | 'hasSucceeded' | 'hasErrored' | 'transitioning' | 'hasNavigated'; + }; + parseAction: { + execute: (payload: any) => void; + status: 'idle' | 'executing' | 'hasSucceeded' | 'hasErrored' | 'transitioning' | 'hasNavigated'; + }; + triggerAutoAnswer: (payload: { + vendorId: string; + organizationId: string; + questionsAndAnswers: QuestionAnswer[]; + }) => void; + triggerSingleAnswer: (payload: { + question: string; + organizationId: string; + questionIndex: number; + totalQuestions: number; + }) => void; } export function useQuestionnaireActions({ @@ -66,11 +68,13 @@ export function useQuestionnaireActions({ answeringQuestionIndex, setAnsweringQuestionIndex, setQuestionStatuses, - uploadFileAction, - parseAction, - triggerAutoAnswer, - triggerSingleAnswer, - }: UseQuestionnaireActionsProps) { + setParseTaskId, + setParseToken, + uploadFileAction, + parseAction, + triggerAutoAnswer, + triggerSingleAnswer, +}: UseQuestionnaireActionsProps) { const exportAction = useAction(exportQuestionnaire, { onSuccess: ({ data }: { data: any }) => { const responseData = data?.data || data; @@ -106,6 +110,9 @@ export function useQuestionnaireActions({ }, [setSelectedFile]); const handleParse = async () => { + // Clear old parse state before starting new parse to prevent token mismatch + setParseTaskId(null); + setParseToken(null); setIsParseProcessStarted(true); if (selectedFile) { diff --git a/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireParse.ts b/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireParse.ts index c948dcd2a..580cfde90 100644 --- a/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireParse.ts +++ b/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireParse.ts @@ -1,7 +1,7 @@ 'use client'; -import { useRealtimeRun } from '@trigger.dev/react-hooks'; import type { parseQuestionnaireTask } from '@/jobs/tasks/vendors/parse-questionnaire'; +import { useRealtimeRun } from '@trigger.dev/react-hooks'; import { useAction } from 'next-safe-action/hooks'; import { useEffect } from 'react'; import { toast } from 'sonner'; @@ -96,7 +96,9 @@ export function useQuestionnaireParse({ setExtractedContent(extractedContent || null); setQuestionStatuses(new Map()); setHasClickedAutoAnswer(false); - toast.success(`Successfully parsed ${questionsAndAnswers.length} question-answer pairs`); + toast.success( + `Successfully parsed ${questionsAndAnswers.length} question-answer pairs`, + ); } else { toast.error('Parsed data is missing questions'); } @@ -137,6 +139,8 @@ export function useQuestionnaireParse({ return; } + // Clear old token before setting new task ID to prevent using wrong token with new run + setParseToken(null); setParseTaskId(taskId); const tokenResult = await createRunReadToken(taskId); @@ -185,4 +189,3 @@ export function useQuestionnaireParse({ uploadFileAction, }; } - diff --git a/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireParser.ts b/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireParser.ts index 5df07858e..c63c36974 100644 --- a/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireParser.ts +++ b/apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireParser.ts @@ -1,6 +1,6 @@ 'use client'; -import { useMemo, useState, useEffect, useRef } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { useQuestionnaireActions } from './useQuestionnaireActions'; import { useQuestionnaireAutoAnswer } from './useQuestionnaireAutoAnswer'; import { useQuestionnaireParse } from './useQuestionnaireParse'; @@ -65,11 +65,13 @@ export function useQuestionnaireParser() { answeringQuestionIndex: state.answeringQuestionIndex, setAnsweringQuestionIndex: state.setAnsweringQuestionIndex, setQuestionStatuses: state.setQuestionStatuses, - uploadFileAction: parse.uploadFileAction, - parseAction: parse.parseAction, - triggerAutoAnswer: autoAnswer.triggerAutoAnswer, - triggerSingleAnswer: singleAnswer.triggerSingleAnswer, - }); + setParseTaskId: state.setParseTaskId, + setParseToken: state.setParseToken, + uploadFileAction: parse.uploadFileAction, + parseAction: parse.parseAction, + triggerAutoAnswer: autoAnswer.triggerAutoAnswer, + triggerSingleAnswer: singleAnswer.triggerSingleAnswer, + }); const isLoading = useMemo(() => { const isUploading = parse.uploadFileAction.status === 'executing'; @@ -167,7 +169,9 @@ export function useQuestionnaireParser() { ]); // Throttled status for smooth transitions - const [parseStatus, setParseStatus] = useState<'uploading' | 'starting' | 'queued' | 'analyzing' | 'processing' | null>(null); + const [parseStatus, setParseStatus] = useState< + 'uploading' | 'starting' | 'queued' | 'analyzing' | 'processing' | null + >(null); const statusTimeoutRef = useRef(null); const lastStatusRef = useRef(null); const statusStartTimeRef = useRef(null); @@ -207,12 +211,17 @@ export function useQuestionnaireParser() { statusStartTimeRef.current = Date.now(); } else { // Check if current status has been visible for minimum duration - const isEarlyStage = lastStatusRef.current === 'uploading' || lastStatusRef.current === 'starting' || lastStatusRef.current === 'queued'; + const isEarlyStage = + lastStatusRef.current === 'uploading' || + lastStatusRef.current === 'starting' || + lastStatusRef.current === 'queued'; const minDisplayTime = isEarlyStage ? 3000 : 1500; // 3s minimum for early stages, 1.5s for later - - const timeSinceStatusStart = statusStartTimeRef.current ? Date.now() - statusStartTimeRef.current : 0; + + const timeSinceStatusStart = statusStartTimeRef.current + ? Date.now() - statusStartTimeRef.current + : 0; const remainingTime = Math.max(0, minDisplayTime - timeSinceStatusStart); - + statusTimeoutRef.current = setTimeout(() => { setParseStatus(rawParseStatus); lastStatusRef.current = rawParseStatus;