Spaces:
Build error
Build error
| import React from "react"; | |
| import { useTranslation } from "react-i18next"; | |
| import { useCreateConversation } from "#/hooks/mutation/use-create-conversation"; | |
| import { useUserRepositories } from "#/hooks/query/use-user-repositories"; | |
| import { useRepositoryBranches } from "#/hooks/query/use-repository-branches"; | |
| import { useIsCreatingConversation } from "#/hooks/use-is-creating-conversation"; | |
| import { Branch, GitRepository } from "#/types/git"; | |
| import { BrandButton } from "../settings/brand-button"; | |
| import { useSearchRepositories } from "#/hooks/query/use-search-repositories"; | |
| import { useDebounce } from "#/hooks/use-debounce"; | |
| import { sanitizeQuery } from "#/utils/sanitize-query"; | |
| import { | |
| RepositoryDropdown, | |
| RepositoryLoadingState, | |
| RepositoryErrorState, | |
| BranchDropdown, | |
| BranchLoadingState, | |
| BranchErrorState, | |
| } from "./repository-selection"; | |
| interface RepositorySelectionFormProps { | |
| onRepoSelection: (repoTitle: string | null) => void; | |
| } | |
| export function RepositorySelectionForm({ | |
| onRepoSelection, | |
| }: RepositorySelectionFormProps) { | |
| const [selectedRepository, setSelectedRepository] = | |
| React.useState<GitRepository | null>(null); | |
| const [selectedBranch, setSelectedBranch] = React.useState<Branch | null>( | |
| null, | |
| ); | |
| // Add a ref to track if the branch was manually cleared by the user | |
| const branchManuallyClearedRef = React.useRef<boolean>(false); | |
| const { | |
| data: repositories, | |
| isLoading: isLoadingRepositories, | |
| isError: isRepositoriesError, | |
| } = useUserRepositories(); | |
| const { | |
| data: branches, | |
| isLoading: isLoadingBranches, | |
| isError: isBranchesError, | |
| } = useRepositoryBranches(selectedRepository?.full_name || null); | |
| const { | |
| mutate: createConversation, | |
| isPending, | |
| isSuccess, | |
| } = useCreateConversation(); | |
| const isCreatingConversationElsewhere = useIsCreatingConversation(); | |
| const { t } = useTranslation(); | |
| const [searchQuery, setSearchQuery] = React.useState(""); | |
| const debouncedSearchQuery = useDebounce(searchQuery, 300); | |
| const { data: searchedRepos } = useSearchRepositories(debouncedSearchQuery); | |
| // Auto-select main or master branch if it exists, but only if the branch wasn't manually cleared | |
| React.useEffect(() => { | |
| if ( | |
| branches && | |
| branches.length > 0 && | |
| !selectedBranch && | |
| !isLoadingBranches && | |
| !branchManuallyClearedRef.current // Only auto-select if not manually cleared | |
| ) { | |
| // Look for main or master branch | |
| const mainBranch = branches.find((branch) => branch.name === "main"); | |
| const masterBranch = branches.find((branch) => branch.name === "master"); | |
| // Select main if it exists, otherwise select master if it exists | |
| if (mainBranch) { | |
| setSelectedBranch(mainBranch); | |
| } else if (masterBranch) { | |
| setSelectedBranch(masterBranch); | |
| } | |
| } | |
| }, [branches, isLoadingBranches, selectedBranch]); | |
| // We check for isSuccess because the app might require time to render | |
| // into the new conversation screen after the conversation is created. | |
| const isCreatingConversation = | |
| isPending || isSuccess || isCreatingConversationElsewhere; | |
| const allRepositories = repositories?.concat(searchedRepos || []); | |
| const repositoriesItems = allRepositories?.map((repo) => ({ | |
| key: repo.id, | |
| label: decodeURIComponent(repo.full_name), | |
| })); | |
| const branchesItems = branches?.map((branch) => ({ | |
| key: branch.name, | |
| label: branch.name, | |
| })); | |
| const handleRepoSelection = (key: React.Key | null) => { | |
| const selectedRepo = allRepositories?.find( | |
| (repo) => repo.id.toString() === key, | |
| ); | |
| if (selectedRepo) onRepoSelection(selectedRepo.full_name); | |
| setSelectedRepository(selectedRepo || null); | |
| setSelectedBranch(null); // Reset branch selection when repo changes | |
| branchManuallyClearedRef.current = false; // Reset the flag when repo changes | |
| }; | |
| const handleBranchSelection = (key: React.Key | null) => { | |
| const selectedBranchObj = branches?.find((branch) => branch.name === key); | |
| setSelectedBranch(selectedBranchObj || null); | |
| // Reset the manually cleared flag when a branch is explicitly selected | |
| branchManuallyClearedRef.current = false; | |
| }; | |
| const handleRepoInputChange = (value: string) => { | |
| if (value === "") { | |
| setSelectedRepository(null); | |
| setSelectedBranch(null); | |
| onRepoSelection(null); | |
| } else if (value.startsWith("https://")) { | |
| const repoName = sanitizeQuery(value); | |
| setSearchQuery(repoName); | |
| } | |
| }; | |
| const handleBranchInputChange = (value: string) => { | |
| // Clear the selected branch if the input is empty or contains only whitespace | |
| // This fixes the issue where users can't delete the entire default branch name | |
| if (value === "" || value.trim() === "") { | |
| setSelectedBranch(null); | |
| // Set the flag to indicate that the branch was manually cleared | |
| branchManuallyClearedRef.current = true; | |
| } else { | |
| // Reset the flag when the user starts typing again | |
| branchManuallyClearedRef.current = false; | |
| } | |
| }; | |
| // Render the appropriate UI based on the loading/error state | |
| const renderRepositorySelector = () => { | |
| if (isLoadingRepositories) { | |
| return <RepositoryLoadingState />; | |
| } | |
| if (isRepositoriesError) { | |
| return <RepositoryErrorState />; | |
| } | |
| return ( | |
| <RepositoryDropdown | |
| items={repositoriesItems || []} | |
| onSelectionChange={handleRepoSelection} | |
| onInputChange={handleRepoInputChange} | |
| defaultFilter={(textValue, inputValue) => { | |
| if (!inputValue) return true; | |
| const repo = allRepositories?.find((r) => r.full_name === textValue); | |
| if (!repo) return false; | |
| const sanitizedInput = sanitizeQuery(inputValue); | |
| return sanitizeQuery(textValue).includes(sanitizedInput); | |
| }} | |
| /> | |
| ); | |
| }; | |
| // Render the appropriate UI for branch selector based on the loading/error state | |
| const renderBranchSelector = () => { | |
| if (!selectedRepository) { | |
| return ( | |
| <BranchDropdown | |
| items={[]} | |
| onSelectionChange={() => {}} | |
| onInputChange={() => {}} | |
| isDisabled | |
| /> | |
| ); | |
| } | |
| if (isLoadingBranches) { | |
| return <BranchLoadingState />; | |
| } | |
| if (isBranchesError) { | |
| return <BranchErrorState />; | |
| } | |
| return ( | |
| <BranchDropdown | |
| items={branchesItems || []} | |
| onSelectionChange={handleBranchSelection} | |
| onInputChange={handleBranchInputChange} | |
| isDisabled={false} | |
| selectedKey={selectedBranch?.name} | |
| /> | |
| ); | |
| }; | |
| return ( | |
| <div className="flex flex-col gap-4"> | |
| {renderRepositorySelector()} | |
| {renderBranchSelector()} | |
| <BrandButton | |
| testId="repo-launch-button" | |
| variant="primary" | |
| type="button" | |
| isDisabled={ | |
| !selectedRepository || | |
| isCreatingConversation || | |
| isLoadingRepositories || | |
| isRepositoriesError | |
| } | |
| onClick={() => | |
| createConversation({ | |
| selectedRepository, | |
| selected_branch: selectedBranch?.name, | |
| }) | |
| } | |
| > | |
| {!isCreatingConversation && "Launch"} | |
| {isCreatingConversation && t("HOME$LOADING")} | |
| </BrandButton> | |
| </div> | |
| ); | |
| } | |