'use client' import type { Branch, Org, Project, Variable, } from '~/components/ProjectConfigVariables/ProjectConfigVariables.utils' import type { ProjectApiData } from '~/lib/fetch/projectApi' import { Copy, Check } from 'lucide-react' import Link from 'next/link' import { useEffect, useMemo } from 'react' import CopyToClipboard from 'react-copy-to-clipboard' import { withErrorBoundary } from 'react-error-boundary' import { proxy, useSnapshot } from 'valtio' import { useIsLoggedIn, useIsUserLoading } from 'common' import { Button_Shadcn_ as Button, Input_Shadcn_ as Input, cn } from 'ui' import { ComboBox, ComboBoxOption, } from '~/components/ProjectConfigVariables/ProjectConfigVariables.ComboBox' import { fromBranchValue, fromOrgProjectValue, prettyFormatVariable, toBranchValue, toDisplayNameOrgProject, toOrgProjectValue, } from '~/components/ProjectConfigVariables/ProjectConfigVariables.utils' import { useCopy } from '~/hooks/useCopy' import { useBranchesQuery } from '~/lib/fetch/branches' import { useOrganizationsQuery } from '~/lib/fetch/organizations' import { useProjectApiQuery } from '~/lib/fetch/projectApi' import { useProjectsQuery } from '~/lib/fetch/projects' import { LOCAL_STORAGE_KEYS, retrieve, storeOrRemoveNull } from '~/lib/storage' import { useOnLogout } from '~/lib/userAuth' type ProjectOrgDataState = | 'userLoading' | 'loggedOut' | 'loggedIn.dataPending' | 'loggedIn.dataError' | 'loggedIn.dataSuccess.hasData' | 'loggedIn.dataSuccess.hasNoData' type BranchesDataState = | 'userLoading' | 'loggedOut' | 'loggedIn.noBranches' | 'loggedIn.branches.dataPending' | 'loggedIn.branches.dataError' | 'loggedIn.branches.dataSuccess.hasData' | 'loggedIn.branches.dataSuccess.noData' type VariableDataState = | 'userLoading' | 'loggedOut' | 'loggedIn.noSelectedProject' | 'loggedIn.selectedProject.dataPending' | 'loggedIn.selectedProject.dataError' | 'loggedIn.selectedProject.dataSuccess' const projectsStore = proxy({ selectedOrg: null as Org | null, selectedProject: null as Project | null, setSelectedOrgProject: (org: Org | null, project: Project | null) => { projectsStore.selectedOrg = org storeOrRemoveNull('local', LOCAL_STORAGE_KEYS.SAVED_ORG, org?.id.toString()) projectsStore.selectedProject = project storeOrRemoveNull('local', LOCAL_STORAGE_KEYS.SAVED_PROJECT, project?.ref) }, selectedBranch: null as Branch | null, setSelectedBranch: (branch: Branch | null) => { projectsStore.selectedBranch = branch storeOrRemoveNull('local', LOCAL_STORAGE_KEYS.SAVED_BRANCH, branch?.id) }, clear: () => { projectsStore.setSelectedOrgProject(null, null) projectsStore.setSelectedBranch(null) }, }) function OrgProjectSelector() { const isUserLoading = useIsUserLoading() const isLoggedIn = useIsLoggedIn() const { selectedOrg, selectedProject, setSelectedOrgProject } = useSnapshot(projectsStore) const { data: organizations, isPending: organizationsIsPending, isError: organizationsIsError, } = useOrganizationsQuery({ enabled: isLoggedIn }) const { data: projects, isPending: projectsIsPending, isError: projectsIsError, } = useProjectsQuery({ enabled: isLoggedIn }) const anyIsPending = organizationsIsPending || projectsIsPending const anyIsError = organizationsIsError || projectsIsError const stateSummary: ProjectOrgDataState = isUserLoading ? 'userLoading' : !isLoggedIn ? 'loggedOut' : anyIsPending ? 'loggedIn.dataPending' : anyIsError ? 'loggedIn.dataError' : projects?.length === 0 ? 'loggedIn.dataSuccess.hasNoData' : 'loggedIn.dataSuccess.hasData' const formattedData: ComboBoxOption[] = useMemo( () => stateSummary !== 'loggedIn.dataSuccess.hasData' ? [] : projects.map((project) => { const organization = organizations.find((org) => org.id === project.organization_id) return { id: project.ref, value: toOrgProjectValue(organization, project), displayName: toDisplayNameOrgProject(organization, project), } }), [organizations, projects, stateSummary] ) useEffect(() => { if (stateSummary === 'loggedIn.dataSuccess.hasData' && (!selectedOrg || !selectedProject)) { const storedMaybeOrgId = retrieve('local', LOCAL_STORAGE_KEYS.SAVED_ORG) const storedMaybeProjectRef = retrieve('local', LOCAL_STORAGE_KEYS.SAVED_PROJECT) let storedOrg: Org let storedProject: Project if (storedMaybeOrgId && storedMaybeProjectRef) { storedOrg = organizations.find((org) => org.id === Number(storedMaybeOrgId)) storedProject = projects.find((project) => project.ref === storedMaybeProjectRef) } if (storedOrg && storedProject && storedProject.organization_id === storedOrg.id) { setSelectedOrgProject(storedOrg, storedProject) } else { const firstProject = projects[0] const matchingOrg = organizations.find((org) => org.id === firstProject.organization_id) if (matchingOrg) setSelectedOrgProject(matchingOrg, firstProject) } } }, [organizations, projects, selectedOrg, selectedProject, setSelectedOrgProject, stateSummary]) return ( { const [orgId, projectRef] = fromOrgProjectValue(optionValue) if (!orgId || !projectRef) return const org = organizations.find((org) => org.id === orgId) const project = projects.find((project) => project.ref === projectRef) if (org && project && project.organization_id === org.id) { setSelectedOrgProject(org, project) } }} /> ) } function BranchSelector() { const userLoading = useIsUserLoading() const isLoggedIn = useIsLoggedIn() const { selectedProject, selectedBranch, setSelectedBranch } = useSnapshot(projectsStore) const hasBranches = selectedProject?.is_branch_enabled ?? false const { data, isPending, isError } = useBranchesQuery( { projectRef: selectedProject?.ref }, { enabled: isLoggedIn && hasBranches } ) const stateSummary: BranchesDataState = userLoading ? 'userLoading' : !isLoggedIn ? 'loggedOut' : !hasBranches ? 'loggedIn.noBranches' : isPending ? 'loggedIn.branches.dataPending' : isError ? 'loggedIn.branches.dataError' : data.length === 0 ? 'loggedIn.branches.dataSuccess.noData' : 'loggedIn.branches.dataSuccess.hasData' const formattedData: ComboBoxOption[] = stateSummary !== 'loggedIn.branches.dataSuccess.hasData' ? [] : data.map((branch) => ({ id: branch.id, displayName: branch.name, value: toBranchValue(branch), })) useEffect(() => { if (stateSummary === 'loggedIn.branches.dataSuccess.hasData' && !selectedBranch) { const storedMaybeBranchId = retrieve('local', LOCAL_STORAGE_KEYS.SAVED_BRANCH) let storedBranch: Branch if (storedMaybeBranchId) { storedBranch = data.find((branch) => branch.id === storedMaybeBranchId) } if (storedBranch) { setSelectedBranch(storedBranch) } else { const productionBranch = data.find( (branch) => branch.project_ref === branch.parent_project_ref ) setSelectedBranch(productionBranch ?? data[0]) } } }, [data, selectedBranch, setSelectedBranch, stateSummary]) return hasBranches ? ( { const [branchId] = fromBranchValue(option) if (branchId) { const branch = data.find((branch) => branch.id === branchId) if (branch) setSelectedBranch(branch) } }} /> ) : null } function VariableView({ variable, className }: { variable: Variable; className?: string }) { const isUserLoading = useIsUserLoading() const isLoggedIn = useIsLoggedIn() const { selectedProject, selectedBranch } = useSnapshot(projectsStore) const hasBranches = selectedProject?.is_branch_enabled ?? false const ref = hasBranches ? selectedBranch?.project_ref : selectedProject?.ref const { data: apiData, isPending, isError, } = useProjectApiQuery( { projectRef: ref, }, { enabled: isLoggedIn && !!ref } ) function isInvalid(apiData: ProjectApiData) { switch (variable) { case 'url': return !apiData.autoApiService.endpoint case 'anonKey': // If the anon key is not available, the backend may return the string: // You're using an older version of Supabase. Create a new project for the latest Auth features. return /older version/.test(apiData.autoApiService.defaultApiKey) } } const stateSummary: VariableDataState = isUserLoading ? 'userLoading' : !isLoggedIn ? 'loggedOut' : !ref ? 'loggedIn.noSelectedProject' : isPending ? 'loggedIn.selectedProject.dataPending' : isError || isInvalid(apiData) ? 'loggedIn.selectedProject.dataError' : 'loggedIn.selectedProject.dataSuccess' let variableValue: string = null if (stateSummary === 'loggedIn.selectedProject.dataSuccess') { switch (variable) { case 'url': variableValue = `${apiData.autoApiService.protocol || 'https'}://${ apiData.autoApiService.endpoint }` break case 'anonKey': variableValue = apiData.autoApiService.defaultApiKey break } } const { copied, handleCopy } = useCopy() return ( <>
{stateSummary === 'loggedIn.selectedProject.dataError' && (

You can also copy your {prettyFormatVariable[variable]} from the{' '} dashboard .

)} ) } function LoginHint({ variable }: { variable: Variable }) { const isUserLoading = useIsUserLoading() const isLoggedIn = useIsLoggedIn() if (isUserLoading || isLoggedIn) return null return (

To get your {prettyFormatVariable[variable]},{' '} log in .

) } function ProjectConfigVariablesInternal({ variable }: { variable: Variable }) { const { clear: clearSharedStoreData } = useSnapshot(projectsStore) useOnLogout(clearSharedStoreData) return (
{prettyFormatVariable[variable]}
) } export const ProjectConfigVariables = withErrorBoundary(ProjectConfigVariablesInternal, { fallback: (

Couldn't display your API settings. You can get them from the{' '} dashboard .

), })