Skip to content

Commit

Permalink
feat: Community 💬 (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zaid-maker authored Jan 1, 2024
2 parents da2a275 + a0f655d commit 9edfa7f
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 9 deletions.
5 changes: 3 additions & 2 deletions .gitpod.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
tasks:
- init: npm install && npm run build
command: npm run start
- name: Start development environment
init: npm install
command: npm run dev
11 changes: 11 additions & 0 deletions app/(browse)/(home)/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { StreamPlayerSkeleton } from "@/components/stream-player";

const CreatorLoading = () => {
return (
<div className="h-full">
<StreamPlayerSkeleton />
</div>
);
};

export default CreatorLoading;
13 changes: 13 additions & 0 deletions app/(dashboard)/u/[username]/community/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from "react";

const Community = () => {
return (
<div className="p-6">
<div className="mb-4">
<h1 className="text-2xl font-bold">Community Settings</h1>
</div>
</div>
);
};

export default Community;
4 changes: 3 additions & 1 deletion app/api/webhooks/clerk/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Webhook } from "svix";
import { headers } from "next/headers";
import { WebhookEvent } from "@clerk/nextjs/server";

import { db } from "@/lib/db";
import { resetIngresses } from "@/actions/ingress";

export async function POST(req: Request) {
// You can find this in the Clerk Dashboard -> Webhooks -> choose the webhook
Expand Down Expand Up @@ -79,7 +81,7 @@ export async function POST(req: Request) {
}

if (eventType === "user.deleted") {
// await resetIngresses(payload.data.id);
await resetIngresses(payload.data.id);

await db.user.delete({
where: {
Expand Down
78 changes: 78 additions & 0 deletions components/stream-player/chat-community.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"use client";

import React, { useMemo, useState } from "react";
import { Input } from "../ui/input";
import { ScrollArea } from "../ui/scroll-area";
import { useDebounce } from "usehooks-ts";
import { useParticipants } from "@livekit/components-react";
import { LocalParticipant, RemoteParticipant } from "livekit-client";
import { CommunityItem } from "./community-item";

interface ChatCommunityProps {
viewerName: string;
hostName: string;
isHidden: boolean;
}

export const ChatCommunity = ({
viewerName,
hostName,
isHidden,
}: ChatCommunityProps) => {
const [value, setValue] = useState("");
const debouncedValue = useDebounce<string>(value, 500);

const participants = useParticipants();

const onChange = (newValue: string) => {
setValue(newValue);
};

const filteredParticipants = useMemo(() => {
const deduped = participants.reduce((acc, participant) => {
const hostAsViewer = `host-${participant.identity}`;
if (!acc.some((p) => p.identity === hostAsViewer)) {
acc.push(participant);
}
return acc;
}, [] as (RemoteParticipant | LocalParticipant)[]);

return deduped.filter((participant) => {
return participant.name
?.toLowerCase()
.includes(debouncedValue.toLowerCase());
});
}, [participants, debouncedValue]);

if (isHidden) {
return (
<div className="flex flex-1 items-center justify-center">
<p className="text-sm text-muted-foreground">Community is disabled</p>
</div>
);
}

return (
<div className="p-4">
<Input
onChange={(e) => onChange(e.target.value)}
placeholder="Search Community"
className="border-white/10"
/>
<ScrollArea className="gap-y-2 mt-4">
<p className="text-center text-sm text-muted-foreground hidden last:block p-2">
No results
</p>
{filteredParticipants.map((participant) => (
<CommunityItem
key={participant.identity}
hostName={hostName}
viewerName={viewerName}
participantName={participant.name}
participantIdentity={participant.identity}
/>
))}
</ScrollArea>
</div>
);
};
12 changes: 11 additions & 1 deletion components/stream-player/chat-list.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use client";

import { ReceivedChatMessage } from "@livekit/components-react";
import React from "react";
import { Skeleton } from "../ui/skeleton";
import { ChatMessage } from "./chat-message";

interface ChatListProps {
Expand All @@ -26,3 +28,11 @@ export const ChatList = ({ messages, isHidden }: ChatListProps) => {
</div>
);
};

export const ChatListSkeleton = () => {
return (
<div className="flex h-full items-center justify-center">
<Skeleton className="w-1/2 h-6" />
</div>
);
};
15 changes: 10 additions & 5 deletions components/stream-player/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import { ChatVariant, useChatSidebar } from "@/store/use-chat-sidebar";
//import { ChatForm, ChatFormSkeleton } from "./chat-form";
//import { ChatList, ChatListSkeleton } from "./chat-list";
import { ChatHeader, ChatHeaderSkeleton } from "./chat-header";
import { ChatForm } from "./chat-form";
import { ChatList } from "./chat-list";
import { ChatForm, ChatFormSkeleton } from "./chat-form";
import { ChatList, ChatListSkeleton } from "./chat-list";
import { ChatCommunity } from "./chat-community";
//import { ChatCommunity } from "./chat-community";

interface ChatProps {
Expand Down Expand Up @@ -88,9 +89,11 @@ export const Chat = ({
</>
)}
{variant === ChatVariant.COMMUNITY && (
<>
<p>Community Mode</p>
</>
<ChatCommunity
viewerName={viewerName}
hostName={hostName}
isHidden={isHidden}
/>
)}
</div>
);
Expand All @@ -100,6 +103,8 @@ export const ChatSkeleton = () => {
return (
<div className="flex flex-col border-l border-b pt-0 h-[calc(100vh-80px)] border-2">
<ChatHeaderSkeleton />
<ChatListSkeleton />
<ChatFormSkeleton />
</div>
);
};
62 changes: 62 additions & 0 deletions components/stream-player/community-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"use client";

import { onBlock } from "@/actions/block";
import { cn, stringToColor } from "@/lib/utils";
import { MinusCircle } from "lucide-react";
import { useTransition } from "react";
import { toast } from "sonner";
import { Hint } from "../hint";
import { Button } from "../ui/button";

interface CommunityItemProps {
hostName: string;
viewerName: string;
participantName?: string;
participantIdentity: string;
}

export const CommunityItem = ({
hostName,
viewerName,
participantName,
participantIdentity,
}: CommunityItemProps) => {
const [isPending, startTransition] = useTransition();

const color = stringToColor(participantName || "");
const isSelf = participantName === viewerName;
const isHost = viewerName === hostName;

const handleBlock = () => {
if (!participantName || isSelf || !isHost) return;

startTransition(() => {
onBlock(participantIdentity)
.then(() => toast.success(`Blocked ${participantName}`))
.catch(() => toast.error("Something went wrong"));
});
};

return (
<div
className={cn(
"group flex items-center justify-between w-full p-2 rounded-md text-sm hover:bg-white/5",
isPending && "opacity-50 pointer-events-none"
)}
>
<p style={{ color: color }}>{participantName}</p>
{isHost && !isSelf && (
<Hint label="Block">
<Button
variant="ghost"
disabled={isPending}
onClick={handleBlock}
className="h-auto w-auto p-1 opacity-0 group-hover:opacity-100 transition"
>
<MinusCircle className="h-4 w-4 text-muted-foreground" />
</Button>
</Hint>
)}
</div>
);
};
48 changes: 48 additions & 0 deletions components/ui/scroll-area.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client"

import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"

import { cn } from "@/lib/utils"

const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName

const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName

export { ScrollArea, ScrollBar }
32 changes: 32 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@prisma/client": "^5.7.1",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2",
Expand Down

0 comments on commit 9edfa7f

Please sign in to comment.