-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Implement search feature. - Create search page. - Create search-service.ts. - Implement results and card result. Users can now search for their streamer.
- Loading branch information
1 parent
f103f81
commit 028abec
Showing
4 changed files
with
238 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import Link from "next/link"; | ||
import { User } from "@prisma/client"; | ||
import { formatDistanceToNow } from "date-fns"; | ||
|
||
import Thumbnail, { ThumbnailSkeleton } from "@/components/thumbnail"; | ||
import { Skeleton } from "@/components/ui/skeleton"; | ||
import VerifiedMark from "@/components/verified-mark"; | ||
|
||
interface ResultCardProps { | ||
data: { | ||
id: string; | ||
name: string; | ||
thumbnailUrl: string | null; | ||
isLive: boolean; | ||
updatedAt: Date; | ||
user: User; | ||
}; | ||
} | ||
|
||
const ResultCard = ({ data }: ResultCardProps) => { | ||
return ( | ||
<Link href={`/${data.user.username}`}> | ||
<div className="w-full flex gap-x-4"> | ||
<div className="relative h-[9rem] w-[16rem]"> | ||
<Thumbnail | ||
src={data.thumbnailUrl} | ||
fallback={data.user.imageUrl} | ||
isLive={data.isLive} | ||
username={data.user.username} | ||
/> | ||
</div> | ||
<div className="space-y-1"> | ||
<div className="flex items-center gap-x-2"> | ||
<p className="font-bold text-lg cursor-pointer hover:text-primary"> | ||
{data.user.username} | ||
</p> | ||
<VerifiedMark /> | ||
</div> | ||
<p className="text-sm text-muted-foreground">{data.name}</p> | ||
<p className="text-sm text-muted-foreground"> | ||
{formatDistanceToNow(new Date(data.updatedAt), { | ||
addSuffix: true, | ||
})} | ||
</p> | ||
</div> | ||
</div> | ||
</Link> | ||
); | ||
}; | ||
|
||
export default ResultCard; | ||
|
||
export const ResultCardSkeleton = () => { | ||
return ( | ||
<div className="w-full flex gap-x-4"> | ||
<div className="relative h-[9rem] w-[16rem]"> | ||
<ThumbnailSkeleton /> | ||
</div> | ||
<div className="space-y-2"> | ||
<Skeleton className="h-4 w-32" /> | ||
<Skeleton className="h-3 w-24" /> | ||
<Skeleton className="h-3 w-12" /> | ||
</div> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { getSearch } from "@/lib/search-service"; | ||
import { Skeleton } from "@/components/ui/skeleton"; | ||
|
||
import ResultCard, { ResultCardSkeleton } from "./result-card"; | ||
|
||
interface ResultsProps { | ||
term?: string; | ||
} | ||
|
||
const Results = async ({ term }: ResultsProps) => { | ||
const data = await getSearch(term); | ||
|
||
return ( | ||
<div> | ||
<h2 className="text-lg font-semibold mb-4"> | ||
Results for "{term}" | ||
</h2> | ||
{data.length === 0 && ( | ||
<p className="text-muted-foreground text-sm"> | ||
No results found. Try searching for someone else. | ||
</p> | ||
)} | ||
<div className="flex flex-col gap-y-4"> | ||
{data.map((result) => ( | ||
<ResultCard | ||
data={result} | ||
key={result.id} | ||
/> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Results; | ||
|
||
export const ResultsSkeleton = () => { | ||
return ( | ||
<div> | ||
<Skeleton className="h-8 w-[290px] mb-4" /> | ||
<div className="flex flex-col gap-y-4"> | ||
{[...Array(4)].map((_, i) => ( | ||
<ResultCardSkeleton key={i} /> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { Suspense } from "react"; | ||
import { redirect } from "next/navigation"; | ||
|
||
import Results, { ResultsSkeleton } from "./_components/results"; | ||
|
||
interface SearchPageProps { | ||
searchParams: { | ||
term?: string; | ||
}; | ||
} | ||
|
||
const SearchPage = ({ searchParams }: SearchPageProps) => { | ||
if (!searchParams.term) { | ||
redirect("/"); | ||
} | ||
|
||
return ( | ||
<div className="h-full p-8 max-w-screen-2xl mx-auto"> | ||
<Suspense fallback={<ResultsSkeleton />}> | ||
<Results term={searchParams.term} /> | ||
</Suspense> | ||
</div> | ||
); | ||
}; | ||
|
||
export default SearchPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { db } from "@/lib/db"; | ||
import { getSelf } from "@/lib/auth-service"; | ||
|
||
export const getSearch = async (term?: string) => { | ||
let userId; | ||
|
||
try { | ||
const self = await getSelf(); | ||
userId = self.id; | ||
} catch { | ||
userId = null; | ||
} | ||
|
||
let streams = []; | ||
|
||
if (userId) { | ||
streams = await db.stream.findMany({ | ||
where: { | ||
user: { | ||
NOT: { | ||
blocking: { | ||
some: { | ||
blockedId: userId, | ||
}, | ||
}, | ||
}, | ||
}, | ||
OR: [ | ||
{ | ||
name: { | ||
contains: term, | ||
}, | ||
}, | ||
{ | ||
user: { | ||
username: { | ||
contains: term, | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
select: { | ||
user: true, | ||
id: true, | ||
name: true, | ||
isLive: true, | ||
thumbnailUrl: true, | ||
updatedAt: true, | ||
}, | ||
orderBy: [ | ||
{ | ||
isLive: "desc", | ||
}, | ||
{ | ||
updatedAt: "desc", | ||
}, | ||
], | ||
}); | ||
} else { | ||
streams = await db.stream.findMany({ | ||
where: { | ||
OR: [ | ||
{ | ||
name: { | ||
contains: term, | ||
}, | ||
}, | ||
{ | ||
user: { | ||
username: { | ||
contains: term, | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
select: { | ||
user: true, | ||
id: true, | ||
name: true, | ||
isLive: true, | ||
thumbnailUrl: true, | ||
updatedAt: true, | ||
}, | ||
orderBy: [ | ||
{ | ||
isLive: "desc", | ||
}, | ||
{ | ||
updatedAt: "desc", | ||
}, | ||
], | ||
}); | ||
} | ||
|
||
return streams; | ||
}; |