mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
feat: Add ConfirmDialog component and integrate into GitHubIssuesView
- Introduced a new ConfirmDialog component for user confirmation prompts. - Integrated ConfirmDialog into GitHubIssuesView to confirm re-validation of issues, enhancing user interaction and decision-making. - Updated handleValidateIssue function to support re-validation options, improving flexibility in issue validation handling.
This commit is contained in:
83
apps/ui/src/components/ui/confirm-dialog.tsx
Normal file
83
apps/ui/src/components/ui/confirm-dialog.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { LucideIcon } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { HotkeyButton } from '@/components/ui/hotkey-button';
|
||||||
|
|
||||||
|
interface ConfirmDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
/** Optional icon to show in the title */
|
||||||
|
icon?: LucideIcon;
|
||||||
|
/** Icon color class. Defaults to "text-primary" */
|
||||||
|
iconClassName?: string;
|
||||||
|
/** Optional content to show between description and buttons */
|
||||||
|
children?: ReactNode;
|
||||||
|
/** Text for the confirm button. Defaults to "Confirm" */
|
||||||
|
confirmText?: string;
|
||||||
|
/** Text for the cancel button. Defaults to "Cancel" */
|
||||||
|
cancelText?: string;
|
||||||
|
/** Variant for the confirm button. Defaults to "default" */
|
||||||
|
confirmVariant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConfirmDialog({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
onConfirm,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
icon: Icon,
|
||||||
|
iconClassName = 'text-primary',
|
||||||
|
children,
|
||||||
|
confirmText = 'Confirm',
|
||||||
|
cancelText = 'Cancel',
|
||||||
|
confirmVariant = 'default',
|
||||||
|
}: ConfirmDialogProps) {
|
||||||
|
const handleConfirm = () => {
|
||||||
|
onConfirm();
|
||||||
|
onOpenChange(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="bg-popover border-border max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
{Icon && <Icon className={`w-5 h-5 ${iconClassName}`} />}
|
||||||
|
{title}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription className="text-muted-foreground">{description}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
|
||||||
|
<DialogFooter className="gap-2 sm:gap-2 pt-4">
|
||||||
|
<Button variant="ghost" onClick={() => onOpenChange(false)} className="px-4">
|
||||||
|
{cancelText}
|
||||||
|
</Button>
|
||||||
|
<HotkeyButton
|
||||||
|
variant={confirmVariant}
|
||||||
|
onClick={handleConfirm}
|
||||||
|
hotkey={{ key: 'Enter', cmdCtrl: true }}
|
||||||
|
hotkeyActive={open}
|
||||||
|
className="px-4"
|
||||||
|
>
|
||||||
|
{Icon && <Icon className="w-4 h-4 mr-2" />}
|
||||||
|
{confirmText}
|
||||||
|
</HotkeyButton>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ function getFeaturePriority(complexity: IssueComplexity | undefined): number {
|
|||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore } from '@/store/app-store';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Markdown } from '@/components/ui/markdown';
|
import { Markdown } from '@/components/ui/markdown';
|
||||||
|
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { ValidationDialog } from './github-issues-view/validation-dialog';
|
import { ValidationDialog } from './github-issues-view/validation-dialog';
|
||||||
@@ -60,6 +61,8 @@ export function GitHubIssuesView() {
|
|||||||
const [cachedValidations, setCachedValidations] = useState<Map<number, StoredValidation>>(
|
const [cachedValidations, setCachedValidations] = useState<Map<number, StoredValidation>>(
|
||||||
new Map()
|
new Map()
|
||||||
);
|
);
|
||||||
|
// Track revalidation confirmation dialog
|
||||||
|
const [showRevalidateConfirm, setShowRevalidateConfirm] = useState(false);
|
||||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||||
const { currentProject, validationModel, muteDoneSound } = useAppStore();
|
const { currentProject, validationModel, muteDoneSound } = useAppStore();
|
||||||
|
|
||||||
@@ -246,7 +249,12 @@ export function GitHubIssuesView() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleValidateIssue = useCallback(
|
const handleValidateIssue = useCallback(
|
||||||
async (issue: GitHubIssue, showDialog = true) => {
|
async (
|
||||||
|
issue: GitHubIssue,
|
||||||
|
options: { showDialog?: boolean; forceRevalidate?: boolean } = {}
|
||||||
|
) => {
|
||||||
|
const { showDialog = true, forceRevalidate = false } = options;
|
||||||
|
|
||||||
if (!currentProject?.path) {
|
if (!currentProject?.path) {
|
||||||
toast.error('No project selected');
|
toast.error('No project selected');
|
||||||
return;
|
return;
|
||||||
@@ -258,9 +266,9 @@ export function GitHubIssuesView() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for cached result - if fresh, show it directly
|
// Check for cached result - if fresh, show it directly (unless force revalidate)
|
||||||
const cached = cachedValidations.get(issue.number);
|
const cached = cachedValidations.get(issue.number);
|
||||||
if (cached && showDialog) {
|
if (cached && showDialog && !forceRevalidate) {
|
||||||
// Check if validation is stale (older than 24 hours)
|
// Check if validation is stale (older than 24 hours)
|
||||||
const validatedAt = new Date(cached.validatedAt);
|
const validatedAt = new Date(cached.validatedAt);
|
||||||
const hoursSinceValidation = (Date.now() - validatedAt.getTime()) / (1000 * 60 * 60);
|
const hoursSinceValidation = (Date.now() - validatedAt.getTime()) / (1000 * 60 * 60);
|
||||||
@@ -568,7 +576,7 @@ export function GitHubIssuesView() {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleValidateIssue(selectedIssue)}
|
onClick={() => setShowRevalidateConfirm(true)}
|
||||||
title="Re-validate"
|
title="Re-validate"
|
||||||
>
|
>
|
||||||
<RefreshCw className="h-4 w-4" />
|
<RefreshCw className="h-4 w-4" />
|
||||||
@@ -591,7 +599,9 @@ export function GitHubIssuesView() {
|
|||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleValidateIssue(selectedIssue)}
|
onClick={() =>
|
||||||
|
handleValidateIssue(selectedIssue, { forceRevalidate: true })
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Wand2 className="h-4 w-4 mr-1" />
|
<Wand2 className="h-4 w-4 mr-1" />
|
||||||
Re-validate
|
Re-validate
|
||||||
@@ -766,6 +776,22 @@ export function GitHubIssuesView() {
|
|||||||
isValidating={selectedIssue ? validatingIssues.has(selectedIssue.number) : false}
|
isValidating={selectedIssue ? validatingIssues.has(selectedIssue.number) : false}
|
||||||
onConvertToTask={handleConvertToTask}
|
onConvertToTask={handleConvertToTask}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Revalidate Confirmation Dialog */}
|
||||||
|
<ConfirmDialog
|
||||||
|
open={showRevalidateConfirm}
|
||||||
|
onOpenChange={setShowRevalidateConfirm}
|
||||||
|
title="Re-validate Issue"
|
||||||
|
description={`Are you sure you want to re-validate issue #${selectedIssue?.number}? This will run a new AI analysis and replace the existing validation result.`}
|
||||||
|
icon={RefreshCw}
|
||||||
|
iconClassName="text-primary"
|
||||||
|
confirmText="Re-validate"
|
||||||
|
onConfirm={() => {
|
||||||
|
if (selectedIssue) {
|
||||||
|
handleValidateIssue(selectedIssue, { forceRevalidate: true });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user