'use client' import { useInfiniteQuery, useQuery } from '@tanstack/react-query' import { LOCAL_STORAGE_KEYS, useIsLoggedIn, useIsUserLoading } from 'common' import { useEffect, useMemo, useState } from 'react' import { ComboBox, type ComboBoxOption } from './ComboBox' import { fromOrgProjectValue, toDisplayNameOrgProject, toOrgProjectValue, type Org } from './utils' import { useDebounce } from '@/hooks/useDebounce' import { organizationsQueryOptions } from '@/lib/fetch/organizations' import { projectKeysQueryOptions } from '@/lib/fetch/projectApi' import { projectsInfiniteQueryOptions, type ProjectInfoInfinite } from '@/lib/fetch/projects' import { retrieve, storeOrRemoveNull } from '@/lib/storage' interface ProjectPickerProps { onProjectResolved: (data: { projectRef: string; anonKey: string } | null) => void } export function ProjectPicker({ onProjectResolved }: ProjectPickerProps) { const isUserLoading = useIsUserLoading() const isLoggedIn = useIsLoggedIn() const [selectedOrg, setSelectedOrg] = useState(null) const [selectedProject, setSelectedProject] = useState(null) const [missingAnonKey, setMissingAnonKey] = useState(false) const [search, setSearch] = useState('') const debouncedSearch = useDebounce(search, 500) const { data: organizations, isPending: organizationsIsPending, isError: organizationsIsError, } = useQuery(organizationsQueryOptions({ enabled: isLoggedIn })) const { data: projectsData, isPending: projectsIsPending, isError: projectsIsError, isFetching, isFetchingNextPage, hasNextPage, fetchNextPage, } = useInfiniteQuery( projectsInfiniteQueryOptions( { 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 isReady = isLoggedIn && !isUserLoading && !anyIsPending && !anyIsError && projects?.length > 0 const formattedData: ComboBoxOption[] = useMemo( () => !isReady ? [] : (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, isReady] ) // Auto-select from localStorage or first project useEffect(() => { if (isReady && (!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) { setSelectedOrg(storedOrg) setSelectedProject(storedProject) } else if (projects!.length > 0) { const firstProject = projects![0] const matchingOrg = organizations!.find((org) => org.id === firstProject.organization_id) if (matchingOrg) { setSelectedOrg(matchingOrg) setSelectedProject(firstProject) } } } }, [organizations, projects, selectedOrg, selectedProject, isReady]) function handleSelectOrgProject(optionValue: string) { 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) { setSelectedOrg(org) setSelectedProject(project) storeOrRemoveNull('local', LOCAL_STORAGE_KEYS.SAVED_ORG, org.id.toString()) storeOrRemoveNull('local', LOCAL_STORAGE_KEYS.SAVED_PROJECT, project.ref) } } // Fetch API keys for the selected project const { data: apiKeysData, isPending: isApiKeysPending, isError: isApiKeysError, } = useQuery( projectKeysQueryOptions( { projectRef: selectedProject?.ref }, { enabled: isLoggedIn && !!selectedProject?.ref } ) ) // Resolve the key and notify parent useEffect(() => { if (!selectedProject?.ref || isApiKeysPending || isApiKeysError || !apiKeysData) { onProjectResolved(null) return } const anonKey = apiKeysData.find((k) => k.type === 'legacy' && k.name === 'anon')?.api_key if (anonKey) { setMissingAnonKey(false) onProjectResolved({ projectRef: selectedProject.ref, anonKey }) } else { setMissingAnonKey(true) onProjectResolved(null) } }, [selectedProject?.ref, apiKeysData, isApiKeysPending, isApiKeysError, onProjectResolved]) return (
{isApiKeysPending && selectedProject && (

Loading API keys...

)} {isApiKeysError && selectedProject && (

Failed to load API keys for this project.

)} {missingAnonKey && selectedProject && (
This project does not have an anon key. Please enable the anon key in your project's API settings to use this block.
)}
) }