@@ -23,7 +23,7 @@ import { z } from "zod";
2323import { AdminDebugTooltip } from "~/components/admin/debugTooltip" ;
2424import { InlineCode } from "~/components/code/InlineCode" ;
2525import { 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" ;
2727import { OctoKitty } from "~/components/GitHubLoginButton" ;
2828import {
2929 MainHorizontallyCenteredContainer ,
@@ -49,6 +49,7 @@ import { useOrganization } from "~/hooks/useOrganizations";
4949import { useProject } from "~/hooks/useProject" ;
5050import {
5151 redirectBackWithErrorMessage ,
52+ redirectBackWithSuccessMessage ,
5253 redirectWithErrorMessage ,
5354 redirectWithSuccessMessage ,
5455} from "~/models/message.server" ;
@@ -62,9 +63,16 @@ import {
6263 githubAppInstallPath ,
6364 EnvironmentParamSchema ,
6465} from "~/utils/pathBuilder" ;
65- import { useEffect , useState } from "react" ;
66+ import React , { useEffect , useState } from "react" ;
6667import { Select , SelectItem } from "~/components/primitives/Select" ;
68+ import { Switch } from "~/components/primitives/Switch" ;
6769import { 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" ;
6876
6977export const meta : MetaFunction = ( ) => {
7078 return [
@@ -138,9 +146,6 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
138146 htmlUrl : true ,
139147 private : true ,
140148 } ,
141- where : {
142- removedAt : null ,
143- } ,
144149 // Most installations will only have a couple of repos so loading them here should be fine.
145150 // However, there might be outlier organizations so it's best to expose the installation repos
146151 // via a resource endpoint and filter on user input.
@@ -156,6 +161,17 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
156161 return typedjson ( { githubAppInstallations, connectedGithubRepository : undefined } ) ;
157162} ;
158163
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+
159175export function createSchema (
160176 constraints : {
161177 getSlugMatch ?: ( slug : string ) => { isMatch : boolean ; projectSlug : string } ;
@@ -193,6 +209,7 @@ export function createSchema(
193209 repositoryId : z . string ( ) . min ( 1 , "Repository is required" ) ,
194210 projectId : z . string ( ) . min ( 1 , "Project ID is required" ) ,
195211 } ) ,
212+ UpdateGitSettingsFormSchema ,
196213 ] ) ;
197214}
198215
@@ -276,6 +293,35 @@ export const action: ActionFunction = async ({ request, params }) => {
276293 ) ;
277294 }
278295 }
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+ }
279325 case "connect-repo" : {
280326 const { repositoryId, projectId } = submission . value ;
281327
@@ -343,7 +389,6 @@ export default function Page() {
343389 const organization = useOrganization ( ) ;
344390 const lastSubmission = useActionData ( ) ;
345391 const navigation = useNavigation ( ) ;
346- const location = useLocation ( ) ;
347392
348393 const [ renameForm , { projectName } ] = useForm ( {
349394 id : "rename-project" ,
@@ -458,7 +503,6 @@ export default function Page() {
458503 { connectedGithubRepository ? (
459504 < ConnectedGitHubRepoForm
460505 connectedGitHubRepo = { connectedGithubRepository }
461- organizationSlug = { organization . slug }
462506 projectId = { project . id }
463507 />
464508 ) : (
@@ -752,34 +796,32 @@ type ConnectedGitHubRepo = {
752796
753797function ConnectedGitHubRepoForm ( {
754798 connectedGitHubRepo,
755- organizationSlug,
756799 projectId,
757800} : {
758801 connectedGitHubRepo : ConnectedGitHubRepo ;
759- organizationSlug : string ;
760802 projectId : string ;
761803} ) {
762804 const lastSubmission = useActionData ( ) as any ;
763805 const navigation = useNavigation ( ) ;
764806
765- const [ renameForm , { projectName } ] = useForm ( {
766- id : "rename-project " ,
807+ const [ gitSettingsForm , fields ] = useForm ( {
808+ id : "update-git-settings " ,
767809 lastSubmission : lastSubmission ,
768810 shouldRevalidate : "onSubmit" ,
769811 onValidate ( { formData } ) {
770812 return parse ( formData , {
771- schema : createSchema ( ) ,
813+ schema : UpdateGitSettingsFormSchema ,
772814 } ) ;
773815 } ,
774816 } ) ;
775817
776- const isRenameLoading =
777- navigation . formData ?. get ( "action" ) === "rename " &&
818+ const isGitSettingsLoading =
819+ navigation . formData ?. get ( "action" ) === "update-git-settings " &&
778820 ( navigation . state === "submitting" || navigation . state === "loading" ) ;
779821
780822 return (
781823 < >
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" >
783825 < div className = "flex items-center gap-2" >
784826 < OctoKitty className = "size-4" />
785827 < a
@@ -789,31 +831,78 @@ function ConnectedGitHubRepoForm({
789831 >
790832 { connectedGitHubRepo . repository . fullName }
791833 </ a >
834+ { connectedGitHubRepo . repository . private && (
835+ < LockClosedIcon className = "size-3 text-text-dimmed" />
836+ ) }
792837 </ div >
793838 < Button variant = "minimal/small" > Disconnect</ Button >
794839 </ 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 } />
796843 < Fieldset >
797844 < 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 >
807895 </ InputGroup >
896+
808897 < FormButtons
809898 confirmButton = {
810899 < Button
811900 type = "submit"
812901 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 }
817906 >
818907 Save
819908 </ Button >
0 commit comments