Skip to content

Commit

Permalink
add frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
florian committed Jun 14, 2024
1 parent c3f1b4f commit 6a7ef7a
Show file tree
Hide file tree
Showing 24 changed files with 5,364 additions and 1 deletion.
1 change: 0 additions & 1 deletion frontend
Submodule frontend deleted from 13c3e1
1 change: 1 addition & 0 deletions frontend/.next/server/app-paths-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
self.__INTERCEPTION_ROUTE_REWRITE_MANIFEST = "[]";
6 changes: 6 additions & 0 deletions frontend/.next/server/middleware-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"version": 3,
"middleware": {},
"functions": {},
"sortedMiddleware": []
}
1 change: 1 addition & 0 deletions frontend/.next/server/pages-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
2 changes: 2 additions & 0 deletions frontend/.next/server/server-reference-manifest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
self.__RSC_SERVER_MANIFEST =
'{\n "node": {},\n "edge": {},\n "encryptionKey": "adPBT6Z0eWjwDrphRpwcn9P1qVh/lI4oWRB7WkyTzI4="\n}';
5 changes: 5 additions & 0 deletions frontend/.next/server/server-reference-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"node": {},
"edge": {},
"encryptionKey": "adPBT6Z0eWjwDrphRpwcn9P1qVh/lI4oWRB7WkyTzI4="
}
3 changes: 3 additions & 0 deletions frontend/.next/types/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}
3 changes: 3 additions & 0 deletions frontend2/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
36 changes: 36 additions & 0 deletions frontend2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
36 changes: 36 additions & 0 deletions frontend2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
Binary file added frontend2/app/favicon.ico
Binary file not shown.
33 changes: 33 additions & 0 deletions frontend2/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
}
22 changes: 22 additions & 0 deletions frontend2/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
100 changes: 100 additions & 0 deletions frontend2/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"use client";

import { useState } from "react";
import axios from "axios";
import SearchResultsTable from "../components/SearchResultsTable";
import { ClipLoader } from "react-spinners";

export default function Home() {
const [text, setText] = useState("");
const [results, setResults] = useState([]);
const [sortField, setSortField] = useState("weekly_downloads");
const [sortDirection, setSortDirection] = useState("desc");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");

const handleSearch = async () => {
setLoading(true);
setError("");
try {
const response = await axios.post(
"http://localhost:8000/search",
{
query: text,
},
{
headers: {
"Content-Type": "application/json",
},
},
);
const sortedResults = response.data.matches.sort(
(a, b) => b.weekly_downloads - a.weekly_downloads,
);
setResults(sortedResults);
} catch (error) {
setError("Error fetching search results.");
console.error("Error fetching search results:", error);
} finally {
setLoading(false);
}
};

const sortResults = (field) => {
const direction =
sortField === field && sortDirection === "asc" ? "desc" : "asc";
const sorted = [...results].sort((a, b) => {
if (a[field] < b[field]) return direction === "asc" ? -1 : 1;
if (a[field] > b[field]) return direction === "asc" ? 1 : -1;
return 0;
});
setResults(sorted);
setSortField(field);
setSortDirection(direction);
};

return (
<main className="flex flex-col items-center justify-start min-h-screen p-4 space-y-4 bg-gray-100">
<header className="w-full text-center mb-4">
<h1 className="text-4xl font-bold p-2 mt-4">✨PyPi Package Finder</h1>
<p className="text-lg text-gray-600">
Enter your query to search for Python packages
</p>
</header>
<div className="flex flex-col items-center space-y-4 w-3/5 bg-white p-6 rounded-lg shadow-lg">
<textarea
className="w-full h-24 p-2 border rounded resize-none overflow-auto focus:outline-none focus:ring-2 focus:ring-blue-500"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Enter your query here..."
></textarea>
<button
className="w-[250px] p-2 border rounded bg-blue-500 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
onClick={handleSearch}
>
Search
</button>
{loading && (
<ClipLoader color={"#123abc"} loading={loading} size={50} />
)}
{error && <p className="text-red-500">{error}</p>}
</div>

{results.length > 0 && (
<div className="w-full flex justify-center mt-6">
<div className="w-11/12 bg-white p-6 rounded-lg shadow-lg flex flex-col items-center">
<p className="mb-4 text-gray-700">
Displaying the {results.length} most similar results:
</p>
<SearchResultsTable
results={results}
sortField={sortField}
sortDirection={sortDirection}
onSort={sortResults}
/>
</div>
</div>
)}
</main>
);
}
101 changes: 101 additions & 0 deletions frontend2/components/SearchResultsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from "react";
import { FaExternalLinkAlt } from "react-icons/fa"; // Import the icon

interface Match {
name: string;
similarity: number;
weekly_downloads: number;
summary: string;
}

interface SearchResultsTableProps {
results: Match[];
sortField: string;
sortDirection: string;
onSort: (field: string) => void;
}

const SearchResultsTable: React.FC<SearchResultsTableProps> = ({
results,
sortField,
sortDirection,
onSort,
}) => {
const getSortIndicator = (field: string) => {
return sortField === field ? (sortDirection === "asc" ? "▲" : "▼") : "";
};

return (
<div className="overflow-x-auto w-full">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th
className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer whitespace-nowrap"
onClick={() => onSort("name")}
>
<div className="flex items-center">
Name <span className="ml-1">{getSortIndicator("name")}</span>
</div>
</th>
<th
className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer whitespace-nowrap"
onClick={() => onSort("similarity")}
>
<div className="flex items-center">
Similarity{" "}
<span className="ml-1">{getSortIndicator("similarity")}</span>
</div>
</th>
<th
className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer whitespace-nowrap"
onClick={() => onSort("weekly_downloads")}
>
<div className="flex items-center">
Weekly Downloads{" "}
<span className="ml-1">
{getSortIndicator("weekly_downloads")}
</span>
</div>
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Summary
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Link
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{results.map((result, index) => (
<tr key={index} className="hover:bg-gray-100">
<td className="px-4 py-2 whitespace-nowrap">{result.name}</td>
<td className="px-4 py-2 whitespace-nowrap">
{result.similarity.toFixed(3)}
</td>
<td className="px-4 py-2 whitespace-nowrap">
{result.weekly_downloads.toLocaleString()}
</td>
<td className="px-4 py-2 whitespace-normal break-words">
{result.summary}
</td>
<td className="px-4 py-2 whitespace-nowrap">
<a
href={`https://pypi.org/project/${result.name}/`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline flex items-center"
>
<FaExternalLinkAlt className="mr-1" />
PyPI
</a>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};

export default SearchResultsTable;
4 changes: 4 additions & 0 deletions frontend2/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};

export default nextConfig;
Loading

0 comments on commit 6a7ef7a

Please sign in to comment.