GitHubIntegrationConnectionForm.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. // @ts-nocheck
  2. import { zodResolver } from '@hookform/resolvers/zod'
  3. import { PermissionAction } from '@supabase/shared-types/out/constants'
  4. import { AnimatePresence, motion } from 'framer-motion'
  5. import { Loader2 } from 'lucide-react'
  6. import { useEffect, useState } from 'react'
  7. import { useForm } from 'react-hook-form'
  8. import { toast } from 'sonner'
  9. import {
  10. Button,
  11. Card,
  12. CardContent,
  13. CardFooter,
  14. cn,
  15. Form,
  16. FormControl,
  17. FormField,
  18. Input,
  19. Switch,
  20. } from 'ui'
  21. import { Admonition } from 'ui-patterns/admonition'
  22. import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
  23. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  24. import * as z from 'zod'
  25. import {
  26. GitHubRepositoryField,
  27. useGitHubRepositoryOptions,
  28. } from '@/components/interfaces/Settings/Integrations/GithubIntegration/GitHubRepositoryField'
  29. import { InlineLink } from '@/components/ui/InlineLink'
  30. import { UpgradeToPro } from '@/components/ui/UpgradeToPro'
  31. import { useBranchCreateMutation } from '@/data/branches/branch-create-mutation'
  32. import { useBranchUpdateMutation } from '@/data/branches/branch-update-mutation'
  33. import { useBranchesQuery } from '@/data/branches/branches-query'
  34. import { useCheckGithubBranchValidity } from '@/data/integrations/github-branch-check-query'
  35. import { useGitHubConnectionCreateMutation } from '@/data/integrations/github-connection-create-mutation'
  36. import { useGitHubConnectionDeleteMutation } from '@/data/integrations/github-connection-delete-mutation'
  37. import { useGitHubConnectionUpdateMutation } from '@/data/integrations/github-connection-update-mutation'
  38. import type { GitHubConnection } from '@/data/integrations/integrations.types'
  39. import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
  40. import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
  41. import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
  42. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  43. import { DOCS_URL } from '@/lib/constants'
  44. interface GitHubIntegrationConnectionFormProps {
  45. connection?: GitHubConnection
  46. }
  47. export const GitHubIntegrationConnectionForm = ({
  48. connection,
  49. }: GitHubIntegrationConnectionFormProps) => {
  50. const { data: selectedProject } = useSelectedProjectQuery()
  51. const { data: selectedOrganization } = useSelectedOrganizationQuery()
  52. const [isConfirmingBranchChange, setIsConfirmingBranchChange] = useState(false)
  53. const [isConfirmingRepoChange, setIsConfirmingRepoChange] = useState(false)
  54. const isParentProject = !selectedProject?.parent_project_ref
  55. const { hasAccess: hasAccessToGitHubIntegration, isLoading: isLoadingEntitlements } =
  56. useCheckEntitlements('integrations.github_connections')
  57. const { hasAccess: hasAccessToBranching } = useCheckEntitlements('branching_limit')
  58. const { can: canUpdateGitHubConnection } = useAsyncCheckPermissions(
  59. PermissionAction.UPDATE,
  60. 'integrations.github_connections'
  61. )
  62. const { can: canCreateGitHubConnection } = useAsyncCheckPermissions(
  63. PermissionAction.CREATE,
  64. 'integrations.github_connections'
  65. )
  66. const {
  67. gitHubAuthorization,
  68. githubRepos,
  69. hasPartialResponseDueToSSO,
  70. isLoading: isLoadingRepositoryOptions,
  71. refetch: refetchRepositoryOptions,
  72. } = useGitHubRepositoryOptions()
  73. const { mutate: updateBranch } = useBranchUpdateMutation({
  74. onSuccess: () => {
  75. toast.success('Production branch settings successfully updated')
  76. },
  77. })
  78. const { mutate: createBranch } = useBranchCreateMutation({
  79. onSuccess: () => {
  80. toast.success('Production branch settings successfully updated')
  81. },
  82. onError: (error) => {
  83. console.error('Failed to enable branching:', error)
  84. },
  85. })
  86. const { data: existingBranches } = useBranchesQuery(
  87. { projectRef: selectedProject?.ref },
  88. { enabled: !!selectedProject?.ref }
  89. )
  90. const { mutateAsync: checkGithubBranchValidity, isPending: isCheckingBranch } =
  91. useCheckGithubBranchValidity({ onError: () => {} })
  92. const { mutate: createConnection, isPending: isCreatingConnection } =
  93. useGitHubConnectionCreateMutation({
  94. onSuccess: () => {
  95. toast.success('GitHub integration successfully updated')
  96. },
  97. onError: (error) => {
  98. // Don't show error toast when connection already exists - the branch
  99. // settings update will still proceed and show its own success toast
  100. if (!error.message?.includes('already exists')) {
  101. toast.error(`Failed to create GitHub connection: ${error.message}`)
  102. }
  103. },
  104. })
  105. const { mutateAsync: deleteConnection, isPending: isDeletingConnection } =
  106. useGitHubConnectionDeleteMutation({
  107. onSuccess: () => {
  108. toast.success('Successfully removed GitHub integration')
  109. },
  110. })
  111. const { mutate: updateConnectionSettings, isPending: isUpdatingConnection } =
  112. useGitHubConnectionUpdateMutation()
  113. const prodBranch = existingBranches?.find((branch) => branch.is_default)
  114. // Combined GitHub Settings Form
  115. const GitHubSettingsSchema = z
  116. .object({
  117. repositoryId: z.string().min(1, 'Please select a repository'),
  118. enableProductionSync: z.boolean().default(true),
  119. branchName: z.string().default('main'),
  120. new_branch_per_pr: z.boolean().default(true),
  121. brivenDirectory: z.string().default('.'),
  122. brivenChangesOnly: z.boolean().default(true),
  123. branchLimit: z.string().default('50'),
  124. })
  125. .superRefine(async (val, ctx) => {
  126. if (val.enableProductionSync && val.branchName && val.branchName.length > 0) {
  127. const repositoryId = val.repositoryId || connection?.repository.id.toString()
  128. if (repositoryId) {
  129. try {
  130. await checkGithubBranchValidity({
  131. repositoryId: Number(repositoryId),
  132. branchName: val.branchName,
  133. })
  134. } catch {
  135. const selectedRepo = githubRepos.find((repo) => repo.id === repositoryId)
  136. const repoName =
  137. selectedRepo?.name || connection?.repository.name || 'selected repository'
  138. ctx.addIssue({
  139. code: z.ZodIssueCode.custom,
  140. message: `Branch "${val.branchName}" not found in ${repoName}`,
  141. path: ['branchName'],
  142. })
  143. }
  144. }
  145. }
  146. })
  147. const githubSettingsForm = useForm<z.infer<typeof GitHubSettingsSchema>>({
  148. resolver: zodResolver(GitHubSettingsSchema as any),
  149. mode: 'onSubmit',
  150. reValidateMode: 'onBlur',
  151. defaultValues: {
  152. repositoryId: connection?.repository.id.toString() || '',
  153. enableProductionSync: true,
  154. branchName: 'main',
  155. new_branch_per_pr: true,
  156. brivenDirectory: '.',
  157. brivenChangesOnly: true,
  158. branchLimit: '3',
  159. },
  160. })
  161. const enableProductionSync = githubSettingsForm.watch('enableProductionSync')
  162. const newBranchPerPr = githubSettingsForm.watch('new_branch_per_pr')
  163. const currentRepositoryId = githubSettingsForm.watch('repositoryId')
  164. const handleCreateOrUpdateConnection = async (data: z.infer<typeof GitHubSettingsSchema>) => {
  165. if (!selectedProject?.ref || !selectedOrganization?.id) return
  166. try {
  167. if (connection) {
  168. // Check if repository is being changed
  169. if (connection.repository.id.toString() !== data.repositoryId) {
  170. setIsConfirmingRepoChange(true)
  171. return
  172. }
  173. // Update existing connection
  174. await handleUpdateConnection(data, connection)
  175. } else {
  176. // Create new connection
  177. const selectedRepo = githubRepos.find((repo) => repo.id === data.repositoryId)
  178. if (!selectedRepo) {
  179. toast.error('Please select a repository')
  180. return
  181. }
  182. await handleCreateConnection(data, selectedRepo)
  183. }
  184. } catch (error) {
  185. console.error('Error managing connection:', error)
  186. }
  187. }
  188. const handleCreateConnection = async (
  189. data: z.infer<typeof GitHubSettingsSchema>,
  190. selectedRepo: { id: string; installation_id: number }
  191. ) => {
  192. if (!selectedProject?.ref || !selectedOrganization?.id) return
  193. createConnection({
  194. organizationId: selectedOrganization.id,
  195. connection: {
  196. installation_id: selectedRepo.installation_id,
  197. project_ref: selectedProject.ref,
  198. repository_id: Number(selectedRepo.id),
  199. workdir: data.brivenDirectory,
  200. briven_changes_only: data.brivenChangesOnly,
  201. branch_limit: Number(data.branchLimit),
  202. new_branch_per_pr: data.new_branch_per_pr,
  203. },
  204. })
  205. if (!prodBranch) {
  206. createBranch({
  207. projectRef: selectedProject.ref,
  208. branchName: 'main',
  209. gitBranch: data.branchName,
  210. is_default: true,
  211. })
  212. } else {
  213. updateBranch({
  214. branchRef: prodBranch.project_ref,
  215. projectRef: selectedProject.ref,
  216. gitBranch: data.branchName,
  217. })
  218. }
  219. }
  220. const handleUpdateConnection = async (
  221. data: z.infer<typeof GitHubSettingsSchema>,
  222. currentConnection: GitHubConnection
  223. ) => {
  224. if (!selectedProject?.ref || !selectedOrganization?.id) return
  225. const originalBranchName = prodBranch?.git_branch
  226. if (originalBranchName && data.branchName !== originalBranchName && data.enableProductionSync) {
  227. setIsConfirmingBranchChange(true)
  228. return
  229. }
  230. await executeUpdate(data, currentConnection)
  231. }
  232. const executeUpdate = async (
  233. data: z.infer<typeof GitHubSettingsSchema>,
  234. currentConnection: GitHubConnection
  235. ) => {
  236. if (!selectedProject?.ref || !selectedOrganization?.id) return
  237. updateConnectionSettings({
  238. connectionId: currentConnection.id,
  239. organizationId: selectedOrganization.id,
  240. connection: {
  241. workdir: data.brivenDirectory,
  242. briven_changes_only: data.brivenChangesOnly,
  243. branch_limit: Number(data.branchLimit),
  244. new_branch_per_pr: data.new_branch_per_pr,
  245. },
  246. })
  247. if (prodBranch) {
  248. updateBranch({
  249. branchRef: prodBranch.project_ref,
  250. projectRef: selectedProject.ref,
  251. gitBranch: data.enableProductionSync ? data.branchName : '',
  252. branchName: data.branchName || 'main',
  253. })
  254. } else {
  255. // if for some reason, the project doesn't have a default branch yet, create it.
  256. createBranch({
  257. projectRef: selectedProject.ref,
  258. gitBranch: data.enableProductionSync ? data.branchName : '',
  259. branchName: data.branchName || 'main',
  260. is_default: true,
  261. })
  262. }
  263. setIsConfirmingBranchChange(false)
  264. }
  265. const onConfirmBranchChange = async () => {
  266. if (connection) {
  267. await executeUpdate(githubSettingsForm.getValues(), connection)
  268. }
  269. }
  270. const handleRemoveIntegration = async () => {
  271. if (!connection || !selectedOrganization?.id) return
  272. try {
  273. await deleteConnection({
  274. organizationId: selectedOrganization.id,
  275. connectionId: connection.id,
  276. })
  277. githubSettingsForm.reset({
  278. repositoryId: '',
  279. enableProductionSync: true,
  280. branchName: 'main',
  281. new_branch_per_pr: true,
  282. brivenDirectory: '.',
  283. brivenChangesOnly: true,
  284. branchLimit: '3',
  285. })
  286. } catch (error) {
  287. console.error('Error removing integration:', error)
  288. toast.error('Failed to remove integration')
  289. }
  290. }
  291. const onConfirmRepoChange = async () => {
  292. const data = githubSettingsForm.getValues()
  293. const selectedRepo = githubRepos.find((repo) => repo.id === data.repositoryId)
  294. if (!selectedRepo || !connection || !selectedOrganization?.id) return
  295. try {
  296. await deleteConnection({
  297. organizationId: selectedOrganization.id,
  298. connectionId: connection.id,
  299. })
  300. await handleCreateConnection(data, selectedRepo)
  301. setIsConfirmingRepoChange(false)
  302. } catch (error) {
  303. console.error('Error changing repository:', error)
  304. toast.error('Failed to change repository')
  305. }
  306. }
  307. useEffect(() => {
  308. if (connection) {
  309. const hasGitBranch = Boolean(prodBranch?.git_branch?.trim())
  310. githubSettingsForm.reset({
  311. repositoryId: connection.repository.id.toString(),
  312. enableProductionSync: hasGitBranch,
  313. branchName: prodBranch?.git_branch || 'main',
  314. new_branch_per_pr: connection.new_branch_per_pr,
  315. brivenDirectory: connection.workdir || '',
  316. brivenChangesOnly: connection.briven_changes_only,
  317. branchLimit: String(connection.branch_limit),
  318. })
  319. }
  320. }, [connection, prodBranch, githubSettingsForm])
  321. // Handle clearing branch name when production sync is disabled
  322. useEffect(() => {
  323. if (!enableProductionSync) {
  324. githubSettingsForm.setValue('branchName', '')
  325. } else if (enableProductionSync && !githubSettingsForm.getValues().branchName) {
  326. githubSettingsForm.setValue('branchName', 'main')
  327. }
  328. }, [enableProductionSync, githubSettingsForm])
  329. const isLoading =
  330. isLoadingEntitlements ||
  331. isCreatingConnection ||
  332. isUpdatingConnection ||
  333. isDeletingConnection ||
  334. isLoadingRepositoryOptions
  335. return (
  336. <>
  337. <Form {...githubSettingsForm}>
  338. <form
  339. onSubmit={githubSettingsForm.handleSubmit(handleCreateOrUpdateConnection)}
  340. className={cn(!isParentProject && 'opacity-25 pointer-events-none')}
  341. >
  342. <Card>
  343. <CardContent className="space-y-6">
  344. <GitHubRepositoryField
  345. form={githubSettingsForm}
  346. name="repositoryId"
  347. label="GitHub Repository"
  348. layout="flex-row-reverse"
  349. description={
  350. connection
  351. ? 'Change the connected repository'
  352. : 'Select the repository to connect to your project'
  353. }
  354. disabled={
  355. (!connection && !canCreateGitHubConnection) ||
  356. (connection && !canUpdateGitHubConnection)
  357. }
  358. selectedRepositoryName={connection?.repository.name}
  359. repositories={githubRepos}
  360. gitHubAuthorization={gitHubAuthorization}
  361. hasPartialResponseDueToSSO={hasPartialResponseDueToSSO}
  362. isLoading={isLoadingRepositoryOptions}
  363. refetch={refetchRepositoryOptions}
  364. onRepositorySelect={(repo) => {
  365. githubSettingsForm.setValue('branchName', repo.default_branch || 'main')
  366. }}
  367. />
  368. </CardContent>
  369. <AnimatePresence>
  370. {gitHubAuthorization !== null && !!currentRepositoryId && (
  371. <motion.div
  372. initial={{ opacity: 0, y: -16 }}
  373. animate={{ opacity: 1, y: 0 }}
  374. exit={{ opacity: 0, y: -16 }}
  375. >
  376. <CardContent>
  377. <FormField
  378. control={githubSettingsForm.control}
  379. name="brivenDirectory"
  380. render={({ field }) => (
  381. <FormItemLayout
  382. layout="flex-row-reverse"
  383. label="Working directory"
  384. description={
  385. <>
  386. Relative path to the directory containing your{' '}
  387. <code className="text-code-inline whitespace-nowrap">briven/</code>{' '}
  388. folder.{' '}
  389. <InlineLink
  390. href={`${DOCS_URL}/guides/deployment/branching/github-integration#set-the-working-directory`}
  391. >
  392. Learn more
  393. </InlineLink>
  394. </>
  395. }
  396. >
  397. <FormControl>
  398. <Input
  399. {...field}
  400. placeholder="."
  401. autoComplete="off"
  402. disabled={!canUpdateGitHubConnection}
  403. />
  404. </FormControl>
  405. </FormItemLayout>
  406. )}
  407. />
  408. </CardContent>
  409. <CardContent>
  410. {/* Production Branch Sync Section */}
  411. <div className="space-y-4">
  412. <FormField
  413. control={githubSettingsForm.control}
  414. name="enableProductionSync"
  415. render={({ field }) => (
  416. <FormItemLayout
  417. layout="flex-row-reverse"
  418. label="Deploy to production"
  419. description="Deploy changes to production on push including PR merges"
  420. >
  421. <FormControl>
  422. <Switch
  423. checked={field.value}
  424. onCheckedChange={field.onChange}
  425. disabled={!canUpdateGitHubConnection}
  426. />
  427. </FormControl>
  428. </FormItemLayout>
  429. )}
  430. />
  431. <div
  432. className={cn(
  433. 'space-y-4 pl-6 border-l',
  434. !enableProductionSync && 'opacity-25 pointer-events-none'
  435. )}
  436. >
  437. <FormField
  438. control={githubSettingsForm.control}
  439. name="branchName"
  440. render={({ field }) => (
  441. <FormItemLayout
  442. layout="flex-row-reverse"
  443. label="Production branch name"
  444. description="The GitHub branch to sync with your production database (e.g., main, master)"
  445. >
  446. <div className="relative w-full">
  447. <FormControl>
  448. <Input
  449. {...field}
  450. autoComplete="off"
  451. disabled={!canUpdateGitHubConnection || !enableProductionSync}
  452. />
  453. </FormControl>
  454. <div className="absolute top-2.5 right-3 flex items-center gap-2">
  455. {isCheckingBranch && (
  456. <Loader2 size={14} className="animate-spin" />
  457. )}
  458. </div>
  459. </div>
  460. </FormItemLayout>
  461. )}
  462. />
  463. </div>
  464. </div>
  465. </CardContent>
  466. <CardContent>
  467. {hasAccessToBranching ? (
  468. <Admonition type="warning" title="Branching and billing" className="mb-4">
  469. Branching Compute is not covered by your organization&apos;s Spend Cap.
  470. Costs should be closely monitored, as they may be incurred.{' '}
  471. <InlineLink
  472. href={`${DOCS_URL}/guides/platform/cost-control#usage-items-not-covered-by-the-spend-cap`}
  473. >
  474. Learn more
  475. </InlineLink>
  476. </Admonition>
  477. ) : (
  478. <UpgradeToPro
  479. className="mb-4"
  480. layout="vertical"
  481. source="projectIntegrations"
  482. featureProposition="automatically create preview branches from pull requests"
  483. primaryText="Branching with GitHub integration"
  484. secondaryText="Upgrade to the Pro Plan to enable branching and automatically create preview branches for every pull request"
  485. docsUrl={`${DOCS_URL}/guides/deployment/branching`}
  486. />
  487. )}
  488. {/* Automatic Branching Section */}
  489. <div className="space-y-4">
  490. <FormField
  491. disabled={!hasAccessToBranching}
  492. control={githubSettingsForm.control}
  493. name="new_branch_per_pr"
  494. render={({ field }) => (
  495. <FormItemLayout
  496. layout="flex-row-reverse"
  497. label="Automatic branching"
  498. className={cn(!hasAccessToBranching && 'opacity-25')}
  499. description="Create preview branches for every pull request"
  500. >
  501. <FormControl>
  502. <Switch
  503. checked={!hasAccessToBranching ? false : field.value}
  504. onCheckedChange={field.onChange}
  505. disabled={!hasAccessToBranching || !canCreateGitHubConnection}
  506. />
  507. </FormControl>
  508. </FormItemLayout>
  509. )}
  510. />
  511. <div
  512. className={cn(
  513. 'space-y-4 pl-6 border-l',
  514. (!hasAccessToBranching || !newBranchPerPr) &&
  515. 'opacity-25 pointer-events-none'
  516. )}
  517. >
  518. <FormField
  519. control={githubSettingsForm.control}
  520. name="branchLimit"
  521. render={({ field }) => (
  522. <FormItemLayout
  523. layout="flex-row-reverse"
  524. label="Branch limit"
  525. description="Maximum number of preview branches"
  526. >
  527. <FormControl>
  528. <Input
  529. {...field}
  530. type="number"
  531. autoComplete="off"
  532. value={!hasAccessToBranching ? 0 : field.value}
  533. disabled={
  534. !hasAccessToBranching ||
  535. !newBranchPerPr ||
  536. !canUpdateGitHubConnection
  537. }
  538. />
  539. </FormControl>
  540. </FormItemLayout>
  541. )}
  542. />
  543. <FormField
  544. control={githubSettingsForm.control}
  545. name="brivenChangesOnly"
  546. render={({ field }) => (
  547. <FormItemLayout
  548. layout="flex-row-reverse"
  549. label="Briven changes only"
  550. description="Only create branches when Briven files change"
  551. >
  552. <FormControl>
  553. <Switch
  554. checked={!hasAccessToBranching ? false : field.value}
  555. onCheckedChange={(val) => field.onChange(val)}
  556. disabled={
  557. !hasAccessToBranching ||
  558. !newBranchPerPr ||
  559. !canUpdateGitHubConnection
  560. }
  561. />
  562. </FormControl>
  563. </FormItemLayout>
  564. )}
  565. />
  566. </div>
  567. </div>
  568. </CardContent>
  569. <CardFooter className="flex justify-between items-center">
  570. <div>
  571. {connection && (
  572. <Button
  573. type="outline"
  574. onClick={handleRemoveIntegration}
  575. disabled={isDeletingConnection || isCheckingBranch}
  576. loading={isDeletingConnection}
  577. >
  578. Disable integration
  579. </Button>
  580. )}
  581. </div>
  582. <div className="flex space-x-2">
  583. {githubSettingsForm.formState.isDirty && (
  584. <Button
  585. type="default"
  586. onClick={() => githubSettingsForm.reset()}
  587. disabled={!canUpdateGitHubConnection || isCheckingBranch}
  588. >
  589. Cancel
  590. </Button>
  591. )}
  592. <Button
  593. type="primary"
  594. htmlType="submit"
  595. disabled={
  596. !hasAccessToGitHubIntegration ||
  597. (!connection && !canCreateGitHubConnection) ||
  598. (connection && !canUpdateGitHubConnection) ||
  599. isCheckingBranch ||
  600. isLoading ||
  601. (!connection && !githubSettingsForm.getValues().repositoryId) ||
  602. (connection && !githubSettingsForm.formState.isDirty)
  603. }
  604. loading={isLoading}
  605. >
  606. {connection ? 'Save changes' : 'Enable integration'}
  607. </Button>
  608. </div>
  609. </CardFooter>
  610. </motion.div>
  611. )}
  612. </AnimatePresence>
  613. </Card>
  614. </form>
  615. </Form>
  616. <ConfirmationModal
  617. variant="warning"
  618. visible={isConfirmingBranchChange}
  619. title="Changing production git branch"
  620. confirmLabel="Confirm"
  621. size="medium"
  622. onCancel={() => setIsConfirmingBranchChange(false)}
  623. onConfirm={onConfirmBranchChange}
  624. loading={isUpdatingConnection}
  625. >
  626. <p className="text-sm text-foreground-light">
  627. Open pull requests will only update your Briven project on merge if the git base branch
  628. matches this new production git branch.
  629. </p>
  630. </ConfirmationModal>
  631. <ConfirmationModal
  632. variant="warning"
  633. visible={isConfirmingRepoChange}
  634. title="Changing GitHub repository"
  635. confirmLabel="Change repository"
  636. size="medium"
  637. onCancel={() => setIsConfirmingRepoChange(false)}
  638. onConfirm={onConfirmRepoChange}
  639. loading={isLoading}
  640. >
  641. <div className="space-y-3">
  642. <p className="text-sm text-foreground-light">
  643. This will disconnect your current repository and create a new connection with the
  644. selected repository. All existing Briven branches that are connected to the old
  645. repository will no longer be synced.
  646. </p>
  647. </div>
  648. </ConfirmationModal>
  649. </>
  650. )
  651. }