Finished
This commit is contained in:
56
package.json
56
package.json
@@ -9,19 +9,65 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/jetbrains-mono": "^5.2.6",
|
||||
"@hookform/resolvers": "^5.2.1",
|
||||
"@radix-ui/react-accordion": "^1.2.11",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.14",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.7",
|
||||
"@radix-ui/react-avatar": "^1.1.10",
|
||||
"@radix-ui/react-checkbox": "^1.3.2",
|
||||
"@radix-ui/react-collapsible": "^1.1.11",
|
||||
"@radix-ui/react-context-menu": "^2.2.15",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
"@radix-ui/react-hover-card": "^1.1.14",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-menubar": "^1.1.15",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.13",
|
||||
"@radix-ui/react-popover": "^1.1.14",
|
||||
"@radix-ui/react-progress": "^1.1.7",
|
||||
"@radix-ui/react-radio-group": "^1.3.7",
|
||||
"@radix-ui/react-scroll-area": "^1.2.9",
|
||||
"@radix-ui/react-select": "^2.2.5",
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
"@radix-ui/react-slider": "^1.3.5",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-switch": "^1.2.5",
|
||||
"@radix-ui/react-tabs": "^1.1.12",
|
||||
"@radix-ui/react-toggle": "^1.1.9",
|
||||
"@radix-ui/react-toggle-group": "^1.1.10",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.536.0",
|
||||
"motion": "^12.23.12",
|
||||
"next": "15.4.5",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.1.0",
|
||||
"react-day-picker": "^9.8.1",
|
||||
"react-dom": "19.1.0",
|
||||
"next": "15.4.5"
|
||||
"react-hook-form": "^7.62.0",
|
||||
"react-resizable-panels": "^3.0.4",
|
||||
"recharts": "2.15.4",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "^4.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"tailwindcss": "^4",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.4.5",
|
||||
"@eslint/eslintrc": "^3"
|
||||
"tailwindcss": "^4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const config = {
|
||||
plugins: ["@tailwindcss/postcss"],
|
||||
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
BIN
public/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf
Normal file
BIN
public/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf
Normal file
Binary file not shown.
BIN
public/fonts/JetBrainsMono-VariableFont_wght.ttf
Normal file
BIN
public/fonts/JetBrainsMono-VariableFont_wght.ttf
Normal file
Binary file not shown.
@@ -1,26 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
import "../styles/globals.css";
|
||||
import { ThemeProvider } from "@/components/theme-provider"
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
@@ -23,11 +23,18 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<html lang="en" suppressHydrationWarning={true}>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
className={`antialiased`}
|
||||
>
|
||||
{children}
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
269
src/app/page.tsx
269
src/app/page.tsx
@@ -1,103 +1,176 @@
|
||||
"use client";
|
||||
import Image from "next/image";
|
||||
import { PlaceholdersAndVanishInput } from "@/components/ui/placeholders-and-vanish-input";
|
||||
import { BackgroundGradient } from "@/components/ui/background-gradient";
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation"; // Import useRouter
|
||||
import { Search, Settings, X } from "lucide-react";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
</ol>
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||
const [advancedFields, setAdvancedFields] = useState({
|
||||
title: "",
|
||||
author: "",
|
||||
abstract: ""
|
||||
});
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
const router = useRouter(); // Initialize router
|
||||
|
||||
const handleAdvancedSearch = () => {
|
||||
// Build query params for advanced search
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (searchQuery) params.set("q", searchQuery);
|
||||
params.set("p", "1");
|
||||
params.set("pageSize", "20");
|
||||
|
||||
// Add advanced fields if they have values
|
||||
if (advancedFields.title) params.set("title", advancedFields.title);
|
||||
if (advancedFields.author) params.set("author", advancedFields.author);
|
||||
if (advancedFields.abstract) params.set("abstract", advancedFields.abstract);
|
||||
|
||||
router.push(`/search?${params.toString()}`); // Use router.push instead of redirect
|
||||
};
|
||||
|
||||
const handleBasicSearch = () => {
|
||||
router.push("/search?q=" + searchQuery + "&p=1" + "&pageSize=" + 20); // Use router.push instead of redirect
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white dark:bg-black flex flex-col items-center pt-32 pb-16">
|
||||
<h1 className="text-center text-black dark:text-white jetbrains text-6xl mb-12">
|
||||
Refinity
|
||||
</h1>
|
||||
|
||||
{/* Main Search Bar */}
|
||||
<BackgroundGradient containerClassName="w-[35vw] rounded-3xl" className={"w-full"}>
|
||||
<PlaceholdersAndVanishInput
|
||||
placeholders={[
|
||||
"Machine Learning Algorithms For Healthcare",
|
||||
"Climate Change Impact On Agriculture",
|
||||
"Quantum Computing Applications In Cryptography",
|
||||
"Deep Learning Techniques For Image Recognition",
|
||||
"Renewable Energy Technologies And Efficiency",
|
||||
"Natural Language Processing In Social Media Analysis",
|
||||
"Genome Editing Tools And Ethical Considerations",
|
||||
"Cybersecurity Threats And Prevention Strategies",
|
||||
"Robotics Automation In Manufacturing Industry",
|
||||
"Blockchain Technology For Supply Chain Management",
|
||||
"AI Fairness And Bias Mitigation Methods",
|
||||
"Virtual Reality Applications In Education",
|
||||
"Big Data Analytics For Financial Markets",
|
||||
"Nanomaterials In Drug Delivery Systems",
|
||||
"Sustainable Urban Development Strategies",
|
||||
"Human-Computer Interaction Design Principles",
|
||||
"Neural Network Architectures For Speech Recognition",
|
||||
"Privacy Issues In Internet Of Things",
|
||||
"Solar Cell Efficiency Improvement Techniques",
|
||||
"Autonomous Vehicles And Traffic Management",
|
||||
]}
|
||||
onChange={(v) => setSearchQuery(v.target.value)}
|
||||
onSubmit={handleBasicSearch}
|
||||
/>
|
||||
</BackgroundGradient>
|
||||
|
||||
{/* Advanced Search Toggle */}
|
||||
<div className="mt-6">
|
||||
<button
|
||||
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||
className="flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 transition-colors"
|
||||
>
|
||||
<Settings size={16} />
|
||||
<span className="text-sm">
|
||||
{showAdvanced ? "Hide Advanced Search" : "Advanced Search"}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Advanced Search Window */}
|
||||
{showAdvanced && (
|
||||
<div className="mt-8 w-[35vw] bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl p-6 shadow-lg">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Advanced Search
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setShowAdvanced(false)}
|
||||
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Title Search */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Title
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={advancedFields.title}
|
||||
onChange={(e) => setAdvancedFields(prev => ({ ...prev, title: e.target.value }))}
|
||||
placeholder="Search in article titles..."
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Author Search */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Author
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={advancedFields.author}
|
||||
onChange={(e) => setAdvancedFields(prev => ({ ...prev, author: e.target.value }))}
|
||||
placeholder="Search by author name..."
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Abstract Search */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Abstract
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={advancedFields.abstract}
|
||||
onChange={(e) => setAdvancedFields(prev => ({ ...prev, abstract: e.target.value }))}
|
||||
placeholder="Search in abstracts..."
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Advanced Search Button */}
|
||||
<div className="mt-6 flex gap-3">
|
||||
<button
|
||||
onClick={handleAdvancedSearch}
|
||||
className="flex-1 flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition-colors"
|
||||
>
|
||||
<Search size={16} />
|
||||
Advanced Search
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setAdvancedFields({ title: "", author: "", abstract: "" });
|
||||
setSearchQuery("");
|
||||
}}
|
||||
className="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Info Text */}
|
||||
<p className="mt-4 text-xs text-gray-500 dark:text-gray-400">
|
||||
Leave fields empty to search all content. Combine with general search above for more specific results.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
BIN
src/app/search/favicon.ico
Normal file
BIN
src/app/search/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
41
src/app/search/layout.tsx
Normal file
41
src/app/search/layout.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "@/styles/globals.css";
|
||||
import { ThemeProvider } from "@/components/theme-provider"
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
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" suppressHydrationWarning={true}>
|
||||
<body
|
||||
className={`antialiased`}
|
||||
>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
289
src/app/search/page.tsx
Normal file
289
src/app/search/page.tsx
Normal file
@@ -0,0 +1,289 @@
|
||||
"use client";
|
||||
import {getArticlesPage, getArticlesPageAdvanced} from "@/lib/CrossRefAPI"
|
||||
import { useState, useEffect } from "react";
|
||||
import Article from "@/components/ui/articles"
|
||||
import { Search, Settings, ChevronDown, ChevronUp, X } from "lucide-react";
|
||||
import {redirect, useSearchParams} from "next/navigation";
|
||||
import { PlaceholdersAndVanishInput } from "@/components/ui/placeholders-and-vanish-input";
|
||||
import { BackgroundGradient } from "@/components/ui/background-gradient";
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationEllipsis,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from "@/components/ui/pagination"
|
||||
import ArticlesContainer from "@/components/ui/articles";
|
||||
|
||||
export default function SearchPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const page = parseInt(searchParams.get("p") ?? "1");
|
||||
const pageSize = parseInt(searchParams.get("pageSize") ?? "10");
|
||||
const query = searchParams.get("q") ?? "";
|
||||
const titleQuery = searchParams.get("title") ?? "";
|
||||
const authorQuery = searchParams.get("author") ?? "";
|
||||
const abstractQuery = searchParams.get("abstract") ?? "";
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const [showInput, setShowInput] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState(query);
|
||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||
const [advancedFields, setAdvancedFields] = useState({
|
||||
title: titleQuery,
|
||||
author: authorQuery,
|
||||
abstract: abstractQuery
|
||||
});
|
||||
|
||||
// Control rendering input with delay for smooth collapse animation
|
||||
useEffect(() => {
|
||||
if (hovered) {
|
||||
setShowInput(true);
|
||||
} else {
|
||||
const timeout = setTimeout(() => setShowInput(false), 300); // match CSS transition duration
|
||||
return () => clearTimeout(timeout);
|
||||
}
|
||||
}, [hovered]);
|
||||
|
||||
// Update search query when URL changes
|
||||
useEffect(() => {
|
||||
setSearchQuery(query);
|
||||
setAdvancedFields({
|
||||
title: titleQuery,
|
||||
author: authorQuery,
|
||||
abstract: abstractQuery
|
||||
});
|
||||
}, [query, titleQuery, authorQuery, abstractQuery]);
|
||||
|
||||
const handleAdvancedSearch = () => {
|
||||
// Build query params for advanced search
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (searchQuery) params.set("q", searchQuery);
|
||||
params.set("p", "1");
|
||||
params.set("pageSize", pageSize.toString());
|
||||
|
||||
// Add advanced fields if they have values
|
||||
if (advancedFields.title) params.set("title", advancedFields.title);
|
||||
if (advancedFields.author) params.set("author", advancedFields.author);
|
||||
if (advancedFields.abstract) params.set("abstract", advancedFields.abstract);
|
||||
|
||||
redirect(`/search?${params.toString()}`);
|
||||
};
|
||||
|
||||
const handleBasicSearch = () => {
|
||||
redirect("/search?q=" + searchQuery + "&p=1" + "&pageSize=" + pageSize);
|
||||
};
|
||||
|
||||
const clearAdvancedFields = () => {
|
||||
setAdvancedFields({ title: "", author: "", abstract: "" });
|
||||
};
|
||||
|
||||
// Check if any advanced fields are active
|
||||
const hasAdvancedFilters = titleQuery || authorQuery || abstractQuery;
|
||||
|
||||
return (
|
||||
<div className="w-full h-full min-h-screen bg-black jetbrains flex flex-col items-center">
|
||||
{/* Top Search Bar */}
|
||||
<div
|
||||
className="fixed top-4 left-4 z-50 flex items-center rounded-full bg-gray-900 shadow-lg"
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
{!showInput ? (
|
||||
<div className="p-3 flex items-center justify-center cursor-pointer">
|
||||
<Search className="text-white" size={24} />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={`
|
||||
transition-[width] duration-300 ease-in-out
|
||||
overflow-hidden
|
||||
bg-gray-800
|
||||
rounded-full
|
||||
p-2
|
||||
flex
|
||||
items-center
|
||||
${hovered ? "w-[35vw]" : "w-0"}
|
||||
`}
|
||||
>
|
||||
<BackgroundGradient containerClassName="w-full rounded-3xl" className="w-full">
|
||||
<PlaceholdersAndVanishInput
|
||||
placeholders={[
|
||||
"Machine Learning Algorithms For Healthcare",
|
||||
"Climate Change Impact On Agriculture",
|
||||
"Quantum Computing Applications In Cryptography",
|
||||
"Deep Learning Techniques For Image Recognition",
|
||||
"Renewable Energy Technologies And Efficiency",
|
||||
"Natural Language Processing In Social Media Analysis",
|
||||
"Genome Editing Tools And Ethical Considerations",
|
||||
"Cybersecurity Threats And Prevention Strategies",
|
||||
"Robotics Automation In Manufacturing Industry",
|
||||
"Blockchain Technology For Supply Chain Management",
|
||||
"AI Fairness And Bias Mitigation Methods",
|
||||
"Virtual Reality Applications In Education",
|
||||
"Big Data Analytics For Financial Markets",
|
||||
"Nanomaterials In Drug Delivery Systems",
|
||||
"Sustainable Urban Development Strategies",
|
||||
"Human-Computer Interaction Design Principles",
|
||||
"Neural Network Architectures For Speech Recognition",
|
||||
"Privacy Issues In Internet Of Things",
|
||||
"Solar Cell Efficiency Improvement Techniques",
|
||||
"Autonomous Vehicles And Traffic Management",
|
||||
]}
|
||||
onChange={(v) => setSearchQuery(v.target.value)}
|
||||
onSubmit={handleBasicSearch}
|
||||
value={searchQuery}
|
||||
/>
|
||||
</BackgroundGradient>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Advanced Search Toggle - Top Right */}
|
||||
<div className="fixed top-4 right-4 z-50">
|
||||
<button
|
||||
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-full transition-all duration-300 ${
|
||||
showAdvanced || hasAdvancedFilters
|
||||
? "bg-blue-600 text-white shadow-lg"
|
||||
: "bg-gray-800 text-gray-300 hover:bg-gray-700"
|
||||
}`}
|
||||
>
|
||||
<Settings size={16} />
|
||||
<span className="text-sm font-medium">Advanced</span>
|
||||
{showAdvanced ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||
{hasAdvancedFilters && (
|
||||
<div className="w-2 h-2 bg-white rounded-full"></div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Advanced Search Panel */}
|
||||
<div className={`fixed top-16 right-4 z-40 transition-all duration-300 ${
|
||||
showAdvanced ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-4 pointer-events-none"
|
||||
}`}>
|
||||
<div className="w-80 bg-gray-800 border border-gray-700 rounded-xl shadow-2xl backdrop-blur-sm">
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-white">
|
||||
Advanced Search
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setShowAdvanced(false)}
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Title Search */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Title
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={advancedFields.title}
|
||||
onChange={(e) => setAdvancedFields(prev => ({ ...prev, title: e.target.value }))}
|
||||
placeholder="Search in article titles..."
|
||||
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Author Search */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Author
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={advancedFields.author}
|
||||
onChange={(e) => setAdvancedFields(prev => ({ ...prev, author: e.target.value }))}
|
||||
placeholder="Search by author name..."
|
||||
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Abstract Search */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Abstract
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={advancedFields.abstract}
|
||||
onChange={(e) => setAdvancedFields(prev => ({ ...prev, abstract: e.target.value }))}
|
||||
placeholder="Search in abstracts..."
|
||||
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="mt-6 flex gap-3">
|
||||
<button
|
||||
onClick={handleAdvancedSearch}
|
||||
className="flex-1 flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition-colors font-medium"
|
||||
>
|
||||
<Search size={16} />
|
||||
Search
|
||||
</button>
|
||||
<button
|
||||
onClick={clearAdvancedFields}
|
||||
className="px-4 py-2 border border-gray-600 text-gray-300 hover:bg-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Active Filters Indicator */}
|
||||
{hasAdvancedFilters && (
|
||||
<div className="mt-4 p-3 bg-blue-900/30 border border-blue-700/50 rounded-lg">
|
||||
<div className="text-xs text-blue-200 mb-2">Active filters:</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{titleQuery && (
|
||||
<span className="px-2 py-1 bg-blue-600/50 text-blue-200 text-xs rounded">
|
||||
Title: {titleQuery}
|
||||
</span>
|
||||
)}
|
||||
{authorQuery && (
|
||||
<span className="px-2 py-1 bg-blue-600/50 text-blue-200 text-xs rounded">
|
||||
Author: {authorQuery}
|
||||
</span>
|
||||
)}
|
||||
{abstractQuery && (
|
||||
<span className="px-2 py-1 bg-blue-600/50 text-blue-200 text-xs rounded">
|
||||
Abstract: {abstractQuery}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info Text */}
|
||||
<p className="mt-4 text-xs text-gray-400">
|
||||
Combine with general search for more specific results.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content area below */}
|
||||
<div className={"pt-[5vh] pb-[10vh]"}>
|
||||
<ArticlesContainer
|
||||
searchQuery={query}
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
advancedFields={hasAdvancedFilters ? {
|
||||
title: titleQuery || undefined,
|
||||
author: authorQuery || undefined,
|
||||
abstract: abstractQuery || undefined
|
||||
} : undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
20
src/styles/globals.css
Normal file
20
src/styles/globals.css
Normal file
@@ -0,0 +1,20 @@
|
||||
@import "./theme.css";
|
||||
@import "../../node_modules/tailwindcss/index.css";
|
||||
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Add your JetBrains Mono font */
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
src: url('/fonts/JetBrainsMono-VariableFont_wght.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
.jetbrains {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
Reference in New Issue
Block a user