fix: address multiple CodeRabbit review feedback items
Changes included: 1. **InvestigationDialog.tsx** - Prevent state updates after unmount: - Add isMounted flag to prevent state updates after component unmounts - Add fetchCommentsError state to surface API errors to the user - Display error state in UI with styled error message - Ensure cleanup function properly sets isMounted = false 2. **EnvConfigModal.tsx** - Improve type safety: - Replace `any` type with proper `ClaudeProfile` type in filter callback 3. **GenerationProgressScreen.tsx** & **RoadmapGenerationProgress.tsx**: - Add double-click prevention for stop button - Add isStopping state with proper error handling
This commit is contained in:
@@ -30,6 +30,7 @@ import {
|
||||
TooltipTrigger
|
||||
} from './ui/tooltip';
|
||||
import { cn } from '../lib/utils';
|
||||
import type { ClaudeProfile } from '../../shared/types';
|
||||
|
||||
interface EnvConfigModalProps {
|
||||
open: boolean;
|
||||
@@ -101,7 +102,7 @@ export function EnvConfigModal({
|
||||
// Handle Claude profiles
|
||||
if (profilesResult.success && profilesResult.data) {
|
||||
const authenticatedProfiles = profilesResult.data.profiles.filter(
|
||||
(p: any) => p.oauthToken || (p.isDefault && p.configDir)
|
||||
(p: ClaudeProfile) => p.oauthToken || (p.isDefault && p.configDir)
|
||||
);
|
||||
setClaudeProfiles(authenticatedProfiles);
|
||||
|
||||
|
||||
@@ -197,6 +197,23 @@ export function RoadmapGenerationProgress({
|
||||
}: RoadmapGenerationProgressProps) {
|
||||
const { phase, progress, message, error } = generationStatus;
|
||||
const reducedMotion = useReducedMotion();
|
||||
const [isStopping, setIsStopping] = useState(false);
|
||||
|
||||
/**
|
||||
* Handle stop button click with error handling and double-click prevention
|
||||
*/
|
||||
const handleStopClick = async () => {
|
||||
if (!onStop || isStopping) return;
|
||||
|
||||
setIsStopping(true);
|
||||
try {
|
||||
await onStop();
|
||||
} catch (err) {
|
||||
console.error('Failed to stop generation:', err);
|
||||
} finally {
|
||||
setIsStopping(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Don't render anything for idle phase
|
||||
if (phase === 'idle') {
|
||||
@@ -260,10 +277,11 @@ export function RoadmapGenerationProgress({
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={onStop}
|
||||
onClick={handleStopClick}
|
||||
disabled={isStopping}
|
||||
>
|
||||
<Square className="h-4 w-4 mr-1" />
|
||||
Stop
|
||||
{isStopping ? 'Stopping...' : 'Stop'}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Stop generation</TooltipContent>
|
||||
|
||||
+21
-1
@@ -35,16 +35,21 @@ export function InvestigationDialog({
|
||||
const [comments, setComments] = useState<GitHubComment[]>([]);
|
||||
const [selectedCommentIds, setSelectedCommentIds] = useState<number[]>([]);
|
||||
const [loadingComments, setLoadingComments] = useState(false);
|
||||
const [fetchCommentsError, setFetchCommentsError] = useState<string | null>(null);
|
||||
|
||||
// Fetch comments when dialog opens
|
||||
useEffect(() => {
|
||||
if (open && selectedIssue && projectId) {
|
||||
let isMounted = true;
|
||||
|
||||
setLoadingComments(true);
|
||||
setComments([]);
|
||||
setSelectedCommentIds([]);
|
||||
setFetchCommentsError(null);
|
||||
|
||||
window.electronAPI.getIssueComments(projectId, selectedIssue.number)
|
||||
.then((result: { success: boolean; data?: GitHubComment[] }) => {
|
||||
if (!isMounted) return;
|
||||
if (result.success && result.data) {
|
||||
setComments(result.data);
|
||||
// By default, select all comments
|
||||
@@ -52,11 +57,21 @@ export function InvestigationDialog({
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!isMounted) return;
|
||||
console.error('Failed to fetch comments:', err);
|
||||
setFetchCommentsError(
|
||||
err instanceof Error ? err.message : 'Failed to load comments'
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoadingComments(false);
|
||||
if (isMounted) {
|
||||
setLoadingComments(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}
|
||||
}, [open, selectedIssue, projectId]);
|
||||
|
||||
@@ -108,6 +123,11 @@ export function InvestigationDialog({
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : fetchCommentsError ? (
|
||||
<div className="rounded-lg bg-destructive/10 border border-destructive/30 p-4">
|
||||
<p className="text-sm text-destructive font-medium">Failed to load comments</p>
|
||||
<p className="text-xs text-destructive/80 mt-1">{fetchCommentsError}</p>
|
||||
</div>
|
||||
) : comments.length > 0 ? (
|
||||
<div className="space-y-2 flex-1 min-h-0 flex flex-col">
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@@ -51,6 +51,23 @@ export function GenerationProgressScreen({
|
||||
}: GenerationProgressScreenProps) {
|
||||
const logsEndRef = useRef<HTMLDivElement>(null);
|
||||
const [showLogs, setShowLogs] = useState(false);
|
||||
const [isStopping, setIsStopping] = useState(false);
|
||||
|
||||
/**
|
||||
* Handle stop button click with error handling and double-click prevention
|
||||
*/
|
||||
const handleStopClick = async () => {
|
||||
if (isStopping) return;
|
||||
|
||||
setIsStopping(true);
|
||||
try {
|
||||
await onStop();
|
||||
} catch (err) {
|
||||
console.error('Failed to stop generation:', err);
|
||||
} finally {
|
||||
setIsStopping(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-scroll to bottom when logs update
|
||||
useEffect(() => {
|
||||
@@ -98,10 +115,11 @@ export function GenerationProgressScreen({
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={onStop}
|
||||
onClick={handleStopClick}
|
||||
disabled={isStopping}
|
||||
>
|
||||
<Square className="h-4 w-4 mr-1" />
|
||||
Stop
|
||||
{isStopping ? 'Stopping...' : 'Stop'}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Stop generation</TooltipContent>
|
||||
|
||||
@@ -28,8 +28,8 @@ export function RoadmapHeader({ roadmap, competitorAnalysis, onAddFeature, onRef
|
||||
<TooltipContent className="max-w-md">
|
||||
<div className="space-y-2">
|
||||
<div className="font-semibold">Analyzed {competitorAnalysis.competitors.length} competitors:</div>
|
||||
{competitorAnalysis.competitors.map((comp: { name: string; painPoints: unknown[] }, idx: number) => (
|
||||
<div key={idx} className="text-sm">
|
||||
{competitorAnalysis.competitors.map((comp) => (
|
||||
<div key={comp.id} className="text-sm">
|
||||
<div className="font-medium">• {comp.name}</div>
|
||||
<div className="text-muted-foreground ml-3">{comp.painPoints.length} pain points identified</div>
|
||||
</div>
|
||||
@@ -79,8 +79,8 @@ export function RoadmapHeader({ roadmap, competitorAnalysis, onAddFeature, onRef
|
||||
<TooltipContent className="max-w-md">
|
||||
<div className="space-y-1">
|
||||
<div className="font-semibold mb-2">Secondary Personas:</div>
|
||||
{roadmap.targetAudience.secondary.map((persona, idx) => (
|
||||
<div key={idx} className="text-sm">• {persona}</div>
|
||||
{roadmap.targetAudience.secondary.map((persona) => (
|
||||
<div key={persona} className="text-sm">• {persona}</div>
|
||||
))}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
|
||||
Reference in New Issue
Block a user