So now let's go ahead and let's see if we actually need all of the items in our search page. So in our results we use the getSearch function and that function seems to include the entire stream document. So let's go ahead and let's visit our result card to see actually what we need. So we seem to need the user which we can include entirely like that seems to be fine like there's no need to there's not None of these items here are secret or anything. External user ID, that's completely fine.
They cannot do much with that. But our stream, we definitely should not send ingress ID, server URL, and the stream key. Those things should be private to the creator. So inside of my stream search service here in the getSearch() function I'm gonna go ahead and modify this. So instead of include user, so first I'm doing this inside of this if we have the user ID.
So in here, I'm going to change this to be select, like that, and we can take the whole user. And then for this, let's go ahead and take the ID of the stream to be true. Let's get the name of the stream to be true, isLive to be true, and thumbnail URL to be true. And I think that's everything we need. So we have the user entirely, we have the thumbnail URL now, we have the isLive property, we have the name property and oh yeah we need updated at so let's add that as well.
Updated at true. Like that and now I should be having an error inside of my search page results.csx because this data has some very specific items here and you can actually copy it from here yeah you can hover over the data and you can then copy id name thumbnail url and this if you want to right you're gonna see where I'm gonna paste that now and then go inside of the result card in the search folder so this specifically and we're gonna modify this so it's not a stream entirely instead this is what it's going to be so it's gonna be an object which has this properties and user which is user like this So make sure you extend the data to be an object which takes in an ID, name, thumbnail URL, isLive, updatedAt, and user. And then you can remove stream from Prisma Client. And let's see if I have any errors. And it seems like I don't have any errors inside any of my pages.
Let's confirm that my search is still working so if I search for Antonio here I should be getting my streams. Perfect! Great! We resolved that security issue and now we are finally ready to create the last part of this which is the community tab. So let's create that page so that when we click on it we no longer get this 404 error.
Let's go ahead and close everything here and let's go inside of our app folder dashboard username and the same way we created chat and keys we are now gonna create so instead of our sidebar we have navigation and in here for the community the route is a slash you username community So we have to create a folder called community inside of the username folder. Just make sure you're doing that inside of dashboard where you have the u prefix. So let's create the community folder and inside let's create page.vsx and let's create this community page and let's write a div community page like that and there we go, we no longer have a 404 error instead we are now selecting a community. Great! Let's go ahead and let's give this div a class name of padding 6 like this and inside let's create another div with a class name of margin-bottom of 4 and let's create an h1 element community settings like this and let's give this h1 element a class name of text to Excel and font bold like this.
There we go. So now our community settings title matches the same thing we have for example in our chat. Great. What we have to do now is we have to add a component called our data table. So we're going to go ahead and follow Shazam instructions on how to do that.
So go to Shazam UI. I mean, if this page changed in the future, you can always just visit my github and look at the exact components that I created. That's the great thing about ShadCN. We own the components, right? We don't depend on anyone else updating them.
So find the data table in ShadCN components here and let's go ahead and install everything we need. So first we need to add the basic table from Shazam UI. So make sure you add that. So let me go inside of the terminal here. I will shut down my Prisma Studio and do npx Shazam UI at latest add table like this.
And the next thing is we have to install 10-stack react table dependency all right let's go ahead and run that as well so 10-stack react table And now I think we are ready to start kind of building this. So we're going to create the columns data table and we already have the page. So let's go ahead and first create our column definitions, right? So what you can do is you can copy this exact component. You're going to see it now in a second.
So it's under number one, column definitions here. And you have this kind of example where you would store them. And let's go ahead and let's go inside of our community folder and create a new folder underscore components and inside create columns.cs like this and you can paste the entire thing inside so you can see it's a very simple code we import column depth from tanstack react table we define some random model right and then we just create some columns here so I'm gonna leave it like this for now obviously this is not what we need but let's leave it like this for now so it's easier for us to understand And now we have to create a data table component. So go to step 2 here and copy this entire data table component. So let's go and create a new file dataTable.psx and paste the entire thing inside and since we have this installed we have no errors and since we added the table we have no errors as well.
Great, so we have this entire component here, we have the columns and let's see if we need something else. Let's see what is step 3 here. So step 3 is to render the table and to fill the table we can use this mock function get data. So that's what I'm gonna do. I'm gonna go ahead and copy this.
And I'm gonna go inside of my community folder page.csx. I'm gonna go outside of here and I'm gonna add this mock data like this and I think I can import payment from components columns yeah because we filled the columns with this type payment right and we exported it so you can import it from here for now you know it doesn't matter great and now we have to await that data and we have to render this so let's do that Let's turn the community page to be into an asynchronous function. Let's get that data which we mocked in here. So it's just going to return this. And let's go ahead now and let's render our data table component like this.
So I'm gonna go ahead and just outside of this div which wraps our h1 element render the data table. So we have to import data table from components data table and we have to import columns from this import which we already have where we imported the payments like that and I think you can save this and you should now see a table here there we go we have a nice little table here and now what I want to do is I want to modify the columns and I want to modify the data so it actually fills our elements. So first we have to modify our block service so that it actually returns all the users we blocked. So let's go inside of lib and we have block service here. We have a function isBlockedByUser and we have a function blockUser and we have unblockUser.
So let's go all the way to the end here and let's create export const getBlockedUsers to be an asynchronous function. Let's get self using await getSelf() and I believe we already have this imported. If not, you can import it from auth service. Now let's define our blocked users to be await database block find many where blocker ID is self.id and let's include the blocked person so we can display their information and simply return blocked users like that. Great, that is our service.
So now we can go back inside of the app folder, dashboard, username, community page, and we no longer have to use this mock data. So I'm gonna go ahead and remove this mock data like that and I'm going to remove this type payment here for now and instead what I'm going to do is I'm going to write const blocked users to be await get blocked users from our lib block service like that and now we have to format that data so it fits the columns of our table so let's write const formatted data to be blocked users dot map get the individual block Spread all the information in the block and now let's get the user ID to be block blocked ID. Image URL to be block blocked image URL. Let's pass in the username to be block blocked username. And created at is going to be format not from that but let's go ahead and let's import format from date FNS so we're going to format new date block blocked created at like this and pass in day month and then year.
So in Europe we say we put days first if you're in the US go ahead and reverse this right. Okay so we have that and we should spread block not blocked. Great now we have the formatted data and let's now pass that to our data and of course we're going to get a little issue here because that's not what this column support. You can see that in here we have... Well, we actually have no results because we don't block anyone.
So here's what I recommend you do. Go ahead and block someone. So we can do that by going inside of my stream here. And how about we start streaming, right? So I think I'm actually streaming on another user now there we go so I'm gonna go in here in this user right here there are two users let me just refresh this So go inside of the one that is streaming and just block one user.
Let's go ahead and see. There we go. So I'm going to block this user. So now I should get disconnected from here. Okay.
And now inside of community here, I have one result here, you can see that it no longer says no results because I actually blocked someone. So you can confirm this. Well, first of all, this blocked user should no longer be seeing this. So 404, right, because that user is blocked now. And the second way is that you can do console.log formatted data here just to confirm that you actually have someone blocked So inside of your terminal when you run npm run dev you should be seeing a log here.
So I'm going to go ahead and refresh this page where I can load my blocked users and there we go. You can see that CodeWithAntonio is blocked and I have my formatted data. Great, so that is working. So now what I'm gonna do is I'm just gonna switch my logged in user so I can see my table here instead of having to switch to this browser. So I'm just gonna go ahead and jump in another user.
All right, so I am in the user that is currently streaming. I'm going inside of my dashboard and in here I blocked one user inside of my community. So now we have to go ahead and we have to modify the columns so that it actually fits our user. So let's go inside of components, columns here, and let's go ahead and modify the type. So the type is going to be blocked user, right?
And the information we need is ID which is a string, user ID which is a string, image URL which is a string, username which is a string, and createdAt which is also a string because we pass in the formatted data and this format returns a string so it's not a type of data it's a type of string and then we can set that to be our column definition and there we go we no longer have any errors in this data because now it matches what we're passing here but now we have to modify this accessor key So let's make the first one to be username and let's make the header be username as well. Then the second accessor key is gonna be created at and that's gonna say date blocked and the last one is well it's not gonna have anything It's gonna have an ID of actions, right? And in here, what we can do is just return a cell, which is very simply going to render a button from components UI button, not button props, sorry. So button, and we have to turn this from .ts to .tsx like this. And then we can import JSX elements inside.
So make sure you change columns and then you should be able to import button from components UI button and IntelliSense should not be playing around. Great and let's go ahead and write unblock in here like this in your page. Let's see if I have to change anything. What I'm going to do is I'm going to shut down npm run dev and run it again because I think by renaming from .ts to .tsx we might have caused some caching issues here. So let's just wait a second for this to come back.
And there we go. You can see that my blocked user is code with Antonio and I have the exact date where I blocked that user. So what I want to do now is I want to extend my data table to have pagination, to have search, and to be able to sort by username, to sort by the date we blocked user. So we want to make this table a bit more usable. So let's go ahead and back inside the data table here.
And we don't have to do this one which says cell formatting we're gonna do that later in actually we can actually do this we can actually do this okay okay how about we do go inside of columns here and how about we extend our first one here which is username so besides header add a cell property here get the row so we have the information about it and inside we're going to render a div with a class name flex item center and gap x4 and let's render the user avatar component from components user avatar so make sure you add this import let's give it the username to be a row.original.username and image URL is going to be a row.original.imageURL so let me collapse those elements so you can see like that and let's create a span row.original.username like this so now our cell should display the user's image like this so I can now see the image of the user that I've blocked perfect so that was step one cell formatting And now let's go ahead and do some row actions here. So let's see if we are going to need this. So row actions.
Okay, I'm going to leave this for later. Let's first do pagination. So in order to do pagination, we have to go inside of our data table. So let's prepare that. Go inside of your data table component.
So let me expand this so you can see exactly where I went. So inside of community components we have data table so make sure you are here and import get pagination row model from tan stack react table then inside of use react table you have to add get pagination row model like this So let me zoom out so you can see. So this is a key and this is the value. All right. And now we have to add pagination controls.
So let's go ahead and import button and let's place it. I'm going to place it just below the table like this and now we have to wrap the entire thing inside of a div first so let's do that so wrap the entire thing inside of a div go all the way to the bottom like that and then indent everything like this. All right and now we have to add this line of code right here so you can go ahead and copy you can see it's clearly highlighted right so just go ahead and copy it and we don't have to copy the last one div because that's the div which ends this div, right? So just this part. If it's easier you can just go into my github and copy the data table component.
We are copying this anyways. So we have to paste this all the way to the bottom just before this new div ends like this. Great! And I think this should already be appearing here. There we go!
We have previous and next and when we get to more than 10 blocked users this is going to work because it's only going to render the first 10. Great! So we did that. Now I want to add sorting so let's go ahead and import everything from React here at the top. Let's import sorting state.
Let's import sorted model. Like this. Let's add this sorting state to the function. So here, like this. Now let's go ahead inside of the use react table and let's add on sorting change set sorting get sorted row model and state sorting so these three elements like that I'm going to copy all of this and I'm going to paste it inside of the use react table so I added this I added this and I added this all right and now we have to make this header cells sortable because right now we do have the functionality but we don't have anything to trigger that functionality.
So go back inside of your columns here and change the header to now go ahead and extract the column like this and go ahead and immediately render a button you should already have that because we use it I believe yes make sure just you have the button and inside let's go ahead and give it a variant of ghost and let's give it an on click option an arrow function which calls column dot toggle sorting column get is sorted execute this function so let me zoom out so you can see getIsSorted is equal to ascending like that So write it exactly like this and inside the button we're gonna go ahead and render label username and the icon arrow up down from Lucid React so make sure you import this right here and let's go ahead and give this a class name of ML2 height of 4 and width of 4 and there we go. You can now click on this but since I only have one user there's no effect but if you had more users it's going to sort by username, right? Perfect. So now I also want to do that for date blocked.
So what we can do is we can just copy and paste this, I believe, and use it exactly as we expected inside of this header where it says date blocked. So let me see what did I copy exactly. So I'm just gonna copy the entire header and I'm gonna replace the header here like that. And I'm gonna change this label to date blocked. And I don't think we have to change anything in the onClick function.
This is still going to work as intended. Great, so we added our sorting and now I want to add filtering so we can search for a user. So we have to go back inside the DataTable component and we have to import ColumnFiltersState and GetFilteredRowModel and we have to import our input component so I'm gonna add that just below our button component here and now we have to add the state for our search so just below sorting I'm going to add the column filters so this can all go in one line let me zoom out so there we go so react to state with the default value of an empty array. Alright, let's see what do we have to do next. So now, instead of the useReact table, we have to add onColumnFiltersChange.
So let's add that. OnColumnFiltersChange is going to be setColumnFilters but we don't execute that. And then we add getFilteredRowModel so add this and we also add column filters inside of this state. Great! And now we have to go ahead and add this input here right so this is how I'm gonna do it I'm gonna go ahead inside of here at the top I'm gonna open a new div with the class name flex item center and py of 4 and then I'm going to render that input component and let's go ahead and give it a placeholder of filter users let's go ahead and give it a value of open parenthesis, table, get column username, because that's what we're going to be searching ?.getFilterValue like this as string let me zoom out so you can see it in one line and outside of the parenthesis add an optional operator to be an empty string like this then let's add onChange function here which is going to call an event and then it's going to call table.getColumn username ?.setFilterValue is going to call table get column username question mark dot set filter value, event target value like that.
So if you want to, you can also write this in one line like this. Let me zoom out so you can see and let me just collapse this like this so this can be one line so we immediately return this make sure you do it like this great and I also want to add one class name to the input. MaxWidthSmall like that and I believe that is everything we need to add. Yeah everything we can now shut this down and there we go now if I type something else no results but if I type Antonio we have the user here. Perfect!
So what we have to create now is the actual unblock button because currently what we have is just a dummy button. So let's go ahead inside of components and create a new file. UnblockButton.csx. Let's mark this as useClient. Let's create an interface unblockButtonProps let's give it a userId of string let's export const unblockButton Let's go ahead and destructure the props, so unblock button props.
Let's destructure user ID. Let's go ahead and let's add useTransition from React. And in here we can extract isPending and startTransition. Let's go ahead and create const on click to call start transition and it's going to call our server action on unblock from actions block and we already have that when we were playing around with our block service. So just make sure that inside of your actions folder block you have exported on unblock.
So let's go ahead here and pass in the user ID. Let's chain dot then. We can get the result here and let's call toast from sonar so make sure you import that. So toast dot success and let's add backticks and let's write user and now let's get result.blocked.username unblocked and let's chain.catch to simply render toast.error something went wrong great And now we can return our button from Components UI buttons, so make sure you add this import. Whoops.
Alright. Let's render unblocked text. And let's give it disabled if it's pending. Let's give it on click. On click.
Let's give it variant of link. Let's give it size of small and class name of text blue 500 and pool width and now let's go back inside of our columns and let's go ahead and extract row from this cell and let's render unblock button so make sure you import that from slash unblock button and pass in the user ID to be row original user ID like this great and let's try it out now So it seems like I'm having some error here. Let's see if that's something temporary. Looks like it is. So if I go ahead and click unblock, it's pending, it's pending, and there we go!
The user is unblocked. Now I'm going to go ahead and log in with that user here. And as you can see, I am logged in as CodeWithAntonio and I can look at the other Antonio's stream because I am unblocked. Perfect. And let's just confirm one more time if I go into my creator dashboard now and so this is me streaming like this is the streamer I'm going to block CodeWithAntonio again.
CodeWithAntonio is disconnected And now if I go ahead out and I can still see the user here, that's fine. Because when we refresh, we're not gonna be able to see them. So if I click here, now I should just get an error. There we go. We cannot connect to this stream and then they try to refresh and there we go.
We cannot see them in the homepage, we cannot see them in the recommended, let's try searching. And we cannot see them here either, we can only see ourselves. Great! And let's try and unblock ourselves now. So I'm going to click unblock here and let's see if that is working as well.
When I refresh here, there we go. I am unblocked. Great, great job. You finished the entire tutorial. Absolutely everything.
Amazing job. All that's left is to deploy the application which will just take five to ten minutes don't worry the hard part is officially over. I just want to confirm one couple of things here so inside of our actions block I don't think we need to do this on unblock. So just remove this line, it's not needed. Instead, what you can do is get self from await get self and you should revalidate your community tab so you slash self dot username slash community so this is what you should do here so just for good luck I'm gonna try again so in here I'm joining again as a stream and I'm gonna go ahead and block myself again then this user is immediately disconnected I'm going inside of my community let me refresh I will unblock the user and this should revalidate.
There we go. Great! It's working great. And one last thing that I think I forgot to tell you. Inside of your app folder API webhooks clerk we have a case when user deletes their entire profile.
And what I want to do here is I want to ensure that if they have a running stream, we reset that. So I'm going to simply add await reset ingresses from actions ingress, so you can import that from here reset ingresses payload data id so in case you know the stream is running and the account gets deleted it's an edge case you know there's a very low chance of this happening but we're going to end the ingress for that user so that they don't spam our application. Great great job amazing job all that's left is to deploy.