So now I want to go ahead and create our chat component. So first things first, I want to create our store which can handle whether the chat is collapsed or expanded. So let's go ahead and let's copy the creator sidebar and paste it here in the store and let's rename it to use chat sidebar like this. Now what we have to do is rename all the instances of creator sidebar to chat sidebar like that. And then I quickly want to add one more thing here which is export enum chat variant like that so we're going to be able to toggle between chat and between community to look at all the people who are currently in the stream, right?
And from then we can ban the user or something else. So let's give it the default. Let's go ahead and add the variant. Sorry, that's what I meant to say here to be the type of chat variant. And let's go ahead and add one more function here, onChangeVariant, which accepts the variant, which is a type of chat variant, and simply returns a void.
And now we have to add a default value for the variant to be chatVariant.chat and we have to add the onChangeVariant to be variant and accepts the chatVariant type and returns set, which simply assigns the variant like that, great and now I want to go back inside of the stream player component so we have stream player index right here And in here I want to go ahead and I want to import useChatSidebar. And I also want to import CN from libutils like that. And now in here just after we do the viewer token hook let's go ahead and let's extract from use chat sidebar where we're gonna get the entire state here let's go ahead and extract collapsed like this and then I want to go ahead inside of this options here and kind of modify this class names here so let's wrap this inside of curly brackets let's wrap it inside of CN like this and let's go ahead and collapse this classes inside of the first line and then let's set dynamically collapsed in that case on large grid calls to on Excel grid calls to as well and then to Excel grid calls to as well like that.
Great and I want to go ahead and now render our chat component so we're going to do that outside of this div which encapsulates our video so go ahead and add a div here and let's go ahead and give it class name to be CN so dynamic so either call span1 or if it is collapsed it's gonna have a hidden class name like that, great. And now inside we can render the chat component which we're gonna create in a second and let's pass in the viewer name to be the name prop. Let's pass in the host name to be user.username. Let's go ahead and pass the host identity to be user.id. Let's go ahead and pass in isFollowing here to go from isFollowing which we have as a prop from here.
Let's give it isChatEnabled to be stream isChatEnabled from our settings, right, And we have the stream inside of our props because we pass it inside of app dashboard, you username, home page in here. As you can see for the stream player, we pass the stream. So we definitely have the stream option here. Alright, besides is chat enabled we also have is chat delayed so that's gonna be stream is chat delayed and is chat followers only is going to be stream is chat followers only like that. Great.
And now let's go ahead and let's create the chat component. Since I have stream player create a new file chat.tsx. Let's mark this as use client and let's create an interface chat props to accept the host name, to accept the host identity, the viewer name, the isFollowing, which is a boolean and then we have our is chat enabled, we have the is chat delayed and we have is chat followers only. And let's do export const chat and let's assign all of those props so chat props like that and we can return a div which simply renders the text chat. Now let's go back inside of the stream player index and you can import that from .slash chat like I did right here like we did with our video component, right?
Great, now let's go back inside of this chat here and let's go ahead and extract all the props we need. So we need a host name, we need the host identity, viewer name, is following, is chat enabled, is chat delayed and is chat followers only like this and make sure you add the appropriate commas here and let me just go ahead and refresh this a little bit just to confirm that this is still working and there we go you can see how chat is now here below but if I expand to desktop mode you can see how chat is here on the side right so when it collapses on mobile it's going to be below the stream but here it's going to be on this side and we're going to be able to collapse it and expand it. Great! So now I want to go ahead and add all the necessary stuff we need in here. So const matches is going to be useMediaQuery from useHooks.ts and let's go ahead and do the same thing as we did in our wrapper and container components.
We're going to automatically collapse, right? So let's use max width in parentheses max width is 1024 pixels. Now let's go ahead and extract from use chat sidebar where we get the entire state. So make sure you added use chat sidebar import. So in here we're going to extract the variant and on expand like that and then we're gonna use some LiveKit hooks to establish whether this chat is connected or not so const connection state is going to be used connection state and you can import that from LiveKit components react like that and let's go ahead and let's do const participant to be useRemoteParticipant and passing the host identity and make sure you imported useRemoteParticipant from our LiveKit package And now let's go ahead and write const isOnline is simply gonna be if participant exists and if connectionState is equal to connectionState.connected.
And we have to import the connection state from LiveKit client. So go ahead and import this from LiveKit client like that. And let me just move my imports here and let me just collapse this there we go so these are the imports we need so make sure that you check is online to have a participant and to confirm that the connection state from the use connection state hook matches the connected status. Like that. Great and now let's go ahead and write is hidden to see if we are able to display the chat or not so for that we're going to use the opposite value of is chat enabled or is online so we are going to hide the chat if the user is online is offline and now I'm going to go ahead and prepare some items from our form right because we're going to have form to send messages so value and set value come from use state which you can import from react so just make sure you do the import use state from react like this and now let's go ahead and let's import chat messages and send from use chat from LiveKit components react right so this is going to be using webhooks right here use chat from livekin components react it's going to use webhooks for this specific chat room And let's go ahead and remap this chat messages to messages like that.
And now I'm going to go ahead and write a use effect here which you can import from React. And in here I'm simply going to go ahead and I'm going to reset the collapsed state depending on our matches query here so the same thing we did a couple of times in our container components so if matches on expand like that and let's pass in the matches and on expand great and now in order to render this chat messages we first have to reverse them because this is chat right so it works in a reverse way the newest messages as are shown at the bottom. So let's do const reversed messages to be used memo from a react so we're going to memorize them so make sure you import the news memo from react and then we're gonna go ahead and pass the messages in the dependency array and then we're gonna do return messages dot sort get message a and message B and we're simply gonna compare using B dot timestamp minus a dot timestamp like this and now let's go ahead and do const on submit here. If send doesn't exist from this use chat hook we're immediately going to break the function and then let's do send value and then let's reset set value to an empty string like this and lastly let's add a function const on change to accept the value which is a string and calls set value and sets that value great like that And now let's go ahead and let's create a div here with a class name of flex flex call VG background border left, border bottom, padding top of 0 and height is going to be open square brackets calc 100vh minus 80 pixels.
Make sure you don't put any spaces like this because that's not a valid Tailwind CSS class. So let me zoom out so you can see it. So this is the class. This specific height is what we want. And now you can see when I zoom out, you can see how it takes the entire space here and it has a nice little color.
Great. So what I want to create now is my chat header so it indicates what's currently being seen here so let's render chat header here Alright, and now let's go ahead and create that component inside of StreamPlayer, chat-header.tsx let's mark this as useClient And let's go ahead and do export const chat header. And let's return a div with a class name of relative p3 and border bottom. And now I want to go ahead and create a paragraph stream chat like this with a class name of font-semi-bold, text-primary and text-center like that. And in here I'm going to add a little comment to do toggle chat sidebar and in here I'm going to add to do toggle chat community like this.
Great! And now I want to go ahead and already prepare my chat skeleton so let's import the skeleton from dot dot ui skeleton but I'm going to change it to slash components and let's export const chat header skeleton so we can later use it while the chat is loading so it's going to be very simple We are simply going to go ahead and return a div with a class name of relative padding of 3, border bottom, hidden and md visible. Like that and let's add a skeleton component here with a class name of absolute h6 with 6 left 3 and top 3. So that's going to represent this button which currently doesn't exist yet, right? In case you're confused.
And the other one is going to simply represent the text in the middle h6 and mx-auto like that. Great, now let's go back inside of the chat component and let's import the chat header from ./.chat-header as I added right here and once you save this you should no longer be having any errors and instead you should have a nice text which says stream chat at the top. So now I want to go ahead and actually create the chat toggle component for which we wrote a to-do right So go inside of the chat header here and replace this with a chat toggle component like that. And make sure you don't import this from anywhere. And now go inside of stream player and oops, create a new file, chat toggle.tsx like that.
Let's go ahead and mark this as use client. Let's go ahead and import arrow left from line and arrow right from line. This is going to be very similar to our existing toggle components, but this one is specific for the chat. Let's import hint from ./.hint. I'm going to replace it to go to ./.components.
Let's go ahead and import the button from ./.ui-button. And I'm just going to go ahead and replace this to go from slash components as well. And let's import useChatSidebar from the AdStore. UseChatSidebar. Now let's export const chat toggle here.
It's not going to have any props. Instead, it's going to be using the useChatSidebar and get the entire state here and from the state we're gonna extract the collapsed, onExpand and onCollapse like that and now let's go ahead and let's assign our icon dynamically so if it is collapsed it's going to be arrow left from line otherwise arrow right from line and I believe this can actually be a constant no need for let and make sure the icon is capitalized like that and now let's create const onToggle here if we are collapsed very simply this is going to call the onExpand function otherwise it's going to call onCollapse function Now let's create a label for our hint component. So if it's collapsed, it's gonna say expand, otherwise it's gonna say collapse, like this. And now let's finally return our hint component, which is going to have a label of label, side of left, and an as child property. And inside we're going to render our button component so we have both of those imported right here, right?
Our button component is going to have an onClick of onToggle, a variant of Ghost and a class name of HAutoPadding2, hover BG White 10 and hover Text Primary and on default BG is going to be Transparent, like that. And then inside we can simply render our icon component with a class name of h4 and width of 4. Like this. Now we can go back inside of the chat header and you can import the chat toggle from .slash chat toggle which we just created and now inside of your chat you should be seeing this collapse component right here but it looks like it messed up our text here so let's go ahead and see if we are missing something from the chat header. Yes, let's go ahead and wrap this inside of a div and give it a class name of absolute left minus two top minus two hidden and lg block.
So on mobile as you can see here the collapse button is not going to be visible. But when we expand, it's going to be visible right here. And you can see how it already works because we hide that if it's not visible. So now what we need is to render this chat toggle once it's collapsed somewhere, right? We need a way to bring it back.
So for that, what we can do is go back inside of the StreamPlayer component. So in the StreamPlayer index right here, you can see how already this works because we use that dynamic CN hidden here, right? So let's go ahead and do the following. We already have collapsed here so what I'm gonna do is go outside of the LiveKit room and in here I'm going to dynamically render if collapsed in that case render a div with a class name of hidden but visible on desktop, fixed top 100 pixels from above, 2 from the right and z-index of 50. And inside I'm going to render the chat toggle from ./.chat-toggle which we just created.
So now what should happen is that you can click here and there we go you have a button to bring it back. Perfect! Our content is nicely expanding now because of course we added that dynamic CN library here as well so you can see how we change the grid if we are collapsed so that's why it automatically expands. Great and our chat is hidden if it is collapsed. Perfect.
So now what I want to do is I want to create a similar component but to toggle the variant of the chat. So what we can do is simply copy and paste the chat toggle component inside of the stream player folder and rename it to variant toggle like this and now let's rename all instances here so export const variant toggle as well like that and now let's go ahead and modify the icons here so actually we're not going to need first let's do the chat sidebar so We're not going to need any of this. Instead, we're going to need the variant and onChange variant. Like that. And then we're going to use the following.
If variant is equal to chat variant, which you can import from store useChatSidebar, so from where we get the use chat sidebar itself just confirm that you have an export for the enum chat variant so now we can type safely confirm that something is a chat for example and in that case the icon is going to be users otherwise it's going to be message square so make sure you import both the users and the message square from lucid react like this great so now what I want to do next is I want to create one stable constant here const isChat to be variant chatVariant.chat like this, so I don't have to repeat this too many times and I can actually then do this, we can replace that with isChat, right? And then let's go ahead inside of our on toggle and let's write const new variant to be is chat chat variant dot community otherwise chat variant dot chat. And let's do on change variant new variant very simple like that and let's change the label to check if is chat in that case the label is going to be community otherwise is gonna be go back to chat like that and we can pretty much leave this the same in fact we can leave it exactly as it is this is great and now let's go back inside of our chat header component right here where we render the chat toggle and we have this to do comment and let's go ahead and add a div here with a class name of absolute right to and top to and let's render the variant toggle from .slash variant toggle and there we go now we have our variant toggle you can see how it can switch between community and chat and when it's collapsed I mean when it's on mobile mode we still have it visible here perfect Now I want to modify what's rendered inside of the stream chat depending if we are on the community variant or the chat variant.
So for that let's go back inside of our chat.tsx component in here and below the chat header we're gonna go ahead and check if variant is equal to chat variant which you can import from store use chat sidebar so just ensure that you have chat variant imported from store use chat sidebar and ensure that you also extract the variant from use chat sidebar here. So you can do this check if chat variant is chat in that case go ahead and render a fragment for now it's going to say a paragraph chat mode and then below that write variant is equal to chat variant dot community and go ahead and simply render a paragraph. Okay let's go ahead and do a fragment and then let's do a paragraph community like this. And now when you try it, it says chat mode. When I click here, it switches to community.
Great. Now first, let's do the chat mode. So what I want to do first is create a form. So let's do chat form here. And let's go ahead and pass on submit to be on submit function which we've defined value is value which we already have in our state here on change is on change is hidden is hidden is followers only is followers only, sorry is chat followers only, right, is delayed, is chat delayed and is following, is following like this and now isDelayed, isChatDelayed, and isFollowing isFollowing.
Like this. And now let's go ahead and create the chat form inside of the StreamPlayer folder. So chat form right here, let's go ahead and mark this as use client. Let's go ahead and import use state from react. Let's go ahead and import CN from libutils.
Let's go ahead and import the input component from slash ui input which I will change to use slash components. Let's do the same thing for the button and let's do the same thing for the skeleton component like that so I'm just going to replace those two to use components as well. Like this. Now let's go ahead and create an interface chatform props to have onSubmit which is a void, a value which is a string, onChange which accepts the value and returns a void is hidden which is a boolean is followers only which is a boolean as well and now we're going to have is following and we're going to have is delayed like that and now let's do export const chatForm let's go ahead and assign the props so chatForm props onSubmit, value, onChange, isHidden, isFollowersOnly, isFollowing and isDelayed And now go back to the chat and import the chat form from ./.chatform the same way we did with the header and let's go ahead and see what we did wrong. Oh it looks like we're not rendering anything so we can just return a div chat form and now the error is gone and we can see the text chat form.
Great so now I want to go ahead and create a form element here instead of a div element like this. I want to give it a class name of flex flex call items center and gap y four and padding of three. I want to go ahead and also give it an on submit, which for now is simply going to be an empty arrow function. So I'm going to collapse these elements here. Inside of the chat form, I'm going to go ahead and render the input component.
So let's go ahead and pass in the onChange for now to be an empty arrow function. Let's pass in the value to be the value which we get from the props. Disabled can go from isDisabled. If we don't have isDisabled, So let's just go ahead and give it false for now. We are later gonna create that.
Let's give it a placeholder of send a message and let's give it a class name that uses CN right here. And we're gonna go with border white slash 10 like this and then later we're gonna create a case if we are supposed to be following this user and we are not following so we're just gonna prepare this to be dynamic for now it's just gonna be this right and then below this let's actually go ahead and do this. Let's wrap this input inside of a div and let's go ahead and give it a class name of width of full like this and then below that a new div with a button component which is going to say chat and let's go ahead and give it a type of submit, a variant of primary and a size of small and disabled for now let's manually write false and let's give it's div a class name of ML auto, like this. There we go. So now we have our chat component and this is later going to be rendered all the way to the bottom but for now it should be rendered here.
Great. So what I want to do now is I want to create all the necessary functions and constants here. So let's go ahead and let's create the delay functionality. So if the chat is delayed, this is where we're going to simulate that. So if is delay blocked, set is delay blocked from use state.
Default is gonna be false and make sure you import the useState from react and now let's define isFollowersOnly and notFollowing in that case let's go ahead and do that by matching if isFollowersOnly and if it's not following and now let's do const isDisabled to be if isHidden from our global props or if isDelayBlocked or isFollowersOnly and not following. So this is now finally our is disabled and now we can go ahead and use it inside of this input for the disabled and for this button for the disabled. And now what we can also do here is passing another value here is followers only and and rounded T none and border P 0 so we're gonna remove the border from the top you can see that I for my chat have that enabled So mine is must be following the chat. So if you enable this and go into your stream, you're going to see that it looks like our button is cut off at the top. That's what we want because above that there's going to be an info message to tell the user they need to be following, right?
Great. And now what I'm going to add next is a function to submit. So let's do const handle submit here to be a function which takes the event which is react.formEvent and takes in the HTML form element. Let's do e.preventDefault and e.stopPropagation. If we have no value or if the chat is disabled without the exclamation point at the end.
Break the function like this. Now let's check if isDelay() and if we are not currently blocked by the delay because it's delayed, this is the proper one. Then and only then we can send the message. So let's first say that for the next part we are going to be blocked by the delay for 3 seconds, right, if it is enabled. And now let's do set timeout here and the timeout is gonna be 3000 milliseconds and inside of the timeout we're gonna go ahead and simply do set is delay blocked like this to be false and then onSubmit and then in here we're going to go ahead and write else if delay is not enabled for this chat settings we are directly going to submit like this and now below that I'm gonna write if is hidden go ahead and return null like this so now your chat should actually disappear because we are not live streaming but if I go ahead and click start streaming in my OBS the chat should be enabled.
There we go. You can see the moment I start streaming I can see this but the moment I stop streaming this is going to be disabled like that. Alright and now what I want to do is just assign all of this on submits here so let's give this form a function of handle submit like that and let's go ahead and give this one on change to take the event and then call on change from our props to be event.target.value like that and I believe we don't need to fill anything else everything else seems to be correct and now let's go ahead and let's create a skeleton here so export const chat form skeleton return a div with a class name of flex-flex-col-item-center-gap-y4 and padding of 3, and inside render a skeleton component with a class name of width full and h10. Below that, render a div with a class name flex item center, got x2 and ml auto. And inside, let's go ahead and render two more skeletons so this one is going to be class name h7 and width of 7 and the one below it is going to be h7 and width of 12.
So we're practically representing what we have above. Great, now I want to create a component called chat info so we can put it above our input here. So let's go ahead and already prepare for that. So in here I'm going to render chat info which we don't have yet so let's go ahead and create it inside of the stream player chat-info.tsx let's go ahead and import useMemo from react let's go ahead and import info from lucid react and let's go ahead and import hint from dot dot slash hint or slash components hint now let's create an interface chat info props is delayed is going to be a boolean and is followers only is going to be a boolean and isFollowersOnly is going to be a boolean as well. Let's do export const chatInfo here.
Let's return a div chatInfo. Let's go ahead and assign the props chatInfoProps and let's extract isDelayed and isFollowersOnly. And now we can go back inside of the original chat form and import the chat info. So make sure you're doing this inside of chat form, right? And now we have to pass some props here so we get rid of this typescript errors so above the input we have chat info and let's pass in is delayed to be is delayed from our props and we're gonna have is followers only to come from is followers only like this.
Now go back inside of the chat info here and let's go ahead and create some arbitrary values here. So const hint is use memo which we have imported and in the dependency array we're gonna pass in the isDelayed and isFollowersOnly so depending on those we're going to render a specific message for the user So if isFollowersOnly and if we are not delayed, so onlyFollowersOnly, we're going to return onlyFollowersCanChat. And then let's do if isDelayed and not isFollowersOnly return messages are delayed by 3 seconds. Like that. And let's go ahead and write a default like that.
So that's going to be our hint. And now let's create our label so you can copy and paste this function hint and paste it here. Rename it to label. And the first one is simply going to say followers only, the second one is simply going to say slow mode and the last one is going to say followers only and slow mo. Like that.
And now let's go ahead and add if none of these props are active so if it's not delayed or if the chat is not followers only then no need to show any message, right? It's just a normal chat. Great. And now we can go ahead and modify this and give it a class name of h4 and width 4 and outside of the hint render the label and give it a class name of TextExtraSmallT and FontSemiBold. Like this and now I'm going to go ahead and go inside of your chat settings here and go ahead and enable, make sure the chat is enabled and go ahead and enable both delay chat and must be following and go back inside of your stream and click start streaming and now when this gets connected there we go we have info that followers only is on like that and if I go inside of my chat and just do for example turn off delay chat and just be the followers then you're gonna see that it's gonna say a different message now it says followers only great so now what we have to do is we have to create an actual list of messages.
So let's go ahead back inside of our chat component. So chat.psx and in here we have a fragment which renders the chat form and now above that we're going to render chat list like this and we're gonna have to pass in some props here so let's go ahead and give it messages which are our reversed messages and let's give it is hidden which is our is hidden prop like this and now let's go ahead inside of the well we have to create the chat list component so let me just close everything but this right Now let's go inside of stream player and create chatlist.tsx like this. Let's create an interface chatlist props to accept messages which are going to be the received chat message array so received chat message from LiveKit components react and is hidden which is a boolean and now let's export const chat list like this and let's return a div chatlist and let's go ahead and assign the props chatlist props like that and let's go ahead and mark this as used client go back inside of your chat and you can then import chatlist from .slash chatlist the same way we did with our chat header and our chat form.
And now what I want to do is extract messages and isHidden and what we're going to do now is we're going to write if isHidden or if there are no messages or if messages.length is 0 go ahead and return a text which says class name flex flex1 items center and justify center. Then in here write a paragraph which is going to check if is hidden we're gonna write chat is disabled otherwise welcome to the chat and go ahead and give this paragraph a class name of text small and text muted foreground like that. Great! And now since I am not streaming you can see that it says chat is disabled and same is true if you go ahead and turn on and disable the chat from the settings right. So what we have to do now is we have to create our chat message component so let's go ahead and do this here so I'm gonna write class name flex flex one flex call reverse overflow y auto padding of 3 and pull height and inside let's do messages.map get the individual message here whoops get the individual message and in here render chat message component and let's go ahead and pass in the key to be message.timestamp and data is gonna be the message itself.
Like that. And now we have to create our we have to create our chat message components inside of the stream player create chat message dot VSX let's go ahead and mark this as use client and let's create an interface. Chat message props, data received the chat message, but without an array since it's single and let's export common chat message like this. Let's return a div and let's go ahead and assign the props chat message props and we can extract the data. Go back to chat list and you can now import this from .slash chat message and you should not be getting any type errors.
Let's go inside of here and now what I want to do is I want to go inside of my lib utils so where we have the CN function and we're going to add a new function export cons string to color. So we're going to generate a unique color based on the username of the sender. So we're going to accept a string. Let's go ahead and write hash is equal to zero and let's go ahead and do a for loop so for let I be zero I is less than the total length of the string and let's increase I on every iteration and we're gonna change the hash to be string dot char code at one plus hash make sure you do double of this sign right here minus hash, like this and then let's do let color be a hashtag and let's do four let I is equal to zero I is less than three and I plus plus so we are practically generating an rgb color using the string right So that's why we need these values. Now let's do hash in the opposite direction doubles, right?
Multiply by 8 like this and now what you have to write is very specific. You have to write this and 0xff you can also just copy this from my github so this is just some snippet that I found which uses string to generate a hex code that's all this does right and now let's add this to the caller variable here so 0 00 plus value to string 16 dot substring minus two like that and let's return color. Great. Or you can just copy it simply from my GitHub if it's simpler. Let's go back inside of the chat message here and let's go ahead and write const color is string to color from libutils which we just modified right so make sure you added an export for string to color or you can do use an npm package for this it really doesn't matter and let's do data dot from question mark name or an empty string like that And then let's go ahead and give this a class name of FlexGap2Padding2RoundedMD hoverBGWhite5 Let's render a paragraph here and inside of Here I'm going to render a timestamp, so we need to install npm install datefns.
So make sure you add that to your project. Let's go ahead and let's import format from datefns. And inside of the paragraph, we're going to format data timestamp in a format of hhmm and let's add this comma in a proper place right here let's give this a class name of text small and text wide slash 40 and then let's create a div with a class name flex flex wrap items baseline gap one and grow and then create a paragraph here with a class name of text small font semi-bold and white space no wrap then create a span which is going to render data from name. And let's go ahead and let's give this a class name of truncate and let's give it a style color color like that and then outside of that paragraph add a new one with a class name of text small and break all and inside data dot message like this great and now let's go ahead and try this out so start your stream Make sure that the chat is enabled and let me try and send the message here and there we go. You can see how we have a nice name generated from myself here.
You can see that the messages are here in real time and let me just go ahead and do one thing. Oh, so I added a space here for the format. So let's just join it like this. Great! So you can go ahead and test this with a user in another browser to see if it's working.
But actually you cannot test this because this is only in the creator dashboard. So we're going to test it later at the end. But there you go you created the chat functionality you have real time you have sockets and they reset after you refresh because you know streams are not here forever so it's a business decision for you whether you want to save the chat messages or not. In this tutorial I'm just gonna make them temporary during the live stream, right? Because that kind of is the purpose of these messages.
Great! So our chat is working great in real time and it also handles like long messages. You can see how they collapse here nicely. Great, great job.