@@ -3,6 +3,7 @@ import { api, RepositorySummary } from "../hooks/useApi";
33import { Panel } from "../components/Panel" ;
44import { TabView } from "../components/TabView" ;
55import { Modal } from "../components/Modal" ;
6+ import { DiamondIcon } from "../components/icons/DiamondIcon" ;
67import { TrashIcon } from "../components/icons/TrashIcon" ;
78import "../styles/page.css" ;
89
@@ -17,6 +18,16 @@ export const RepositoriesPage: React.FC = () => {
1718 const [ cloneBranch , setCloneBranch ] = useState ( "" ) ;
1819 const [ isCloning , setIsCloning ] = useState ( false ) ;
1920 const [ isRemovingWorktree , setIsRemovingWorktree ] = useState ( false ) ;
21+ const [ templates , setTemplates ] = useState < string [ ] > ( [ ] ) ;
22+ const [ templateModal , setTemplateModal ] = useState < {
23+ open : boolean ;
24+ repoName : string | null ;
25+ } > ( { open : false , repoName : null } ) ;
26+ const [ isApplyingTemplate , setIsApplyingTemplate ] = useState ( false ) ;
27+ const [ templateResult , setTemplateResult ] = useState < {
28+ type : "success" | "error" ;
29+ message : string ;
30+ } | null > ( null ) ;
2031 const [ removeWorktreeModal , setRemoveWorktreeModal ] = useState < {
2132 open : boolean ;
2233 name : string | null ;
@@ -47,6 +58,51 @@ export const RepositoriesPage: React.FC = () => {
4758 loadRepositories ( ) ;
4859 } , [ ] ) ;
4960
61+ const openTemplateModal = async ( repoName : string ) => {
62+ setTemplateModal ( { open : true , repoName } ) ;
63+ setTemplateResult ( null ) ;
64+ setIsApplyingTemplate ( false ) ;
65+ try {
66+ const response = await api . listRepositoryTemplates ( ) ;
67+ setTemplates ( response . templates ) ;
68+ } catch ( error ) {
69+ console . error ( "Failed to load templates" , error ) ;
70+ setTemplates ( [ ] ) ;
71+ setTemplateResult ( {
72+ type : "error" ,
73+ message :
74+ error instanceof Error ? error . message : "Failed to load templates" ,
75+ } ) ;
76+ }
77+ } ;
78+
79+ const closeTemplateModal = ( ) => {
80+ if ( isApplyingTemplate ) return ;
81+ setTemplateModal ( { open : false , repoName : null } ) ;
82+ setTemplateResult ( null ) ;
83+ } ;
84+
85+ const handleApplyTemplate = async ( templateName : string ) => {
86+ if ( ! templateModal . repoName || isApplyingTemplate ) return ;
87+ setIsApplyingTemplate ( true ) ;
88+ setTemplateResult ( null ) ;
89+ try {
90+ await api . applyRepositoryTemplate ( templateModal . repoName , templateName ) ;
91+ setTemplateResult ( {
92+ type : "success" ,
93+ message : `Template '${ templateName } ' applied successfully.` ,
94+ } ) ;
95+ } catch ( error ) {
96+ setTemplateResult ( {
97+ type : "error" ,
98+ message :
99+ error instanceof Error ? error . message : "Failed to apply template" ,
100+ } ) ;
101+ } finally {
102+ setIsApplyingTemplate ( false ) ;
103+ }
104+ } ;
105+
50106 const handleCreate = async ( ) => {
51107 if ( ! newRepoName . trim ( ) ) {
52108 setAlert ( { type : "error" , message : "Repository name cannot be empty" } ) ;
@@ -154,21 +210,36 @@ export const RepositoriesPage: React.FC = () => {
154210 to = { `/repositories/${ repo . name } ` }
155211 className = { repo . isWorktreeChild ? "worktree-child-pill" : undefined }
156212 actions = {
157- repo . isWorktreeChild ? (
213+ < div className = "repo-panel-actions" >
158214 < button
159215 type = "button"
160216 className = "copy-button"
161217 onClick = { ( event ) => {
162218 event . preventDefault ( ) ;
163219 event . stopPropagation ( ) ;
164- setRemoveWorktreeModal ( { open : true , name : repo . name } ) ;
220+ openTemplateModal ( repo . name ) ;
165221 } }
166- aria-label = { `Remove ${ repo . name } worktree ` }
167- title = { `Remove ${ repo . name } worktree ` }
222+ aria-label = { `Apply template to ${ repo . name } ` }
223+ title = { `Apply template to ${ repo . name } ` }
168224 >
169- < TrashIcon />
225+ < DiamondIcon />
170226 </ button >
171- ) : undefined
227+ { repo . isWorktreeChild ? (
228+ < button
229+ type = "button"
230+ className = "copy-button"
231+ onClick = { ( event ) => {
232+ event . preventDefault ( ) ;
233+ event . stopPropagation ( ) ;
234+ setRemoveWorktreeModal ( { open : true , name : repo . name } ) ;
235+ } }
236+ aria-label = { `Remove ${ repo . name } worktree` }
237+ title = { `Remove ${ repo . name } worktree` }
238+ >
239+ < TrashIcon />
240+ </ button >
241+ ) : null }
242+ </ div >
172243 }
173244 >
174245 < div className = "metadata" >
@@ -276,6 +347,36 @@ export const RepositoriesPage: React.FC = () => {
276347 </ button >
277348 </ div >
278349 </ Modal >
350+ < Modal
351+ open = { templateModal . open }
352+ title = {
353+ templateModal . repoName
354+ ? `Apply Template: ${ templateModal . repoName } `
355+ : "Apply Template"
356+ }
357+ onClose = { closeTemplateModal }
358+ >
359+ { templateResult && (
360+ < div className = { `alert ${ templateResult . type } ` } > { templateResult . message } </ div >
361+ ) }
362+ < div className = "repo-template-grid" >
363+ { templates . length === 0 ? (
364+ < div className = "empty" > No templates found.</ div >
365+ ) : (
366+ templates . map ( ( templateName ) => (
367+ < button
368+ key = { templateName }
369+ type = "button"
370+ className = "primary"
371+ disabled = { isApplyingTemplate }
372+ onClick = { ( ) => handleApplyTemplate ( templateName ) }
373+ >
374+ { templateName }
375+ </ button >
376+ ) )
377+ ) }
378+ </ div >
379+ </ Modal >
279380 < Modal
280381 open = { removeWorktreeModal . open }
281382 title = "Remove Worktree"
0 commit comments