So now what we have to build is the community tab, right? So right now we have the chat and you can see how it's disabled when I'm not streaming but if I go ahead and click start streaming in OBS it's gonna get triggered right here. One thing that I see missing here is that we don't have the semicolon for who is talking right? So let's go ahead and quickly fix that. So inside of this chat message I'm gonna add a semicolon at the end of this, sorry a colon here at the end and there we go now we have an indicator that is Antonio that's saying this.
And now while I am inside of, not inside of chat message, but go back inside of the chat list component where we render the chat messages, I want to go ahead and just create a little skeleton here. So let's go ahead and do export const chatSkeleton and let's return a div that's going to have a class name of flex-h-full-item-center and justify-center and inside we're simply going to render a skeleton which is going to represent our text welcome to the chat, right? So I'm going to change this to use components and I'm going to move it above my local import here and let's just give it a class name with one and a half and height of six like this. There we go. And also if by any chance you're confused about how this chat is working.
I didn't really go around explaining that too much because I do have two hours left to finish this video because YouTube has a video limit. So inside of our chat.tsx in here, Is it in here? I believe it is. Just let me find it. There we go.
In here we use the use chat hook right from LiveKit components reactors. So how does this hook know to which room to connect? Well because of the context. So if you go ahead and hover over this, you can see that it actually has some options that you can pass and in here you can specify the exact room you want this chat to work with both the send functionality and the messages received. But the reason it knows that this is in this exact room where we created the viewer token for this stream is because it has context from the stream player.
So go inside of the index for the stream player and in here we are wrapping the entire thing inside of LiveKit room with the token and the server URL. So that's how it knows because it's wrapped inside of a component which gives it its context right? So that's how it knows where to send and where to read the messages. Great, so what I want to do now is go inside of the chat component here and I want to create the other state. So we just finished the variant for chat and now let's go ahead and create a component called chat community and this these are the props it's going to accept so viewer name which is viewer name and host name which is host name and is hidden which is is hidden like this.
So make sure you save that and now let's go inside of the stream player and create chat community.tsx Let's mark this as use client. Let's export const chat community here and return a div. And let's create an interface chat community props to accept the host name, which is a string, the viewer name, which is a string, and the is hidden, which is a boolean like that. Let's go ahead and assign the props chat community props and now we can go back inside of the chat component and import chat community the same way we did with chat header, list and form. And now we should no longer be having any errors and you can already try and click here.
And there we go. Now we should just be an empty div if you switch to community mode. So now what I want to do is I want to load all participants that are inside of here, right? So let's go ahead and do the following. So let's add participants from use participants from LiveKit components react like this.
So make sure you add this. And now what you can actually do here immediately is try it out inside of here so I'm going to write participants.map and get the individual participant. And let's just go ahead, participants, right? And inside, I'm just gonna return, actually, I already returned. So let's just do a div with JSON, stringify, and let's give it an entire participant and let's go ahead and just write math.random for the key here for now and there we go you can see that you should be having a big JSON output here and here I can find my name Antonio right so now we're gonna go ahead and style this so it actually renders the proper participants and we're also going to enable the ability to search for participants.
So let's go ahead and do that. I'm going to go ahead and add a value and set the value in this component here, which comes from React. So that's going to be used to filter between our participants and now what I'm going to do is I'm also going to import useDebounce from useHooks so we slow down the queries while the user is searching and let's go ahead and assign that to the value so const debounce value is going to be used debounce and passing the value and the debounce is 500 like this and you can also define that this is a string. So every half a second is when we're actually going to filter through participants. We're not going to immediately filter through them.
Now let's write our onChange function which is going to be binded to the input which we will add in a second to filter through this participants. And this useParticipants() uses the same method as useChat() so it has context because of that LiveKit component, LiveKit room component from StreamPlayer index file. So that's how it knows in which room is it looking for participants. And now let's simply write set value and passing the value from here. And let's actually call it new value.
And then let's pass in new value here. So we don't conflict with the existing name. And now let's write const filtered participants. Actually, let's do that later. For now, I'm just going to use participants.
We're going to do this later. One of the first is do if isHidden. Let's return a div with a class name here, flex flex1, items center and justify center and let's see if we are missing isHidden, we are missing it because I did not add any of the props so host name, viewer name and is hidden like this. There we go. So now go back here and inside of this div and there a paragraph community is disabled like this and give this a class name of text small and text muted foreground like that and now it should say well if you turn off the stream it's going to say communities disabled right but since I am active that's not what I'm seeing here and now let's go ahead and give this div a class name of padding4 let's go ahead and let's add an input component which you can input from slash ui input or from slash components like I'm going to do that input is going to be used to trigger our onChange and then filter through the participants so let's give it an onChange to get the event and call our onChange function which accepts the new value which is going to be event.target.value so that's going to go in this onChange function the value is going to change and then later we're gonna have a new constant here which I started writing first filtered participants but I'm gonna leave it in the end first I want to show you how we render the participants and then we're going to filter to them later.
And again, let's give this a placeholder of search community and a class name, BorderWhite10, just so it's a bit more visible. And now what I wanna do is I wanna add a scroll area here from chatCNUI so let's do npx chatCNUI add latest add scroll area like that make sure you add this to your project and once it is installed you can go ahead and import scroll area from ./.ui-scroll-area or from ./.components-ui-scroll-area. Now let's go ahead and use the scroll area here. And I want to give it a class name of gap y2 and margin top of 4 and first I'm gonna add a paragraph no results which is only gonna be visible if it is the last item in this element so we can use CSS instead of JavaScript to do that. So let's go ahead and center this.
Let's go ahead and make it smaller. Let's give it a specific color like this. And it's going to be hidden by default and it's only going to appear if this last decorator matches, right? Meaning that it's the last child inside of this element and right now that is true because it's the only child and let's also give it the padding of two like this but then what we're gonna do is we're gonna go through our participants.map get the individual participant like that and then let's render the community item so previously we just stringified the entire data of the participant but now we're actually going to use it inside of this component so give it a key of participant and this should be a single participant inside of this dot map so participant dot key sorry dot identity host name is going to be host name viewer name is is going to be viewerName, and participantName is going to be participant.name, and participantIdentity is going to be participant.identity. Like that.
And now let's go ahead and let's create our community item component. So let's go inside of the stream player and create community item.psx. Let's go ahead and mark this as use client and let's go ahead and import toast. Let's go ahead and import use transition because from here we're gonna enable our block functionality, right? So import this three, toast use transition in minus circle, import the hint from dot dot slash hint, change it to components, import on block from actions block, which we already played around with when we created our block service and go ahead and import cn and string toColor from libutils and also import the button component from dot dot slash or components ui button now let's create an interface community item props to accept the host name which is a string viewer name which is a string participant name which is an optional string and participant identity which is a string like this and export const community item go ahead and return a div here and let's go ahead and assign the community item props and let's go ahead and extract the host name, viewer name, participant identity and participant name basically everything that we have inside of our props here.
Great and you can go ahead and render participant name here like this and go back to chat community and import community item from .slash community item and now since we use this hook use participants here and we iterate over them in here and give the data to the community item and we render the participant name you should be seeing two Antonio's here or two of your usernames here So the first one is you as the streamer and the second one is you as the viewer. So don't worry we are going to deduplicate this but I just want to show you that already you can see this working. Great. So now let's go ahead and let's do the following. Let's give this div a class name which is going to be dynamic so go ahead and fire up cn.
Let's give it group flex item center justify between width full padding two rounded md text small hover BG white slash 5 like this and now let's go ahead and wrap this inside of a paragraph and let's give the paragraph a class name actually not a class name but what we're gonna give it is our dynamic color right so you can do that by adding const color to be string to color and passing the participant name or an empty string like this So make sure you import the string to color from our previous module. Or again, you don't even have to do this. Like this is just some cool thing that I think it is. So just pass in the matching color or you can do the color like this. And now what's cool is that the community tab will match the exact identifier, which in our case is the color, with the chat, right?
So if the user sends a blue message in the chat, it's gonna be blue here in the community as well. Great, and now I'm gonna go ahead and just, well, I'm gonna go ahead and define a couple of more constants here so const is self is gonna be if the participant name is equal to the viewer name and then I'm gonna write const is host to be if viewer name is equal to host name so we have to define whether the person we are looking at here is ourselves right so what I'm gonna do now is go ahead below this paragraph and check if if we are the host and if the current user in this community tab is not ourselves in that case we're gonna go ahead and add a little hint here that we can block this user so let's give it a label block and inside let's render the button component which we imported from add slash components UI button and let's render the minus circle which we imported from Lucid React and let's go ahead and give this a class name of hAuto width of auto padding of 1 opacity 0 group dash hover opacity 100 so group hover refers to this group class name in this div so when we hover on this this little icon is going to appear on the side, right?
So let's go ahead and continue with this and also let's give it a transition like that. Great and now besides the class name this button will also have on click to be on block will be handle block which we don't yet have so let's go ahead and quickly create it here so const handle block if there is no participant name if we are self if we are not a host in any of those cases return and otherwise start transition actually we first have to extract the transition so isPending and startTransition from useTransition which we imported from react so let's go ahead here and write start transition and let's go ahead and call our on block and passing the participant identity now let's do dot then here and write those dot success blocked and just render the participant participant name and let me not misspell this participant name and let's do .catch postError something went wrong like this great And now what we can do is we can go ahead and add this here. We can go ahead and add a disabled isPending here. And we can also add a different color here while it is pending so I'm going to write opacity 50% and pointer events are going to be none so it's going to be pretty clear that we are currently in progress of blocking a user and right now you're not going to see this right because we don't show this if we are trying to block ourselves and this is me right these are two instances of me inside of the community tab So what you can do is you can temporarily just hide this ternary, right?
Just so you can see how this looks. But don't click on it, right? Because you're going to get an error. And there we go. We actually have the modify.
Good thing that we did this. Okay. So Let's make sure this looks a little bit better. So I wanna go ahead and give this button a variant of ghost like this. And I think now it should look a bit better.
And let's also give this minus circle a class name of H4 with 4M text muted foreground like that. And there we go. Now it looks better. And remember to bring back this ternary here so you can only block a specific type of user and okay like this great so now we have this finished what we have to do now is go back inside of the chat community and in here I now want to create the ability to filter and search throughout our participants so let's go ahead and create const filtered participants to be useMemo so make sure you import useMemo from react we are now in the chat community component let's go ahead and pass in the participants and the debounced value as the dependency array and let's first dedupe our items so const deduped is going to be participants.reduce get the accumulator and the participant like this let's do const host as viewer So we're going to add that little prefix here because we are a host which is currently inside of here so we're going to recognize ourselves because of this prefix that we have, right? If you remember when we created the viewer token if we are a host we add this prefix because we cannot have the same identity twice in one stream.
So let's go ahead and write participant.identity like that and now let's do if not accumulator.sum accumulator.sum oh it says that accumulator.sum does not exist on this method, so let's go ahead and just see why it doesn't exist. I think it's just due to the fact that I haven't finished the function. So let's continue. If not accumulator.sum p as participant, p.identity is equal to host as viewer, accumulator.push participant, like that. And then let's just return the accumulator like this and then let's go ahead and give it a default value of an array.
And now that error from above is gone, but we also have to define the types to be as remote participants from LiveKit client. So make sure you import a remote participant from LiveKit client or local participant from LiveKit client, and then put an array at the end and there we go now this should not be having any errors and I'm just going to move this import here so make sure you have both local participant and remote participant so That's why we have two of them because one of them is our local participant and one of them is us as viewer looking at the remote participant, which in our case is us again. So now let's go ahead and do return deduped.filter, get the participant and do return participant.name?2 participant and the return participant.name?2 lowercase includes the bounced value.2 lowercase like that. So in here we do the actual search and then we're going to use, let me just zoom out so you can see this in one line. Right, so this is the function which we just created.
And now we're going to use this filter participants in the dot map instead. Like this. So let's go ahead and refresh our page now and now we should be able to search throughout our participants so make sure you are streaming so that you can see this and you can see no longer we have any duplicates and now if I search for Antonio it says Antonio but if I search for something else it says no results great so we finished our community tab, we finished our chat, you can see how fast this is working and now what I want to do is I want to create a little loading skeleton, right? That's what we created those skeleton items. So we're gonna go ahead and do that now.
So let's go back inside of the original chat component. So inside of the stream player we have the chat component and in here let's go ahead and let's export const chat skeleton and let's go ahead and return a div with a class name of plex plex skull border left border bottom padding top of 0 height calculate 100% sorry 100vh minus 80 pixels and border 2 and let me zoom out so you can see it in one line like that and then inside we're gonna render the chat header skeleton we're going to render the chat list skeleton and it looks like we did not create that one so we have the chat header skeleton right from chat header but our chat list didn't we create one? Oh we call it chat skeleton it should be chat list skeleton so inside of your chat list component make sure that you rename the skeleton to be appropriately named chat list and then you can import chat list skeleton the same way we added chat header skeleton a second ago and below that add chat form skeleton. Great! So make sure that all of those are important and we don't need one for the community.
And now I just want to check if we have a video skeleton. So go inside of video, not live, not loading, just video and in here we also have to create a quick skeleton so let's export const video skeleton here and let's go ahead and return a div with a class name aspect video border x border background and inside we are simply going to render a skeleton so make sure you import that from .uskeleton or from the components like I'm gonna do now and in here just go ahead and give this skeleton a class name of HFull widthFull and roundedNone like this And now you can go inside of the index of the StreamPlayer component, go all the way to the bottom and we're gonna go ahead and create export const stream player skeleton like this and go ahead and create a div with a class name and this is gonna have the following so grid calls one, lg sorry, gap y zero, lg grid calls three, excel grid calls three, to excel grid calls six and h full and then inside another div with a class name of space y4 call span 1, LG call span 2, Excel call span 2, 2xl call span 5, LG call span 5 as well, LG overflow, sorry the last one is supposed to be 2xl Okay, I made a mistake So the LG is the overflow like this So the last one is 2xl, colspan 5 This one is overflow y auto hidden scrollbar and padding bottom of 10.
And then inside, render the video skeleton component, render the header skeleton. We don't have this yet so I'm going to add to do header skeleton like this. And then below that add a div with a class name col-span1-nbg-background and render the chat skeleton component from ./.chat like this. So make sure you imported the video skeleton and the chat skeleton. And what you can do now is instead of rendering this while it's loading you can render stream player skeleton like this and now if you go ahead and expand, if you go ahead and expand and refresh, you can see how we have a much nicer loading skeleton now.
Great, so it's not that weird state now. Perfect. And one more place where we have to add this is inside of where we're actually rendering this. So app folder, dashboard, you username, home inside of this page, but we're going to create loading.dsx here. And let's go ahead and do creator loading.
And let's do div class name hfull and render stream player skeleton from components stream player like this and there we go now it is fully supported as you can see. We also have a special loading state when it is on mobile and it also collapses when it's on desktop. Perfect! So we have a nice little skeleton here. We can expand, we can collapse, we can search the community, we can ban users, we can chat here.
And what we have to do now is create a little header here so that we can follow the user, right? Great, great job.