feat: search functionality
This commit is contained in:
parent
8f731dd358
commit
fb399734c0
3 changed files with 110 additions and 33 deletions
|
|
@ -11,9 +11,10 @@ interface Props {
|
||||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
// for use on profiles
|
// for use on profiles
|
||||||
userId?: number;
|
userId?: number;
|
||||||
|
where?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function MiiList({ searchParams, userId }: Props) {
|
export default async function MiiList({ searchParams, userId, where }: Props) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
const resolvedSearchParams = await searchParams;
|
const resolvedSearchParams = await searchParams;
|
||||||
|
|
||||||
|
|
@ -56,21 +57,26 @@ export default async function MiiList({ searchParams, userId }: Props) {
|
||||||
const shownMiiCount = await prisma.mii.count({
|
const shownMiiCount = await prisma.mii.count({
|
||||||
where: {
|
where: {
|
||||||
...whereTags,
|
...whereTags,
|
||||||
|
...where,
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const miis = await prisma.mii.findMany({
|
const miis = await prisma.mii.findMany({
|
||||||
where: {
|
where: {
|
||||||
...whereTags,
|
...whereTags,
|
||||||
|
...where,
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
orderBy,
|
orderBy,
|
||||||
include: {
|
include: {
|
||||||
...userInclude,
|
...userInclude,
|
||||||
likedBy: {
|
likedBy: {
|
||||||
where: {
|
where: userId
|
||||||
userId: Number(session?.user.id),
|
? {
|
||||||
},
|
userId: Number(session?.user.id),
|
||||||
|
}
|
||||||
|
: {},
|
||||||
select: {
|
select: {
|
||||||
userId: true,
|
userId: true,
|
||||||
},
|
},
|
||||||
|
|
@ -119,39 +125,43 @@ export default async function MiiList({ searchParams, userId }: Props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-4 gap-4 max-lg:grid-cols-3 max-sm:grid-cols-2 max-[25rem]:grid-cols-1">
|
{miis.length > 0 ? (
|
||||||
{formattedMiis.map((mii) => (
|
<div className="grid grid-cols-4 gap-4 max-lg:grid-cols-3 max-sm:grid-cols-2 max-[25rem]:grid-cols-1">
|
||||||
<div
|
{formattedMiis.map((mii) => (
|
||||||
key={mii.id}
|
<div
|
||||||
className="flex flex-col bg-zinc-50 rounded-3xl border-2 border-zinc-300 shadow-lg p-3 transition hover:scale-105 hover:bg-cyan-100 hover:border-cyan-600"
|
key={mii.id}
|
||||||
>
|
className="flex flex-col bg-zinc-50 rounded-3xl border-2 border-zinc-300 shadow-lg p-3 transition hover:scale-105 hover:bg-cyan-100 hover:border-cyan-600"
|
||||||
<Carousel images={["https://placehold.co/600x400", "https://placehold.co/600x400", "https://placehold.co/600x400"]} />
|
>
|
||||||
|
<Carousel images={["https://placehold.co/600x400", "https://placehold.co/600x400", "https://placehold.co/600x400"]} />
|
||||||
|
|
||||||
<div className="p-4 flex flex-col gap-1 h-full">
|
<div className="p-4 flex flex-col gap-1 h-full">
|
||||||
<Link href={`/mii/${mii.id}`} className="font-bold text-2xl overflow-hidden text-ellipsis line-clamp-2" title={mii.name}>
|
<Link href={`/mii/${mii.id}`} className="font-bold text-2xl overflow-hidden text-ellipsis line-clamp-2" title={mii.name}>
|
||||||
{mii.name}
|
{mii.name}
|
||||||
</Link>
|
</Link>
|
||||||
<div id="tags" className="flex gap-1 *:px-2 *:py-1 *:bg-orange-300 *:rounded-full *:text-xs">
|
<div id="tags" className="flex gap-1 *:px-2 *:py-1 *:bg-orange-300 *:rounded-full *:text-xs">
|
||||||
{mii.tags.map((tag) => (
|
{mii.tags.map((tag) => (
|
||||||
<Link href={{ query: { tags: tag } }} key={tag}>
|
<Link href={{ query: { tags: tag } }} key={tag}>
|
||||||
{tag}
|
{tag}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-auto grid grid-cols-2 items-center">
|
<div className="mt-auto grid grid-cols-2 items-center">
|
||||||
<LikeButton likes={mii.likes} miiId={mii.id} isLiked={mii.isLikedByUser} isLoggedIn={session?.user != null} />
|
<LikeButton likes={mii.likes} miiId={mii.id} isLiked={mii.isLikedByUser} isLoggedIn={session?.user != null} />
|
||||||
|
|
||||||
{userId == null && (
|
{userId == null && (
|
||||||
<Link href={`/profile/${mii.user.id}`} className="text-sm text-right overflow-hidden text-ellipsis">
|
<Link href={`/profile/${mii.user.id}`} className="text-sm text-right overflow-hidden text-ellipsis">
|
||||||
@{mii.user?.username}
|
@{mii.user?.username}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
<p className="text-xl text-center mt-10">No results found.</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,45 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
import { Icon } from "@iconify/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() {
|
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 (
|
return (
|
||||||
<div className="max-w-md w-full flex rounded-xl focus-within:ring-[3px] ring-orange-400/50 transition shadow-md">
|
<div className="max-w-md w-full flex rounded-xl focus-within:ring-[3px] ring-orange-400/50 transition shadow-md">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => 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"
|
className="bg-orange-200 border-2 border-orange-400 py-2 px-3 rounded-l-xl outline-0 w-full placeholder:text-black/40"
|
||||||
/>
|
/>
|
||||||
<button className="bg-orange-400 p-2 w-12 rounded-r-xl flex justify-center items-center cursor-pointer text-2xl transition-all hover:text-[1.75rem] active:text-2xl">
|
<button
|
||||||
|
onClick={handleSearch}
|
||||||
|
className="bg-orange-400 p-2 w-12 rounded-r-xl flex justify-center items-center cursor-pointer text-2xl transition-all hover:text-[1.75rem] active:text-2xl"
|
||||||
|
>
|
||||||
<Icon icon="ic:baseline-search" />
|
<Icon icon="ic:baseline-search" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
38
src/app/search/page.tsx
Normal file
38
src/app/search/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<div>
|
||||||
|
<p className="text-lg">
|
||||||
|
Search results for "<span className="font-bold">{query}</span>"
|
||||||
|
</p>
|
||||||
|
<MiiList
|
||||||
|
searchParams={searchParams}
|
||||||
|
where={{
|
||||||
|
OR: [{ name: { contains: query, mode: "insensitive" } }, { tags: { has: query } }],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue