@@ -17,23 +17,33 @@ import {
1717 SelectTrigger ,
1818 SelectValue ,
1919} from "@/components/ui/select" ;
20+ import {
21+ Tooltip ,
22+ TooltipContent ,
23+ TooltipProvider ,
24+ TooltipTrigger ,
25+ } from "@/components/ui/tooltip" ;
2026import { api } from "@/utils/api" ;
2127import { zodResolver } from "@hookform/resolvers/zod" ;
22- import { KeyRoundIcon , LockIcon } from "lucide-react" ;
28+ import { KeyRoundIcon , LockIcon , X } from "lucide-react" ;
2329import { useRouter } from "next/router" ;
30+ import Link from "next/link" ;
2431
2532import { useEffect } from "react" ;
2633import { useForm } from "react-hook-form" ;
2734import { toast } from "sonner" ;
2835import { z } from "zod" ;
36+ import { Badge } from "@/components/ui/badge" ;
37+ import { GitIcon } from "@/components/icons/data-tools-icons" ;
2938
3039const GitProviderSchema = z . object ( {
40+ buildPath : z . string ( ) . min ( 1 , "Path is required" ) . default ( "/" ) ,
3141 repositoryURL : z . string ( ) . min ( 1 , {
3242 message : "Repository URL is required" ,
3343 } ) ,
3444 branch : z . string ( ) . min ( 1 , "Branch required" ) ,
35- buildPath : z . string ( ) . min ( 1 , "Build Path required" ) ,
3645 sshKey : z . string ( ) . optional ( ) ,
46+ watchPaths : z . array ( z . string ( ) ) . optional ( ) ,
3747} ) ;
3848
3949type GitProvider = z . infer < typeof GitProviderSchema > ;
@@ -56,6 +66,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
5666 buildPath : "/" ,
5767 repositoryURL : "" ,
5868 sshKey : undefined ,
69+ watchPaths : [ ] ,
5970 } ,
6071 resolver : zodResolver ( GitProviderSchema ) ,
6172 } ) ;
@@ -67,6 +78,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
6778 branch : data . customGitBranch || "" ,
6879 buildPath : data . customGitBuildPath || "/" ,
6980 repositoryURL : data . customGitUrl || "" ,
81+ watchPaths : data . watchPaths || [ ] ,
7082 } ) ;
7183 }
7284 } , [ form . reset , data , form ] ) ;
@@ -78,6 +90,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
7890 customGitUrl : values . repositoryURL ,
7991 customGitSSHKeyId : values . sshKey === "none" ? null : values . sshKey ,
8092 applicationId,
93+ watchPaths : values . watchPaths || [ ] ,
8194 } )
8295 . then ( async ( ) => {
8396 toast . success ( "Git Provider Saved" ) ;
@@ -102,9 +115,22 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
102115 name = "repositoryURL"
103116 render = { ( { field } ) => (
104117 < FormItem >
105- < FormLabel > Repository URL</ FormLabel >
118+ < div className = "flex items-center justify-between" >
119+ < FormLabel > Repository URL</ FormLabel >
120+ { field . value ?. startsWith ( "https://" ) && (
121+ < Link
122+ href = { field . value }
123+ target = "_blank"
124+ rel = "noopener noreferrer"
125+ className = "flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
126+ >
127+ < GitIcon className = "h-4 w-4" />
128+ < span > View Repository</ span >
129+ </ Link >
130+ ) }
131+ </ div >
106132 < FormControl >
107- < Input placeholder = "[email protected] " { ...
field } /> 133+ < Input placeholder = "Repository URL " { ...field } />
108134 </ FormControl >
109135 < FormMessage />
110136 </ FormItem >
@@ -160,27 +186,109 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
160186 </ Button >
161187 ) }
162188 </ div >
189+ < div className = "space-y-4" >
190+ < FormField
191+ control = { form . control }
192+ name = "branch"
193+ render = { ( { field } ) => (
194+ < FormItem >
195+ < FormLabel > Branch</ FormLabel >
196+ < FormControl >
197+ < Input placeholder = "Branch" { ...field } />
198+ </ FormControl >
199+ < FormMessage />
200+ </ FormItem >
201+ ) }
202+ />
203+ </ div >
204+
163205 < FormField
164206 control = { form . control }
165- name = "branch "
207+ name = "buildPath "
166208 render = { ( { field } ) => (
167209 < FormItem >
168- < FormLabel > Branch </ FormLabel >
210+ < FormLabel > Build Path </ FormLabel >
169211 < FormControl >
170- < Input placeholder = "Branch " { ...field } />
212+ < Input placeholder = "/ " { ...field } />
171213 </ FormControl >
172214 < FormMessage />
173215 </ FormItem >
174216 ) }
175217 />
176218 < FormField
177219 control = { form . control }
178- name = "buildPath "
220+ name = "watchPaths "
179221 render = { ( { field } ) => (
180- < FormItem >
181- < FormLabel > Build Path</ FormLabel >
222+ < FormItem className = "md:col-span-2" >
223+ < div className = "flex items-center gap-2" >
224+ < FormLabel > Watch Paths</ FormLabel >
225+ < TooltipProvider >
226+ < Tooltip >
227+ < TooltipTrigger >
228+ < div className = "size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold" >
229+ ?
230+ </ div >
231+ </ TooltipTrigger >
232+ < TooltipContent className = "max-w-[300px]" >
233+ < p >
234+ Add paths to watch for changes. When files in these
235+ paths change, a new deployment will be triggered. This
236+ will work only when manual webhook is setup.
237+ </ p >
238+ </ TooltipContent >
239+ </ Tooltip >
240+ </ TooltipProvider >
241+ </ div >
242+ < div className = "flex flex-wrap gap-2 mb-2" >
243+ { field . value ?. map ( ( path , index ) => (
244+ < Badge key = { index } variant = "secondary" >
245+ { path }
246+ < X
247+ className = "ml-1 size-3 cursor-pointer"
248+ onClick = { ( ) => {
249+ const newPaths = [ ...( field . value || [ ] ) ] ;
250+ newPaths . splice ( index , 1 ) ;
251+ form . setValue ( "watchPaths" , newPaths ) ;
252+ } }
253+ />
254+ </ Badge >
255+ ) ) }
256+ </ div >
182257 < FormControl >
183- < Input placeholder = "/" { ...field } />
258+ < div className = "flex gap-2" >
259+ < Input
260+ placeholder = "Enter a path to watch (e.g., src/*, dist/*)"
261+ onKeyDown = { ( e ) => {
262+ if ( e . key === "Enter" ) {
263+ e . preventDefault ( ) ;
264+ const input = e . currentTarget ;
265+ const value = input . value . trim ( ) ;
266+ if ( value ) {
267+ const newPaths = [ ...( field . value || [ ] ) , value ] ;
268+ form . setValue ( "watchPaths" , newPaths ) ;
269+ input . value = "" ;
270+ }
271+ }
272+ } }
273+ />
274+ < Button
275+ type = "button"
276+ variant = "secondary"
277+ onClick = { ( ) => {
278+ const input = document . querySelector (
279+ 'input[placeholder="Enter a path to watch (e.g., src/*, dist/*)"]' ,
280+ ) as HTMLInputElement ;
281+ const value = input . value . trim ( ) ;
282+ if ( value ) {
283+ const newPaths = [ ...( field . value || [ ] ) , value ] ;
284+ form . setValue ( "watchPaths" , newPaths ) ;
285+ input . value = "" ;
286+ }
287+ } }
288+ >
289+ Add
290+ </ Button >
291+ </ div >
184292 </ FormControl >
185293 < FormMessage />
186294 </ FormItem >
0 commit comments