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",