Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search ecosystem split #53

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
1,660 changes: 1,143 additions & 517 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@mdx-js/loader": "^3.0.1",
"@mdx-js/react": "^3.0.1",
"@next/mdx": "^15.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-popover": "^1.1.5",
"@types/mdx": "^2.0.13",
Expand All @@ -40,6 +41,9 @@
"prettier": "^3.3.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.4.0",
"react-toastify": "^11.0.3",
"string-similarity-js": "^2.1.4",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7"
},
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/search/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("query");
const ecosystem = searchParams.get("ecosystem") || "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will fail with "" will it not?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we are using this file anymore. It looks to be handling package search within the API

if (query == null || query.length == 0) {
return Response.json({ pkgs: [] });
}
const pgks = await search_packages(query);
const pgks = await search_packages(query, ecosystem);
return Response.json({ pgks });
}
2 changes: 1 addition & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function Home() {
<h1 className="my-8 text-center text-3xl font-bold">
Welcome to OpenTeams • Open Source Scoring
</h1>
<div className="mx-auto max-w-2xl">
<div className="mx-auto flex max-w-2xl">
<SearchAutocomplete />
</div>
<div className="mt-12 text-center">
Expand Down
21 changes: 1 addition & 20 deletions src/components/Search/AutoCompleteOption.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { maturityColor, maturityIconPath } from "@/components/Maturity";
import { riskColor, riskIconPath } from "@/components/Risk";
import { PackageResult } from "@/utils/search_packages";
import { ComboboxOption } from "@headlessui/react";
import { mdiSearchWeb } from "@mdi/js";
Expand Down Expand Up @@ -27,24 +25,7 @@ export default function AutoCompleteOption({ item }: Props) {
>
{`${item.ecosystem}/${item.name}`}
</span>
<div className="absolute inset-y-0 right-0 flex items-center space-x-2 pr-3">
<div
className={`bg- flex size-6 items-center justify-center rounded${riskColor(item.health_risk)}`}
>
<Icon
className={`size-4 text-white`}
path={riskIconPath(item.health_risk)}
/>
</div>
<div
className={`flex size-6 items-center justify-center rounded ${maturityColor(item.maturity)}`}
>
<Icon
className="size-4 text-white"
path={maturityIconPath(item.maturity)}
/>
</div>
</div>
<div className="absolute inset-y-0 right-0 flex items-center space-x-2 pr-3"></div>
{selected ? (
<span
className={`absolute inset-y-0 left-0 flex items-center pl-3 ${
Expand Down
93 changes: 75 additions & 18 deletions src/components/Search/SearchAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,104 @@ import {
ComboboxInput,
ComboboxOptions,
} from "@headlessui/react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import Icon from "@mdi/react";
import { mdiChevronDown } from "@mdi/js";
import useSearchPackages from "./useSearchPackages";
import Options from "./Options";
import { PackageResult } from "@/utils/search_packages";
import { useRouter } from "next/navigation";
import { SiPypi, SiAnaconda, SiNpm } from "react-icons/si";

export default function SearchAutocomplete() {
const [query, setQuery] = React.useState("");
const { packages, loading } = useSearchPackages(query);
const [selectedSource, setSelectedSource] = React.useState("");
const { packages, loading } = useSearchPackages(query, selectedSource || "");

const sources = [
{
id: "pypi",
label: "pypi",
icon: <SiPypi className="mr-2 text-blue-500" />,
},
{
id: "conda",
label: "conda",
icon: <SiAnaconda className="mr-2" style={{ color: "#3EB022" }} />,
},
{
id: "npm",
label: "npm",
icon: <SiNpm className="mr-2" style={{ color: "#C23B33" }} />,
},
];

const router = useRouter();

return (
<div className="mx-auto w-full max-w-md">
<div className="mx-auto w-screen">
<Combobox
onChange={(value: PackageResult | null) => {
if (value == null) {
return;
}
onChange={(value: { ecosystem: string; name: string } | null) => {
if (!value) return;
router.push(`/${value.ecosystem}/${value.name}`);
}}
>
<div className="relative mt-1">
<div className="focus-visible:ring-opacity/75 relative w-full cursor-default overflow-hidden rounded-lg bg-white text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-teal-300 sm:text-sm">
<div className="relative mt-1 flex justify-center">
<div className="flex w-full cursor-default overflow-hidden rounded-lg bg-white text-left shadow-md focus:outline-none sm:text-sm">
<DropdownMenu>
<DropdownMenuTrigger className="flex w-32 items-center justify-center rounded-l-lg bg-blue-500 px-4 text-sm font-medium text-white lg:w-52">
{selectedSource
? sources.find((s) => s.id === selectedSource)?.label
: "Source"}
<Icon
path={mdiChevronDown}
className="ml-2 size-5 text-white"
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-32 lg:w-52">
<DropdownMenuRadioGroup
value={selectedSource}
onValueChange={setSelectedSource}
>
{sources.map((source) => (
<DropdownMenuRadioItem key={source.id} value={source.id}>
{source.icon} {source.label}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>

<ComboboxInput
className="w-full rounded-lg border-none p-2 pl-3 pr-10 text-xl leading-10 text-gray-900 focus:ring-0"
className="w-full rounded-r-lg border-none p-2 pl-3 pr-10 text-xl leading-10 text-gray-900 focus:ring-0"
autoComplete="off"
onChange={(event) => setQuery(event.target.value)}
displayValue={(item: PackageResult | null) =>
item ? `${item.ecosystem}/${item.name}` : ""
}
placeholder="Search packages..."
placeholder={
selectedSource ? "Search packages..." : "Select a source first"
}
disabled={!selectedSource}
/>
<ComboboxButton className="absolute inset-y-0 right-0 flex items-center pr-2">
<Icon
path={mdiChevronDown}
className="size-5 text-gray-400"
aria-hidden="true"
/>
</ComboboxButton>
{selectedSource && (
<ComboboxButton className="absolute inset-y-0 right-0 flex items-center pr-2">
<Icon
path={mdiChevronDown}
className="size-5 text-gray-400"
aria-hidden="true"
/>
</ComboboxButton>
)}
</div>
<ComboboxOptions className="ring-opacity/5 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black focus:outline-none sm:text-sm">

<ComboboxOptions className="absolute left-0 top-full mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black focus:outline-none sm:text-sm">
<Options query={query} packages={packages} loading={loading} />
</ComboboxOptions>
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/components/Search/useSearchPackages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import search_packages, { PackageResult } from "@/utils/search_packages";
import { debounce } from "lodash";

const debouncedFetchPackages = debounce(
async (query, setPackages, setLoading) => {
async (query, ecosystem, setPackages, setLoading) => {
if (query.trim().length >= 3) {
const results = await search_packages(query);
const results = await search_packages(query, ecosystem);
setPackages(results);
} else {
setPackages([]);
Expand All @@ -15,17 +15,17 @@ const debouncedFetchPackages = debounce(
300,
); // 300ms delay

export default function useSearchPackages(query: string) {
export default function useSearchPackages(query: string, ecosystem: string) {
const [packages, setPackages] = React.useState<PackageResult[]>([]);
const [loading, setLoading] = React.useState<boolean>(false);

React.useEffect(() => {
setLoading(true);
debouncedFetchPackages(query, setPackages, setLoading);
debouncedFetchPackages(query, ecosystem, setPackages, setLoading);
return () => {
debouncedFetchPackages.cancel();
};
}, [query]);
}, [query, ecosystem]);

return { packages, loading };
}
Loading