diff --git a/.gitpod.yml b/.gitpod.yml
index c4b48c3..940e901 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -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
diff --git a/app/(browse)/(home)/loading.tsx b/app/(browse)/(home)/loading.tsx
new file mode 100644
index 0000000..66848b1
--- /dev/null
+++ b/app/(browse)/(home)/loading.tsx
@@ -0,0 +1,11 @@
+import { StreamPlayerSkeleton } from "@/components/stream-player";
+
+const CreatorLoading = () => {
+ return (
+
+
+
+ );
+};
+
+export default CreatorLoading;
diff --git a/app/(dashboard)/u/[username]/community/page.tsx b/app/(dashboard)/u/[username]/community/page.tsx
new file mode 100644
index 0000000..f3dbc5c
--- /dev/null
+++ b/app/(dashboard)/u/[username]/community/page.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+
+const Community = () => {
+ return (
+
+
+
Community Settings
+
+
+ );
+};
+
+export default Community;
diff --git a/app/api/webhooks/clerk/route.ts b/app/api/webhooks/clerk/route.ts
index 4c35491..2fc0b4a 100644
--- a/app/api/webhooks/clerk/route.ts
+++ b/app/api/webhooks/clerk/route.ts
@@ -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
@@ -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: {
diff --git a/components/stream-player/chat-community.tsx b/components/stream-player/chat-community.tsx
new file mode 100644
index 0000000..5932ae9
--- /dev/null
+++ b/components/stream-player/chat-community.tsx
@@ -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(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 (
+
+
Community is disabled
+
+ );
+ }
+
+ return (
+
+
onChange(e.target.value)}
+ placeholder="Search Community"
+ className="border-white/10"
+ />
+
+
+ No results
+
+ {filteredParticipants.map((participant) => (
+
+ ))}
+
+
+ );
+};
diff --git a/components/stream-player/chat-list.tsx b/components/stream-player/chat-list.tsx
index bcc3d75..b7a272d 100644
--- a/components/stream-player/chat-list.tsx
+++ b/components/stream-player/chat-list.tsx
@@ -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 {
@@ -26,3 +28,11 @@ export const ChatList = ({ messages, isHidden }: ChatListProps) => {
);
};
+
+export const ChatListSkeleton = () => {
+ return (
+
+
+
+ );
+};
diff --git a/components/stream-player/chat.tsx b/components/stream-player/chat.tsx
index 53e01dd..3c02112 100644
--- a/components/stream-player/chat.tsx
+++ b/components/stream-player/chat.tsx
@@ -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 {
@@ -88,9 +89,11 @@ export const Chat = ({
>
)}
{variant === ChatVariant.COMMUNITY && (
- <>
- Community Mode
- >
+
)}
);
@@ -100,6 +103,8 @@ export const ChatSkeleton = () => {
return (
+
+
);
};
diff --git a/components/stream-player/community-item.tsx b/components/stream-player/community-item.tsx
new file mode 100644
index 0000000..e6dcf57
--- /dev/null
+++ b/components/stream-player/community-item.tsx
@@ -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 (
+
+
{participantName}
+ {isHost && !isSelf && (
+
+
+
+ )}
+
+ );
+};
diff --git a/components/ui/scroll-area.tsx b/components/ui/scroll-area.tsx
new file mode 100644
index 0000000..0b4a48d
--- /dev/null
+++ b/components/ui/scroll-area.tsx
@@ -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,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+ {children}
+
+
+
+
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, orientation = "vertical", ...props }, ref) => (
+
+
+
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }
diff --git a/package-lock.json b/package-lock.json
index 04d1e49..0994b8c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,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",
@@ -1197,6 +1198,37 @@
}
}
},
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.5.tgz",
+ "integrity": "sha512-b6PAgH4GQf9QEn8zbT2XUHpW5z8BzqEc7Kl11TwDrvuTrxlkcjTD5qa/bxgKr+nmuXKu4L/W5UZ4mlP/VG/5Gw==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/number": "1.0.1",
+ "@radix-ui/primitive": "1.0.1",
+ "@radix-ui/react-compose-refs": "1.0.1",
+ "@radix-ui/react-context": "1.0.1",
+ "@radix-ui/react-direction": "1.0.1",
+ "@radix-ui/react-presence": "1.0.1",
+ "@radix-ui/react-primitive": "1.0.3",
+ "@radix-ui/react-use-callback-ref": "1.0.1",
+ "@radix-ui/react-use-layout-effect": "1.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-select": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.0.0.tgz",
diff --git a/package.json b/package.json
index 779f714..27c1d58 100644
--- a/package.json
+++ b/package.json
@@ -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",