Skip to content

Commit

Permalink
Merge pull request #25 from RicardoGEsteves/search-bar
Browse files Browse the repository at this point in the history
feat: ✨ Type: Enhancement | Title: Adding Search Bar Functionality
  • Loading branch information
RicardoGEsteves authored Dec 26, 2023
2 parents 3e03ddc + bd2d7d9 commit dad6c9d
Show file tree
Hide file tree
Showing 11 changed files with 778 additions and 8 deletions.
3 changes: 2 additions & 1 deletion app/(main)/r/[slug]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { buttonVariants } from "@/components/ui/button";
import { getAuthSession } from "@/lib/auth";
import { db } from "@/lib/db";
import SubscribeLeaveToggle from "@/components/posts/subscribe-leave-toggle";
import ToFeedButton from "@/components/to-feed-button";

export const metadata: Metadata = {
title: "SpreadIt",
Expand Down Expand Up @@ -64,7 +65,7 @@ const SlugLayout = async ({ children, params: { slug } }: SlugLayoutProps) => {
return (
<div className="sm:container max-w-7xl mx-auto h-full pt-12">
<div>
{/* <ToFeedButton /> */}
<ToFeedButton />

<div className="grid grid-cols-1 md:grid-cols-3 gap-y-4 md:gap-x-4 py-6">
<ul className="flex flex-col col-span-2 space-y-6">{children}</ul>
Expand Down
4 changes: 2 additions & 2 deletions app/(main)/r/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const SlugPage = async ({ params }: SlugPageProps) => {
if (!subSpreadIt) return notFound();

return (
<>
<div suppressHydrationWarning>
<h1 className="font-bold text-primary text-3xl md:text-4xl h-14">
r/{subSpreadIt.name}
</h1>
Expand All @@ -47,7 +47,7 @@ const SlugPage = async ({ params }: SlugPageProps) => {
initialPosts={subSpreadIt.posts}
subSpreadItName={subSpreadIt.name}
/>
</>
</div>
);
};

Expand Down
22 changes: 22 additions & 0 deletions app/api/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { db } from "@/lib/db";

export async function GET(req: Request) {
const url = new URL(req.url);
const q = url.searchParams.get("q");

if (!q) return new Response("Invalid query", { status: 400 });

const results = await db.subSpreadIt.findMany({
where: {
name: {
startsWith: q,
},
},
include: {
_count: true,
},
take: 5,
});

return new Response(JSON.stringify(results));
}
5 changes: 4 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ export default function RootLayout({
lang="en"
className={cn("dark antialiased", inter.className)}
>
<body className="min-h-screen pt-12 antialiased">
<body
className="min-h-screen pt-12 antialiased"
suppressHydrationWarning
>
<QueryProviders>
<Toaster />

Expand Down
8 changes: 4 additions & 4 deletions components/navigation/navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { authOptions } from "@/lib/auth";
import { getServerSession } from "next-auth";
import Link from "next/link";

import { authOptions } from "@/lib/auth";
import { Icons } from "../icons";
import { buttonVariants } from "../ui/button";
import { cn } from "@/lib/utils";
import UserAccountNav from "./user-account-nav";

// import SearchBar from "./SearchBar";
import SearchBar from "./search-bar";

const Navbar = async () => {
const session = await getServerSession(authOptions);
Expand All @@ -23,7 +23,7 @@ const Navbar = async () => {
</p>
</Link>

{/* <SearchBar /> */}
<SearchBar />

{session?.user ? (
<UserAccountNav user={session.user} />
Expand Down
109 changes: 109 additions & 0 deletions components/navigation/search-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { usePathname, useRouter } from "next/navigation";
import debounce from "lodash.debounce";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { Prisma, SubSpreadIt } from "@prisma/client";
import { Users } from "lucide-react";

import { useOnClickOutside } from "@/hooks/use-on-click-outside";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "../ui/command";

const SearchBar = () => {
const [input, setInput] = useState<string>("");
const pathname = usePathname();
const commandRef = useRef<HTMLDivElement>(null);
const router = useRouter();

useOnClickOutside(commandRef, () => {
setInput("");
});

const request = debounce(async () => {
refetch();
}, 300);

const debounceRequest = useCallback(() => {
request();

// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const {
isFetching,
data: queryResults,
refetch,
isFetched,
} = useQuery({
queryFn: async () => {
if (!input) return [];
const { data } = await axios.get(`/api/search?q=${input}`);
return data as (SubSpreadIt & {
_count: Prisma.SubSpreadItCountOutputType;
})[];
},
queryKey: ["search-query"],
enabled: false,
});

useEffect(() => {
setInput("");
}, [pathname]);

return (
<Command
ref={commandRef}
className="relative rounded-lg border max-w-lg z-50 overflow-visible"
>
<CommandInput
// isLoading={isFetching}
onValueChange={(text) => {
setInput(text);
debounceRequest();
}}
value={input}
className="outline-none border-none focus:border-none focus:outline-none ring-0"
placeholder="Search communities & SpreadIt..."
/>

{input.length > 0 && (
<CommandList className="absolute bg-background top-full inset-x-0 shadow rounded-b-md">
{isFetched && (
<CommandEmpty>
<span className="text-muted-foreground">No results found.</span>
</CommandEmpty>
)}
{(queryResults?.length ?? 0) > 0 ? (
<CommandGroup heading="Communities">
{queryResults?.map((subSpreadIt) => (
<CommandItem
onSelect={(e) => {
router.push(`/r/${e}`);
router.refresh();
}}
key={subSpreadIt.id}
value={subSpreadIt.name}
className="text-muted-foreground aria-selected:text-emerald-500"
>
<Users className="mr-2 h-4 w-4" />
<a href={`/r/${subSpreadIt.name}`}>r/{subSpreadIt.name}</a>
</CommandItem>
))}
</CommandGroup>
) : null}
</CommandList>
)}
</Command>
);
};

export default SearchBar;
40 changes: 40 additions & 0 deletions components/to-feed-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use client";

import { ChevronLeft } from "lucide-react";
import { usePathname } from "next/navigation";

import { buttonVariants } from "./ui/button";
import { cn } from "@/lib/utils";

const ToFeedButton = () => {
const pathname = usePathname();

// if path is /r/mycom, turn into /
// if path is /r/mycom/post/cligad6jf0003uhest4qqkeco, turn into /r/mycom

const subSpreadItPath = getSubSpreadItPath(pathname);

return (
<a
href={subSpreadItPath}
className={cn(
buttonVariants({ variant: "ghost" }),
"text-emerald-500 hover:text-emerald-600"
)}
>
<ChevronLeft className="h-4 w-4 mr-1" />
{subSpreadItPath === "/" ? "Back home" : "Back to community"}
</a>
);
};

const getSubSpreadItPath = (pathname: string) => {
const splitPath = pathname.split("/");

if (splitPath.length === 3) return "/";
else if (splitPath.length > 3) return `/${splitPath[1]}/${splitPath[2]}`;
// default path, in case pathname does not match expected format
else return "/";
};

export default ToFeedButton;
Loading

0 comments on commit dad6c9d

Please sign in to comment.