11import {
22 CodeBracketIcon ,
33 MagnifyingGlassIcon ,
4+ PlusIcon ,
45} from "@heroicons/react/24/outline" ;
56import React , { useMemo , useState } from "react" ;
6- import { Badge , Button , Modal } from "~/components/ui" ;
7+ import { Badge , Button , Input , Modal } from "~/components/ui" ;
8+ import { useBranchStore } from "~/stores/branchStore" ;
9+ import { useRepoStore } from "~/stores/repoStore" ;
710
811interface BranchListProps {
912 branches : string [ ] ;
@@ -20,6 +23,14 @@ export const BranchList: React.FC<BranchListProps> = ({
2023} ) => {
2124 const [ isOpen , setIsOpen ] = useState ( false ) ;
2225 const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
26+ const [ showCreateModal , setShowCreateModal ] = useState ( false ) ;
27+ const [ newBranchName , setNewBranchName ] = useState ( "" ) ;
28+ const [ sourceBranch , setSourceBranch ] = useState ( currentBranch || "" ) ;
29+ const [ creating , setCreating ] = useState ( false ) ;
30+ const [ createError , setCreateError ] = useState < string | null > ( null ) ;
31+
32+ const createBranch = useBranchStore ( ( s ) => s . createBranch ) ;
33+ const selectedRepo = useRepoStore ( ( s ) => s . selectedRepo ) ;
2334
2435 const filteredBranches = useMemo ( ( ) => {
2536 if ( ! searchQuery ) return branches ;
@@ -30,13 +41,52 @@ export const BranchList: React.FC<BranchListProps> = ({
3041
3142 const previewBranches = branches . slice ( 0 , previewCount ) ;
3243
44+ const handleCreateBranch = async ( ) => {
45+ if ( ! newBranchName . trim ( ) || ! sourceBranch . trim ( ) || ! selectedRepo ) return ;
46+
47+ setCreating ( true ) ;
48+ setCreateError ( null ) ;
49+
50+ try {
51+ await createBranch (
52+ selectedRepo ,
53+ newBranchName . trim ( ) ,
54+ sourceBranch . trim ( ) ,
55+ ) ;
56+ setNewBranchName ( "" ) ;
57+ setShowCreateModal ( false ) ;
58+ } catch ( err : unknown ) {
59+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60+ const e = err as any ;
61+ const msg =
62+ e ?. response ?. data ?. error || e ?. message || "Failed to create branch" ;
63+ setCreateError ( msg ) ;
64+ } finally {
65+ setCreating ( false ) ;
66+ }
67+ } ;
68+
3369 return (
3470 < div >
35- < div className = "flex items-center gap-2 mb-4" >
36- < CodeBracketIcon className = "w-4 h-4 text-text-tertiary" />
37- < h2 className = "text-sm font-semibold text-text-primary uppercase tracking-wider" >
38- Branches
39- </ h2 >
71+ < div className = "flex items-center justify-between mb-4" >
72+ < div className = "flex items-center gap-2" >
73+ < CodeBracketIcon className = "w-4 h-4 text-text-tertiary" />
74+ < h2 className = "text-sm font-semibold text-text-primary uppercase tracking-wider" >
75+ Branches
76+ </ h2 >
77+ </ div >
78+ < Button
79+ variant = "secondary"
80+ size = "sm"
81+ onClick = { ( ) => {
82+ setSourceBranch ( currentBranch || branches [ 0 ] || "" ) ;
83+ setShowCreateModal ( true ) ;
84+ } }
85+ className = "text-xs"
86+ >
87+ < PlusIcon className = "w-3.5 h-3.5" />
88+ New
89+ </ Button >
4090 </ div >
4191
4292 { branches . length === 0 ? (
@@ -67,6 +117,7 @@ export const BranchList: React.FC<BranchListProps> = ({
67117 </ div >
68118 ) }
69119
120+ { /* View All Branches Modal */ }
70121 < Modal
71122 isOpen = { isOpen }
72123 onClose = { ( ) => setIsOpen ( false ) }
@@ -108,6 +159,79 @@ export const BranchList: React.FC<BranchListProps> = ({
108159 </ div >
109160 </ div >
110161 </ Modal >
162+
163+ { /* Create Branch Modal */ }
164+ < Modal
165+ isOpen = { showCreateModal }
166+ onClose = { ( ) => {
167+ setShowCreateModal ( false ) ;
168+ setCreateError ( null ) ;
169+ setNewBranchName ( "" ) ;
170+ } }
171+ title = "Create New Branch"
172+ size = "sm"
173+ closeOnBackdrop = { true }
174+ footer = {
175+ < div className = "flex gap-3 w-full sm:w-auto" >
176+ < Button
177+ variant = "secondary"
178+ onClick = { ( ) => {
179+ setShowCreateModal ( false ) ;
180+ setCreateError ( null ) ;
181+ setNewBranchName ( "" ) ;
182+ } }
183+ className = "flex-1 sm:flex-none"
184+ >
185+ Cancel
186+ </ Button >
187+ < Button
188+ onClick = { handleCreateBranch }
189+ disabled = { creating || ! newBranchName . trim ( ) }
190+ className = "flex-1 sm:flex-none"
191+ >
192+ { creating ? "Creating..." : "Create Branch" }
193+ </ Button >
194+ </ div >
195+ }
196+ >
197+ < div className = "space-y-4" >
198+ { createError && (
199+ < div className = "bg-error/10 border border-error/20 text-error text-sm p-3 rounded-lg" >
200+ { createError }
201+ </ div >
202+ ) }
203+
204+ < Input
205+ label = "Branch Name"
206+ placeholder = "feature/my-new-branch"
207+ value = { newBranchName }
208+ onChange = { ( e ) => setNewBranchName ( e . target . value ) }
209+ required
210+ helperText = "Use lowercase with hyphens or slashes."
211+ />
212+
213+ < div >
214+ < label className = "block text-sm font-medium text-text-primary mb-1.5" >
215+ Source Branch
216+ </ label >
217+ < select
218+ value = { sourceBranch }
219+ onChange = { ( e ) => setSourceBranch ( e . target . value ) }
220+ className = "w-full h-9 px-3 bg-app-surface border border-app-border rounded text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-app-accent focus:border-app-accent transition-colors"
221+ >
222+ { branches . map ( ( b ) => (
223+ < option key = { b } value = { b } >
224+ { b }
225+ { b === currentBranch ? " (current)" : "" }
226+ </ option >
227+ ) ) }
228+ </ select >
229+ < p className = "text-xs text-text-tertiary mt-1" >
230+ The new branch will be created from this branch.
231+ </ p >
232+ </ div >
233+ </ div >
234+ </ Modal >
111235 </ div >
112236 ) ;
113237} ;
0 commit comments