Skip to content

Commit

Permalink
feat: ✨ Home Page
Browse files Browse the repository at this point in the history
- Implement home page where users can see all the streamers available and live.
- Implement streams feed to populate the home page.
- Create feed-service.ts.

Users will now be welcome with the list of streamers when they join the platform. The streamers live will get
pushed to the start of the list to be displayed first.
  • Loading branch information
RicardoGEsteves committed Dec 17, 2023
1 parent 36c22cc commit f103f81
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 1 deletion.
60 changes: 60 additions & 0 deletions app/(browse)/(home)/_components/result-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Link from "next/link";
import { User } from "@prisma/client";

import Thumbnail, { ThumbnailSkeleton } from "@/components/thumbnail";
import { Skeleton } from "@/components/ui/skeleton";
import UserAvatar, { UserAvatarSkeleton } from "@/components/user-avatar";

interface ResultCardProps {
data: {
user: User;
isLive: boolean;
name: string;
thumbnailUrl: string | null;
};
}

const ResultCard = ({ data }: ResultCardProps) => {
return (
<Link href={`/${data.user.username}`}>
<div className="h-full w-full space-y-4">
<Thumbnail
src={data.thumbnailUrl}
fallback={data.user.imageUrl}
isLive={data.isLive}
username={data.user.username}
/>
<div className="flex gap-x-3">
<UserAvatar
username={data.user.username}
imageUrl={data.user.imageUrl}
isLive={data.isLive}
/>
<div className="flex flex-col text-sm overflow-hidden">
<p className="truncate font-semibold hover:text-primary">
{data.name}
</p>
<p className="text-muted-foreground">{data.user.username}</p>
</div>
</div>
</div>
</Link>
);
};

export default ResultCard;

export const ResultCardSkeleton = () => {
return (
<div className="h-full w-full space-y-4">
<ThumbnailSkeleton />
<div className="flex gap-x-3">
<UserAvatarSkeleton />
<div className="flex flex-col gap-y-1">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-3 w-24" />
</div>
</div>
</div>
);
};
47 changes: 47 additions & 0 deletions app/(browse)/(home)/_components/results.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { getStreams } from "@/lib/feed-service";
import { Skeleton } from "@/components/ui/skeleton";

import ResultCard, { ResultCardSkeleton } from "./result-card";

const Results = async () => {
const data = await getStreams();

return (
<div>
<h2 className="text-mb font-semibold mb-6">
<span className="uppercase text-muted-foreground text-lg mr-2">
Unleash the Thrill:
</span>{" "}
Discover the hottest{" "}
<span className="uppercase text-primary text-lg mx-2">Streams</span>{" "}
await you now!
</h2>
{data.length === 0 && (
<div className="text-muted-foreground text-sm">No streams found.</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4">
{data.map((result) => (
<ResultCard
key={result.id}
data={result}
/>
))}
</div>
</div>
);
};

export default Results;

export const ResultsSkeleton = () => {
return (
<div>
<Skeleton className="h-8 w-[600px] mb-4" />
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4">
{[...Array(4)].map((_, i) => (
<ResultCardSkeleton key={i} />
))}
</div>
</div>
);
};
12 changes: 11 additions & 1 deletion app/(browse)/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
import { Suspense } from "react";

import Results, { ResultsSkeleton } from "./_components/results";

export default function RootPage() {
return <div className="h-full p-8 max-w-screen-2xl mx-auto">Home Page</div>;
return (
<div className="h-full p-8 max-w-screen-2xl mx-auto">
<Suspense fallback={<ResultsSkeleton />}>
<Results />
</Suspense>
</div>
);
}
61 changes: 61 additions & 0 deletions components/thumbnail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Image from "next/image";

import { Skeleton } from "@/components/ui/skeleton";
import LiveBadge from "@/components/live-badge";
import UserAvatar from "@/components/user-avatar";

interface ThumbnailProps {
src: string | null;
fallback: string;
isLive: boolean;
username: string;
}

const Thumbnail = ({ src, fallback, isLive, username }: ThumbnailProps) => {
let content;

if (!src) {
content = (
<div className="bg-secondary/40 flex flex-col items-center justify-center gap-y-4 h-full w-full transition-transform group-hover:translate-x-2 group-hover:-translate-y-2 rounded-md">
<UserAvatar
size="lg"
showBadge
username={username}
imageUrl={fallback}
isLive={isLive}
/>
</div>
);
} else {
content = (
<Image
src={src}
fill
alt="Thumbnail"
className="object-cover transition-transform group-hover:translate-x-2 group-hover:-translate-y-2 rounded-md bg-secondary/40"
/>
);
}

return (
<div className="group aspect-video relative rounded-md cursor-pointer">
<div className="rounded-md absolute inset-0 bg-primary opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center" />
{content}
{isLive && src && (
<div className="absolute top-2 left-2 group-hover:translate-x-2 group-hover:-translate-y-2 transition-transform">
<LiveBadge />
</div>
)}
</div>
);
};

export default Thumbnail;

export const ThumbnailSkeleton = () => {
return (
<div className="group aspect-video relative rounded-xl cursor-pointer">
<Skeleton className="h-full w-full" />
</div>
);
};
66 changes: 66 additions & 0 deletions lib/feed-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { db } from "@/lib/db";
import { getSelf } from "@/lib/auth-service";

export const getStreams = async () => {
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,
},
},
},
},
},
select: {
id: true,
user: true,
isLive: true,
name: true,
thumbnailUrl: true,
},
orderBy: [
{
isLive: "desc",
},
{
updatedAt: "desc",
},
],
});
} else {
streams = await db.stream.findMany({
select: {
id: true,
user: true,
isLive: true,
name: true,
thumbnailUrl: true,
},
orderBy: [
{
isLive: "desc",
},
{
updatedAt: "desc",
},
],
});
}

return streams;
};

0 comments on commit f103f81

Please sign in to comment.