So now what I want to do is create our recommended list of users here. So when users open this sidebar or when it's collapsed, I want to render a couple of user items here and the small label which reads recommended. So let's go ahead first and let's create an auth service which we're going to reuse throughout our project. So I'm going to keep my services inside of the lib folder here. Let's go ahead and create one called auth service like this.
So it's going to be a very simple service which is going to be using the current user from Clerk and then fetch the matching user inside of our database. So that way we're going to have the exact information we need which we might not hold in clerk but instead hold in our database. So let's go ahead and do import current user from clerk next.js. Let's go ahead and import our database and I'm going to change this to use slash lib. You don't have to but I just like it more this way.
And now let's export const getself. So that's gonna be the function which we're gonna use whenever we need to fetch the currently logged in user and also fetch what we have about that user in our own database. So first we're going to just get normal self from await current user. Then if there is no user or if there is no user username we are immediately gonna go ahead and throw a new error unauthorized and let me just not misspell this unauthorized like that and then we're gonna attempt to fetch the user from the database so wait database user find unique where Username matches self username like that and actually we don't even have to use the username what we can do is match the external user id and then use self.id because this self is the clerk object and clerk's ID is what we store in the external user ID. So this would be an even better idea because usernames can change but IDs cannot.
And if we cannot find that user it means we don't have them synchronized in our database so we're just gonna throw an error not found and then let's just return the user themselves. So this is what we're gonna reuse throughout our project now wherever we need to get the full user in combination with our database user and now that we have that service let's go ahead and create a new one called recommended service. So this is what is going to take care of recommending new users to the currently logged in or logged out user. So recommended-service. Let's go ahead and let's import the database and let's go ahead and import getself from the out service and I'm gonna change this imports to use lib.
I like it more that way. You didn't have to, of course. And now let's write export const getRecommended. Like this. And make sure this is an asynchronous function.
And first, we're going to do a very, very simple one. So we're just going to go ahead and write const users to be await db.user find many and we're going to order them by the latest creation like this and just return the users and we're gonna use this out service just a bit later because right now I just want to make it very simple what this does is just loads all the users inside of our database, right? And later we're going to use this getSelf() to detect whether we are logged in or not and if we are logged in we're gonna exclude ourselves from the list of recommended users right no point to recommend yourself to yourself right and if we're not logged in we're just going to load all users and we're also gonna later go even further here and make sure that none of these users have blocked each other because we're going to have blocking functionality as well and then we're going to do a similar service for followers But let's leave this recommended as it is right now. Just ignore that we're not using this for now. We're going to come back to that later.
And now let's go back inside of the app. Browse components sidebar index.tsx right here and below the toggle let's add a div with a class name space y4 padding top of 4 but on large devices padding top is going to be 0. So the reason we have padding top of 4 on mobile but not on 0 is because on mobile we are also not gonna have this toggle as you can see it's going to be hidden so we have to add a little bit of space there so it looks just a bit better on mobile devices and then in here I'm gonna add recommended which currently does not exist so let's go ahead and just quickly create that so go inside of the sidebar here and create a new file recommended.tsx and let's export const recommended and just return a div recommended like that and now we can go back here and import recommended from .slash recommended the same way we did with toggle and wrapper and now you should have this text which says recommended inside of your sidebar. Great and now we're going to use this sidebar to actually load the recommended users.
So let's go ahead and turn this into an asynchronous function ensure that you don't have accidentally have use client at the top because this needs to be a server component. And let's go ahead and give it a const recommended to be await getRecommended from our libRecommendedService like this. And now that we have these users, let's go ahead and pass them as data to the recommended component right here. So go back inside of here. And now what we have to do is modify the props so it can accept that.
So interface recommended props is going to accept data which is going to be a type of user which we can import from Prisma client. So the reason it has this user with the exact properties which we added in Prisma schema is because of that command npx prisma generate. So npx prisma db push synchronizes the remote database but npx prisma generates synchronizes your local development instance so you can do stuff like this. Great! So it's going to be an array of users from Prisma like that.
And then we can go ahead and match that here so recommended crops and extract the data and now you should no longer be having any errors when you use this recommended right here. Perfect! So, now what I want to do is I want to make sure that this recommended is also a client component because it's going to behave differently regarding, depending whether our sidebar is collapsed or not. So let's go ahead and extract collapsed from useSidebar. You can import that from store useSidebar.
I'm just going to separate them. And let's get the state and return the state. So we have the collapsed state here. And now let's add a property which is gonna decide whether to show the label of recommended or not so we are only going to show the label which says recommended if we are not collapsed and if we actually have something to show so if data.length is larger than 0 otherwise no point in even telling the user that there is something recommended for them, right? So inside of this div let's use that show label and let's go ahead and render a div with a class name of PL6 and margin bottom of 4 and then inside of that a paragraph which says recommended like that and class name is going to be text small text muted foreground and now if you go ahead and expand your screen you should be seeing the recommended right here and if you're not seeing it, it could be that you don't have the enough information in your database.
So what you can do is console.log data length. Sorry for that. So data length. And since this is a client component, it's going to be logged inside of our inspect element. You can see when I log and let me just close this and let's refresh there we go you can see that it says one right here so I have one element in my database and I can even confirm that by going inside of my terminal here besides my npm run I'm gonna do npx prisma studio like this and you can see that I have one user right here so ensure that you have a user in your database otherwise it's not going to work great so now that we have that so on mobile it's still hidden right This is only gonna be visible when we are not collapsed.
Even if we collapse here it's not gonna be visible. So this little label is only visible on desktop and if the sidebar is extended or not collapsed more precisely. All right and now what I want to do is below this show label create an unordered list with a class name space y2 and px of 2 and I want to go over data.map, get the individual user, and then I just want to go ahead and add a little div here, which will render the user username like this and give it a key of user ID. And now you should have a list right here, which says the username of your, well, I only have one user in my database so this is exactly what I have right here I am that user, you can see that it says something else that's what I last renamed my user when we were testing whether our webhooks for updating the user is working so my name here is something else. Perfect!
So we are successfully loading our users and now what I want to do is turn this into a component called UserItem. We're gonna need a couple of Shazian components to create that in full. So I'm going to shut down my Prisma Studio here and you can even shut down your npm run dev. You didn't have to but I'm going to do it. And let's do npx shazian ui at latest and let's add avatar which we're going to need to create our reusable component called user avatar and let's also go ahead and let's add a component called skeleton so we can start doing some loading states here because we can finally load some data So add this too and let's do npm run dev again refresh your localhost to ensure that everything is in sync and now I'm going to go ahead and replace this div with a user item like that and I'm just going to go ahead and give it a key of user.id so I don't forget that and we're gonna have an error that user item does not exist so let's go ahead and create that inside of sidebar create a new one useritem.psx and let's go ahead and mark this as use client and let's export const user item here and return a div user item so just a very simple component go back inside of the recommended and then you can import that from .slash userItem and you should no longer be having any errors.
Great! So now that we are here let's go ahead and pass some more props to this user item component. So I'm gonna pass in the username which is gonna be user username. I'm going to pass in the image URL which is gonna be user image URL and I'm also going to pass in the is alive property which for now we are going to control manually so we're going to change it from true and to false so leave it as true for now because it's going to be easier to see how it's supposed to look like when the user is live so it has a nice little indicator in the sidebar that they are currently streaming. So now we have to assign all of those props inside of the user items.
So let's create an interface user item props which accepts the username which is a string, the image URL which is a string, and isLive which is going to be a boolean like that. And let's also make it optional. And now we can go ahead and apply those props, the user item props, and extract the username, image URL, and isLive. And now you should no longer be having any errors when rendering this user item in your recommended component. And now inside of this user item, let's go ahead and let's import a couple of stuff which we're gonna need.
So we're gonna have to have usePathName from NextNavigation and we're also gonna have to import CN from libutils. We're also gonna need to have a button from components UI button let's also go ahead and import useSidebar from store useSidebar so we know how to show this user item whether it's collapsed or not and let's also go ahead and import skeleton which we just created so which we just added from chatc and ui so this five packages right here. And now inside of here, let's go ahead and get the path name. Whoops, path name. Path name is going to be used path name.
Then let's go ahead and let's extract collapsed from use sidebar. Let's get the state and let's return all state back. Let's go ahead and generate the href. So where are we going to redirect the user once they click on this user item? That's going to be to slash username like that.
And we're also going to add a boolean is active. So we indicate to the user whether they're currently seeing the stream of this user in the sidebar. So we're going to do a very simple check. If path name is equal to href. Just like that.
Great, and now we are ready to actually style this a bit. So let's go ahead and replace this div with a button, which we added an import right here. And what I want to do is give this a property as a child I want to give it a... It doesn't need a role actually it's already a button I want to give it a variant of ghost and I want to give it a class name which is going to be dynamic like this So let's write first the default classes, which is full width and height of 12. And then let's say if it is collapsed, in that case, we're going to justify center, otherwise we're going to justify start.
And if it's active, in that case, we're going to give it a different background color of BG accent like this great and now inside of the user item let's render a link from next link so make sure you add this and the href to that link is simply going to be the href which we defined right here in the constant and then inside let's go ahead and open a div and let's go ahead and write a class name, which again is gonna be dynamic depending on whether we are collapsed or not. So flex item center, pool width and gap X of four. And then if we are collapsed, let's go ahead and do justify center here as well, like that. Great, and what I wanna render inside is the user avatar, which is a component we're gonna have to create. So add a user avatar here, and let's go ahead and immediately pass in the image URL which is gonna be the image URL which we have from the props, username, which is gonna be username, and isLive isLive.
So we have all of these three from our props right here. And now let's go ahead and create this user avatar and we're not going to create that inside of the sidebar but rather we're going to create it inside of our components folder because we're going to reuse it throughout the project a lot of times so inside create user avatar.tsx like this and then let's go ahead and let's import cda and type variant props from class Variants Authority So we're going to use this package to create custom variants and sizes, which users can pass, well, developers more specifically can pass. So we have a nice developer experience here. Let's go ahead and let's import a CN from libutils. We're also going to need a skeleton and I'm going to use components for this one.
And let's also go ahead and import everything we need from at slash components UI avatar. And that's going to be the avatar, the avatar fallback and the avatar image. Great and now let's go ahead and let's just quickly create our user avatar here. User avatar and let's just return a div user avatar. Now go back inside of the user item and import the user avatar from add slash components user avatar.
And now you should be seeing a text which says user avatar like this and you can already see how this is a nice button and if you click it's a 404 error so just make sure you are here on the sidebar so now we're going to change this to actually display the avatar of the currently logged in user. What we have to do is allow this component to accept these three props here. So go back inside of the user avatar and what we're going to do is simply write an interface user avatar user avatar props like that and let's give it a username, which is a string. Let's give it an image URL, which is a string as well, is live, which is going to be an optional Boolean. And also we're going to have show badge, which is also going to be an optional Boolean.
So the badge is going to be the live badge on the avatar so depending on where we're using that sometimes I want the live so little red live badge to display and sometimes I'm not gonna want that to happen. So let's go ahead and just assign this user avatar props like that and I should no longer be having any errors for passing this props here. But before we continue developing this I actually want to create my avatar sizes here. Right? So that's why we imported this to CVA and type variant props.
So let's go ahead and write const avatar sizes is CVA. Let's leave the first default classes to be completely empty and then open an object and write variance size the default size of this avatar is going to be h8 and w8 and the large is going to be h14 and w14 And now let's just give it some default variants here. So I wanted to always use the default size. Whoops, my apologies. I wanted to always use the default size unless user or in our case developer specifically tells us that they want a larger version of this avatar.
So why am I doing this in this way? Why not just use any ternaries? Well, I got the inspiration from this, from the actual chat-cn components. So if you go in any of our components like button, you can see that that's what they do. In the button variants here, this is how they define variants.
And I think this is a much more structured way to do that rather than having a bunch of ternaries here and if else, if else comparison with the props, right? So this is a much more structured way to do it. You can see how complex it is here. But our version in UserAvatar now it's quite simple. So we have the variants.
We define the type of variant which we want the developer to be able to control which is the size. So they're gonna have a new prop now for this user avatar which is gonna be called size and inside they're gonna be able to either pass default or large and if they don't pass anything then it's just gonna fall back to default. So now we have to combine this avatar sizes with our existing props right here and the way you can do that is by adding extends so I'm going to do this in a new line so extends variant props and then passing type of avatar sizes like that So we have this variant props here which we imported and then we use the type of avatar sizes and now we don't have to manually define this size prop inside instead it's going to read from this and it's automatically going to do the TypeScript for us. So if you will add an extra large here, you know, something like this in the future, that's it. You don't have to change your props.
It's automatically gonna allow you, you can see now when I go to User Avatar here in my user item, right? If I add a size prop, you can see how it allows me to add default, large or extra large. So that's what we achieved right now. I'm going to remove extra large because we're going to not going to need it for this project, but I just want to show you how useful it is to do your variants for components in this way. Right?
Great. So now that we have this, let's go ahead and let's actually render all of these items. So username, image URL, isLive, showBadge and size. And you can see how we didn't have size defined in here but because we do extends right here that reads the size props that's how cool this is now let's go ahead and define a little boolean const can a show badge so we are only going to show the live badge if the user explicitly tells us to show the badge I mean the developer and if the user for which this avatar is being referred to is actually live and streaming otherwise we're not going to show that batch. So let's go ahead and give this div a class name of relative and inside let's add an avatar.
So we have avatar imported right here make sure you have avatar, avatar fallback and avatar image from components UI avatar here. Inside of here I want to add the avatar image and I want to give it a source of image URL and a class name of object cover and about this avatar here I want to give it a class name which is going to be dynamic so make sure you add cn from libutils here and let's go ahead and give it first a dynamic if is live in that case we're gonna add a little border around our avatar so you can already see it right here right you can see our little avatar here on you can see how it looks this is how it looks on desktop so we're gonna have a space for our username here but on mobile it's just like a little badge here so this is how it looks when the user is offline, right? But now if it is live what we're gonna do add a little ring to ringrose500 border and border background So now if I go ahead back inside of my inside of my recommended right here you can see that I set isLive to true and you can see how Now I have a nice little red border right here but if I change this to be isLive false you can see how the border disappears.
Nice! So let's go ahead and enable this so you can clearly see how you're developing. So go back inside of the user avatar here and now what I want to do is I want to apply those sizes which we talked about so we can do that quite easily now by just using this avatar sizes constant inside of the cn library so just passing the avatar sizes and inside of the object passing whatever size the user provided if they didn't provide anything it's gonna fall back to the default option great! And now if we fail to load an avatar image or if it's taking some time we also need an avatar fallback So let's go ahead and render the username, the first letter and then username, username length minus one which is the last letter like this. Great and now what we have to do is we have to go outside of the avatar and dynamically render that badge, right?
So canShowBadge Let's go ahead here and let's add a little div with a class name of absolute minus bottom minus three left one and a half transform, and minus translate, minus x 1.5. So these are just very specific values, which I found to be the perfect for putting the live badge right in the middle of the avatar. So we actually cannot see this text right now because we need to create another component called LiveBadge. So let's go ahead and just quickly do that. I know you can't see anything now.
I'm sorry for that. So let's go ahead and just quickly do that. I know you can't see anything now I'm sorry for that. So let's go ahead and just quickly create that so we can see that I maybe should have done this first. So inside of our components folder create a new component called a live badge .tsx.
It's gonna be very simple. This is just styling. So this live badge is going to actually show that little indicator So import CN from libutils Create a little interface here, LiveBadge.props and I want it to accept optional class name since we're gonna be reusing this you can see that I put it inside of our components folder sometimes we are also gonna want to have it styled a bit more differently so export const live badge here go ahead and assign live badge props and extract the class name and in here just return a div which is going to say live and go ahead and give this div a dynamic class name so we're gonna pass in the class name prop and above it I'm gonna go ahead and write our classes so that's gonna be BG Rose 500, text center padding of 0.5, PX of 1.5, whoops I think I switched my component, let me just go back to components live badge, my apologies for that. So PX 1.5, rounded, MD, uppercase, text, 10 pixels, border, border background, font, semi-bold and tracking wide, not tranking but tracking wide. Like that.
That's it. That's our live badge. And now you can go back inside of the user avatar here. And let's go ahead inside this user avatar. And inside of here, go ahead and render the live badge component from .slash live badge and you should be seeing the little live badge here or perhaps oh we cannot see it yes because we have to enable the prop for that so just make sure you import the live badge from .slash live badge and I'm going to change it to slash components and in order to see this badge what we have to do is go inside of the user item find the user avatar and give it a prop show badge and there we go You can see how it looks now.
It's huge, right? But later when we use it in combination with a larger avatar it's gonna look a little bit better. So for this case that's why I didn't even pass that prop in the UserItem component because we're not gonna need it. So you can just hide this for now. Just confirm that it actually works for you.
Right? Great. So now that we have our user avatar I want to go ahead and also create a skeleton for our user avatar so whenever we are loading something we have a nice little indicator of the appropriate size that this component is going to be when it loads. So let's go ahead and do interface user avatar skeleton props extends variant props whoops and the type of avatar size so let me just collapse extends like this and just add an empty object at the end avatar sizes like this and now let's do export const user avatar skeleton here Which is going to accept that size so user avatar Skeleton props like that and in here we can get the size so our skeleton is also going to be different depending on what size the developer passes and very simply just return a skeleton we have this imported from Chassis.nui and let's go ahead and give it a default of rounded full and then very simply just passing the avatar sizes here be the size component. That's it.
That's going to be our avatar. So this is what we're going to show while something is loading and in the end it's going to use the avatar component. Great! So if you're having any trouble with this, this was a lot of code, you can always visit my GitHub directly. This is the finished user avatar.
So you can just jump in here and see if something is missing, if you're having any problems. Now let's go back inside of our user item and what we can do now is besides this user avatar we can also render the username but we're only gonna render the username if we are not collapsed so make sure you put a little exclamation point here. If we are not collapsed in that case let's go ahead and render a paragraph username and let's go ahead and give a class name of Truncate so if it's too long it won't spoil the entire sidebar and I think already, there we go, you can see how I have a nice little username here. But on mobile it's hidden. Perfect.
And last thing we're gonna do again if we are not collapsed and if we are live, I mean this specific user, then we're gonna reuse that live badge which we just created and we used it inside of UserAvatar but now we use it separately here so that's why it was useful for us to create that inside of the components folder because we're gonna reuse it a lot of times so let's go ahead and give this LiveBatch a class name of MLAuto So we use that little class name prop which I was talking about because sometimes it's going to be useful for us to add some specific styles without having to do the whole you know div and then wrap that inside of this. This way is just simpler. And now if you expand there we go you have a nice little live badge nice little indicator perfect so it's very clear that this user in the sidebar is alive and if you go back inside of your recommended here and change is live to false you can see how it looks on mobile and you can also see how it looks on desktop. So now let's go ahead and create the user item skeleton because we just did the user avatar skeleton, right?
So now let's go ahead and create the user item so make sure you are in the user item here and go ahead and write export const user item skeleton go ahead and return a list element here and give it a class name of flex-items-center-gap-x4-px3-py2 So these are just some specific values that I know will look good. A lot of you sometimes write to me and tell me that I'm not exactly clearing up why I use specific class names. A big part of that is definitely my fault. I try to do better at that, at explaining my class names, but sometimes it's just trial and error. I already finished this project before, obviously, that's how I know what to write.
And some specific CSS values here I just tried a bunch of times until it looked good you know what sometimes it needed a little bit more space sometimes a little bit less space so that's how I get this specific values there there isn't any logic behind this right I just go ahead and try and I of course I try to be consistent with the overall spacings that I use throughout my project just in case you were wondering all right and now let's add our little skeleton component here and let's go ahead and we have this imported, great. So now let's give this skeleton a class name of minH32px and let's give it a minWidth of 32 pixels as well and rounded full. So this is actually representing our user avatar skeleton. Perhaps we can actually use the user avatar skeleton here. We're gonna try it out in a second.
So let's do it like this for now. And then we're gonna see if maybe we can actually reuse that from the actual user avatar. You can see that in here we have that exact skeleton. But I believe we're gonna need some specific sizes right here just for this edge case or it's just gonna look a bit better. So give this div below that a class name of flex1 and what this following skeleton is going to represent is the username right here so we're going to kind of give it some class name h6 like this so that's the size that I want to give it.
All right, so now we have our user item skeleton here and what we can do now is go inside of our recommended here and now we can finally create recommended skeleton which is going to be very simple because we have all the necessary skeletons. So just go ahead and return an unordered list here with a class name of Px2. And inside, we're gonna create a mock array. So we're gonna pretend like we are loading three users. And let's just forget this first index here.
My apologies, you have to open another parenthesis inside of the map here. So you can just skip the first parameter and go immediately to the index here and just render user item skeleton and make sure you import user item skeleton from user item like that and let's go ahead and simply give it a key of I like that so that's it we created our recommended skeleton very very fast And what we have to do now is we have to go ahead and add that to our sidebar skeleton right here because this is where we are actually loading our user. So go inside of sidebar index.psx right here and let's go ahead and do export const sidebar skeleton and I know you're kind of not even seeing the result of what we're typing now with the skeletons but I promise in just a second when we actually add the suspense and include this skeleton it's gonna be clear why we did it this way. So what I want to do now is do this wrapper and we know that that is an aside property, right? So let's go ahead and do that here.
So it's going to be aside and it's not going to be anything dynamic. So it's just going to be a class name of FixedLeft0 FlexFlexCol is going to have a width of 70 pixels automatically on mobile but on large it's gonna be a width of 60 So this skeleton is actually gonna fix that flickering which we have right now. You can see how for a second when I refresh here it loads the desktop mode, right? And only then it goes to the mobile mode. So this skeleton is gonna fix that because automatically it's gonna load only a small skeleton.
And then when it loads at that time our use effect with matching query with media query is already going to trigger so there's not going to be this flickering effect here. So on large the width is 60 we're gonna have age full, BG background, border R and border is specifically going to be 2d2e3 5. And let's also give it a z-index of 50. Also just to clear this up so this with 60 is larger than this with 70 because this is a specific value that I'm telling it exactly 70 pixels but this is 15 rem which is 240 pixels So the reason I'm doing this specifically here is because I'm not sure if Tailwind has the exact width which is 70 pixels. Maybe it has.
Maybe I could have just wrote like width 10 which I think then is 40 pixels. I don't know. Maybe it has but it's just simpler for me this way. So just if you're having any confusion why is it smaller and large it's not. It's bigger and large.
It's 240 pixels. And then what we can do inside is we can add the recommended skeleton. Like that. So make sure you import RecommendedSkeleton from Recommended and now what we can do is go back inside of our layout so inside of Browse, layout here where we render the sidebar and we're gonna wrap it in suspense from react so make sure you import the suspense from react here and then we're gonna give it a fallback to use whoops to use the sidebar skeleton like that and you can import sidebar skeleton from sidebar right here. So make sure you have that and now Let's go ahead and see if we seem to be having some errors here.
So let's just see what that is and why exactly is it happening. Let me just go ahead and debug this. I'll be back with you in a second and tell you what I found out. All right, this is what I'm gonna do. For now I'm just gonna leave it like this.
So obviously we are getting some error but it only happens when we have suspense. Oh now it doesn't happen when I expand on desktop it doesn't happen but on mobile oh so it only happens on mobile on desktop it seems to be fine okay so I'm gonna go ahead and debug this in the next module because I want to end this part for now it's already 40 minutes long and I'm gonna tell you what I found and how I debug this but for now just leave it as it is so add a suspense and a fallback of sidebar skeleton and I think you should be kind of seeing that What we can do is we can add a little delay for you to see this. So let's go inside of our lib, inside of the recommended service here. And you can add, just before you load the users, this. So Await new promise and timeout the resolve for 5000 milliseconds.
And this should make it load for 5 seconds. And there we go. You can see how for 5 seconds we have a nice little skeleton here and after 5 seconds it loads. So just make sure to remove this later bar. But on mobile it seems to work on mobile as well now.
I'm not exactly sure why we are getting that hydration error. It could be just cache. If I remove this from getRecommended so it loads instantly. Yeah, now it's super fast so we're not even seeing it. And here we're having an error.
Okay, that's interesting. That's what I'm going to debug in the next module. But for now, I hope I kind of made it clear what we did with this layout here. Make sure you have the suspense and the sidebar skeleton. And basically we built from the lowest component to the highest component so we can easily reuse those skeletons with recommended skeleton, then the user skeleton, all the way to the user avatar skeleton.
So that's the way we're gonna do all of our components. So all of them have reusable skeletons and then we can easily build larger skeleton layouts for better loading states. Great, so leave it as it is for now. Perhaps you're not even getting this error. Maybe this is something that just I'm having but I'm not sure.
I'm gonna go ahead and debug that in the next module. For now, great, great job! You finished loading recommended users here and after we finish this bug we're gonna go ahead and improve this recommended service a little bit so it shows more relevant users