diff --git a/frontend/src/pages/public/UpgradePathPage/UpgradePathPage.tsx b/frontend/src/pages/public/UpgradePathPage/UpgradePathPage.tsx
index a8a8551f2..74317d02f 100644
--- a/frontend/src/pages/public/UpgradePathPage/UpgradePathPage.tsx
+++ b/frontend/src/pages/public/UpgradePathPage/UpgradePathPage.tsx
@@ -1,18 +1,31 @@
/* eslint-disable no-nested-ternary */
-import { useEffect, useMemo, useRef, useState } from "react";
+import { useMemo, useState } from "react";
import { Helmet } from "react-helmet";
-import { faExternalLink, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
+import { SingleValue } from "react-select";
+import { faExternalLink } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useMutation } from "@tanstack/react-query";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, Input } from "@app/components/v2";
+import { Button, FilterableSelect, FormControl } from "@app/components/v2";
import {
useCalculateUpgradePath,
useGetUpgradePathVersions
} from "../../../hooks/api/upgradePath/queries";
+type VersionOption = {
+ label: string;
+ value: string;
+ isLatest: boolean;
+};
+
+const formatVersionOption = (option: VersionOption) => (
+
+ {option.label}
+ {option.isLatest && (Latest)}
+
+);
interface UpgradeResult {
path: Array<{
version: string;
@@ -39,66 +52,9 @@ interface UpgradeResult {
}
export const UpgradePathPage = () => {
- const [fromVersion, setFromVersion] = useState("");
- const [toVersion, setToVersion] = useState("");
- const [fromSearch, setFromSearch] = useState("");
- const [toSearch, setToSearch] = useState("");
- const [showFromDropdown, setShowFromDropdown] = useState(false);
- const [showToDropdown, setShowToDropdown] = useState(false);
+ const [fromVersion, setFromVersion] = useState(null);
+ const [toVersion, setToVersion] = useState(null);
const [upgradeResult, setUpgradeResult] = useState(null);
- const [debouncedFromSearch, setDebouncedFromSearch] = useState("");
- const [debouncedToSearch, setDebouncedToSearch] = useState("");
-
- const fromDropdownRef = useRef(null);
- const toDropdownRef = useRef(null);
- const fromSearchTimeoutRef = useRef();
- const toSearchTimeoutRef = useRef();
-
- useEffect(() => {
- if (fromSearchTimeoutRef.current) {
- clearTimeout(fromSearchTimeoutRef.current);
- }
- fromSearchTimeoutRef.current = setTimeout(() => {
- setDebouncedFromSearch(fromSearch);
- }, 300);
-
- return () => {
- if (fromSearchTimeoutRef.current) {
- clearTimeout(fromSearchTimeoutRef.current);
- }
- };
- }, [fromSearch]);
-
- useEffect(() => {
- if (toSearchTimeoutRef.current) {
- clearTimeout(toSearchTimeoutRef.current);
- }
- toSearchTimeoutRef.current = setTimeout(() => {
- setDebouncedToSearch(toSearch);
- }, 300);
-
- return () => {
- if (toSearchTimeoutRef.current) {
- clearTimeout(toSearchTimeoutRef.current);
- }
- };
- }, [toSearch]);
-
- useEffect(() => {
- const handleClickOutside = (event: MouseEvent) => {
- if (fromDropdownRef.current && !fromDropdownRef.current.contains(event.target as Node)) {
- setShowFromDropdown(false);
- }
- if (toDropdownRef.current && !toDropdownRef.current.contains(event.target as Node)) {
- setShowToDropdown(false);
- }
- };
-
- document.addEventListener("mousedown", handleClickOutside);
- return () => {
- document.removeEventListener("mousedown", handleClickOutside);
- };
- }, []);
const {
data: versions,
@@ -128,44 +84,26 @@ export const UpgradePathPage = () => {
}
});
- const filteredFromVersions = useMemo(() => {
+ const versionOptions = useMemo(() => {
if (!versions?.versions) return [];
- const filtered = versions.versions
+ return versions.versions
.filter((version) => !version.tagName.includes("nightly"))
- .filter((version) => {
- if (!debouncedFromSearch) return true;
- const searchTerm = debouncedFromSearch.toLowerCase();
- return version.tagName.toLowerCase().includes(searchTerm);
- });
+ .map((version) => ({
+ label: version.tagName,
+ value: version.tagName,
+ isLatest: versions.versions[0]?.tagName === version.tagName
+ }));
+ }, [versions?.versions]);
- return filtered.slice(0, 25);
- }, [versions?.versions, debouncedFromSearch]);
-
- const filteredToVersions = useMemo(() => {
- if (!versions?.versions) return [];
-
- const filtered = versions.versions
- .filter((version) => !version.tagName.includes("nightly"))
- .filter((version) => {
- if (!debouncedToSearch) return true;
- const searchTerm = debouncedToSearch.toLowerCase();
- return version.tagName.toLowerCase().includes(searchTerm);
- });
-
- return filtered.slice(0, 25);
- }, [versions?.versions, debouncedToSearch]);
-
- const handleFromVersionSelect = (version: string) => {
- setFromVersion(version);
- setFromSearch(version);
- setShowFromDropdown(false);
+ const handleFromVersionSelect = (value: unknown) => {
+ const selected = value as SingleValue;
+ setFromVersion(selected?.value || null);
};
- const handleToVersionSelect = (version: string) => {
- setToVersion(version);
- setToSearch(version);
- setShowToDropdown(false);
+ const handleToVersionSelect = (value: unknown) => {
+ const selected = value as SingleValue;
+ setToVersion(selected?.value || null);
};
const handleCalculate = () => {
@@ -232,121 +170,36 @@ export const UpgradePathPage = () => {
{/* From Version Selector */}
-
-
-
-
{
- setFromSearch(e.target.value);
- setFromVersion("");
- setShowFromDropdown(true);
- }}
- onFocus={() => setShowFromDropdown(true)}
- placeholder="Search or select version..."
- className="border-mineshaft-600 bg-mineshaft-900"
- leftIcon={
}
- isDisabled={versionsLoading || versionsFetching}
- />
- {showFromDropdown && (
-
- {(() => {
- if (versionsLoading || versionsFetching) {
- return (
-
-
-
- Loading versions...
-
-
- );
- }
- if (filteredFromVersions.length > 0) {
- return filteredFromVersions.slice(0, 8).map((version) => (
-
- ));
- }
- return (
-
- {debouncedFromSearch
- ? `No versions found matching "${debouncedFromSearch}"`
- : "No versions available"}
-
- );
- })()}
-
- )}
-
-
-
+
+ opt.value === fromVersion) || null}
+ onChange={handleFromVersionSelect}
+ placeholder="Search or select version..."
+ isLoading={versionsLoading || versionsFetching}
+ isDisabled={versionsLoading || versionsFetching}
+ isSearchable
+ isClearable
+ menuPortalTarget={document.body}
+ formatOptionLabel={formatVersionOption}
+ />
+
{/* To Version Selector */}
-
-
-
-
{
- setToSearch(e.target.value);
- setToVersion("");
- setShowToDropdown(true);
- }}
- onFocus={() => setShowToDropdown(true)}
- placeholder="Search or select version..."
- className="border-mineshaft-600 bg-mineshaft-900"
- leftIcon={
}
- isDisabled={versionsLoading || versionsFetching}
- />
- {showToDropdown && (
-
- {(() => {
- if (versionsLoading || versionsFetching) {
- return (
-
-
-
- Loading versions...
-
-
- );
- }
- if (filteredToVersions.length > 0) {
- return filteredToVersions.slice(0, 8).map((version) => (
-
- ));
- }
- return (
-
- {debouncedToSearch
- ? `No versions found matching "${debouncedToSearch}"`
- : "No versions available"}
-
- );
- })()}
-
- )}
-
-
-
+
+ opt.value === toVersion) || null}
+ onChange={handleToVersionSelect}
+ placeholder="Search or select version..."
+ isLoading={versionsLoading || versionsFetching}
+ isDisabled={versionsLoading || versionsFetching}
+ isSearchable
+ isClearable
+ menuPortalTarget={document.body}
+ formatOptionLabel={formatVersionOption}
+ />
+