diff --git a/src/app/components/mii-list.tsx b/src/app/components/mii-list.tsx index e8a40c9..7636a8a 100644 --- a/src/app/components/mii-list.tsx +++ b/src/app/components/mii-list.tsx @@ -11,9 +11,10 @@ interface Props { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; // for use on profiles userId?: number; + where?: Record; } -export default async function MiiList({ searchParams, userId }: Props) { +export default async function MiiList({ searchParams, userId, where }: Props) { const session = await auth(); const resolvedSearchParams = await searchParams; @@ -56,21 +57,26 @@ export default async function MiiList({ searchParams, userId }: Props) { const shownMiiCount = await prisma.mii.count({ where: { ...whereTags, + ...where, userId, }, }); + const miis = await prisma.mii.findMany({ where: { ...whereTags, + ...where, userId, }, orderBy, include: { ...userInclude, likedBy: { - where: { - userId: Number(session?.user.id), - }, + where: userId + ? { + userId: Number(session?.user.id), + } + : {}, select: { userId: true, }, @@ -119,39 +125,43 @@ export default async function MiiList({ searchParams, userId }: Props) { -
- {formattedMiis.map((mii) => ( -
- + {miis.length > 0 ? ( +
+ {formattedMiis.map((mii) => ( +
+ -
- - {mii.name} - -
- {mii.tags.map((tag) => ( - - {tag} - - ))} -
+
+ + {mii.name} + +
+ {mii.tags.map((tag) => ( + + {tag} + + ))} +
-
- +
+ - {userId == null && ( - - @{mii.user?.username} - - )} + {userId == null && ( + + @{mii.user?.username} + + )} +
-
- ))} -
+ ))} +
+ ) : ( +

No results found.

+ )}
); } diff --git a/src/app/components/search-bar.tsx b/src/app/components/search-bar.tsx index 6ad320c..56456f0 100644 --- a/src/app/components/search-bar.tsx +++ b/src/app/components/search-bar.tsx @@ -1,16 +1,45 @@ "use client"; +import { useState } from "react"; import { Icon } from "@iconify/react"; +import { redirect } from "next/navigation"; +import { z } from "zod"; + +const searchSchema = z + .string() + .trim() + .min(2) + .max(64) + .regex(/^[a-zA-Z0-9_]+$/); export default function SearchBar() { + const [query, setQuery] = useState(""); + + const handleSearch = () => { + const result = searchSchema.safeParse(query); + if (!result.success) redirect("/"); + + redirect(`/search?q=${query}`); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter") handleSearch(); + }; + return (
setQuery(e.target.value)} + onKeyDown={handleKeyDown} className="bg-orange-200 border-2 border-orange-400 py-2 px-3 rounded-l-xl outline-0 w-full placeholder:text-black/40" /> -
diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx new file mode 100644 index 0000000..c23381c --- /dev/null +++ b/src/app/search/page.tsx @@ -0,0 +1,38 @@ +import { notFound } from "next/navigation"; +import { z } from "zod"; + +import MiiList from "../components/mii-list"; + +interface Props { + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; +} + +const searchSchema = z + .string() + .trim() + .min(2) + .max(64) + .regex(/^[a-zA-Z0-9_]+$/); + +export default async function SearchPage({ searchParams }: Props) { + const { q: rawQuery } = await searchParams; + + const result = searchSchema.safeParse(rawQuery); + if (!result.success) notFound(); + + const query = result.data.toLowerCase(); + + return ( +
+

+ Search results for "{query}" +

+ +
+ ); +}