Skip to content

Commit

Permalink
feat: ✨ Search feature.
Browse files Browse the repository at this point in the history
- 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
RicardoGEsteves committed Dec 17, 2023
1 parent f103f81 commit 028abec
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 0 deletions.
66 changes: 66 additions & 0 deletions app/(browse)/search/_components/result-card.tsx
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>
);
};
48 changes: 48 additions & 0 deletions app/(browse)/search/_components/results.tsx
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 &quot;{term}&quot;
</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>
);
};
26 changes: 26 additions & 0 deletions app/(browse)/search/page.tsx
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;
98 changes: 98 additions & 0 deletions lib/search-service.ts
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;
};

0 comments on commit 028abec

Please sign in to comment.