Files
vibe-kanban/frontend/src/components/dialogs/tasks/CreatePRDialog.tsx
Louis Knight-Webb ba256bbb42 Update Create GitHub PR style (vibe-kanban) (#850)
* Done! I've successfully updated the CreatePRDialog to use the Alert component pattern, matching the style used in DeleteTaskConfirmationDialog:

- Added `Alert` import from `@/components/ui/alert`
- Replaced the custom error div with `<Alert variant="destructive">`
- Added error clearing when starting PR creation for better UX

The error messages now use a consistent, accessible Alert component across the application.

* Cleanup script changes for task attempt 3757c2b1-becb-4b8f-80ab-03be9bc3f096

* Perfect! I've successfully refactored all dialog error messages to use the consistent Alert component pattern:

##  Completed Changes:

**1. [TaskTemplateEditDialog.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3757-update-create-gi/frontend/src/components/dialogs/tasks/TaskTemplateEditDialog.tsx)**
- Added `Alert` and `AlertDescription` imports
- Replaced simple error div with proper Alert component

**2. [GitHubLoginDialog.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3757-update-create-gi/frontend/src/components/dialogs/auth/GitHubLoginDialog.tsx)**
- Added `Alert` and `AlertDescription` imports
- Replaced both custom styled error divs with Alert components
- Maintained the same error display locations for proper context

## 🎯 Benefits Achieved:
- **Consistent UI/UX**: All dialogs now use the same error styling
- **Better Accessibility**: Semantic alert components with proper roles
- **Unified Theming**: All errors follow the same design system
- **Maintainability**: Single pattern for error display across the app

All dialog error messages now follow the same pattern as DeleteTaskConfirmationDialog and the updated CreatePRDialog.

* Done! I've simplified all Alert components to use the same pattern as CreatePRDialog:

- **Removed AlertDescription wrapper** from all error messages
- **Updated imports** to only import Alert
- **Consistent pattern**: `<Alert variant="destructive">{error}</Alert>` across all dialogs

Now all dialog error messages use the exact same simple Alert pattern throughout the application.

* Cleanup script changes for task attempt 3757c2b1-becb-4b8f-80ab-03be9bc3f096
2025-09-25 13:33:28 +01:00

200 lines
6.4 KiB
TypeScript

import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Label } from '@radix-ui/react-label';
import { Textarea } from '@/components/ui/textarea.tsx';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Alert } from '@/components/ui/alert';
import BranchSelector from '@/components/tasks/BranchSelector';
import { useCallback, useEffect, useState } from 'react';
import { attemptsApi } from '@/lib/api.ts';
import {
GitBranch,
GitHubServiceError,
TaskAttempt,
TaskWithAttemptStatus,
} from 'shared/types';
import { projectsApi } from '@/lib/api.ts';
import { Loader2 } from 'lucide-react';
import NiceModal, { useModal } from '@ebay/nice-modal-react';
const CreatePrDialog = NiceModal.create(() => {
const modal = useModal();
const data = modal.args as
| { attempt: TaskAttempt; task: TaskWithAttemptStatus; projectId: string }
| undefined;
const [prTitle, setPrTitle] = useState('');
const [prBody, setPrBody] = useState('');
const [prBaseBranch, setPrBaseBranch] = useState('');
const [creatingPR, setCreatingPR] = useState(false);
const [error, setError] = useState<string | null>(null);
const [branches, setBranches] = useState<GitBranch[]>([]);
const [branchesLoading, setBranchesLoading] = useState(false);
useEffect(() => {
if (modal.visible && data) {
setPrTitle(`${data.task.title} (vibe-kanban)`);
setPrBody(data.task.description || '');
// Always fetch branches for dropdown population
if (data.projectId) {
setBranchesLoading(true);
projectsApi
.getBranches(data.projectId)
.then((projectBranches) => {
setBranches(projectBranches);
// Set smart default: task base branch OR current branch
if (data.attempt.base_branch) {
setPrBaseBranch(data.attempt.base_branch);
} else {
const currentBranch = projectBranches.find((b) => b.is_current);
if (currentBranch) {
setPrBaseBranch(currentBranch.name);
}
}
})
.catch(console.error)
.finally(() => setBranchesLoading(false));
}
setError(null); // Reset error when opening
}
}, [modal.visible, data]);
const handleConfirmCreatePR = useCallback(async () => {
if (!data?.projectId || !data?.attempt.id) return;
setError(null);
setCreatingPR(true);
const result = await attemptsApi.createPR(data.attempt.id, {
title: prTitle,
body: prBody || null,
base_branch: prBaseBranch || null,
});
if (result.success) {
setError(null); // Clear any previous errors on success
// Reset form and close dialog
setPrTitle('');
setPrBody('');
setPrBaseBranch('');
modal.hide();
} else {
if (result.error) {
modal.hide();
switch (result.error) {
case GitHubServiceError.TOKEN_INVALID:
NiceModal.show('github-login');
break;
case GitHubServiceError.INSUFFICIENT_PERMISSIONS:
NiceModal.show('provide-pat');
break;
case GitHubServiceError.REPO_NOT_FOUND_OR_NO_ACCESS:
NiceModal.show('provide-pat', {
errorMessage:
'Your token does not have access to this repository, or the repository does not exist. Please check the repository URL and/or provide a Personal Access Token with access.',
});
break;
}
} else if (result.message) {
setError(result.message);
} else {
setError('Failed to create GitHub PR');
}
}
setCreatingPR(false);
}, [data, prBaseBranch, prBody, prTitle, modal]);
const handleCancelCreatePR = useCallback(() => {
modal.hide();
// Reset form to empty state
setPrTitle('');
setPrBody('');
setPrBaseBranch('');
}, [modal]);
// Don't render if no data
if (!data) return null;
return (
<>
<Dialog open={modal.visible} onOpenChange={() => handleCancelCreatePR()}>
<DialogContent className="sm:max-w-[525px]">
<DialogHeader>
<DialogTitle>Create GitHub Pull Request</DialogTitle>
<DialogDescription>
Create a pull request for this task attempt on GitHub.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="pr-title">Title</Label>
<Input
id="pr-title"
value={prTitle}
onChange={(e) => setPrTitle(e.target.value)}
placeholder="Enter PR title"
/>
</div>
<div className="space-y-2">
<Label htmlFor="pr-body">Description (optional)</Label>
<Textarea
id="pr-body"
value={prBody}
onChange={(e) => setPrBody(e.target.value)}
placeholder="Enter PR description"
rows={4}
/>
</div>
<div className="space-y-2">
<Label htmlFor="pr-base">Base Branch</Label>
<BranchSelector
branches={branches}
selectedBranch={prBaseBranch}
onBranchSelect={setPrBaseBranch}
placeholder={
branchesLoading ? 'Loading branches...' : 'Select base branch'
}
className={
branchesLoading ? 'opacity-50 cursor-not-allowed' : ''
}
/>
</div>
{error && <Alert variant="destructive">{error}</Alert>}
</div>
<DialogFooter>
<Button variant="outline" onClick={handleCancelCreatePR}>
Cancel
</Button>
<Button
onClick={handleConfirmCreatePR}
disabled={creatingPR || !prTitle.trim()}
className="bg-blue-600 hover:bg-blue-700"
>
{creatingPR ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Creating...
</>
) : (
'Create PR'
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
});
export { CreatePrDialog as CreatePRDialog };
export default CreatePrDialog;