| ... | @@ -8,11 +8,9 @@ import { queryOptions, useMutation, useQuery, useQueryClient } from "@tanstack/r | ... | @@ -8,11 +8,9 @@ import { queryOptions, useMutation, useQuery, useQueryClient } from "@tanstack/r |
| 8 | import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | 8 | import { useCallback, useEffect, useMemo, useRef, useState } from "react"; |
| 9 | import { | 9 | import { |
| 10 | CheckIcon, | 10 | CheckIcon, |
| 11 | CircleIcon, | | |
| 12 | CloudUploadIcon, | 11 | CloudUploadIcon, |
| 13 | GitCommitIcon, | 12 | GitCommitIcon, |
| 14 | Loader2Icon, | 13 | Loader2Icon, |
| 15 | MinusIcon, | | |
| 16 | XIcon, | 14 | XIcon, |
| 17 | } from "lucide-react"; | 15 | } from "lucide-react"; |
| 18 | import { GitHubIcon } from "./Icons"; | 16 | import { GitHubIcon } from "./Icons"; |
| ... | @@ -64,20 +62,11 @@ function GitActionIcon(props: { icon: GitActionIconName; disabled: boolean }) { | ... | @@ -64,20 +62,11 @@ function GitActionIcon(props: { icon: GitActionIconName; disabled: boolean }) { |
| 64 | return <GitHubIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; | 62 | return <GitHubIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; |
| 65 | } | 63 | } |
| 66 | | 64 | |
| 67 | function GitProgressStatusIcon(props: { status: GitProgressStepStatus }) { | 65 | function runningStepMessage(stepId: GitProgressStep["id"]): string { |
| 68 | if (props.status === "running") { | 66 | if (stepId === "generate") return "Generating commit message..."; |
| 69 | return <Loader2Icon className="h-4 w-4 animate-spin text-foreground" />; | 67 | if (stepId === "commit") return "Committing changes..."; |
| 70 | } | 68 | if (stepId === "push") return "Pushing branch..."; |
| 71 | if (props.status === "completed") { | 69 | return "Preparing pull request..."; |
| 72 | return <CheckIcon className="h-4 w-4 text-emerald-600 dark:text-emerald-300" />; | | |
| 73 | } | | |
| 74 | if (props.status === "skipped") { | | |
| 75 | return <MinusIcon className="h-4 w-4 text-muted-foreground/70" />; | | |
| 76 | } | | |
| 77 | if (props.status === "failed") { | | |
| 78 | return <XIcon className="h-4 w-4 text-rose-500 dark:text-rose-300" />; | | |
| 79 | } | | |
| 80 | return <CircleIcon className="h-4 w-4 text-muted-foreground/60" />; | | |
| 81 | } | 70 | } |
| 82 | | 71 | |
| 83 | function gitActionModalTitle(): string { | 72 | function gitActionModalTitle(): string { |
| ... | @@ -315,6 +304,20 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp | ... | @@ -315,6 +304,20 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 315 | gitModalProgress.length > 0 || | 304 | gitModalProgress.length > 0 || |
| 316 | gitModalError !== null || | 305 | gitModalError !== null || |
| 317 | gitModalResult !== null); | 306 | gitModalResult !== null); |
| | 307 | const activeGitProgressStep = useMemo( |
| | 308 | () => gitModalProgress.find((step) => step.status === "running") ?? null, |
| | 309 | [gitModalProgress], |
| | 310 | ); |
| | 311 | const gitProgressToastStatus = isGitModalActionRunning |
| | 312 | ? "running" |
| | 313 | : gitModalError |
| | 314 | ? "failed" |
| | 315 | : "done"; |
| | 316 | const gitProgressToastMessage = isGitModalActionRunning |
| | 317 | ? runningStepMessage(activeGitProgressStep?.id ?? "commit") |
| | 318 | : gitModalError |
| | 319 | ? "Git action failed." |
| | 320 | : "Done."; |
| 318 | | 321 | |
| 319 | const refreshGitStatus = useCallback(async () => { | 322 | const refreshGitStatus = useCallback(async () => { |
| 320 | if (!api || !gitCwd) return; | 323 | if (!api || !gitCwd) return; |
| ... | @@ -803,92 +806,40 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp | ... | @@ -803,92 +806,40 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 803 | | 806 | |
| 804 | {showGitProgressToast && ( | 807 | {showGitProgressToast && ( |
| 805 | <div | 808 | <div |
| 806 | className="fixed bottom-4 right-4 z-[90] w-[min(420px,calc(100vw-2rem))]" | 809 | className="fixed bottom-4 right-4 z-[90]" |
| 807 | role="status" | 810 | role="status" |
| 808 | aria-live="polite" | 811 | aria-live="polite" |
| 809 | > | 812 | > |
| 810 | <div className="rounded-2xl border border-border bg-popover/95 p-4 shadow-2xl backdrop-blur"> | 813 | <div className="flex max-w-[calc(100vw-2rem)] items-center gap-2 rounded-xl border border-border bg-popover/95 px-3 py-2 shadow-xl backdrop-blur"> |
| 811 | <div className="flex items-start justify-between gap-3"> | 814 | <span className="shrink-0"> |
| 812 | <div className="min-w-0"> | 815 | {gitProgressToastStatus === "running" ? ( |
| 813 | <p className="text-[11px] uppercase tracking-[0.14em] text-muted-foreground/65"> | 816 | <Loader2Icon className="h-4 w-4 animate-spin text-foreground" /> |
| 814 | Git actions | 817 | ) : gitProgressToastStatus === "failed" ? ( |
| 815 | </p> | 818 | <XIcon className="h-4 w-4 text-rose-500 dark:text-rose-300" /> |
| 816 | <p className="mt-1 text-sm font-medium text-foreground"> | 819 | ) : ( |
| 817 | {isGitModalActionRunning | 820 | <CheckIcon className="h-4 w-4 text-emerald-600 dark:text-emerald-300" /> |
| 818 | ? "Running action..." | | |
| 819 | : gitModalError | | |
| 820 | ? "Action failed" | | |
| 821 | : "Action complete"} | | |
| 822 | </p> | | |
| 823 | </div> | | |
| 824 | <button | | |
| 825 | type="button" | | |
| 826 | className="rounded-md p-1 text-muted-foreground/60 transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50" | | |
| 827 | onClick={dismissGitProgressToast} | | |
| 828 | disabled={isGitModalActionRunning} | | |
| 829 | aria-label="Dismiss git action progress toast" | | |
| 830 | > | | |
| 831 | <XIcon className="h-4 w-4" /> | | |
| 832 | </button> | | |
| 833 | </div> | | |
| 834 | {gitModalProgress.length > 0 && ( | | |
| 835 | <div className="mt-3 overflow-hidden rounded-xl border border-border/80"> | | |
| 836 | {gitModalProgress.map((step, index) => { | | |
| 837 | const borderClass = | | |
| 838 | index < gitModalProgress.length - 1 ? "border-b border-border/70" : ""; | | |
| 839 | | | |
| 840 | return ( | | |
| 841 | <div | | |
| 842 | key={step.id} | | |
| 843 | className={`flex items-start gap-3 bg-card/45 px-3.5 py-2.5 ${borderClass}`} | | |
| 844 | > | | |
| 845 | <span className="mt-0.5"> | | |
| 846 | <GitProgressStatusIcon status={step.status} /> | | |
| 847 | </span> | | |
| 848 | <div className="min-w-0"> | | |
| 849 | <p className="text-sm text-foreground">{step.label}</p> | | |
| 850 | {step.detail && ( | | |
| 851 | <p | | |
| 852 | className={`mt-0.5 text-xs ${ | | |
| 853 | step.status === "failed" | | |
| 854 | ? "text-rose-500 dark:text-rose-300" | | |
| 855 | : "text-muted-foreground/70" | | |
| 856 | }`} | | |
| 857 | > | | |
| 858 | {step.detail} | | |
| 859 | </p> | | |
| 860 | )} | | |
| 861 | </div> | | |
| 862 | </div> | | |
| 863 | ); | | |
| 864 | })} | | |
| 865 | </div> | | |
| 866 | )} | | |
| 867 | {gitModalError && ( | | |
| 868 | <div className="mt-3 rounded-lg border border-rose-300/50 bg-rose-500/10 px-3 py-2 text-xs text-rose-600 dark:text-rose-200"> | | |
| 869 | {gitModalError} | | |
| 870 | </div> | | |
| 871 | )} | | |
| 872 | <div className="mt-3 flex justify-end gap-2"> | | |
| 873 | {!isGitModalActionRunning && gitModalResult && gitProgressToastOpenPrUrl && ( | | |
| 874 | <button | | |
| 875 | type="button" | | |
| 876 | className="rounded-xl border border-border px-4 py-2 text-sm text-foreground transition-colors duration-150 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-60" | | |
| 877 | onClick={openPrFromToast} | | |
| 878 | disabled={isGitModalActionRunning} | | |
| 879 | > | | |
| 880 | Open PR | | |
| 881 | </button> | | |
| 882 | )} | 821 | )} |
| | 822 | </span> |
| | 823 | <span className="min-w-0 truncate text-sm text-foreground">{gitProgressToastMessage}</span> |
| | 824 | {!isGitModalActionRunning && gitModalResult && gitProgressToastOpenPrUrl && ( |
| 883 | <button | 825 | <button |
| 884 | type="button" | 826 | type="button" |
| 885 | className="rounded-xl bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors duration-150 hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60" | 827 | className="rounded-md bg-foreground px-2.5 py-1 text-xs font-medium text-background transition-colors duration-150 hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60" |
| 886 | onClick={dismissGitProgressToast} | 828 | onClick={openPrFromToast} |
| 887 | disabled={isGitModalActionRunning} | 829 | disabled={isGitModalActionRunning} |
| 888 | > | 830 | > |
| 889 | Done | 831 | Open PR |
| 890 | </button> | 832 | </button> |
| 891 | </div> | 833 | )} |
| | 834 | <button |
| | 835 | type="button" |
| | 836 | className="rounded p-1 text-muted-foreground/60 transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50" |
| | 837 | onClick={dismissGitProgressToast} |
| | 838 | disabled={isGitModalActionRunning} |
| | 839 | aria-label="Dismiss git action progress toast" |
| | 840 | > |
| | 841 | <XIcon className="h-4 w-4" /> |
| | 842 | </button> |
| 892 | </div> | 843 | </div> |
| 893 | </div> | 844 | </div> |
| 894 | )} | 845 | )} |