choose-project.tsx167 lines · main
1import { useParams } from 'common'
2import { keyBy } from 'lodash'
3import { useCallback, useMemo } from 'react'
4import { toast } from 'sonner'
5
6import { ENV_VAR_RAW_KEYS } from '@/components/interfaces/Integrations/Vercel/Integrations-Vercel.constants'
7import { isVercelUrl } from '@/components/interfaces/Integrations/Vercel/VercelIntegration.utils'
8import ProjectLinker, {
9 ForeignProject,
10} from '@/components/interfaces/Integrations/VercelGithub/ProjectLinker'
11import { Markdown } from '@/components/interfaces/Markdown'
12import VercelIntegrationWindowLayout from '@/components/layouts/IntegrationsLayout/VercelIntegrationWindowLayout'
13import { ScaffoldColumn, ScaffoldContainer } from '@/components/layouts/Scaffold'
14import { vercelIcon } from '@/components/to-be-cleaned/ListIcons'
15import { useOrgIntegrationsQuery } from '@/data/integrations/integrations-query-org-only'
16import { useIntegrationVercelConnectionsCreateMutation } from '@/data/integrations/integrations-vercel-connections-create-mutation'
17import { useVercelProjectsQuery } from '@/data/integrations/integrations-vercel-projects-query'
18import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
19import { BASE_PATH } from '@/lib/constants'
20import { EMPTY_ARR } from '@/lib/void'
21import { useIntegrationInstallationSnapshot } from '@/state/integration-installation'
22import type { NextPageWithLayout, Organization } from '@/types'
23
24const VERCEL_ICON = (
25 <img src={`${BASE_PATH}/img/icons/vercel-icon.svg`} alt="Vercel Icon" className="w-4" />
26)
27
28const VercelIntegration: NextPageWithLayout = () => {
29 const { slug, configurationId, next } = useParams()
30
31 /**
32 * Fetch the list of organization based integration installations for Vercel.
33 *
34 * Array of integrations installed on all
35 */
36 const { data: integrationData } = useOrgIntegrationsQuery({ orgSlug: slug })
37
38 const { data } = useOrganizationsQuery({ enabled: slug !== undefined })
39
40 const organization = data?.find((organization: Organization) => organization.slug === slug)
41
42 const integration = integrationData?.find(
43 (x) =>
44 x.metadata !== undefined &&
45 'configuration_id' in x.metadata &&
46 x.metadata?.configuration_id === configurationId
47 )
48
49 const { data: vercelProjectsData, isPending: isLoadingVercelProjectsData } =
50 useVercelProjectsQuery(
51 {
52 organization_integration_id: integration?.id,
53 },
54 { enabled: integration?.id !== undefined }
55 )
56
57 const vercelProjects = useMemo(() => vercelProjectsData ?? EMPTY_ARR, [vercelProjectsData])
58 const vercelProjectsById = useMemo(() => keyBy(vercelProjects, 'id'), [vercelProjects])
59
60 const getForeignProjectIcon = useCallback(
61 (_project: ForeignProject) => {
62 const project = vercelProjectsById[_project.id]
63
64 return !project?.framework ? (
65 vercelIcon
66 ) : (
67 <img
68 src={`${BASE_PATH}/img/icons/frameworks/${project.framework}.svg`}
69 width={21}
70 height={21}
71 alt={`icon`}
72 />
73 )
74 },
75 [vercelProjectsById]
76 )
77
78 const snapshot = useIntegrationInstallationSnapshot()
79
80 const { mutate: createConnections, isPending: isCreatingConnection } =
81 useIntegrationVercelConnectionsCreateMutation({
82 onSuccess() {
83 if (next && isVercelUrl(next)) {
84 snapshot.setLoading(false)
85 window.location.href = next
86 }
87 },
88 onMutate() {
89 snapshot.setLoading(true)
90 },
91 onError(error) {
92 snapshot.setLoading(false)
93 toast.error(`Creating connection failed: ${error.message}`)
94 },
95 })
96
97 const onCreateConnections = useCallback(
98 (vars: any) => {
99 createConnections({
100 ...vars,
101 connection: {
102 ...vars.connection,
103 metadata: {
104 ...vars.connection.metadata,
105 brivenConfig: {
106 projectEnvVars: {
107 write: true,
108 },
109 },
110 },
111 },
112 })
113 },
114 [createConnections]
115 )
116
117 return (
118 <>
119 <ScaffoldContainer className="flex flex-col gap-6 grow py-8">
120 <ScaffoldColumn className="max-w-[900px]! mx-auto w-full">
121 <header>
122 <h2>Create your first Project Connection</h2>
123 <Markdown
124 className="text-foreground-lighter"
125 content={`
126This Briven integration manages your environment variables automatically to provide the latest keys in the unlikely event that you will need to refresh your JWT token.
127`}
128 />
129 </header>
130 <ProjectLinker
131 slug={organization?.slug}
132 organizationIntegrationId={integration?.id}
133 foreignProjects={vercelProjects}
134 onCreateConnections={onCreateConnections}
135 installedConnections={integration?.connections}
136 isLoading={isCreatingConnection}
137 integrationIcon={VERCEL_ICON}
138 getForeignProjectIcon={getForeignProjectIcon}
139 choosePrompt="Choose Vercel Project"
140 onSkip={() => {
141 if (next && isVercelUrl(next)) {
142 window.location.href = next
143 }
144 }}
145 loadingForeignProjects={isLoadingVercelProjectsData}
146 mode="Vercel"
147 />
148 <Markdown
149 content={`
150The following environment variables will be added:
151
152${ENV_VAR_RAW_KEYS.map((x) => {
153 return `\n - \`${x}\``
154})}
155`}
156 />
157 </ScaffoldColumn>
158 </ScaffoldContainer>
159 </>
160 )
161}
162
163VercelIntegration.getLayout = (page) => (
164 <VercelIntegrationWindowLayout>{page}</VercelIntegrationWindowLayout>
165)
166
167export default VercelIntegration