@@ -23,7 +23,7 @@ import { z } from "zod";
23
23
import { AdminDebugTooltip } from "~/components/admin/debugTooltip" ;
24
24
import { InlineCode } from "~/components/code/InlineCode" ;
25
25
import { Dialog , DialogContent , DialogHeader , DialogTrigger } from "~/components/primitives/Dialog" ;
26
- import { DialogClose , DialogDescription } from "@radix-ui/react-dialog" ;
26
+ import { DialogClose } from "@radix-ui/react-dialog" ;
27
27
import { OctoKitty } from "~/components/GitHubLoginButton" ;
28
28
import {
29
29
MainHorizontallyCenteredContainer ,
@@ -49,6 +49,7 @@ import { useOrganization } from "~/hooks/useOrganizations";
49
49
import { useProject } from "~/hooks/useProject" ;
50
50
import {
51
51
redirectBackWithErrorMessage ,
52
+ redirectBackWithSuccessMessage ,
52
53
redirectWithErrorMessage ,
53
54
redirectWithSuccessMessage ,
54
55
} from "~/models/message.server" ;
@@ -62,9 +63,16 @@ import {
62
63
githubAppInstallPath ,
63
64
EnvironmentParamSchema ,
64
65
} from "~/utils/pathBuilder" ;
65
- import { useEffect , useState } from "react" ;
66
+ import React , { useEffect , useState } from "react" ;
66
67
import { Select , SelectItem } from "~/components/primitives/Select" ;
68
+ import { Switch } from "~/components/primitives/Switch" ;
67
69
import { BranchTrackingConfigSchema , type BranchTrackingConfig } from "~/v3/github" ;
70
+ import {
71
+ EnvironmentIcon ,
72
+ environmentFullTitle ,
73
+ environmentTextClassName ,
74
+ } from "~/components/environments/EnvironmentLabel" ;
75
+ import { GitBranchIcon } from "lucide-react" ;
68
76
69
77
export const meta : MetaFunction = ( ) => {
70
78
return [
@@ -138,9 +146,6 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
138
146
htmlUrl : true ,
139
147
private : true ,
140
148
} ,
141
- where : {
142
- removedAt : null ,
143
- } ,
144
149
// Most installations will only have a couple of repos so loading them here should be fine.
145
150
// However, there might be outlier organizations so it's best to expose the installation repos
146
151
// via a resource endpoint and filter on user input.
@@ -156,6 +161,17 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
156
161
return typedjson ( { githubAppInstallations, connectedGithubRepository : undefined } ) ;
157
162
} ;
158
163
164
+ const UpdateGitSettingsFormSchema = z . object ( {
165
+ action : z . literal ( "update-git-settings" ) ,
166
+ projectId : z . string ( ) ,
167
+ productionBranch : z . string ( ) . min ( 1 , "Production branch is required" ) ,
168
+ stagingBranch : z . string ( ) . min ( 1 , "Staging branch is required" ) ,
169
+ previewDeploymentsEnabled : z
170
+ . string ( )
171
+ . optional ( )
172
+ . transform ( ( val ) => val === "on" ) ,
173
+ } ) ;
174
+
159
175
export function createSchema (
160
176
constraints : {
161
177
getSlugMatch ?: ( slug : string ) => { isMatch : boolean ; projectSlug : string } ;
@@ -193,6 +209,7 @@ export function createSchema(
193
209
repositoryId : z . string ( ) . min ( 1 , "Repository is required" ) ,
194
210
projectId : z . string ( ) . min ( 1 , "Project ID is required" ) ,
195
211
} ) ,
212
+ UpdateGitSettingsFormSchema ,
196
213
] ) ;
197
214
}
198
215
@@ -276,6 +293,35 @@ export const action: ActionFunction = async ({ request, params }) => {
276
293
) ;
277
294
}
278
295
}
296
+ case "update-git-settings" : {
297
+ const { projectId, productionBranch, stagingBranch, previewDeploymentsEnabled } =
298
+ submission . value ;
299
+
300
+ const existingConnection = await prisma . connectedGithubRepository . findFirst ( {
301
+ where : {
302
+ projectId : projectId ,
303
+ } ,
304
+ } ) ;
305
+
306
+ if ( ! existingConnection ) {
307
+ return redirectBackWithErrorMessage ( request , "No connected GitHub repository found" ) ;
308
+ }
309
+
310
+ await prisma . connectedGithubRepository . update ( {
311
+ where : {
312
+ projectId : projectId ,
313
+ } ,
314
+ data : {
315
+ branchTracking : {
316
+ production : { branch : productionBranch } ,
317
+ staging : { branch : stagingBranch } ,
318
+ } satisfies BranchTrackingConfig ,
319
+ previewDeploymentsEnabled : previewDeploymentsEnabled ,
320
+ } ,
321
+ } ) ;
322
+
323
+ return redirectBackWithSuccessMessage ( request , "Git settings updated successfully" ) ;
324
+ }
279
325
case "connect-repo" : {
280
326
const { repositoryId, projectId } = submission . value ;
281
327
@@ -343,7 +389,6 @@ export default function Page() {
343
389
const organization = useOrganization ( ) ;
344
390
const lastSubmission = useActionData ( ) ;
345
391
const navigation = useNavigation ( ) ;
346
- const location = useLocation ( ) ;
347
392
348
393
const [ renameForm , { projectName } ] = useForm ( {
349
394
id : "rename-project" ,
@@ -458,7 +503,6 @@ export default function Page() {
458
503
{ connectedGithubRepository ? (
459
504
< ConnectedGitHubRepoForm
460
505
connectedGitHubRepo = { connectedGithubRepository }
461
- organizationSlug = { organization . slug }
462
506
projectId = { project . id }
463
507
/>
464
508
) : (
@@ -752,34 +796,32 @@ type ConnectedGitHubRepo = {
752
796
753
797
function ConnectedGitHubRepoForm ( {
754
798
connectedGitHubRepo,
755
- organizationSlug,
756
799
projectId,
757
800
} : {
758
801
connectedGitHubRepo : ConnectedGitHubRepo ;
759
- organizationSlug : string ;
760
802
projectId : string ;
761
803
} ) {
762
804
const lastSubmission = useActionData ( ) as any ;
763
805
const navigation = useNavigation ( ) ;
764
806
765
- const [ renameForm , { projectName } ] = useForm ( {
766
- id : "rename-project " ,
807
+ const [ gitSettingsForm , fields ] = useForm ( {
808
+ id : "update-git-settings " ,
767
809
lastSubmission : lastSubmission ,
768
810
shouldRevalidate : "onSubmit" ,
769
811
onValidate ( { formData } ) {
770
812
return parse ( formData , {
771
- schema : createSchema ( ) ,
813
+ schema : UpdateGitSettingsFormSchema ,
772
814
} ) ;
773
815
} ,
774
816
} ) ;
775
817
776
- const isRenameLoading =
777
- navigation . formData ?. get ( "action" ) === "rename " &&
818
+ const isGitSettingsLoading =
819
+ navigation . formData ?. get ( "action" ) === "update-git-settings " &&
778
820
( navigation . state === "submitting" || navigation . state === "loading" ) ;
779
821
780
822
return (
781
823
< >
782
- < div className = "flex items-center justify-between rounded-sm border bg-grid-dimmed p-2" >
824
+ < div className = "mb-4 flex items-center justify-between rounded-sm border bg-grid-dimmed p-2" >
783
825
< div className = "flex items-center gap-2" >
784
826
< OctoKitty className = "size-4" />
785
827
< a
@@ -789,31 +831,78 @@ function ConnectedGitHubRepoForm({
789
831
>
790
832
{ connectedGitHubRepo . repository . fullName }
791
833
</ a >
834
+ { connectedGitHubRepo . repository . private && (
835
+ < LockClosedIcon className = "size-3 text-text-dimmed" />
836
+ ) }
792
837
</ div >
793
838
< Button variant = "minimal/small" > Disconnect</ Button >
794
839
</ div >
795
- < Form method = "post" { ...renameForm . props } className = "mt-4" >
840
+
841
+ < Form method = "post" { ...gitSettingsForm . props } >
842
+ < input type = "hidden" name = "projectId" value = { projectId } />
796
843
< Fieldset >
797
844
< InputGroup fullWidth >
798
- < Label htmlFor = { projectName . id } > Project name</ Label >
799
- < Input
800
- { ...conform . input ( projectName , { type : "text" } ) }
801
- defaultValue = { "asdf" }
802
- placeholder = "Project name"
803
- icon = { FolderIcon }
804
- autoFocus
805
- />
806
- < FormError id = { projectName . errorId } > { projectName . error } </ FormError >
845
+ < Hint >
846
+ Every commit on the selected tracking branch creates a deployment in the corresponding
847
+ environment.
848
+ </ Hint >
849
+ < div className = "grid grid-cols-[120px_1fr] gap-3" >
850
+ < div className = "flex items-center gap-1.5" >
851
+ < EnvironmentIcon environment = { { type : "PRODUCTION" } } className = "size-4" />
852
+ < span className = { `text-sm ${ environmentTextClassName ( { type : "PRODUCTION" } ) } ` } >
853
+ { environmentFullTitle ( { type : "PRODUCTION" } ) }
854
+ </ span >
855
+ </ div >
856
+ < Input
857
+ { ...conform . input ( fields . productionBranch , { type : "text" } ) }
858
+ defaultValue = { connectedGitHubRepo . branchTracking ?. production ?. branch }
859
+ placeholder = "Branch name"
860
+ variant = "tertiary"
861
+ icon = { GitBranchIcon }
862
+ />
863
+ < div className = "flex items-center gap-1.5" >
864
+ < EnvironmentIcon environment = { { type : "STAGING" } } className = "size-4" />
865
+ < span className = { `text-sm ${ environmentTextClassName ( { type : "STAGING" } ) } ` } >
866
+ { environmentFullTitle ( { type : "STAGING" } ) }
867
+ </ span >
868
+ </ div >
869
+ < Input
870
+ { ...conform . input ( fields . stagingBranch , { type : "text" } ) }
871
+ defaultValue = { connectedGitHubRepo . branchTracking ?. staging ?. branch }
872
+ placeholder = "Branch name"
873
+ variant = "tertiary"
874
+ icon = { GitBranchIcon }
875
+ />
876
+
877
+ < div className = "flex items-center gap-1.5" >
878
+ < EnvironmentIcon environment = { { type : "PREVIEW" } } className = "size-4" />
879
+ < span className = { `text-sm ${ environmentTextClassName ( { type : "PREVIEW" } ) } ` } >
880
+ { environmentFullTitle ( { type : "PREVIEW" } ) }
881
+ </ span >
882
+ </ div >
883
+ < Switch
884
+ name = "previewDeploymentsEnabled"
885
+ defaultChecked = { connectedGitHubRepo . previewDeploymentsEnabled }
886
+ variant = "small"
887
+ label = "create preview deployments for pull requests"
888
+ labelPosition = "right"
889
+ />
890
+ </ div >
891
+ < FormError > { fields . productionBranch ?. error } </ FormError >
892
+ < FormError > { fields . stagingBranch ?. error } </ FormError >
893
+ < FormError > { fields . previewDeploymentsEnabled ?. error } </ FormError >
894
+ < FormError > { gitSettingsForm . error } </ FormError >
807
895
</ InputGroup >
896
+
808
897
< FormButtons
809
898
confirmButton = {
810
899
< Button
811
900
type = "submit"
812
901
name = "action"
813
- value = "rename "
814
- variant = { "secondary/small" }
815
- disabled = { isRenameLoading }
816
- LeadingIcon = { isRenameLoading ? SpinnerWhite : undefined }
902
+ value = "update-git-settings "
903
+ variant = "secondary/small"
904
+ disabled = { isGitSettingsLoading }
905
+ LeadingIcon = { isGitSettingsLoading ? SpinnerWhite : undefined }
817
906
>
818
907
Save
819
908
</ Button >
0 commit comments