'use client' import type { Branch, Org, Variable, } from '~/components/ProjectConfigVariables/ProjectConfigVariables.utils' import { Check, Copy } from 'lucide-react' import Link from 'next/link' import { useEffect, useMemo, useState } from 'react' import CopyToClipboard from 'react-copy-to-clipboard' import { withErrorBoundary } from 'react-error-boundary' import { proxy, useSnapshot } from 'valtio' import { LOCAL_STORAGE_KEYS, useIsLoggedIn, useIsUserLoading } from 'common' import { Button_Shadcn_ as Button, cn, Input_Shadcn_ as Input } 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 { useDebounce } from '~/hooks/useDebounce' import { useBranchesQuery } from '~/lib/fetch/branches' import { useOrganizationsQuery } from '~/lib/fetch/organizations' import { useSupavisorConfigQuery, type SupavisorConfigData } from '~/lib/fetch/pooler' import { useProjectKeysQuery, useProjectSettingsQuery } from '~/lib/fetch/projectApi' import { isProjectPaused, ProjectInfoInfinite, useProjectsInfiniteQuery, } from '~/lib/fetch/projects-infinite' import { 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.projectPaused' | 'loggedIn.selectedProject.dataPending' | 'loggedIn.selectedProject.dataError' | 'loggedIn.selectedProject.dataSuccess' const projectsStore = proxy({ selectedOrg: null as Org | null, selectedProject: null as ProjectInfoInfinite | null, setSelectedOrgProject: (org: Org | null, project: ProjectInfoInfinite | 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 [search, setSearch] = useState('') const debouncedSearch = useDebounce(search, 500) const { selectedOrg, selectedProject, setSelectedOrgProject } = useSnapshot(projectsStore) const { data: organizations, isPending: organizationsIsPending, isError: organizationsIsError, } = useOrganizationsQuery({ enabled: isLoggedIn }) const { data: projectsData, isPending: projectsIsPending, isError: projectsIsError, isFetching, isFetchingNextPage, hasNextPage, fetchNextPage, } = useProjectsInfiniteQuery( { search: search.length === 0 ? search : debouncedSearch }, { enabled: isLoggedIn } ) const projects = useMemo(() => projectsData?.pages.flatMap((page) => page.projects), [projectsData?.pages]) || [] 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), } }) .filter(Boolean) as ComboBoxOption[]), [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 | undefined let storedProject: ProjectInfoInfinite | undefined 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 if (projects!.length > 0) { 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) } }} search={search} isFetching={isFetching} isFetchingNextPage={isFetchingNextPage} hasNextPage={hasNextPage} fetchNextPage={fetchNextPage} setSearch={setSearch} useCommandSearch={false} /> ) } function BranchSelector() { const userLoading = useIsUserLoading() const isLoggedIn = useIsLoggedIn() const { selectedProject, selectedBranch, setSelectedBranch } = useSnapshot(projectsStore) const [branchSearch, setBranchSearch] = useState('') const projectPaused = isProjectPaused(selectedProject) const hasBranches = selectedProject?.is_branch_enabled ?? false const { data, isPending, isError } = useBranchesQuery( { projectRef: selectedProject?.ref }, { enabled: isLoggedIn && !projectPaused && hasBranches } ) const stateSummary: BranchesDataState = userLoading ? 'userLoading' : !isLoggedIn ? 'loggedOut' : !hasBranches || projectPaused ? '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 | undefined 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 projectPaused = isProjectPaused(selectedProject) const hasBranches = selectedProject?.is_branch_enabled ?? false const ref = hasBranches ? selectedBranch?.project_ref : selectedProject?.ref const needsApiQuery = variable === 'publishable' || variable === 'anon' || variable === 'url' const needsSupavisorQuery = variable === 'sessionPooler' const { data: apiSettingsData, isPending: isApiSettingsPending, isError: isApiSettingsError, } = useProjectSettingsQuery( { projectRef: ref, }, { enabled: isLoggedIn && !!ref && !projectPaused && needsApiQuery } ) const { data: apiKeysData, isPending: isApiKeysPending, isError: isApiKeysError, } = useProjectKeysQuery( { projectRef: ref, }, { enabled: isLoggedIn && !!ref && !projectPaused && needsApiQuery } ) const { data: supavisorConfig, isPending: isSupavisorPending, isError: isSupavisorError, } = useSupavisorConfigQuery( { projectRef: ref, }, { enabled: isLoggedIn && !!ref && !projectPaused && needsSupavisorQuery } ) function isInvalidSupavisorData(supavisorData: SupavisorConfigData) { return supavisorData.length === 0 } const stateSummary: VariableDataState = isUserLoading ? 'userLoading' : !isLoggedIn ? 'loggedOut' : !ref ? 'loggedIn.noSelectedProject' : projectPaused ? 'loggedIn.selectedProject.projectPaused' : (needsApiQuery ? isApiSettingsPending || isApiKeysPending : isSupavisorPending) ? 'loggedIn.selectedProject.dataPending' : ( needsApiQuery ? isApiSettingsError || isApiKeysError : isSupavisorError || isInvalidSupavisorData(supavisorConfig!) ) ? 'loggedIn.selectedProject.dataError' : 'loggedIn.selectedProject.dataSuccess' let variableValue: string = '' if (stateSummary === 'loggedIn.selectedProject.dataSuccess') { switch (variable) { case 'url': variableValue = `https://${apiSettingsData?.app_config?.endpoint}` break case 'anon': variableValue = apiKeysData?.find((key) => key.type === 'legacy' && key.id === 'anon')?.api_key || '' break case 'publishable': variableValue = apiKeysData?.find((key) => key.type === 'publishable')?.api_key || '' break case 'sessionPooler': variableValue = supavisorConfig?.[0]?.connection_string || '' } } 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 .

), })