choose-project.tsx167 lines · main
| 1 | import { useParams } from 'common' |
| 2 | import { keyBy } from 'lodash' |
| 3 | import { useCallback, useMemo } from 'react' |
| 4 | import { toast } from 'sonner' |
| 5 | |
| 6 | import { ENV_VAR_RAW_KEYS } from '@/components/interfaces/Integrations/Vercel/Integrations-Vercel.constants' |
| 7 | import { isVercelUrl } from '@/components/interfaces/Integrations/Vercel/VercelIntegration.utils' |
| 8 | import ProjectLinker, { |
| 9 | ForeignProject, |
| 10 | } from '@/components/interfaces/Integrations/VercelGithub/ProjectLinker' |
| 11 | import { Markdown } from '@/components/interfaces/Markdown' |
| 12 | import VercelIntegrationWindowLayout from '@/components/layouts/IntegrationsLayout/VercelIntegrationWindowLayout' |
| 13 | import { ScaffoldColumn, ScaffoldContainer } from '@/components/layouts/Scaffold' |
| 14 | import { vercelIcon } from '@/components/to-be-cleaned/ListIcons' |
| 15 | import { useOrgIntegrationsQuery } from '@/data/integrations/integrations-query-org-only' |
| 16 | import { useIntegrationVercelConnectionsCreateMutation } from '@/data/integrations/integrations-vercel-connections-create-mutation' |
| 17 | import { useVercelProjectsQuery } from '@/data/integrations/integrations-vercel-projects-query' |
| 18 | import { useOrganizationsQuery } from '@/data/organizations/organizations-query' |
| 19 | import { BASE_PATH } from '@/lib/constants' |
| 20 | import { EMPTY_ARR } from '@/lib/void' |
| 21 | import { useIntegrationInstallationSnapshot } from '@/state/integration-installation' |
| 22 | import type { NextPageWithLayout, Organization } from '@/types' |
| 23 | |
| 24 | const VERCEL_ICON = ( |
| 25 | <img src={`${BASE_PATH}/img/icons/vercel-icon.svg`} alt="Vercel Icon" className="w-4" /> |
| 26 | ) |
| 27 | |
| 28 | const 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={` |
| 126 | This 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={` |
| 150 | The 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 | |
| 163 | VercelIntegration.getLayout = (page) => ( |
| 164 | <VercelIntegrationWindowLayout>{page}</VercelIntegrationWindowLayout> |
| 165 | ) |
| 166 | |
| 167 | export default VercelIntegration |