Build and Deploy a Fullstack Reddit Clone: Next.js 13, React, Tailwind, Auth, Prisma, MySQL

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey I'm Josh I'm a web developer from Germany and this is what we are gonna build together in this single video and we are going to leverage the full power of next js13 with all the new stuff it has to build this this is a Reddit clone with so many interesting Technologies in it for example as we scroll down the feed you can see more posts are fetched as we scroll that is called infinite scrolling that's one of the many things you're going to learn to do in this video because what I like to do is not only build local apps but production grade apps that you can actually have users use so I send this to my Discord and everyone was able to upload posts to create their own communities so for example this is a subscriber who created his own Community here on my app as the basis for all of this we're gonna use react with typescript and for The Styling we're going to use one of the most loved libraries at the moment which I've used for the last half a year here it's been amazing to use based on Tailwind It's A pre-styled Accessible fully accessible component library from chat CN if you don't know it yet you're gonna learn to love it because it's I think in my opinion the best UI Library out there and you're gonna learn how to use it in this video and now let's visit a detailed post for example let's scroll all the way down and visit let's go for the react post right here if I click this you can see there I'm gonna rewind this so you can see this in them insert a frame hold here you can see we loaded the information right here there's loading States for the comments and for the votes because we're implementing a very specific really nice caching methodology we're going to implement Advanced caching so only the most necessary information is immediately loaded from redis when you load this page and all the other stuff is streamed in this is gonna be a really cool part of the application and this would not have possible this entire video without a sponsor so for this streaming and advanced caching functionality we're gonna use Upstate redis who are currently sponsoring this video that is what makes this streaming Behavior right here possible where the comments and everything else like the votes are streamed in okay enough of that let's sign in we've got an amazing authentication flow with modals that pop up that we can close and if we reload the page there's also a whole page for this but normally this just pops up as a model because we're gonna make use of intercepting and parallel routes in next.js let's click the Google button and log in with our Google account that's what we're going to use for login and then we can see a customized feed so right now I'm only seeing the posts of these subreddits that I am following and I can interact with them I can upload them I can download them I can go into one let's see this one has a bunch of comments for example I can write a comment my comment hit post that's going to submit my comment right here into the comment section at the very bottom we can upvote download and reply to other comments for example let's say nice reply hit post doing that we can reply to that comment it's gonna pop up right here and then what we can also do right now my username it looks a bit weird we don't want it to look weird so we can also go ahead and change the username right here under settings we can just say the Josh because I'm the Josh and then we can hit save name it's going to give us a beautiful toast notification that we can swipe away your username has been updated because we are properly handling everything success States error States it's properly being handled also on the server also on the client and I paid real attention so this is a production grade application that we're building here together one of the coolest features I haven't even shown you yet and that is the creation of a post so for example we want want to create a post in or react subreddit and we can hit create posts on the subreddit enter or pause the title like that and in here we've got a really clean what you see is what you get editor for example we can post images just like in the normal Reddit I'm going to choose this image it's going to upload the image you can see that state in the editor right here and then whenever it's done it's in the cloud this file has been uploaded it's available on the internet and in our editor and we can insert a caption and what we can also do is insert the links so let's say link and let's post a link to my personal GitHub for example let's insert the link and it's going to make a pre-fetch request to that link and actually show the proper link we can hit post we can see a beautiful notification your post has been submitted and here it is at the very top from the Josh or username my post title and that's up for that because it's our post and it just works really nice I'm very happy to build this application with you I know this is a long video because you we're going to go very in depth I want you to know how to build proper production grade applications and this is how you do it and I say let's get started awesome let's have a look at the game plan that we're going to be following to get this started and also to deploy it so first off we're going to start with start with some basic setup that is going to involve some styling even though most of this is already done for you so CSS will not be a problem then we're gonna move on to authentication very crucial step of this project we're going to do it all together and you're gonna see authentication is surprisingly easy you know what's super important as well creating and joining subreddits that's going to be our third step then we're going to be able to create posts very important afterwards we're going to display posts in a feed and here we're going to differentiate between a regular feed for non-logged in users so anyone can access the feed and also we're gonna have a different feed for subreddits that people are following if you are logged in and we're gonna optimize this with redis what I already explained that it's going to be a very cool part of this video then we're gonna move on to be able to vote for posts upvote download you know how it is on Reddit as well in Step 7 we're going to create comments very important piece right if you can't comment on posts then there is no real interaction and of course we want that interaction so we're gonna have comments and you can also vote up and down for these comments lastly eighth step is gonna be the search bar in the actual content of the website we're going to be able to search for subreddits that we want to post in that we want to participate in and then lastly as step 9 we're going to deploy it together on their cell that is going to be the final thing we're gonna do so you can actually put it on your portfolio share it with friends and actually use it how it's meant to be used awesome that's gonna be the game plan it's gonna be nine steps it sounds more than it really is and we're gonna have a lot of fun on the way and we're gonna learn a lot of really cool web dev Concepts on the way as well great let's get this started and to get started I'm going to open my CMD and navigate to the desktop this is going to be the directory where I want to host this project for me that's going to be the desktop and anywhere on your machine works here and in here I'm gonna say git clone and we're gonna do git clone because if you get lost anywhere during this whole project I have a full GitHub repository for you and not only does this contain all the finished code of the actual project that is completely done but it also contains something called and why doesn't that show up here um the starter code Branch right here if we switch over to starter code you can see I've already set this up for you so we can just copy this branch and everything will be done for you it's essentially just a very basic next.js setup but with some styling for example inside of it you can see if we navigated the Styles there's some very basic styling done and also in the Tailwind config so we don't have to spend our time on that because that shouldn't be the focus of this video this is about Concepts about learning actual Concepts and stuff instead of having to waste your time with the styling so that is why I think this makes a lot of sense so we don't have to worry about this and to get started click here on the code button on GitHub and copy this GitHub URL that's this is what we're going to use to clone this repository let me move this over to my right side again and we are going to clone a branch so we're going to type hyphen B and then the branch name this is going to be starter hyphen code this is the branch name of the repository that we want to copy and then lastly we're going to paste the URL we have just copied from the green GitHub button the github.com my name slash breaded dot get because spread it is going to be the name of the project now I would recommend just hitting enter I'm gonna name mine I'm gonna name it's bread breaded 2 because Reddit is the original folder I already have on my desktop and then hit enter this is going to clone the starter code Branch to your local machine and now we can CD internet for example CD breaded Dash 2 or if you didn't change it at all this would just be um breaded and I can zoom in so you can see this easier let's navigate in that repository and then say code Dot and what code dot is going to do is it's going to open up just this folder inside of our visual code Studio at least that's what I like to do of course you don't have to follow along in Visual Studio code and here we are in the starter code great and I say we quickly walk through what this even is and what the starter code does for you that the regular nexjs repository or regular create next app wouldn't do for you first off we have some database setup already done and there's nothing special about this as you're gonna see and I'm gonna explain every step of the way and the only thing we did is basically initialize a new Prisma project they State how you can do that in your in their documentation or you can just type um it's like npx Prisma in it if you're in a completely new project that's basically what I've already done so we don't need to do that and also I've added the documentation code provided by next auth for the database so I did not come up with this if I did I would have shared it with you and built it with you but this is just this part right here the um account the session and the user in the schema.prisma is just copied from next auth what we're going to use for authentication so there's nothing original about it and so there's no point in writing that or self um there's a fav icon already included so this just works out of the box and looks great and besides that there is only one component I have already done and that is the button now I haven't installed the dependencies yet we can quickly do that in the background actually let's type yarn or npm install whichever package manager you prefer hit enter and that's just going to install the dependencies in the background while I quickly explain this button component so the reason I'm not going to build this button component together with you or why I don't think it makes too much sense building this together is because essentially it is just copy and pasted as well we can go to Shared cnui this is a UI library and as you can see the UI Library we're using for this button is a beautiful button by the way and provides an installation and installing this button is just as simple as typing this Command right here and that is going to install the button component inside of your app it's npx chat cn-ui add button and this is going to set up all this code for you with one thing missing and that is going to be the is loading this is what I have personally added so we can later on easily add a loading state to this button but besides that this button is just copy and paste it as well and having to write out all the CSS is just time we can save then there's one hook again this is just copy and paste it from a website called use hooks we're gonna use the circulator it's called use on click outside and you know if I just copy and paste code there's no real point in walking through it together but it plays a very minor role in our project so I don't think it makes sense writing this out together as well then I've set up the database client this is just copy and paste it as well from a previous project essentially this just ensures that while we're on local development we are not instantiating multiple Prisma clients but instead we're using a cached one then utils utils are very simple we're gonna reuse them across our whole application the main thing here is we don't want to type all this out it's just annoying there's no learning benefit in typing all that out what I'm showing you right now and that is the reason why I've included all this in the starter code you can get into it if you want but it's about learning these interesting Concepts not about stupidly typing out this stuff for displaying when a post has happened right there's no need to get into that some styles for example for the editor we're gonna get an Intel later on very very cool part of this app by the way we're going to use that to create posts and some Global styles that there's no point writing yourself you already know this and that is already basically all there is in the starter code there's one more thing that is some types I'm gonna get into that when we get to the editor again and some Tailwind configs set up that we don't need to type ourselves either mostly styling and unimportant stuff in the starter code I've made sure to not miss out on anything important in the starter code we're going to do everything important together and but again we don't want to waste our time on these very very basics okay and now let's actually get started with this project and to find out what the first step is going to be let's head over again to our game plan and start with a very basic setup great let's switch over to our app and I've got the original project open here on my right hand monitor so I don't miss anything and let's get started with the basic setup in or layout this layout is a file that nexjs creates for us this will always exist in your application and it is under the source directory app and then layout.tsx even if you delete it next yes it's gonna is gonna create it again for you so there's literally no way to get rid of it and as you can see there's some metadata already in here for example the page title as breaded and then a description I've already created in here and besides that only an import of the styles that we're going to use first thing we want to do is use a custom font because the default font looks kind of bad so let's say const enter that is going to be the font we're using for this app is going to be equal to and I'm going to disable GitHub co-pilot it's going to be equal to enter and we get this inter function from importing it let's say import enter and this comes from next slash font slash Google we can just import the enter font like that invoke it and pass it one option and that is going to be subsets and this subsets is going to take an array and in this array we're going to pass you can see we can we can pass a bunch of stuff in here by the way if you're wondering how I open this menu it's control and spacebar and then let's put Latin in here as the font and this we still need to include in our page so this is going to go into the HTML as a class name we're going to do that to use our custom font this is going to be a in currently prevent in curly braces I think it's called and in here we're going to call a function that is CN that already comes from the pre-configured utilities that are included in this starter code if we take a look at these utils let's quickly talk about what this does so the Tailwind merge basically combines Tailwind classes optimally together so say we passed a margin y of two and also a margin X and margin X of 2 right if we passed these two into Tailwind merge then Tailwind merge is pretty smart and we'll combine these into margin two because this right here I can make this a bit larger actually that's a bit too large um these two mean the same thing as margin two so Tailwind merge optimizes or Tailwind code and then clsx is for conditional class names so we can conditionally render alt class amps and combine multiple class stems check out what this looks like as the class name that is always going to be applied to this HTML we're going to import background white a text Dash slate Dash 900 for dark text anti-aliased and then lastly a light theme that we're forcing and now to also merge a second class into this class name we can input a comma and then the inter dot class name so we are appending the inter class name to this class Name by using this CN utility function um it's going to get very clear also later on when we get more into what this MCN utility function does it's a very nice helper to combine multiple class names together and we're going to use it in a lot of places across our application and it's just three lines of code so it's very very convenient to use great and then in the body tag we're also going to apply some basic styling for example a minimum height of screen so we're at least 100 VH and for some reason I can't hover over this right now okay but this is going to apply a minimum height of 100 view height a padding top of 12 to offset a nav bar height that we're going to create later a background slate of 50 and also anti-aliased there we go great okay now the question is what's going to be the first thing that we're going to get started with and it probably makes sense to get started with the navbar first so the navbar will exist be above the children right here and we want to render out the actual children inside of a div to apply some sighting to them as well for example we're going to apply a container class name this comes from Tailwind as well and for some reason I can't hover over this stuff right now but it's fine normally when you hover over these it shows you the underlying classes that are applied to them for some reason that doesn't work okay then we're going to apply a Max width of 7xl so it can't go all the way to the borders an MX of Auto so this is going to be centered on the screen a height of full and a padding top of 12 and we're gonna wrap our children inside of these Styles great now let's quickly comment out the nav bar because that literally does not exist yet and let's start up or local development server I'm going to say yarn Dev or npm run Dev hit enter that's going to Startup or development server and we can take a look at what that looks like if we navigate to localhost 3000 and let it load here for a hot second great this is our home page it shows up right here works perfectly let's move this into a side-by-side View and let's give the editor more space than the actual page all right and that does not seem to be in a side-by-side View like that okay great let's comment back in the nav bar and that's going to be the first thing that we're gonna work on the navbar so to create that I've already created the main photo structure for you it goes into the components folder so source and then we've got app components hooks lib for um and the lip folder is for preparing stuff from libraries to be used in your application you're going to see that when we create the files there Styles and types and this is going to go into components this is going to be our navbar DOT TSX and inside of the nav bar let's start working on it I have got an actually no that doesn't matter right now let's say cons navbar is going to be equal to a function and we want to make this function asynchronous so that we can actually get the session for the user first off let's just return a regular div and that needs to say return so whenever we render out the navbar this div is going to get returned and what that does for us is we can already import it in or layout.tsx and you know what when you can't see the automatic Imports what you can do is and press Ctrl shift and P and click developer reload window that's going to reload your vs code instance and then normally the automatic Imports will work again after that and hopefully hovering over this stuff will not work and it does great so it's kind of like a reset for vs code but it doesn't seem to work on the navbar so let's import it manually let's say import navbar from and then use a relative path that we have defined in our TS config that is going to be add slash components slash navbar and that does not work oh and of course that does not work normally I always use a snippet to do this let's export default navbar from this file this could not have worked and now it does okay um I was a bit too fast with the asynchronous behavior on the nav but I realized let's just build out the regular nav bar and then work um worry about all the auth stuff in the Second Step because that is after all step two and not step one so let's give some life to the navbar and first off we're gonna apply some class names to it class name is gonna be fixed at the very first stiff by the way this is gonna where we are up Jesus this is where we are going to apply this class name um fixed top zero then we want an inset X of zero so that is going to be you know left and right the H is going to be fit for a height matching the content of the actual nav nav bar so it's not too high and let's move this into full screen just for now the background is going to be zinc 100 we're gonna apply a border to the bottom then a border color which is going to be border zinc 300. as that index of 10 so it sticks out above the other content and then and a padding y of two then one more div inside of this diff and this is gonna be class name container this is going to automatically apply some margin and padding and width very very handy class name and by the way in this container class we have extended in our tailwind.config.js as you can see right here so we have applied some custom siding to The Container so it looks a bit better and we can just use that with a one container keyword the max width is going to be 7xl with a height of full that's going to fill out the height of the div as a parent an MX of Auto we want a flex an item stash Center for vertical alignment and also a justify Dash between for horizontal spacing out and lastly a gap of two to provide some gap for the responsive View awesome first thing we're going to create in here is going to be the application logo and what should happen when you click the logo in the nav bar like what you expect to happen on other websites when you click the logo you expect to go to the home page which makes a lot of sense and to achieve that when you click the logo we go back to the homepage of the app we're going to use a link component that is presented to us by nexjs it's kind of like a wrapper around an HTML a tag with a difference of routing only happening client-side without a hard refresh if we press this link component this is going to go to the home page so to slash and we're also going to apply a little class name here or flex gap2 and items Dash Center for vertical alignment awesome now in here we're gonna render out a P tag that is going to contain the text of our app this gets a class name of hidden a text zinc oops of 700 a text of small we don't want this to be too obnoxious a font of medium to make it a bit more visible this is the font weight and on medium devices and upwards we want to actually display this so we're only hiding this for the mobile view and this is going to say bread it this is going to contain or um oops or application name and if we take a look at what we have just created let's go to Reddit we can see there is a nav bar with our breaded name on it amazing let's move this into a side by side once again and sometimes this doesn't really work so let's just do it like that awesome and we also want a logo for our application right and the question is how do we render out that logo as a SVG we don't want it as a PNG because that takes a while to load we want that as an SVG and to create that let's create a new component that is going to be called icons dot TSX and inside of this icons.tsx we are going to have our logo and I should have included the icons in the starter code and but we're just going to copy paste from the GitHub repository um so from the icons component we're going to export the most important reused icons across our whole app we can do that very simply actually by saying export const icons it's going to be just an object and as the logo we want a custom SVG but we also want to be able to pass custom properties wherever we actually render out this logo and to do that we can accept props into this icon and these are going to be of type Lucid props Lucid react is a pre-installed dependency in the starter code and it is an icon Library we can go to lucid.f to see what exactly it is all about and it's just an icon library that we're reusing across our whole app that's why it's pre-installed and then inside of here we can render out the actual SVG however I think it's a better approach to not write on the SVG that doesn't make too much sense but instead let's quickly navigate to the GitHub repository over here and then go to the source components and you'll see in a second why we're not writing this other self and then into the icons this is the reason we're not writing this outer self there's no point we can just grab the SVG icon as the logo from the Icons copy that and paste it over here into our project let's close that we can see the SVG is right in here and we are spreading in the props that we are receiving wherever we are rendering the icon if we want to we don't have to pass that and that are the icons awesome so we can actually now use this logo icon in or navbar right above the text as icons that we import from dot slash icons from the components Dot and we can see there's a logo property on there now because we've just created that this is going to get a class name of h-8 for a height of 8 then a width of 8 and also on small devices and up we want a height of six and also on small devices now we want a width of six so we're just making it a bit smaller and then on small devices it's a bit larger and we want to close this icon off let's save that let's go right here and we can see there is the logo and on large devices also the name appears right beside our logo awesome let's give this a bit less space actually let's move this into full view so you can actually see the whole class M because they are a bit lengthy at times then we are going to implement a search bar right here in the nav bar but before we Implement that search bar a lot is going to happen we can't just implement this without having added any of the functionality that is why the search bar is right down here in Step 8 because in order for a search bar to work we already need subreddits and so on to filter and we don't have them yet so I'm just gonna mark it out for now and leave it in here but we're not gonna create it just yet way more important is the auth right we want to implement authentication already in step two up here and in the nav bar there's going to be the button to do it and we are going to render out a sign in Link in or nav bar this is going to be the same link component from xjs and this is going to lead to the slash sine Dash in path with a class name off and now there's going to be a cool concept if you're not familiar with my previous projects we can now import the button variance and invoke them now what the hell just happened these button variants if I click them these come from the button component that was pre-installed in the starter code and what this does is normally these button variants are only applied to the button element right but in our case we want to render out a link component instead of a button but we still want it to look like the button so what we can do is just import these button variants from here these up here and what this is going to do is it's going to apply all the standard class names to our link component and then also the default variant so if I save that and we put some text into the link like sign in for example we're gonna see what that looks like if I navigate over here it looks like the button it looks really nice but it's not in fact a button it's a link component and that's how we achieve that by importing the button variants and just invoking that function awesome so now we can navigate to the sign in page if we click this button but of course this page does not exist yet and it makes sense to create that next let's move this over again like that and the way we create the sign in page is going to be as a folder that we can navigate to inside of or app router so instead of the inside of the app router let's create a new folder and wrap this in parentheses called auth if you want a really detailed rundown of how this app router routing works I'm going to link an amazing resource where I went really in depth in the description on routing SEO and so on for this probably only watch the routing section for now essentially if we create a folder that's going to be reflected in the URL structure but if we use these parentheses we're opting out of that behavior so the in parenthesis auth is not going to end up as the URL for app but instead it's just for or organization so that we can now create folders in here that actually end up in the URL so for example we want the sign in route therefore we are creating a sign in folder this is actually going to be in the URL under you know slash sign in because this won't show up and then to create the component that shows up when you navigate to the sign in route that is where we create a page.tsx it's important that you name it page.tsx because it's enforced by next.js it needs to be named that way and inside of the page let's say f c and hit tab now what the hell just happened there that's a really handy thing it's a functional component snippet that I have created and if you go to your local settings where are they file no edit uh right here so it's file then preferences and then it is configure user Snippets navigate to your typescript react.json and you're gonna see my snippet right here I'm gonna put the code in the description you don't have to um write this out you can just copy and paste it into this file and then you've got yourself a really handy um snippet that you can use just type FC hit Tab and that's going to create this functional component snippet for you um as well really really handy stuff um however we're not going to receive any props in here so we can remove that just leave it as a functional component and then inside of this page let's apply a class name to the main div and that is going to be absolute let's close out of this and inset Dash zero and let's open this up in a side-by-side view as well great inside of here one more div and this div is going to get a class name of height 4 a maximum width of 2XL and I just noticed the page disappeared if this is not an actual side by side that kind of sucks there we go MX width of 2XL and MX of Auto a flex Flex Dash call and items Dash Center a justify Dash Center for horizontal now this is for vertical alignment now because we're using Flex comb so it's the other way around a gap of 20 and that's it that's the whole class name for this div let's move that into the next line and give it a bit more space so you can actually see the entire thing and inside of here you're gonna render out a link component as well that is going to take us back to the home page and this is important for a good user experience because if they ended up on this page on accident or want to navigate back they should have the possibility to do so therefore we're rendering out this link with a class name of um and now we're going to use our conditional class name combining function again from the utils we're going to import that from add lib utils we have created it there and this is going to say button variants again because we want it to look like the button and if we pass an object to the button variants we can also Define a certain variant that we want this to look like if I put a space here and type ghost what does this mean we have just passed the ghost variant to the button variance so what's going to happen is it's all like in every case it's going to apply these first class names because they are not conditional but then it's also going to apply the ghost class names right here because that's the variant we have specified so it's going to apply all these class names as well and if I just put some text here like home you're going to see what it looks like if I move this over and read out the page that should show up right here you can see it's a very subtle animation but it looks good and it's accessible by pressing tab um it's it looks like the button it looks very very nice that's what we achieved with these button variants right here and we also want to apply a bit more class names to that so after closing this function we're gonna pass a string as well and this is going to be self Dash start and a Dash a a hyphen mt-20 for giving it a negative margin on the top and then that's it safe that's almost or entire component for the sign up done the last thing we want to do is actually render the sign in component or did I say sign up and then sign in so this is the sign in page and if you want to render out the sign in component that is actually going to allow us to sign in now this component doesn't exist yet and we um are informed of that by this error right here so let's create the sign in component sign in dot TSX and or um again use the FC snippet makes it very very easy however in this case again we're not going to pass any props to the sign and component so we can pretty much get rid of most of this and are left with just a function that we're exporting as a default as a regular react component great at the root level div inside of the sign in component let's give this class names container mx-auto Flex a width of full Flex Dash call justify Dash Center is base y of six on small devices and up we want a width off and now a custom value of 400 pixels you're gonna see why we're applying these 400 pixels later um hint hint it's because we're also going to render this component out in a model and then that's it okay that's good that's all the class names done as the second level div in here we want to apply a calcium of flex Flex Dash coal space Y2 and text Dash Center inside of here we're gonna render out our logo again that we import from the icons component so icons dot logo with a class name of mx-auto a height of 6 and a width of 6 to make it rather small and subtle and below this we're gonna render out an H1 that is going to get a class name of text Dash 2XL font semi bold and tracking Dash tie it saying welcome back because chances are if the user signs in again then they are already um they have already signed up you know so we can greet them back and then below this we're going to render out a P tag with a class name of text small maximum width of XS for extra small that is something I have configured myself in the Tailwind config um if we navigate to the Tailwind config right here we can take look at the XS that should be wait did I not do that oh I think that was somewhere else never mind that might just work out of the box actually it's a 20 Ram maximum width all right anyways and then a MX of Auto great and inside of this p-tech we're going to say by continuing you are setting up a Reddit account and agree to or user agreement and privacy policy I just copied this from the actual Reddit page so we can just copy paste it here awesome let's save this and let's render this out side by side first import the sign in component in our page.tsx and we can close everything that we're not using right now just doesn't make it easier and we can already see this looks pretty nice welcome back by continuing you're setting up a account and let's move this into an actual split screen there we go awesome this looks really good already and if we want to actually log in we need some component to do that so let's say for example um sign in form we want a form or a button that allows the user to sign in I'm gonna mark this for now let's quickly finish up this component with ap tag below that sign-in form that we're gonna do in a second here with a class name of padding X of 8. by the way we are creating this P tag with two closing divs below that that's where this goes with a text Center a text small a text um zinc 700 and inside of here we're going to say new to Reddit question mark and then a JavaScript space so we can ensure there's always a space here because we're gonna put some text right next to it instead of a link component again this link component is going to link to the slash oops to the slash sign up page so if they don't have an account yet the users then they're going to get forwarded forwarded to the sign up page with a class name of hover text zinc we can do like 800 for example a text small and let's quickly give this a bit more space it takes small and underline and then underline offset-4 for this and let's go full screen and so you don't miss out on the class names with a sign up text inside of it let's save that quickly take a look at what this looks like we can see new to Reddit sign up and this will forward us to the sign up page that doesn't exist yet more importantly we need an actual button that allows us to sign the users up and the reason we are not putting the button in this sign in component is because this is a server component and we cannot have interactivity in it if you're not at all familiar with server components again I highly recommend you check out the video I've done on the next JS app router it's really good it goes very in depth on these Concepts very briefly this is a server component it's run on the server meaning we cannot have interactivity in it therefore we need to create this in a separate component that is then a client component let's call this user auth form because that's what it's going to be and that is a component that does not exist let's create it the user auth form.tsx goes into our components folder let's hit enter and yet again use the functional component snippet to do so and this component is going to handle all the logic that has to do with signing a user in and the jsx of this component is going to be super straightforward basically we want to render out one button and that's almost all we're gonna do and this button is going to say Google because Google is going to be the main login option that we're offering to the users and that's quickly wrap this button inside of one div so we can apply some styling to the parent element with a class name off and again we're going to use our handy combiner with CN and we are going to pass a default class name of flex justify Center so this is always going to be applied because it's not conditional with a dynamic value of class name and we're like where does this class name come from we don't have it in here and the reason I have included it here is because if we want to apply some sliding from the parent of wherever we want to render out the user auth form we can easily do that if you just extend this component so I'm gonna make this very clear and because I think it's a very cool concept right now if I press Ctrl and spacebar we can see the only prop I can pass to the user all form is a key because a key you can pass literally everything in react if I want to pass like a class name to it or anything that could apply to a div we need to extend this so we can say interface user user auth form props extends react.html attributes right now you don't have to follow along this specific step this is just to learn a cool concept for you um so we're going to extend this by react.html attributes and this has this receives a generic and this generic is of type HTML div element a generic is kind of like um imagine like this is a function and then we're calling that function with a certain argument right that's what a generic is just for typescript and what has just happened is that if I hit Ctrl spacebar now awesome nothing happens yet but it's gonna do in a second and so we can now receive the class name and also any other props in this component right here um so that means wherever we render out the user auth form we can specify everything that a regular div can also get and I'm not sure why this doesn't work let's reload the window once again because that usually fixes a lot of bugs in vs code give it a hot second to reload and let's try if this works no it doesn't man I don't know why because it does in the in the actual project that's very weird it was just a cool concept I wanted to show you but I guess vs code just doesn't want to do it right now um whatever let's just leave it out and apply this as a normal class name that's unfortunate um because it is a cool concept but we weren't gonna make use of it anyways I just thought um it was cool to show you um if I can't then let's rip this off because we're not gonna pass any props in that case and let's quickly import oh because we haven't imported the component oh that's why okay so I can show it to you look at this so now it works all right because we didn't import the component wow um now we can pass everything that you can normally pass to a div in react so everything that you can normally pass to a div we can now pass to this component and if we wanted to we could pass all those props we probably or we could receive from the parent component um right into the actual div so we can style this component not only actually in this component but wherever we render it as well with all just as if this was like a div you know that's a pretty cool concept I think that you should know about let's save this and take a look at what this looks like and we can see awesome we've got the Google button right here that we can then use to sign in very very nice um we're going to apply some some more stuff to this button we're not quite done here and for example we want this to be of a certain size and in my case that's going to be small because we don't want this to be too large a width of full as the class name then some more styles that we're going to get to in a second and um yeah actually actually let's implement the actual login functionality right now so we want two main things um in this app one is going to be a toast notification that we're going to do later and then secondly we want to keep track of one certain State and that is the is loading state if the user is currently signing in this is going to be of type Boolean because we're in typescript you can pass this this would automatically be inferred by the way if we pass this as false we don't have to include this but I find it more readable if we do so we're just telling typescript hey this is this will keep track of a Boolean value and then as for the function that's going to log Us in let's call it login with Google this is going to be an async arrow function and inside of here first off we want to say set is loading to true because no matter what happens we want to reflect that in a loading State this is going to contain a try catch in which we're going to call a weight sign in and this is a function we should be able to import from import sign in from next Dash auth slash react this is the package we are installing this from again this is included in the starter code in fact a lot of packages we're using in this app don't worry it's a very long list we're not going to make extensive use of all of these but they are important for this app to work properly um so what I wanted to say is that one of them is the next auth package that we're using for authentication if you've never used that check out their website they've got awesome documentation it makes authentication in nexjs super simple there's a whole documentation you can read through or just read the get started to get the gist of it if you've never used it it's very very cool I'm going to show you how to use it so um signing in as is as easy as calling this sign and function that we get from next off and in here if vs code loaded we can pass all the providers that next offers to us and in our case we want to log in with the Google provider so we can put it there right there and if we get an error then we want to send out a toast notification to the user which we haven't set up to work yet we're going to do that later and then finally no matter what happens in the try catch we want to set is loading to false again because we don't care what happens the loading set should be disabled afterwards and that allows us to actually work with the button better one click whenever we click the button we want to call the login with Google function and then we want to display a loading state if we are currently um logging in the button should be disabled and displaying a little spinner that's why we're passing the is loading because in the button we are listening to that and then displaying a spinner if we are loading and then we want to also render out the Google logo right that would be very cool and the thing is we don't want to render that out all the time but conditionally 4 is loading so if we are loading we don't want to display anything because the logo is going to be replaced by a spinner but if we are not loading we're gonna render out the icons Dot and right now we only have the logo but we want to change that we also want the Google icon and to use the Google icon very quickly gonna navigate back to the um GitHub repository because there's no point in typing out the SVG orself and copy the Google logo from right here in fact we can also copy the um the props with Lucid props and paste it right below or existing SVG as the Google logo and that allows us to render out the Google logo for this button right here and I think there are going to be some um some Styles apply to this user auth form and those styles are going to be a height of 4 a width of four to make it very small and a bit of padding on the right hand side and if we take a look at this button right now we can't because it says you're importing a component that needs use state it only works in a client component but none of its parents are marked with use client so they're server components this is exactly what I meant because we are demanding client-side interactivity we need to turn this into a client component because by default it is a server component and we cannot have interactivity on the server to do that we're going to put use client directive at the very top of our file and if I read out the page now we can see there's a big Google login button right now that won't work because we haven't actually implemented authentication but once we do that this button will actually log Us in so right now we're getting an arrow if we click the button but that's going to be gone um in a second here because oauth is the next thing that we are setting up first let's completely finish this component and to do that it's important to add those notifications in case anything goes wrong you also want um you know a toast message and to get started with host notifications it's super simple it comes from the same UI library that we're using for the button and it's called toast let's navigate to the toast and the installation is as easy as copying this npx Command right here that is npx chat cnui add toast we can copy that yarn pnpm whatever you're using it doesn't really matter let's navigate into the console quickly stop our development server and then run that command hit enter and this is going to ask us where this component will be installed and because we're using the source folder right here in the starter code and I always do this in my project as well both professionally and from a hobby projects I always use the source folder let's say Source components components slash UI this is where this component should be installed that is going to be put into this folder right here we can see host and use toast it's going to set up all the code for us however it sometimes gets stuck in the actual installation of the packages that we need for this to run properly so I always stop it at this point because it has already done its job pretty much it has set up the code for us the only thing that might be an error is no there won't be an error because we've already installed all the dependencies but what might happen if you're not following with the starter code that's why I recommend you do is some of these packages might not be installed but you would be they would be underlined with a red squiggly line so you could just install them and then you'd be completely set now one thing just for housekeeping I'm gonna move the use toast hook inside our separate hooks folder and because it doesn't really belong in the components in my opinion let's move it over to the hooks folder to have it in there and then what I also don't like about this default installation is that toast has a smaller case T I'm going to change that to an uppercase t for toast and what the hell happened why is my vs code blowing up let's reload this window and that should fix the errors great let's save the use tallest hook and now what we can do is import the where did we want to call that right yep the toast notification we can say at the very top of the auth form that const toast is going to be equal to use toast that we import from our Hooks and now we can call this function so we can say toast and pass it an object and we can pass three properties we can pass more but we only want three which is the title of you know there was a problem for example then secondly we want to pass a description this is going to be there was an error logging in with Google and lastly we want to pass a variant this can be one of two things it can be default or destructive and because this is a bad thing that happened we want to pass a obstructive variant of this toast notification and if I just um throw new error right here so we're going to invoke this definitely let's see what happens let me start up the development server once again because I think we stopped that yes we did to show you what the toast notification looks like because there is one thing that's gonna lead to this toast notification not working just yet let's try this the arrow will get thrown but no tools notification will happen why is that that is because we have never provided the um tools to be rendered anywhere on the page and for the tools notification to actually show up we need to render out the toaster and that sounds funny but we're gonna render out the toaster in the main layout so we can actually display or those notifications we're going to render it out right here it's going to say Toaster and this toaster is going to come from a separate component that we have not gotten now the toaster component is something that should have installed using the CLI command for the toast so it comes directly from Shad CN as well and however it did not install properly so let's create this ourself let's create a toaster.tsx component and navigate over to the GitHub repository and because we don't need to write the toaster itself it should be installed but for some reason it didn't in this case it might have for you actually but if it didn't for you as well then navigate to the GitHub repository and you'll find the toaster component by schatzian not here but you'll find it under components UI and then you'll find the toaster.tsx we can literally just copy and paste this component that is totally fine because there is no Advanced logic in here this is simply for actually rendering out or those notifications in the layout this is where we're going to include this component the toaster import that and let's see what happens is the server running yes it is great then in that case let's navigate to or sign in page and hit Google and you can see because we threw the error the toast notification is immediately gonna get rendered there was a problem um there was an error logging in with Google awesome that is exactly what we want we got toast notifications working we're going to be using why can't I move this there we go we're going to be using tools notifications in a lot of places throughout our app so getting them to work like this is very nice great so we've got toast notifications and I think we are pretty much done let's take a look at the component we've built the user auth form and okay we don't always want to throw an error that is something we don't want to do but we now know that we have proper error handing in case anything goes wrong in the user auth form awesome now the only thing left to do is actually implementing the authentication because right now if you click on the Google button we get an authentication error and of course that is not ideal so the question is how the hell do we do the authentication and either you just watch the video in the next like five to ten minutes to do that with me or you can also read all of this in the next auth documentation they do a great job of explaining that too the gist of it is we need to create an API rod that's the important thing so let's close all of this down let's create an API rod and we do that under app and let's create a new folder in the app and that is going to be API like this and inside of this API folder we're going to create all the API rods that's or that are going to encapsulate the logic of our app importantly is the auth that we need right now let's create an auth route and inside of this author out or folder that's what it is let's create one more folder so API folder auth folder and then in angled brackets we're going to put dot dot dot next off and then hit enter this is a catch all route so everything that goes to auth and then anything behind that with any amount of slashes is going to get caught by this route and the logic for this is going to get handled in the route.ts inside of the next auth folder now the the stuff we're going to write into this route.ts is going to be very simple we want to import two things but first off let's say const Handler is going to be equal to next off and then auth options the auth options is where we're going to declare all the logic that goes into our authentication and then we're going to export Handler as get and also Handler oops as post so if anyone calls this API rod with a get or post request then this Handler is going to accommodate the logic for that request the next auth we can import from next off and then the auth options don't exist yet the reason I don't want to write them in this folder even though we very well could is instead I like to put them in the lib folder because after all we are just preparing a library to be used in our application and there's that's no different with the authentication so inside of the lip let's create an auth.ts folder instead of mixing up the logic in our actual um in our actual API rod so let's create a new auth.ts folder inside of the lib folder and this is where we're going to handle all the logic that is associated with or Authentication great so we know we need to export a cons and that is called um auth options that we can then import in the raw.ts and for typescript this is of type next auth options that we import from next off and this is going to get give us type safety even though we're not declaring this in line right in this file on the route yes but somewhere else and we're going to get an error because we're missing a lot of properties in here but that's good that's like a benefit for us that we get that hype safety and we can already import the auth options in the router TS to get rid of the error there then inside of the auth options let's declare in adapter this adapter is going to be a Prisma adapter and it's going to wrap or database now two things are coming together here first off where the hell is this database coming from this comes from a you know from the starter code I've already configured this again this is just copy and paste no Advanced logic here essentially we are instantiating a Prisma client and we want to avoid instantiating multiple of those if we're in development that is all this file does and you can get it from the documentation it's nothing original in here that's why it's not necessary to write ourselves so we're importing the database from this file and then the second thing is the Prisma adapter we are going to import this as well and the Prisma adapter Prisma it adapter is going to come from at next Dash auth slash Prisma adapter this is a separate npm package that we're installing this does not come by default with next auth it's you can search it on npm it's a separate package just for Prisma so whenever someone logs into our app the corresponding tables and stuff are going to be created in our Prisma database on the database that is managed by the Prisma orm whatever database that might be so very very convenient stuff and then as for the session we're gonna pass the strategy property this can either be database or Json web token I prefer Json web token because we can validate them on the edge in the middleware you're going to see what it looks like later as for the pages property we're going to pass a custom sign in page and the sign in page is going to be slash sign Dash in awesome then for the providers this is the question of who do we want to offer like auth zero providers for authentication in our cast that's going to be the Google provider and this Google provider we can also import from let's import Google provider from next next Dash auth slash providers and if we do another slash we can see there's a lot of providers Discord Facebook email in our case we want Google as our provider we can invoke that and pass it in object now what the Google provider takes is a client ID and why am I not getting type saved here it's because vs code is slow as hell once again but that's fine as the client ID into the Google provider we want to pass an environment variable and this is going to be process.env.google underscore client underscore ID and then an exclamation point if you don't know what this does is it's telling typescript like shot up about not having this variable because typescript is like you know this could not exist or this code depending on if we set this as an environment variable and by using this exclamation point we're asserting that yes in fact typescript chill out this does exist we are going to provide it and then the client secret is going to be the process.env.google client Secret awesome now what are these values and how do we provide them first off how do we provide them well let's go into the main directory and then you'll see a DOT env.example and what this file contains is all the environment variables that we need for this app to work properly now the Google client ID and secret is what we need for authentication with Google and if you want to know how to get these just type into YouTube get Google client ID and client secret or something along those lines there are a million videos on this they take like three four minutes explain it very well but the gist is you're gonna go to the Google Cloud console and configure your application to get these two values it's very straightforward it's super simple however unfortunately I cannot show you this because I've got a lot of sensitive information in my Google Cloud console and would it would just be um you know not very smart of me to show that but again there are a there are very good videos on YouTube explaining this and then you can just feel free to pause this video here and type that into Google and then it's going to take it three four minutes to get these values um and then you can continue all right so I hope you got them these two values the Google client at the end client secret and we're going to create a DOT EnV file to paste this we can copy the um the example envy and just paste our values right in here I'm going to actually show you these two values for me um I don't think it makes much sense hiding the NV file I'm just going to change them after the video so for me this is the Google client ID so you can also get a feeling of what this looks like and then this is the Google client secret we need both of these hit save and then we know we have successfully provided them right here in the authentication file awesome that's it for the providers then we want some callbacks callbacks is going to be an object and first off we want a session callback when a session is created what should happen let's say async session and invoke that with an object and most of this comes from or all of this comes straight from the documentation so for example this session gives us access to something called a token and also the session and what we do inside of the session is Define which values we have access to whenever we call a certain function we get from next auth anywhere in any component um to retrieve the current session the question is which values do we want to pass in there let's say if token if we have a token value then let's append some stuff to the session the session.user.id is going to be the token.id the session.user.name is going to be the token dot name and let's just copy this down three more times and then instead of name we're gonna have email instead of name right here we're gonna have um let's call it image and let's call it picture here because image is the name in the database and on the token it's going to be named picture so we get access to the user you know profile picture that Google gives us and then lastly we want also the username for later awesome so after doing that we can just return the session right here and you can see we've got a lot of Errors why is this and this is because we haven't defined a type for next auth to know what this looks like to fix this let's quickly go into our types and create a next dash off dot d dot TS and inside of the next auth.d.ts we're going to Define some next auth specific types for example the type user ID this is going to be of type string and now we can get started in declaring two modules it's very simple first off we want to declare a module next Dash auth Json web token so we know what the Json web token will look like for later we can say interface JWT and this is going to contain an ID that is of type user ID and it's also going to going to contain the user name because later in our app by the way this is going to be of type string or no later in our app each user will be assigned a username and that is also going to be contained in the Json web token and then we want to declare almost the same module we can just copy this down by pressing shift alt and arrow down and instead of next auth Json web token this is only going to be next off with an interface session and this session has the exact same values awesome we need to do a bunch of imports so for example let's import the session and Json web token types from the next auth package so we can only extend on them right here let's say oh and I switched to the English keyboard import type session and user from next Dash auth and then secondly let's import type um and then here it goes JWT in caps from next dash off slash Json web token awesome that's it done um that's it done that's the typing's done we can quickly restart or typescript server and for now we have the arrows but that's going to get fixed very soon and this is totally fight for now great let's switch back over to the callbacks and finish the auth file I promise there's only um a bit more to do here one more callback and that is the async Json web token this defines what the hell um we include in our Json web token values and here we get access to the token and the user oh okay this is giving us an error because we need to move it up into the callbacks object of course all right so async JWT we get access to the Token end user and then let's get to the logic in here so let's first see if we have a user in our database cons DB user is going to be equal to await DB or database dot user dot find First and the first user user we want to find where password an object where the email is equal to the Token dot email we have in the token awesome and then secondly we want to handle some logic if there is no user in our database yet if we have no DB user then in that case the token.id is going to be equal to the user exclamation point we know that exists dot ID and then we are going to return the token so that handles the case if we do not have a user already in our database and and if we don't have a username if there is no DB user.username in that case and by the way there's an error for now but we're gonna we're gonna get that fixed very soon in that case we want to await DB dot user dot update and what do we want to update we want to update where where there we go past that an object the ID is the DB user.id that's the user we want to update all right and if the user has no username the property we do want to update is their data and what we can pass inside of the data is literally just their username As and we're gonna automatically generate a username for them using a package that comes pre-installed in your starter code that is going to be nanoid of 10 and not the gothic font we want nanoid right here and for some reason the Auto Import doesn't work so let's manually import Nano ID from Nano ID as a named import awesome let's scroll back down and then um that should work but Prisma does not currently recognize the username as something that should exist on the user that's fine let's quickly finish up this um auth.ts file and then we will fix that error by the way if you're wondering um if you restart the um development window just like this and you can see the errors go away for the session right here after we have declared the types in the next auth.t.ts it will now know what a session looks like okay so after the deep user.username and here let's return an object and what do we want to return from this JV JWT callback that ends up in the actual Json web token well we want the ID as the DB user.id and we can copy this down one two three four times and instead of the ID we want the name then we want the email and by the way if you're wondering how I'm selecting multiple it's I select the first one by hand and then um control and D that's how you can select multiple this is going to be the picture and we call it image in the database you can also call it picture there um it doesn't really matter and then last thing is going to be the username which Prisma currently just doesn't know of yet and then lastly to finish this off let's call the redirect function as the last function right here is the last callback and this is just going to return a slash so we're going to get redirected to the home page once we actually log in awesome so now how about we add the username in our Prisma schema I think that makes a lot of sense so each user right here should also have a username that is going to be optional as a string so we can just insert a question mark right here next to the username and let me check if I did everything correctly by comparing it to my actual build out version and let's see yes and we can also add and add unique to enforce that each username can only exist once that is probably a very good idea and after doing that let's push those changes into our database however the thing is there is currently no database if we look at our DOT EnV file we can see we need a database URL to actually connect um to a database and what we are going to be using is called Planet scale I'm probably not logged in in this browser but let me log in in another browser under planetscale.com it's a serverless MySQL platform that's what they say of themselves and it's really enjoyable to use as you can see right here this is my actual dashboard in Planet scale there is already a database I have created for this but this was for the demo project and testing everything out if it works and whatnot but I'm gonna go ahead and delete it and we're gonna create one together so if you haven't yet log into Planet scale you can use any other database if you want with Prisma changing our databases it's really easy but I think Planet scale is great for this use case let's call this database Reddit I'm going to host it in EU Central one and once you click on the create database it's going to you know it does what it says on the can it's going to create the database and then we can get our connection strings that we need to paste into or m dot EnV file in our project so let's give this a second to create the database and then we can copy the credentials we need to connect awesome so up here now process ready to connect to your database and yes we are we're going to create a password and this is what we need if you haven't yet you can select connect with Prisma right here and it's going to give you the database URL we can literally take and copy paste into our environment file as the database URL and for the next auth secret we want to define something let's do that right now that will be used to sign your Json web tokens I'm going to choose my secret but in a real production setting you would generate a key using SSH for example or something or just Spam your keyboard with a lot of different um Keys you know so this is more secure than my secret okay awesome now let's navigate back to our Prisma schema and we can say yarn Prisma DB push and let's see if everything works that means we're pushing up these local changes into our database great everything has worked and now let's run yarn Prisma generate and what the generation does is it allows us to work with the types that are generated locally right here and in most cases we actually need to refresh or type script server in order to do this properly so let's give this a hot second until the typescript server refreshes and then let's navigate back into where was it um into or um auth dot TS file and see if there's any more errors and it seems like the arrows are gone great so the username is now really recognized as something that is attached to a user very nice and we are done in the auth department let's head over to the nav bar and let's close uh let's just close all the other stuff right now we won't need that and let's try if the um if the login system actually works so when we sign in we get navigated to the sign in page where we currently are and now if I click the Google button let's see what happens uh oh and we need to restart the dev server let's restart that and let's click the sign in button it's going to take us to the sign in page and now let's click the Google button awesome now we land actually in the sign in screen we can choose any email that I'm currently logged in with from Google and we are navigated back to the home page that means the authentication was successful because if it wasn't then we wouldn't have been navigated to the home page let's verify that now that the authentication is built in successfully do you remember um this whole thing right so the question is how do we retrieve the current session and what I usually do is write a little helper we can go inside of the auth.ts file and at the very bottom let's declare a little helper called export const get off session and this is going to be a arrow function that Returns the get server session and passes in the auth options we have defined up here you know that helper is going to be really useful across our entire application and we can use this for example in the nav bar by turning the nav bar into a server component into an asynchronous server component that means we can now perform awaiting actions at the top level of the navbar for example fetching the session concession is going to be equal to a weight get auth session that we have just created the little helper and let's say if we have a session if we have a session using a ternary operator we're going to render a P tag you're logged in you don't have to follow along right now just take a look at what we're doing um if we are logged in I'm going to render a P tag you're logged in and if there's no session then there's going to be the sign in link and as we can see awesome the logging in Works successful beautiful we are logged in and the authentication is working let's go into our game plan and we can actually take the Second Step very nice nicely done we've done the authentication um it doesn't look too good though yet right so for example what you saw in the very beginning of the video there was a beautiful drop down for user with the actions and where they could sign out and so on we don't have that yet and that's what we want to do um next so let's finish up the nav bar it's going to be in the nav bar up here and what we are going to do in here is we're going to check for the session.user instead of just the session um I think both works really um yeah I mean definitely both works but let's just check for the session.user and the link can stay the link is going to be the exact same but what we want to change now is if we are logged in we want to show a beautiful user account navigation right up here in the top corner with a drop down so let's create a custom component for that and call it user account nav for example that's the naming I like to go for and that component doesn't exist yet so let's create it let's call it user account nav dot TSX and peek inside of this component we can generate this using the FC snippet that is also going to be in the description um it's one I have used in so many projects it's just very handy for initializing these functional components all right if you want to do the same you can paste the snippet for you and let's get started in the user account nav so one property we desperately need inside of here is the user we need to know who we are dealing with so at the user we're going to receive as a prop inside of this component and we only need certain properties from this user so we can use something called a typescript utility type that is called pick that allows us to enter a custom type for example the user coming from next auth not Prisma client this comes from next off that is important and what we can pick from the user is for example the name then an or operator but only one one of these pipe symbols we can also choose the image and we also want the email of the user that's all we need for the drop down we're going to be creating awesome and to get started with the actual drop down we are going to install one thing from chat cnci library that is going to be the drop down a very convenient and nicely done way to handle drop downs for yourself so we can say npx chat cn-ui add and this is called drop down that is going to ask us where we want this component installed and oh what the hell what did what happened which component would you like to add well I just want the drop down as I said what is this supposed to be oh what the hell that is definitely new that that didn't happen when I was writing out the project um so we want the drop down can we find it under the drop down menu yes Source slash components slash UI I I hope I select it no components like oh bruh okay so let's try this again let's hit space then before for the drop down menu and let's hit enter now we want this under Source slash components slash UI okay let's install that that installed or dropped on menu beautiful and now we can get started with this drop down menu but first let's change one quick thing about this and that is in the UI folder where this has just been created let's rename this because I'm not a fan of this um lowercase naming let's instead call this drop down menu dot TSX way simpler and following the react um naming conventions okay what that allows us to do is actually use that drop down inside of the user account nav component first thing we want to return is a drop down menu the main thing that is exported from the drop down menu and in here we're going to use the drop down menu trigger it's built in it's all built in it's really really convenient and as the trigger ideally we would want a circular image of the user profile picture that would be the goal and to achieve that we're going to make use of another custom component and that is what we're going to call the user Avatar we're going to use that in a bunch of places throughout the application for example later in the comments um so to show the image of the user so we might as well make it reusable and follow best practices now the user Avatar um doesn't exist yet so where the hell do we get it from let's create the file for that under user avatar.tsx and now determine what we want in this file first we can initialize this as a functional component and secondly we want to also accept the user as a prop here now the user is going to be almost what we receive in the user account nav as well with the difference of we just need a little bit less information from them so we can go back to the user account nav and just copy the user account nav props right here the user paste them inside of the user Avatar and we can get rid of the email we won't need that in here we only need the name and the image and also import the user type from next auth and not Prisma client that's important okay let's get rid of the Errors By just importing this component and why is that not working because we need to pass the user in here fine let's pass that and then import that into therefore as well just to get rid of all these errors and from the user called nav in the navbar we're going to pass the session.user as the user and just by doing that we got rid of all the errors it's just more enjoyable to work with if we don't have the errors beautiful inside of this user Avatar we can make use of another um piece of schatzian's UI Library so let's say npx shared cn-ui add and this is called Avatar now this is a very simple component you could entirely do this yourself and we want to have this under Source components slash UI um you could do this entirely yourself but I figured this was a pretty neat approach to get started easily alright it has went ahead and installed the Avatar probably with a lowercase a not what I like let's have this as uppercase Avatar and now we can work with it in the user Avatar so the top level element that we're going to have inside of the user Avatar is going to be the avatar from or custom UI component in here we're gonna do a conditional check so if the user has an image we want to display the image but if the user doesn't have an image in that case we want to display a fallback logo or icon for that matter so let's check if the user has an image in that case we want to render out some jsx and in the other case we want to render out some different jsx that is going to be the Avatar fall back and close off the Avatar fall back and the vs code is really not happy that we're not returning anything here so let's quickly return a div up here for the case that the user has an image and if not then we're going to render out the Avatar fallback that we import from our custom UI component great this div is going to get a class name of relative of aspect Dash Square to make this one one like a square format a height of full and a width of full as well and inside of here we're going to render out a next JS image component it's automatically optimized very convenient with a fill property as the source we're going to use the user.image that we are provided by Google so this is going to be the actual user profile picture from Google and as an ALT we're going to say a profile picture and lastly as the referrer policy we're gonna choose no um referrer because sometimes with Google they give you four or three forbidden errors and if you don't have the refer policy satin or referror and we can easily evade that and always show the correct image by adding the refer policy if the user does not have an image in that case we want to display inside of the fallback a span and this is going to say the user dot name the user in this case is optional and with a class name of sr-only this is only for screen readers and to support people who use screen readers right visually impaired people for example will have an easier time determining what the following icon is about that is going to be the icon dot user and that is icons right here we can import the icons and it seems like we don't have a user in here we have the logo we have Google and we also want a user icon and that is very simple that is coming straight from the user icon is going to be the user from Lucid react right here that stops the error awesome and this is going to get a very simple class name the icon sort user of height 4 and width 4. by the way if that was too fast this icons.user appear from Lucid react right here the user icon that's what I just did awesome so that's the user Avatar and let's go back into the user account nav where we are using the user Avatar and properly give it all the and props it needs so instead of passing the user directly we can just pass the properties we need and that is going to be one the name and this is going to be either the user.name or we can also use null and then secondly as for the image it's going to be the same thing either the user dot image or we can also pass null if we don't have that and then let's apply a very bit of a very tiny amount of styling and why doesn't this work class user Avatar does not take a class name um okay so let's add that in the user Avatar um so how we do that we want to accept a class name right here or any other prop for that matter so let's spread in all other props so that means whatever we pass from the parent as not the user is going to be received as these dot props right here kind of like a um catch all and what we can now do is extend the accepted props beyond the user Avatar props Beyond this to something called extends Avatar props right here that come from Red X UI react Avatar so now Red X UI knows what props we are expecting we can already see that and validate that if we hit Ctrl spacebar in the parent we can see oh we can now pass a lot more props that's cool but we're not using them anywhere these all will be received right here no matter what we pass that is not the user and we're not doing anything with those yet and we just want to spread them in to the Avatar so we can apply some custom styling wherever we render the user Avatar in our case we want to apply a class name of a height 8 and with 8 height 8 with 8 and so this doesn't become too large let's reload the page and restart the server to see what has happened and what has changed give this a hot second to reload and ideally if we are logged in we should see or image pop up in the top right as the trigger for a drop down menu the content we haven't yet implemented we're going to do that here in a second but there is our image beautiful very nice if we click this nothing happens yet but that's what we're implementing right now so what should happen when we click that image well we can Define that in the drop down menu content that we also get from this UI component and let's pass this drop down menu content a class name of BG White and also we want to pass this in a line of end then inside of the drop down menu content let's render out a div in the first instance with a class name of flex items Dash Center justify Dash start a gap of two and a padding of two then inside of here goes one more div with a classroom of flex Flex column space y one for a bit of vertical spacing and a leading Dash none okay then inside of this div we're gonna render out if the user has a username so if user.name that's what we want to render out inside of a P tag and with a class name of font medium so it's a bit bigger the name is noticeable and that's going to be the user dot name like so and right below that we can also do the same thing with the email just change the user.named user.email if you're curious as to how I marked multiple at the same time I'm pressing alt I'm holding alt while doing this and therefore this works so if we have an email we want to render that out but change the class name just a little bit the class name is going to be with and then in angled brackets 200 PX for a custom inline 200 pixel and with value and we want to apply truncate a text of small and a text zinc of 700. format that so you can see this easier this is what we just did with the email amazing okay that's this whole div complex here done let's quickly save that and see what happens and we get an arrow right here probably because we renamed the Avatar so we need to change this to an uppercase a because we rename that in our files so when I click the logo we can see there is already a beautiful drop down without actions yet but that's what we're about to implement So Below the closing div but above the closing drop down menu content let's create a drop down or let's insert a drop down menu separator set Power rater we don't need to do anything it looks awesome out of the box if I save this there we go there it is it looks awesome out of the box we don't even need to do anything for that to look good and then below that we're going to make use of the drop down menu item that we also get from the drop down menu we're gonna say we want this as child which means that the child component we insert right here a link from next.js as a self no that's not going to be self-closing this child is going to be what's triggering the or what's going to be clicked when we um click on this drop down menu item so normally this would render out a separate button which we don't want but instead just this link directly and because we have no need for an additional button here and this is just going to link to the home page to the slash and say feed awesome then we can duplicate this drop down menu item and most stuff is going to be the same except the link this is going to link to slash r slash create where we can create subreddits later and then this is going to say create community and then let's copy this down one more time or one last time and this is going to say um settings so for example where the user can later change their username and this is going to lead to slash settings great let's add yet another drop down menu separator below those because now we want to offer the user the option to sign out we can't do that using another drop down drop down menu item just like this just like the others but the content is going to be a bit different for example the class name is going to be cursor Dash pointer and then inside of this drop down menu item we are going to have a text that says sign Alt however let's save that and try it nothing would happen yet if we tried clicking the sign out button it looks beautiful like beautiful beautiful but nothing happens when I click sign out so what do we want to happen well we get a very convenient Little Helper and that is on select whenever we select this we get an event and can invoke a custom inline function the event is just a generic event from react and we can say event dot prevent default to prevent the default behavior and then we want to you know sign the user out how do we do that well we can use the sign out Hook from next auth react that makes it super easy invoke that pass it an option and we can enter a callback URL and that callback URL where people are going to get thrown or redirected to once they hit the sign out button is going to be the window dot location dot origin and then slash sign in so just or sign in page right if I try that refresh the page let's try out if we sign out works and that does not work because we need to declare the user account nav as a client component to handle this kind of client-side interactivity we can do that by using the use client directive at the very top of the file turning this from a default server component into a client component great let's try the sign out let's hit sign out and that should ideally sign us out amazing it works the auth flow looks very very nice sign out works just as it should and well these actions don't exist yet but they look amazing already and it makes it very easy for us to extend on just this drop down later awesome next up let's see auth and then creating and joining subreddits one thing I still want to do for the auth that I think is going to be a very nice learning learning experience for you is how to handle Advanced routing patterns in next js13 because this is a really cool and you know more advanced topic um so what we're gonna do is intercept the routes that means when I hit something like the sign in button when I'm signed out let's try this when I hit the sign in Button as you can see we are redirected to the actual sign in page but what if we were on the home page hit sign in and it would just open as a model but if I then reloaded the page then it would lead us to the actual sign um in page so how this looks conceptually is Imagine This we're on the home page these are just the paths imagine the home page and when we redirect to the sign in so slash sign in normally we get redirected to the actual page right from the home page to the sign in page but if we intercept this request we can actually render out something different some other page that we Define ourselves like a model for example and the user will not actually land on the sign in page anymore unless they hard reload while they're on the model then they will get to the sign in page it's an advanced routing pattern not insanely easy to explain but makes for a very very clean user experience and I'm going to show you exactly how this works I've done a separate video on this as well because I think it's really cool and let's intercept these routes right now so let's go into our app folder we've already got the auth right here and I think what makes sense is before we intercept any route or actually now let's just go ahead and do it let's just do it the fun way and just intercept right away so to get started with the intercepting and the models let's first create a new folder in our app directory and that's going to be called add of model the ad is a Convention of how we can create these intercepting and parallel routes inside of this auth model um let's not do anything yet but Define a default dot TSX we're going to do the rest here in a second and the default.tsx determines what should be shown from this component if no route is able to be intercepted so kind of like the default state of it and the default State should be nothing so we can export default function default and this should simply just return null because we don't want to show anything if we are not actually intercepting a route now let's head over to our layout and there's a little arrow here and likely it is this one its return type promise element it's not a valid jsx element what this arrow means is essentially there's nothing wrong with the navbar but typescript doesn't know how to deal with this asynchronous Behavior because it doesn't really know react server components again don't worry there's nothing wrong with your code and typescript just doesn't recognize this so we can simply use a TS expect error of server component and get rid of the arrow that way okay so what we initially or what we originally wanted to do is now that we have this intercepting route and we can close all of so much of this this adds auth model right and what this allows us to do is receive this auth model as a property in our layout if you have any questions during this again I created a whole separate video on this and but I'm gonna do my absolute best to explain it here and very well too so just by creating that um file or that folder we can receive the auth model in our layout at the same level as that folder in our case or root layout and we can type this out in typescript as react Dot react node this is nothing else than a regular you know children compo or the regular children that are passed into their the layout it's the same thing and that means we can render it alongside with the other content just by putting it in here just the same way as we would with the children I mean currently this doesn't do anything right because we're not intercepting anything we are allowing for parallel routes so parallel showing the oauth model and the children but we're not actually making use of that and what we want to intercept are the authentication routes for once these sign up and for for the other hand they sign in right let me show you how this is done Let's ignore the um why for now let me just show you how this is done and then get into the um the nitty-gritty why we're doing this later so the way we do this is by in parentheses saying dot so this is kind of like a file system where you have Dot and dot dot and CD and so on not really because it's not exactly the file system but you can kind of imagine it like that way so we're going ahead in the same directory like the auth model and intercepting the requests and where do we want to intercept the request to that is going to be to sign in so whenever we call this route right here we are intercepting it in the auth model and can custom Define a page.dsx that should be shown whenever that intercepting is successful let's just say intercept and this page so the example gets clear to you so we're on the home page right now and if I hit the sign in we should now be intercepting that route which worked successfully as you can see it says intercept right up here so we can see we intercepted that request but if I read out the page then we actually get to the normal sign in page so obviously we wouldn't want to show just an ugly intercept text but actually a pretty model in the middle of the screen and that handles this logic and to show that model let's create it and this is going to be the model that shows up whenever somebody makes a request or the sign in and route that we're intercepting let's render out a div in here with a class name of fixed and inset of zero again this is maybe a bit hard to explain maybe I lost you there I hope I didn't and but you're gonna see what this looks like um what we're doing right now a background zinc of 900 20 for the backdrop this is what we're doing right now and a z index of 10 with another div inside that has a class name of container of flex items Dash Center a height of full we want a maximum width of large and an MX of Auto for horizontal alignment inside of here let's put in one more div and this is going to be of class name relative then a background of white we want a width of four a height of fit to only be as high as we absolutely have to a padding y of 20 like so and a padding X of 2 and lastly a rounded rounded of large this is going to be the actual model that's going to show up in the middle of the screen and inside of that model let's render out a div with a class name of absolute a top of four and a right of four and then here we're going to show a button to close the model and so because we open this through a route change we also need to close this through a route change let's just put an X here for now and we're going to expand on that here in a second and right below the closing div with three more closing divs to go after that let's put our sign in component like so we can import that so it's the exact same thing let's save that and let's try out the model let's go to the home page click sign in and we can see there is the model it feels like we're on a different page but we're not really only if we reload do we actually get the underlying page beautiful so instead of actually navigating the user to a page sometimes when they're not expecting it so for example if they click the Vault button on a comment and they have to log in to do that you could just send them to the model which is very non-invasive compared to actually navigate them to navigating them to the proper route so this can be a really good approach to improve user experience and be less invasive in suggesting them to sign up to perform a certain action great now the x button doesn't do anything yet which is not ideal so let's call this x a close model component that should you know just take the role of closing a model this is a component that doesn't exist yet so let's create it under close modal.tsx and the only thing this does is render out a button let's create this as a functional component but we can get rid of most of the stuff and because we're not expecting props or anything in here so we can also get rid of this and the FC none of that is necessary um and then we're going to render out a button or trusted the UI button that says or that has an X inside of it an icon we get from Lucid react this is going to be a self-closing icon with a class name of height 4 and with 4. just like so awesome and let's give this button in area label Dash label because we're just rendering out an icon and visually impaired people could not know what it does because they can't see the icon so let's have an area label of close models so we have a more accessible app great the variant of this button is going to be subtle and we want a class name of height of 6 a width of 6 a padding of 0 and a rounded of medium for this button great let's import the closed model right here and take a look at what this looks like by refreshing the page that's probably gonna land us right here and by the way one thing that I find a bit off-putting is let's insert an icon right here into the home button let's go to the page.tsx for the actual sign in and then here let's put a Chevron left icon just like so we can import that from Lucid react and give it a class name of margin right 2 a height of 4 and a width of 4 as well just looks a bit cleaner you know right here with the with the little icon there just looks better awesome and with that out of the way we can now go back to the homepage and test if this button actually does what it's supposed to and it doesn't that's not ideal and but it's understandable because currently we're not handling any sort of logic in this close model button so we don't have an on click Handler or such and to fix that we want to declare this as a client component as the first step because we're going to make use of a hook that is provided to us by next JS and that is going to be the use router hook but very important this does not come from next slash router this hook comes the use router from next slash navigation important differentiation to make because the other one literally will not work and now on the click of this button we can call the router dot back just like so let's try this out let's press the x button and it closes the model beautiful on development it might take some time apparently takes quite long let's reload the page let's go to the home page click the sign in button now click the close button okay now it works great when I click the button it closes the model just as expected we just had to reload the page awesome really really good currently this only works for the sign in route but we also might have a sign up right currently we don't even have a page for that but don't worry the pages are going to be very similar to this sign in so for example we have the uh auth right here the sign in and let's just do the same for the sign up both pages are going to be very similar let's create a page.tsx for the sign up and just copy all the contents of the sign in page into the sign up page just like so the only thing we want to change is to change the sign in to a sign up component that doesn't exist yet so let's quickly go ahead and create it as the sign up dot TSX and we can just copy the content from the sign in the TSX into the sign up.tsx the reason I still decided to split this is because you might have your very own authentication flow and it's way easier if you know how it's done to implement that yourself so in our case this is going to be very similar but it allows for a very easy extension of your own auth flow if you wanted to extend on this in the future so what we are changing though is right here the new to Reddit does make sense on a sign up instead we're gonna say already a [Music] it sounds kind of stupid but if you're already a predator we're gonna um link you to the sign in page and the sign in page also lives under slash sign Dash in and the rest is going to say the exact same or maybe we want to say instead of welcome back let's say sign up awesome and then the rest can literally stay the same so now we have created our sign up component can import that um if this worked and we exported at it as an actual sign up component didn't want to do that now we are exporting it as the sign up component and now we should be able to import it in our page.tsx and get rid of the unused Imports by the way if you're wondering um that is shift alt and O to do that great now let's also intercept that route so we can also show it inside of the model the logic is going to be again very simple in parentheses we want to intercept the sign up and nothing else only to sign in and sign up routes are going to be intercepted and we can just copy the code from the sign in page.tsx into this sign up page.tsx so as you can see both of these Logics are going to be very similar between the sign in and sign up across their two pages you can literally just paste this in here by the way we don't need any of these props in here same goes for the sign in page we got from the FC snippet no point in having those because there are no props passed into these whatsoever awesome so there's a lot of code duplication here which is fine um again if you wanted to extend your own authentication flow this is a good idea and then we're going to render our design up component instead of the sign in component awesome let's see if this works let's move it over and navigate to the home page of localhost 3000 click the sign in now to sign up did I click it no I didn't click it there we go and we can now switch between the different models so if we're on the sign up page and wanted to sign in we could click that it's going to open up as a model as well but if I reloaded the page in that case we're actually landing on the underlying page like the what is this the sign in page for example very very nice and that concludes the route interception so when we want we can invoke the model we can close that using the x button and then when we refresh the page we can actually send users to the proper full size login and so sign up and sign in Pages awesome let's check out our current status so go over to the game plan and I mean okay I already checked this but now we can really check it we have very much done the authentication can I not go back hello why can't I delete that anyways we have now really done the authentication and are done with that step awesome so now comes creating and joining subreddits the question is Where Do We Begin do we just create the subreddit page right away or what do we do well what I propose we do is first off let's model this in our database and while we're at it we can do all the models for what we need in our database we're going to do that in or Prisma schema and we are going to model something like the subreddits the posts and everything we need for the entire application and what I think makes sense getting started with is gonna be the subreddit let's create a model subreddit and this is going to be what did I do oh I switched to the English keyboard and we can close everything else by the way let's just focus on the database modeling for now with a subreddit to start with the subreddit is going to get an ID of type string uppercase and Prisma and this is going to be an ad ID and a add default a collision resistant unique identifier and invoke that the subreddit is also going to get a name as a string and this name is going to be unique so there are no two subreddits with the same name we want a created add and this is going to be of type date time also as a default with a now value and same thing goes for the updated ad just with the exception that this is an add updated ad that's how we Define that in Prisma okay then we want um to keep track of posts right so a subreddit can contain multiple posts not just add them posts and we want to call that model um the post now this doesn't exist yet this is going to give us an error it's not a built-in type nor refers to another model yada yada that's fine because we're going to create it right below the subreddit as a separate model and that's going to be post a post gets an ID of type string as well with an ads ID default Collision resistant unique identifier we can just copy it down from the subreddit a post gets a title and so the actual type you might be viewing a prism oh because I had a column there there we go uh okay it automatically did some stuff for us that is mostly fine but let's not do that right now um a post gets a title a post gets some content and this content is going to be of type Json but it doesn't have to be there so the Json is optional we want a created ad and we can literally copy those to the created and updated ad from the subreddit as well and post them right below the content then we want a to link them with a subreddit right and to do that I'm gonna press my prettier hotkey which is shift alt and F for me and that's going to do some stuff for us like the subreddit um linking with the relation however a subreddit cannot be optional for a post it automatically marks this as optional with a question mark this can't be optional that is not a good idea and let's turn this into a um lowercase subreddit right here but the other the other stuff is good and then we want to keep track of authors right one post can have one author and to keep track of an author um that is just going to be a user right we're not going to create a different model for an author but instead we're going to link it to a certain user so the author is going to be a user with an ad relation that is going to be containing some fields and those fields is going to be the author that field is going to be the author ID and that references the ID field and now because we did that we also need to add the author ID field and there's going to be a string also in the post there might be some errors throughout this don't worry about it everything is going to be all right we're going to be golden and once we are done with this Prisma schema then we want to keep track of comments and votes for a post so I bought stonewalls and which comments a post has as for the comments let's say comments and this is going to be a comment array assuming that we have a model comments that we don't yet have but we can easily create right below this each model comments is going to have an ID this is going to be a string and with an ad ID and a at default Collision resistant unique identifier you know the drill each comment is going to have some text and that is going to be just a string and that's it then a created ad as a date time and at default is going to be now then an author and this is nothing more than a user as we previously had then an author ID which is of type string like that and we also want to link each comment to a post so we can say post is of type post Prisma knows what this is because it's a model we've already defined and then link this to a post ID as well if I hit um control F that's going to do the um the formatting for us and the post ID is going to be of type string awesome okay this is still giving us an error don't worry about it for now and then each comment can also be a reply to another comment and we want to also allow for that in our Prisma schema the way we do that is by defining a reply to ID if this is actually a reply and that is an optional string because it doesn't have to be a reply but it if but if it is it's going to be a reply to and that is another comment also optionally of course and each comment can and know that needs to be a reply to comment question mark no not comments hello Prisma and then each comment can also have multiple replies and that is going to be of type comments array and let's mark the relation for this it's going to be at relation for the reply tool as a in semicol or in quotes there's going to be a reply to relation with the fields that are going to be or that is going to be the reply to ID we're going to reference that references the ID and then on delete because this is referencing itself we cannot have any action in Prisma if you try to it would warn you that this wouldn't work so we have to specify no action on delete and on update we also want no action because again this is a reference from a comment to a comment so kind of to itself then as for the replies this is also going to be of type add relation and then reply to okay um great then one more thing after that it's going to be or two more things it's going to be first the votes and this is going to be the comment vote array because we're going to be differentiating between a common vote and a post vote because they are fundamentally different and then lastly the comment ID is going to be a string that is optional okay now um the common vote doesn't exist yet let's create it um right after um this as the comment vote we want to link a user to a certain comment with a type of upvote like user one voted for comment a with votes up right so we want to link those and the reason we Define this as a separate model is so we can prevent multiple votes from happening if we just kept track of this as an integer that would be a problem for example so let's say model comment vault it's going to be a separate model right below this with a user this is going to be a regular user with a user ID of type string with a comment of type comment so we're just linking these two no this not is not comment comments but just a comment and then a comment ID and this is going to be of type string great and then a type and this can either be up or down and because a post vote can also be up or down like you can upload a post you can upload a comment and you can download both as well let's define this as an enum and enum is like let's say enum vault type is going to be just up and then spacebar and then press enter down so it only has these two options it's like a regular typescript enum where you just have two options like up down and that's it no further logic involved and the type for each common vote is going to be of this type volt type so either up or down and the ID of each common vote is going to consist of the user ID and in combination with the comment ID and now it complains on this comment right here so as let's add a relation add relation and this is going to be the fields or the comment ID this one right here and then we also want to say references the ID and same thing goes for the user that's going to get an ad relation um with with the field of user ID and then references also the ID okay and I think are we done we have the post we have the comment comments is a comment oh because we okay that needs to be a singular model um comment just like this so we got the author the post if I press Ctrl F it's going to do most of the relation stuff itself but it's very handy so press your pretty or hotkey and Prisma itself is going to take care of most of this formatting and I think there is one thing still missing and that is the regular Vault the model Vault so when you press upvote on a post this is the model that has been created or instantiated linking one user to one post with one type of a vote just like we did with the comments right so each vote gets a user of type user and its user ID of type string and then also we want the post as a post and the post ID as the string linking those together and what are we linking well each link has to contain the type of Vault that is going to be the same volt type we've used in the comments and because a post can either be up or downvoted and then the ID for each bot is doing going to consist of the user ID in combination with the post ID awesome if I press my prettier hotkey it's going to do all of the relation stuff for us that is really nice and let's see if we missed anything so okay so for example one thing we missed is right here in the comment the author should not be linked by the user ID but let's instead use the author author ID instead of the user ID that references the ID beautiful and I think everything else in here is fine except we don't need a user ID in here Prisma automatically created that we don't need it we can remove it and then we have the votes and the comment ID great let's see if we missed anything on the post and I think we did not author ID separated at the very nice and each subreddit um okay we missed a bunch of stuff on the subreddit that's important to note so for example besides the post each subreddit we want to keep track of the of who created that subreddit that is going to be the Creator ID of type optional string and then also we want to keep track of the Creator as an optional user lastly we want to keep track of the subscribers of the subreddit this is going to be of type subscription array and the subscription is I think the last Model we are going to create until we are completely done and that's quickly Index this subreddit at the name because later on in the video we're going to create a search bar to search by subreddit names and therefore it's just handy to have all the names ready okay let's create the subscription as the last Model so one user can subscribe to a subreddit to also Post in it the model subscription right here is going to contain only four things that's going to be the user as the user then the user ID as a string and Link that user to a subreddit that is of type subreddit with its subreddit ID of type string and the ID of each of these subscriptions is going to consist of a combination of the user ID and the subreddit ID so a joined key and let's see this is that correct what Prisma did here almost one thing we want to change is remove the user ID for a subreddit prism is automatically done that because we don't want to reference the user ID but instead turn this into a add relation with in quotes created by and then as for the field that is addressed right here we want the Creator ID now we get an error right here saying error validating field the relation field Creator or model subreddit is missing an opposite relation so let's go ahead and fix that so essentially what it means is right here in the user we are not mirroring um that correct relation between these two and instead of calling this subreddits let's give each user a property that is called created subreddits and let's move it way up here and below the email verified right here and this is going to be a subreddit array with the add relation off and this is going to be created by there we go that's going to remove the error because now we have linked these two together through a created by a relation awesome and I think we are done if we're not we can always go ahead and change this later let's see for the user we have accounts sessions post comment comment vote and subscription and vote and subreddit great we're done very very good job that's the session that's everything we previously had and now we have completely modeled everything we need to get started with our application beautiful good job if you've done the database modeling I know this certainly wasn't the most interesting step I promise it's going to get more interesting from here especially with the data fetching and error handling and so on it's going to get really cool this wasn't the the most interesting step but knowing how to model your databases is really important when it comes to building complex relational databases that actually mirror what you want to do um in your application and allow you to do so right because the database really is the Cornerstone of an application hey this is me editing Josh and in the last line in the Prisma schema for the post I accidentally messed that up in the recording and it wasn't caught on um on the recording is that just the last line just this right here needs to say lowercase votes and then the Vault array instead of what we currently have which is the um Vault right that's not what we want Instead This needs to be votes and but that's literally the only change just this line everything else is perfect you're doing a really good job and let's get on with the video awesome so next step is to be able to create and join subreddits and where the hell do we do that and I propose we get started on the main page.tsx to do this because on the home page you want to be able to create a new community for you for your followers for um whoever you want to create a community for I don't know what I'm saying essentially what I'm saying is um let's add a create Community button on the homepage that allows you to do so so as for what we want to return from this page.tsx is mainly going to be um for example firstly the H1 that is going to say your feed so we're going to display the feed of the user on one side and then on the other side the info in a grid you're gonna see what it looks like here in a second this H1 is going to get a classroom of font Dash bold of text 3XL and then also medium devices and up a text of 4XL great and then below that H1 we're going to render out a div with a class name of grid grid calls off one and then on medium and up a grid calls off three so space these out a bit give the main feed two chunks and then one chunk to the um info card of that subreddit again you're gonna see what that looks like in a second a gap y of four and medium device isn't up a gap X of 4 and a padding y of six then inside of the surf we want to render out and the first two grid slots imagine them like this in these first two we want to render out the actual user feed but we don't have posts in our application yet to display in the feed so that's the problem therefore let's mark this out for now and just say feed this is where the feed is going to go later and then below that we're going to have the subreddit info and we can actually get started with this so for example let's create a div right below the subredded info with a class name of overflow Dash hidden just like so a height of fit we want a rounded Dash large a border a border Dash grain-200 and Order Dash first and then we also want on medium devices and up and Order Dash last this is to determine how things are shown and positioned um in responsive phase because this app is mostly responsive or I mean it is it is responsive um CSS is not a focus of this video by any means but I want it to look good and every app has to be kind of responsive these days so you can't get around it really even if you don't want you can be sitting there screaming and kicking but you have to make your apps at least kind of responsive okay uh anyways I'm going on a tangent in here let's put a div with a class name of um m a background Emerald of 100 for slight green background a padding X of 6 and a padding y of four and inside of here let's put a P tag that is gonna have a class name of font Dash semi Bolt padding y of three Flex items Dash Center and a gap of 1.5 inside of the speed tag let's render out the text of home to show the user where they currently are and also put a home icon we get from Lucid react just really Hammer the point home where the user currently is with a width of four and a height of 4 as the class name let's render this out and go into a side by side view and you can see this already is starting to take some shape great then one div down with two closing divs to go this is where we're gonna create or next div with a class name of minus m y minus 3 a divide of Y A divides gray 100 a padding X of 6 padding y of four text of small and a leading dash six just like so inside of here one more div with a class name of flex justify between a gap X of 4 and a padding way of three and inside of this diff A P tag with a class name of text zinc 500 saying your personal Reddit homepage come here to check Jesus I'm making a lot of typos right now I'm here to check in with your favorite communities period let's see how that looks like and there we go this looks really good um awesome and then below that let's create a link component that we get from next slash link not self-closing but instead saying create community and inside of this link we first want to apply a style to this so let's apply a class name and the style we want to apply is the same style as a button but we don't want this to be a button right so what we can do is go into our button component and import these button variants so we can make this link look like a button without it actually being a button you can import the button variance just like so these come from the ad components UI button this is where we are exporting them and we can simply invoke them and we can also pass them a custom class name if we want to class name that says with full margin top 4 margin bottom six to give this a bit of nicer spacing and this link also has to have an href and where do we want this link to go we want this to go this doesn't even need to go into the template string just a regular string is enough into slash r slash create where we're going to be able to create our own Community let's see how this looks like awesome so the link looks like just an actual button if we click this we get navigated to a page that doesn't exist yet but we're gonna create it and it just looks beautiful okay and I say that is the page we add next we can close out of most of this clean up our working space just a little bit and then go under app and create that new route in or app folder and we are gonna name it R plus create a folder called R the letter R because the reason is this is going to be reflected in the URL later and when we want something like slash r slash react then it's also going to live under this folder name for now the only thing we want is the create route to send people to to create their own subreddits and create a new page.tsx in there to define the component that we're going to use again if you don't fully understand the routing structure of react and check out my full app router video it's really good on this topic very brief what's going to end up in the URL is the folder names and then the slashes are the subfolder so r slash create would actually lead to this page.tsx inside of the create page.tsx is an enforced Name by next.js it needs to be named page.tsx that is important which also leads to this mess right so everything is named patch.tsx which is not um amazing but it is what it is and I mean overall the app router is a massive Improvement in fact let's say const page is an arrow function like this and Export default page at the very bottom of the page and now let's get started in writing the actual and Page logic so what should happen when we navigate to the create page well there in in theory there should be an input allowing a user to input a community name and then click a button that creates that community so because we demand client-side interactivity we have to turn the default server component into a client component to be able to handle that and then we want to keep track of the input the user can make inside of a state that is called input also set input and this is going to be a string you don't have to type this out so explicitly typescript would also infer it if we didn't pass the string generic here it would work just fine assuming we imported that in the first place this works just fine as well because typescript can in further type but I just find this a bit cleaner to look at okay and then let's get started in the jsx and provide the um input for the user to type in let's return a div with a class name of container by the way the container value right here if you look at your Tailwind config we are using it right here we've extended it a little bit with Center padding and screens and that's why we have it right in here the flex item stash Center a height of full like so and a maximum width of 3XL with an MX of Auto 4 horizontal alignment a div inside of here not a dove a div there we go with a relative class name background white with a full height of fit a padding of four rounded Dash large and space y 6. awesome inside of this diff one more div with a class name or Flex justify Dash between and items Dash Center and this is going to say instead of an H1 tag in this div create a community this H1 gets a class name of text XL and font semi bold great let's add a little visual breakpoint after this with two closing divs remaining let's put an HR in here and this gets a class name of background zinc 500 with a height of PX for just one pixel just for a visual separation let's reload the page and see what this looks like create a community with a little separator there looking really neat below this let's create one div with two closing divs to go that's where we are creating this new one no class name whatsoever just for the flex layout we created this with a P tag inside of it saying name and this P tag gets a class name of text large and font medium awesome below this another P tag that states Community names including Cappy teleization cannot be changed and I grabbed this from the original Reddit this is what they also have in their app and this P tag gets a class name of text extra small and a padding bottom off too awesome below that let's create one div with a class name of relative and this is so we can put the following P tag that states r slash that's it with an absolute class name we can sell it absolutely that's why we have the relative as the parent we are going to use the classroom absolute text small a left Dash zero a width of eight and in set y of 0 a grid a place items Dash Center and let's give this a bit more space so you don't get lost in any of the class names and a text zinc or four hundred just like so really nice after this P tag we're gonna have an input and we're not just going to use any plain old react input for that but there will be a custom input um that we can use that looks good that is accessible out of the box and we can install it using npx Shad cn-ui add input we need to press enter after typing that command and we want this in Source slash components slash UI that really quickly installed the input for us in or components UI input right here let's quickly change the capitalization and generally prefer the uppercase syntax it follows the react conventions on how to name your files and then we can already make use of it just like that installing your UI components like this is so easy this is going to be a self-closing input and we want to make this a controlled input where the value is the input state that we created way at the top of the file up here and then with an on-change Handler that lets us receive the event and set the input to the E dot Target dot value so whatever the user typed into the input is going to be contained in this e.target.value and we are going to apply one class name to this input that is going to be padding left of six so we can prefix it with this absolutely positioned Mr slash let's see what this looks like and this is going to look like really nice if we started the dev server up of course that's a requirement for this to look nice in the first place and let's take a look at it and the absolutely prefixed r slash in front of the input give this a hot second to load and there it is beautiful if we enter anything now it says r slash and then the community we are trying to create it looks really good um however currently we don't have a button to actually make the request right where do we submit this well let's create a button for that and with three closing divs no with two closing diffs to go right here two closing divs and two above it let's create one last div in this um page I promise last one with a class name or Flex justify Dash end and gap of four and this is going to contain two buttons one to cancel and one to submit we can import them from our custom bottom component and the first one is going to say cancel and this variant for this button is going to be subtle we don't want it sticking out too much just if you want to cancel the option is there with an on click Handler off and what we want to do is take the person back to whatever page they came from the way we can do that is by simply using a hook that next.js provides to us and oh there's an arrow here because we need to capitalize the input um we can make use of a hook let's say Collins router is equal to use router that comes from next slash navigation and not next slash router and we can use this router to make the person go back by saying router dot back and invoking that to wherever they previously were before they navigated to this page and then below that is going to be the button to actually make the request to create the community and this button is not going to have any specific styling applied to it but the logic it encapsulates it's going to be very important because now we actually want to make the request to our API endpoint to create a community and because we're doing so in a client component we're going to make use of an amazing library to do that and that is react query or now known as tan stack query and the way 10 stack query works is it's like a fetch but way better let me show you how it works we're gonna say const empty object we're gonna destructure later is going to be equal to use mutation mutation a hook that comes pre-installed in your starter code normally you would have to install this yourself from at 10 stack slash react query as you're going to see once we import this and why are the imports not working import use mutation from add 10 stack slash react query this is where this comes from a dependency you normally have to install yourself but in the starter code it's already included and what this takes is something called a mutation function it's important to understand this because we're going to use react query a lot throughout this application because it's so nice and the mutation function is any function that handles your data fetching for example in async Arrow function that handles our data fetching logic okay so in in theory this could be just a fetch request like fetch a certain API route and it's going to be very similar to that and because we're going to use axials to make the request if you're familiar with axials you know we can destructure something called Data this is the Json converted request done by axios normally if you use the fetch you would say like constata is equal to weight a res dot oops rest dot Json again don't copy this um but this is how you normally do it in axials this is done for you so you can literally just just destructure the data and this comes from await axios yeah the Imports are not working let's try reloading the window once again because normally that fixes things and if not we have to import axios ourself which is not a huge problem either does this work no it doesn't work okay let's import axial from axios as a default um import okay and if we invoke that and say axios dot post because we're going to be making a post request this also needs the end point we're going to be posting to just like with the fetch API this is just a bit different syntactically and the end point we're going to make the post request to to create a community it's going to live under slash API slash subreddit this is where we're gonna send that and we want to send along some data right so for example the name the user has typed in here we want to send that along and here's a best practice I figured out on how to do this first off let's define a const payload as an empty object that we're going to send along in our request right here as the last thing as the payload just pass it in here um now currently in the payload we could pass anything and that is not ideal we want to enforce some kind of type safety on this payload I've done a separate video just on this topic on this full stack type safety thing and why I'm using it instead or not as a replacement but instead of trpc I'm going to link it in the description and if you don't know what trpc is don't be confused I'm going to show you how this works right now it's actually pretty simple so let's enforce a certain kind of payload on this and to do that we are going to go into our lib folder where we prepare libraries to be used in our projects and create a new folder called validators and inside of here we're going to create a file called subreddit.ts okay inside of the subreddit.ts file we are going to import Z from Zod and Zod is a schema validation Library what that means is if we had an object a JavaScript object caused object is going to be equal to this right here and we have a name of John and we have an age of 25 for example right this is a regular JavaScript object nothing about zot in here plain JavaScript object then what we can do with zot at least in um kind of like this let's say the sh schema we're going to declare a schema with sort and this is going to be very similar syntactically to a JavaScript object let's say for example again you're going to see this in just a second let's say for example or schema contained just a name this is the data we are expecting to work with for example on the back end and if we parsed this JavaScript object through the schema then all the data that we are not expecting like the age are going to be stripped away from the object so we are only left with the data that we are expecting So in theory we would state name is going to be of type string so any string we could also declare a literal if we wanted to but that way we can validate also on the server also on the client both works that the data that we are parsing through the schema matches the schema if there was no name property like John if there was only an H for example only like this then the parsing would fail and throw an error so there would be no like this would not exist and that is awesome because we can now server side and client-side validate or um requests and let me show you how this works so to declare a schema let's export a const and call this subreddit validator and this is going to be called m a z dot object so very similar to a JavaScript object just in a bit of a different syntax with a name of Z dot string dot minimum three dot maximum 21. and this is how we enforce security standards this can maximum be 21 characters now if we check this on the server side and while we are here we can already do a second validator we're going to use later that is going to be the export const subreddit subscription validator this is also going to be a z dot object if we want to subscribe to a subreddit we're going to need the subreddit ID to do so again this is not for now just for later while we are already in this file and we can already do this and the very cool thing about Zod is how well it works with typescript so what we can do right here is export a type that is generated or inferred from this schema we can export type create subreddit payload and this is going to be a z dot infer the type of subreddit validator and we get back a valid typescript type that contains the schema we have defined up here for example the name String and we can do the same thing let's call this m subscribe to subreddit payload as the type of subreddit subscription metadata just like so save that and what we can do now is enforce the type we have just created on the payload so it will always match this expected data to do that let's copy the create subreddit payload type and put it right here after the payload and import that we can now see there is an error on the edge because that is not what we're expecting what we are expecting is the name right here and what is the name well it's the input that's what we want to pass along in our API request pass it over the wire so we can actually receive it on our API endpoint and then we can return data the answer we get back from the server as cast it into a string type because we know in a second we're going to create that route and we're going to return a string as a successful response very nice and what we can do now with react query is destructure the mutate function we can actually call to execute this mutation function and we can call it a different name if we want to for example create community and what we can also do is destructure a lot more stuff like if there's an error if there is Success right what we want is the is loading we don't need to keep track of that ourselves we can just destructure that from the use mutation hook and implement it in the create Community button to actually make it usable for example we can pass along the is loading value as it is its loading is is loading then the disabled value for this button should be if the input dot length is triple equal to zero if the user hasn't input anything the button should be disabled and then lastly let's add an on click Handler format this and on click we want to call the create Community function and invoke that so when I now press the button oh no query client set use Query client provider to set one what this means is react query what we're using right here depends on a context and in xjs passing context is not as easy as just slamming it into your layout instead we need to create a separate component for the providers this is a best practice to use context in extra s13 take a look at how this works we can create a providers.tsx component and inside of these providers this is going to be a pretty standard react component cons providers is going to be an arrow function and we export default providers just like this but with a difference of um we are receiving children as props for these providers because we're going to wrap our application with them so we can receive the children inside of here and if your typescript the children are of type react.react node we already know this from our main layout okay and what we can do now is first off declare this as a client component because context only works client-side so this cannot be pre-rendered on the server and what we want to do now is first let's return the something called a query client provider we get from tan seg react query and let that wrap our entire application that is going to be passed in as the children now this query client provider also demands something called a client and this client let's call it const query client is going to be a new query client instantiation that is a class we get from react query and invoke that now what we can do is pass the query client right here into the client don't worry I know this was a bit fast you don't really need to understand what's going on I don't exactly know what the query client is for anyways the important thing is how to use it and this is how to use it and the important thing is also to know how to use react query and that's what we're going to get into not exactly what the query client does it just just know that um react query needs this context to work properly so if we save that go into our main layout.tsx and then wrap our entire app inside of these providers starting from the body up here providers and ending at the very bottom right below here we can now assume that um the react query knows what the query client is and we can also add context in the future just into this component and it will work just fine awesome so we can now see if we give this a bit more space it look better we can actually create a new Community hit create community and currently the apro to do so um doesn't work um there there is no API Rod like this if we go into the network tab we should be able to see this let's click create Community we get a 404 the API route to create this community and just doesn't work which makes sense because we haven't created it yet so let's take a look where we want to create this and exit out of a lot of this stuff by the way don't need that don't need that don't need that we only need the page.tsx so we're sending that to slash API slash subreddit and if we look under slash API there is no subreddit API to send it to so let's create a new folder called subreddit that is going to be reflected in the URL under slash API right here subreddit right here and then a route.ts enforced by next.js this naming and just like with next auth and it needs to be called the route.ts from where you are exporting the HTTP verbs to correspond to whatever you want to do um like a get request for example or a post request a patch request you would export that corresponding HTTP verb from right here let me show you what this looks like so we're going to export an async function post because we expect post requests and the rack is going to be incoming as the request type a standard web request no specific next.js type or anything I'm just um this fetch API interface represents a resource request just very generic you know and inside of this post request I want to show you some best practice Logic for the back end because this is a back-end function um first off we want to do everything in a try catch block so if anything goes wrong we can handle arrows properly that's very important to me that you learn how to properly do this stuff first thing we want to do is determine is the user logged in because in not logged end user should not be able to create a new community we can say concession is going to be equal to a weight get auth session user Little Helper we have declared in the auth file and if there is no session dot user session is optional in this case it doesn't have to exist in that case we want to return a new response saying unauthorized with a status code of 401 back to the user okay now we can assume that the user exists and they are logged in so now we can get access to the body content of the post request and this is not the rec.body this is not any more direct.body in Nexus 13 rather it's the await rack dot Json that's how we get access to the body content and now for the most important part the validation this body can be anything everybody can pass anything into this API rod and we want to make sure that only the data we are expecting to work with is actually passed to do that we're going to use these subreddit validator.powers and we can pass anything in here right we can literally pass anything in here like the body we have no idea what the body is pass it in here but if it parses successfully if it works we know there is gonna be a name so we can destructure it if this fails if there is no object name String in here if there's anything else we are gonna land in the catch block because this is automatically going to throw an error for us and very very handy and we're gonna properly handle the error here in a second too first off let's see if the subreddit already exists because remember this is the separated name and if it already exists we can't create another one with the same name so cons subreddit exists it's going to be a weight DB import or database from lib BB dot subreddit and did we not sub does this not work yet now we still haven't generated the Prisma types okay so what we have done is change our Prisma schema to reflect everything we need in our app but we haven't posted or pushed it to the database yet to do that let's say yarn Prisma DB push that's going to push our local changes up into our database so they're reflected in the cloud and then we want to run yarn Prisma generate to generate the typescript type so we can locally work with the new types we can start the dev server back up again by running yarn Dev afterwards and then press Ctrl shift and P and select restart PS server so the changes from Prisma are reflected actually in or development environment and now we should be able to see that there is more stuff and there is and we want to check for a certain subreddit right here we want to find the first subreddit where the name matches the name that is being passed into this and if there is a subreddit if subreddit exists in that case we're going to return a new response saying subreddit already exists and send back a status of 409 conflict because there is a same name conflict going on and only if that subred does not exist in that case we can actually go ahead and create it so cons subreddit is going to be equal to oh wait DB dot subreddit dot create and we want to create it with the data of the name so r slash react r slash next JS or in our case just react next to S and without the r slash and then secondly the Creator ID in case you wanted to enforce some limits so everyone can only create three subreddits if that's what you wanted to enforce and it's always useful to keep track of how many each user created by using their session.user.id okay and afterwards we're gonna subscribe the Creator to their own subreddit because you can't just create a subreddit and then not be subscribed to it if you create a subreddit you have to be subscribed to it you are the owner of that subreddit and to do that let's say await DB dot subscription dot create and we want to create this with the data of the user ID being the session dot user dot ID and scroll down a bit and then the subreddit ID we're linking those two together with the subreddit dot ID that we have literally just created up here awesome and if everything worked out if both of these database calls are successful let's return and your response saying well actually not any string let's say the subreddit dot name so we can then handle the success State on the front end that's going to be a very cool part as well I'm excited to show you that and then in the error let's catch the arrow and if the error because right now this could be anything we have no idea what the error is it's type unknown anything could go wrong from you know the powers right here failing right to anything in the database just not working right now we have no idea what the error is and to narrow this down we can say if arrow is an instance in stands of n a z Dot Zod error so if this is true we need to import Z if this is true we know that the arrow is thrown because the parsing failed in wrong data was passed into the route in that case we can pass or return a new response saying error.message we know this exists now because we made sure it does using the statement the error. message with a status of 422 unprocessable entity the data you passed is not valid you could also return a bad request status code if you wanted and both work in this case and then otherwise we're going to return a new response because we have no idea what the error is and it's going to say could not create subreddit with a status code in here of 500. great let's save that really really nice so now we should be able to create a subreddit let's see if that is the case is the dev server backup yes it is let's give this a bit more space like so and reload the page this takes really long and then let's create a new subreddit for example let's click create community and see what happens ideally we will be forwarded oh no we won't be forwarded but we can see what happens in the network if the request is successful let's say new subreddit 2 create community and we get a what is that error 4 or 1 unauthorized from the router TS interesting why is that oh because we're not signed in how did I forget that okay but now we know this works properly let's login with Google let's log into my admin account and then let's try that again at least now we know the security measures are actually working and let's go to create Community right here my community let's clear the request click create community and hopefully that's going to have a 200 status code my community it was created successfully awesome very very nice we can now create our own subreddits hey this is me editing Josh and unfortunately one part of my file got corrupted so what you're about to see is the error handling for the part we have literally just done how to properly error handle this so don't be confused if some for example I don't know at this point yet that the create that the creation of a community works and we're gonna find that out here in a few minutes and first to the error handling and the on success handing together so again don't be confused and let's properly handle the arrows and get right on with it and the reason this could be an access error is because we're using axios to do the pulse requests in the first place so if the error is an instance of the axios error class in that case we know which properties exist and we want to do a conditional check if the error.response dot status is equal oops that needs to go into the parentheses is triple equal to 409 in that case we want to return a toast notification that we can import from or Hooks and this tools is going to get a title of the subreddit already exists period then we want to pass a description and that is going to be please choose a different subreddit name and then lastly for this conflict toast notification we want a variant of and we can either choose default or destructive and we want the destructive toast awesome that is some proper error handling however we still don't have the case if the name is invalid for some reason right if it's too short or whatever if it doesn't match the validator we're using on the back end this right here we are going to get back we see that right here a 422 error and we're not handling that yet so let's also go ahead and check and actually we can just copy this down let's remove that let's mark the entire 409 block inside the access Arrow and copy it down using shift alt and arrow down insert a space here and now we want to check for a 422 status code and if we get a 422 we want to say invalid subreddit's name and then we can leave the rest please choose a different name that's good or we could specify um please choose a name between 3 and 21 oops characters just telling the user what they need to do right because if they try again they need to know what the requirements are this is going to be destructive as well awesome if it's neither a 4 9 or 422 let's check if it's a 401 unauthorized and if it is we want to prompt the user to log in so and I change my keyboard to English that's not ideal if the error.response dot status now is triple equal to 401 unauthorized which it will be if the user is not authorized we can see right here we're getting the session as if there is no session we are returning a 401 so let's check for that on the front end and make sure if the user is not logged in let's return and now we want some sort of login tools notification right but um it would make a lot of sense declaring this log and toast notification in its own file because we're going to be reusing it in a lot of different places across our app anytime the user tries to do something and they are unauthorized we want to show this toast and therefore let's create that toast the login toast notification inside and we are going to create it inside of the hooks right here we've already got the use on click and use toast now we want a use custom toast that's what I call it use custom oops custom Dash toast dot TSX important TSX because we're going to be returning some JavaScript XML from this use custom toast file we have just created and inside of here let's export a constant use custom toast this is going to be an arrow function and or login toast cons login toast is going to be also an error function and we can already go ahead and return that log and Toes at the very bottom of our use customtos hook as a named export or a named return in that case awesome and inside of this login host we get access to something called actually let's do that in a second first let's just call the regular toast hook that we have from or hooks the use tools right here and now we can see if we invoke that we can destructure some properties like the update ID and what we want is the dismiss so if we want to if the user clicks the login button we obviously want to dismiss this tools notification this toast is going to get a title of login required period Then a description of you need oops let's capitalize that why like that you need to be logged into do that period and we want a variant of you can already guess it we don't want the default one we want a destructive variant um you know that's going to give it this red appearance something is wrong and the user needs to fix something awesome and then one thing you can also pass in here super convenient is the action and then here we can Define JavaScript XML jsx or TSX in our case which is going to be a link component from next slash link inside of here we're going to say login and you already know the drill for the class name we want it to look like a button so we're going to do a dynamic last name the curly braces with the button variance import those and for the variant we're gonna pass the outline awesome now this link also needs an href and this is going to lead the user to slash sign in and also we want it on click and that is going to be a function right here in this syntax and that is going to be calling the dismiss that we have this structured right here from the toast so whenever let me remove that whenever the user clicks on this dismiss or on the login button rather then the toast is going to be dismissed awesome let's save that and what we can do now is go back to our create page and actually return that toast notification before we can do that we need to import it in our file and let's do that at the very top const or what the distraction later is going to be equal to use custom toast that we can all simply Import in here and then destructure or login toast and if we go down here return we can just return the login toast and invoke that awesome so whenever the user is not logged in trying to perform the create action they're gonna get a toss notification saying they can't do that let's start up the server and try this out let's go to localhost 3000 and try actually I might be logged in which would not be ideal but if I am I'm just going to open this in incognito tab um let's see if everything's loading yep seems to be working awesome okay so I'm not signed in let's click create community that should take us to the create page awesome my community and now we are not logged in we should be able to see the toast notification and we don't nothing happens so let's see what happens in the network Tab and okay so we're not even calling the create Community API Rod so this can't work let's see why that is it's probably because we've never used right here the mutate in the first place okay that makes sense let's call this mutate or all name that is going to be create community I usually do this because the mutate is not very you know it's very generic it doesn't really describe what we're about to do and while we're here let's also get the is loading um state from the use mutation great and now the question is where do we want to create our community when should that action be triggered and of course it should be triggered right here on the create Community button so let's add an on click Handler and that's going to be a function to invoke the create Community function we have these structure from react query awesome very very good we can pass this button a disabled property a dynamic value of the input dot length is going to be triple equal to zero and we can also add an is loading State and that is just going to be the is loading we have also restructured from 10 stack query awesome let's save that go back and see what happens since I'm not logged in we should see a destructive notification if I enter anything here it's going to try to make the API call but there we go there's the toast notification login required you need to be logged in to do that awesome that looks very very good and if you click the login the toast is dismissed and we are forwarded right here to our login model that is beautiful user experience very very nice let's move this into a side by side again and see if there's anything missing uh why did that not work hello move this into a side by side and let's see if there's anything missing so for example we are not catching any error that is not an axis error and because an arrow can pretty much be anything that's not ideal so let's go below this if error instance of access error and handle any other error and we're going to do that by saying toast and now there is going to be a very generic message because we don't know what the error is with a title off there was an error this could be anything right from a API row that takes super long and axios automatically ending the request or whatever um we are just going to return this one notification as the description let's yeah let's say could not create subreddit period and then lastly as the variant this is going to be destructive as well awesome now we can do one last thing and that is handle the on success State and whenever we are successful in this um Endeavor in creating the subreddit we can receive the data the string we get back from our request right here in the on success and now we can say router dot push and what do we want to push in our current routing structure what we want to push is the slash r slash and then a dynamic value of this data which will essentially be our separated name so if I created a community community called r slash react that would be pushed into or console let's try this out let me sign in and then let's try creating a community let me go to Google login with my wordful AI admin account and click create community and let's create the react Community let's click that and see what happens and it forwarded us to the r slash react subreddit which doesn't exist yet but it works awesome very very cool and what that means is that we we can I think we're done here yeah I'm pretty sure we're done on the create page very good job we can close out of all of this that means we can get started on the actual subreddit page with its posts and so on what that means is now we can go and get started in a really fun part and that is the actual subreddit page how does that work we're going to learn about Dynamic routes we're going to learn about how to display posts and conditionally or um subsequently load more as the user Scrolls and it's going to be a really really cool part of the build and how we get started is first off receiving the data for this route in our routing structure currently we only have a create page in our r slash so r slash create Works everything else throws a 404 so in our R folder let's create an angle brackets a folder and that is called Slug and this important needs to be in R and not in create there we go so at the same level as the create folder we want that and inside of the slack folder we can have a and let's create it in hello created in here a page dot TSX and this is going to be the default page whenever somebody visits or um slug so any slug Works let's create a functional component that will be displayed right here if we visit the r slash react again this generic page that doesn't do much now is going to be shown and what we want to do in here is we want to fetch the subreddit the user has just visited so take a look at this there is r slash react in the URL bar right and to fetch the data for the subreddit that we want to display in this Dynamic route we need to receive that from the URL somewhere into our code and the way we do that in nexjs is super simple we can receive the params and these are automatically passed through this page right here and we can Define the type of the params which is going to be page props because we are in typescript now the end okay that doesn't go well let's remove that so we only have this right here and now the page props let's close that are gonna be the params as an object this is always the case because we are getting past the r slash react as the params and now comes whatever we have in the angled brackets right here whatever you call that what you call it is not important but it needs to match the string we put right here in my case that's Slug and in your case if you're following it should be two slug right here as a string if you call this like uh I don't know um react like in angle brackets then this would be react but slug just makes a lot more sense awesome and what that means is we can destructure the slug right here from the params in our component and use it to do the data fetching so whatever this log is we can try fetching the data for that but before we do that we want to verify the user is even authenticated now it's fine if they are not we're not going to block them access to the subreddit obviously let's say a weight get auth session and turn this into an asynchronous component they're still going to have access but we want to just display a bit different stuff um to them if they are not currently logged in now after getting the session we're going to use that in our jsx we can first off get the subreddit that the user has visited for this Dynamic route and that is going to be equal to a weight DB and we're going to make a database call to get the data for this subreddit so db.sub Reddit Dot and now find first now which subreddit do we want to find we can say where and now we're the name of the subreddit like uh react or whatever matches the slug that was passed into this page by next.js automatically so essentially what is in the URL up here in under um react in our case right that is the where condition and we want to include certain data in that request for example the posts should include include pass another included here so a join in Prisma the author we want that and we also want the votes to be included true then the comments we also want those and lastly we want the subreddit data to be included so we're doing a pretty large join here and later on we're going to find out how to optimize these large joints with redis it's going to be very very cool and so it's going to significantly improve performance and what we want to take we want to take is certain amount of posts now the question is how many do we want to take because each post that we take is going to be displayed to the user immediately but it also costs Computing resources to load each post so what we're going to implement a bit later is infinite scrolling Behavior where only a certain number of posts are fetched on the initial request and then only as the user Scrolls we are actually fetching more posts from the database so as a demonstration for that initially let's just take like two posts and instead of declaring this take value in line right here like as a number or whatever let's define it in something called a config.ts at the same level as or Source folder so let's create a config.ts in which go you know Global config variables in our case that's going to be the export cons let's call it infinite underscore scrolling underscore pet oops uh what did I do underscore pagination underscore results it's very verbose I know but I think it you know just makes sense to call it something this long um so so we know exactly what it is and this is going to be the only value for now and again this lives at the same level as our source folder and not the app folder so right here it's not super important you could declare it inline again I just think it's a good practice to keep it separate so if you change it here because we are gonna be relying on that in a lot of places we can just change it in one place let's import that from add config and that is all we need to get the subreddit awesome now if this separate does not exist right we want to throw an error so if not subreddit so either this is going to include the actual subreddit or it's going to be null and if the subreddit could not be found if the slug does not match any subreddit in the database then let's return not found like that and we can import this not found from next slash navigation at the very top of the file import not found from next slash navigation awesome so whenever there's no data this is going to throw a 404 error for us and with everything else handled so it's really convenient helper we can just choose to display four or four to the user if this subreddit doesn't exist now we can assume because of this guard Clause up here that the subreddit does exist and we want to display it to the user so let's render out an H1 right here in the return with a class name of font Dash bold a text of 3 XL on medium devices and up we want a text of 4XL and we want a height of 14 on this H1 and this is going to say r slash and then the subreddit dot name right here awesome so we're displaying that to the user and we can already see what this looks like let's reload the page and give it a hot second to load r slash react that is the subredditable on very very nice and if we go to any subreddit that doesn't exist then we are going to get a 404 which is you know on a white background it doesn't look the best but we can do additional custom styling on that later and for now let's create one component right below this H1 because we want to indicate users that they can participate in the subreddit so there should be a little button encouraging users to post their own stuff like a mini post Creator and that's what we're going to call this component a mini create post component that doesn't exist yet so let's create it and it's going to look very very nice and you're gonna see that here in a second let's go into our components folder create a new file and this is going to be called the mini create oops the mini create post dot TSX this mini create post is going to be a client component so let's define it at the very top as use client so we can make use of client-side apis and especially you know the hooks are going to be important for us let's create a functional component out of this and the hooks that we want in here are first off the router once router is going to be equal to use router from again next slash navigation not next slash router very important and then secondly the const path name is going to be equal to and this is going to be path name written correctly is going to be equal to use path name we also get from next slash navigation those are the two hooks we need and then as props we only receive the session and this is the reason we fetched the session in the page right here up here because we're gonna pass it into this component the reason we're not fetching this client side is because if we do the service side it's immediately going to be available and no layout shifts and no loading States and whatever just way more convenient session that's receive it as props and this type session is from next auth and not from Prisma or it could also be null if the user is not logged in which again is fine we're still going to display data to them but just a bit of different data great now inside of this mini create post let's first return An Li element for a list that gets a class name off overflow Dash hidden that we want a rounded Dash MD for medium ABG of white and lastly a shadow then inside of this Li let's have a div with a class name off height full we want padding X of 6 we want a padding y of four on small devices now we want a flex layout with also on small devices and up a justify between and a gap of six then one more diff with a class name of a relative right in here and in here we want to render out the user Avatar right um I could make you a drawing of what this looks like but this is going to be pretty quick so you're going to see it here in a second anyways and we already know how to do this with the user Avatar component we can simply import that we've already done that and in here we want to pass the user and that is also why this session right here is so handy we can just use it and pass it right into this user Avatar because we know this takes a user and this user takes two properties that is the name and the image and for the name we can pass the session dot user dot name which is optional that's why we have a question mark here it doesn't have to exist and if it doesn't we're gonna pass null and secondly for the image we want to pass the same thing as the username but this time obviously not the name but the image instead or if that doesn't exist we are going to pass null into this user Avatar let's already import this into our page.tsx the mini create post and again we need to pass the session into here so we can say session is the session that we retrieved on the server save both I'm going to switch back to this component in case you missed anything while I reload the page right here and we can see okay uh we can kind of see where this is going but it doesn't look um amazing yet um but we're gonna get to that and part of it is because I'm so zoomed in anyways um below the user Avatar let's have a span element to show the online status of the current user and this is going to be pretty straightforward this is going to be absolute class name absolute with bottom zero right zero rounded Dash full we want a width of three and a height of three then a background green Dash 500 and outline like that and outline Dash 2 and then we're almost done one more thing that is gonna be outline Dash white so the actual color of the outline I'm gonna go to the right side in case you missed anything um with three High three and so on and then after closing that or inside of that span rather um wait there's nothing in this band this is going to be a self-closing span right because this is just a green circle let me save that and show you what this looks like and this looks kind of weird this shouldn't look like that did I mess up the styling hello this is me editing Josh and I noticed yeah this does look weird but it is fixed in the final GitHub repository and don't worry about it and for example one reason why this might look weird is because this is not responsive and to make it responsive the only thing you need to do is to remove these SM properties so we always apply the flex and Always by the justify between no additional CSS classes needed for this um to be responsive and that's just the fix to make it look better it looks kind of weird but I'm gonna fix all the CSS issues later if there are any maybe that will resolve itself by just going on with this component um all right so next up is an input element we already know where to get that from because it's our own component and this input element is going to be the create post input it's going to be a read only so we can pass a read-only prop and what we want to happen is if we click this input we're actually not taken to an input but rather forwarded to the page that lets us actually and create the post because that's how Reddit the actual Reddit does it as well so on click we're going to say router dot push dot push there we go and we are going to push the current path name that we retrieve up here from the use path and hook and concatenate that with Slash submits submit there we go so for whatever subreddit we're in we want to submit a post to that subreddit and we can also enter a placeholder something like create post let's format this so it's a bit easier to see and then right below that we're going to have a button we already know where that comes from it's also our own component and this button is going to have a variant of ghost and then on click the same thing should happen right this is just for decoration purposes so we can also copy the on click Handler and we can take a look at what this looks like reload the page we can see okay there we go it's the create post and because I'm so zoomed in it looks a bit weird I might need to fix that behavior in the final repo um so yeah okay so it does work correctly but it looks a bit weird and responsive I'm gonna fix that in the final repo and don't worry about it for now if you're zoomed out it looks as it should and then lastly we want one more button we can simply copy this button down and this button is also a variant ghost same thing should happen on click and inside of this button this time this is not going to be self-closing but instead we want to render out an icon inside of this button and this icon is going to be a link to Icon we get from Lucid react this one right here just go ahead and import that icon with a class name of text zinc 600 let's save that and take a look at what this looks like reload the page there we go okay the image is still missing because that button should not be self-closing this should also be a button that renders out an icon and this icon is going to be the image icon also have closing we also want to import that from Lucid react with a class name of text zinc 600 as well awesome let's save that and that is our component done very nice job again I'm gonna um I'm gonna fix the CSS for the responsive layout this is not how it should look that looks a bit weird on mobile but on desktop that's exactly how it should look even if I zoom in a bit further very very nice we can see the image the link and if you try to create a poster forward it to the actual and post creation page which you know doesn't exist yet but we're gonna create that in just a second okay so there are two options we could do now let's take a look at our game plan right now we want to be able to create subreds we can't do that and we want to be able to join subreddits that is going to be the next thing then comes creating posts and then comes the Post Feed so after all we're already done with the first step of three so now we want to be able to join subreddits let's do that and The Styling that's going to be involved for each subreddit like this is the most elaborate in this whole project and this is the most CSS that is going to be involved but it's also the biggest step in making making this look really good with the main functionality right the subreddits a very very key part of this app and I think it makes sense to build that next because it is the joining subreddit step as well and because we're going to have the join button so next up let's actually work on the subreddit logic and then the feed later on so below the mini create post we can enter a little to do right here to do um show posts in user feed but in order to show the posts we have to create posts first and so we can exit out of everything else and let's get started in the layout of each subreddit first because there's a lot of logic in each layout that gets handled and the way we create that is as a file in the slug folder that is called the layout dot TSX this is nothing else than a wrapper component around all the page contents that is going to define the layout of everything that is below or on the same level as this folder and we can either use my cool LC snippet or if you don't have that let's create the layout together as a cons layout it's going to be an arrow function component that we also want to export as the default at the bottom of the file awesome because we declared this as a layout file automatically because this is a reserve name from nexjs we got past the children into this component these are nothing else than react components so we can type them out as react dot react node we already know the type of those and now we can render the children in our layout return them somewhere in our code for example inside of a div we could say children and now the page would be displayed just normal nothing changed whatsoever I mean we only wrapped them in a div but that doesn't really matter but now we can go ahead and actually do all the siding for every single subreddit all at once inside of this layout file it's super super powerful and it comes with a new nexjs13 app router that's why I really like it obviously not just because of this but it is one very very neat feature awesome so the div we already created is going to get a class name and that class name is going to be small container then a maximum width of 7xl and MX of Auto a height of four and a padding top of 12. awesome format that and one more div in here actually um yeah I'm just going to leave the children out for now we're gonna do them later so one main div containing everything else and then one div inside of this and inside of this div we want to create a button that takes us back to the feed but first I would say we're gonna finish all the CSS in this file and then worry about that so to do button to take us back because when we're in a subreddit like this we also want to have a button in the top left taking us back it's just for good user experience and we're gonna do that in a second first let's get the layouting and the Styles in this file done okay but we can already take a note here we're gonna do that and then below that it's gonna be a div with a class name off grid grid calls one then on medium devices and up grid calls three gap y4 on medium devices and up a gap X of 4 and the padding y of six not seven inside of the stiff is gonna be one more div with a class name of flex Flex call then a call span of two this is the grid layout of the actual subreddit with a space y of six and in there go the children we get passed into the layout automatically by next.js awesome then below this diff containing the children we want or let's do a little annotation here or info sites bar and we're gonna build this out right now unlike the the button at the top here let's build this all together that's going to contain the subreddit information like the name if you created it the amount of members and so on very elaborate that we're gonna get into later there is going to be some logic involved in this okay first off we're gonna have a div with a class name off hidden a medium block an overflow Dash hidden a height of it rounded Dash large and a border and let me go into full screen so this is easier for you to see a border Dash gray-200 an order Dash first and this is to determine um the order in Mobile versus in desktop view because on medium devices and up we want a order Dash last and so this just determines if it's shown like above the content below the content and that's what the order is for and that's all the class names already quite quite a bunch to be honest and then inside of this div let's create one more div with a class name of padding X6 and then a padding y of four inside here goes one P tag with a class name of font Dash semi bolt and a padding y of three inside of the speed tag we're gonna display the name of the subreddit so this is going to be about r slash and then we want the name of the subreddit now where the hell do we get that from we don't have it in here we don't know what the name of the subreddit is so how do we get the name of the subreddit and when we have a dynamic layout it works just like the page right we were able to get the parameters right here passed automatically into the page in the same way it works with the layout so what we can receive as a second prop in the layout are the params and these are containing the slug that we care about now we already know the type of the params right we knew these in the other Pages well you can log them out by the way if you have no idea what props are passed just receive all of them mark them as any if you want and then log them out to the console they will all show up if you're very unsure about the types or um you read through the next.js documentation that's where it also says what the types aren't how to know what those are we know the slug is of type string and therefore we can just receive it right here in the layout and what that allows us to do is first determine if there is an active session for the user we're gonna need that for later so let's quickly say concession is going to be equal to a weight get off session or Little Helper and because we want to await at the top level we have to mark or layout as asynchronous so let's say async right here before the and before the parentheses on this component and therefore we can now get the session and the weight at the top level in the new next year server components awesome and now to fetch the several information that's nothing else than we already did on the page and by the way if you're wondering isn't this redundant we're going to do the same logic again well it might look redundant and cold but requests are under the hood deep duped by next.js so we don't need to worry about performance overhead in that way that happens automatically under the hood it's very very convenient so we can say can't subreddit is equal to await DB dot subreddit dot find for oops find first there we go and we want to find the first where just like with the other component the name is the slug and then secondly we want to include certain tables so we want to do a join with the posts and what these posts should include again so include posts include now comes two things first of the author is true because we want to display the author name and then secondly the votes are going to be true as well and we need to comma separate those awesome so we now have access to the subreddit um that the user is visiting and we also want to know whether they are subscribed or not right um to show the button allowing them to subscribe or allowing them to leave the subreddit and the subscription if they are already subscribed and the way we can do that is let's say con subscription this is going to be another database fetch we're doing and if we have no session dot user then we want the subscription to be undefined there's no need to do a database Fetch and by the way this needs to be optional because there there might not be a session if there is none we don't want to do a data by fetch it's totally unnecessary because we know there won't be one the user is not logged in however if the user is logged in then let's actually try to fetch their subscription so we can say in that case with a ternary operator if this right here is true and then it's going to be undefined or else that's what they call it is for we're going to await the DB dot subscript oops not subreddit subscription dot find First and let's remove this so we can see better we want to find where the subreddit is where the name matches the slug so inside of an object right here where the subreddit name matches the slug and then below that where the user has the idea of the session Dot user.id and the cool thing is because we have this statement right here and if this is not true if there is no session.user we won't even get to the statement so therefore we always know there will be a session.user.id if we get to this database fetching step very very cool and then let's determine a Boolean from that let's call that Boolean is subscribed and this is just going to be a double exclamation point turned into a Boolean the subscription let's just copy and paste that over there we go so we can see we turn this into a Boolean originally this was either a subscription or null or undefined that's what these two double exclamation points are for they turn a value into a Boolean awesome and then if we have no subreddits no subreddit you know the drill let's return not found that we get from next slash navigation to return a 404 error to the user very very good and then one last thing we want to do is we want to determine how many members are in the subreddit when we visit the subreddit the user or every user visiting should know how many members are in there and to do that let's say cons member count is going to be equal to a weight DB dot subscribe oops subscript subscription Dot and there's a very nice method we get that is called count and now what do we count we want to count where the subreddit and then another object where the name is the slug so for that subreddit we want to count how many subscriptions are there and that is it because everyone who is subscribed will have a subscription in the database we're only counting how many of those active subscriptions there are and with those values out of the way with the session the subreddit the subscription is subscribe Boolean and the member count we can get started in finishing this layout to make it look good and be very informative to every user taking a look at it to get started let's go right below the P tag and then the closing div press double enter so we have four closing divs below that and let's create a DL tag right here this is going to contain a class name of divide Dash Y and divide Gray 100 padding X of 6 a padding y 4 a text of small a leading dash six and a BG Dash White awesome let's format that and go in there in so into that DL in here we want to create a div with a class name of flex justify between Gap X of 4 and a padding y of three and again this file is I'm pretty sure the file that contains the most CSS if you're not into CSS don't worry it's going to be over soon and we can leave the CSS mostly behind after this awesome so inside of this div let's create a DT to contain the information or first of the label with a class name of text oops text Gray 500 that is going to say created then let's create one more DT right below that and this is going to contain the actual information with a class name of text Gray 700 inside of this DT we want to say the time that this subreddit was created so to do that let's use the actual time semantic HTML time there we go and this takes a date time that we can actually pass the date time into for accessibility purposes right so everybody can know when this was created even if they're using something like a screen reader this is going to be the subreddit subreddit dot created at dot to date string invoke that important great and now inside of this time we are going to use a custom function that lets us format or time into a human readable format and to do that that's called The Format function and we get that from a library called Date FNS this is a library you normally have to install again it came pre-installed in your starter code very very handy but normally you'd have to install this yourself and here we can pass the subreddit.created ad and then secondly the format we want it in this is a regular string if you want to know why the string is as it's going to be look into the documentation essentially we can turn regular strings like mmm then a space d comma space yyy so the format we want months date and day and year from the actual time we can simply convert them let's quickly save that layout reload the page and take a look at what this looks like we can see about our slash and the name for some reason doesn't work right now um aha right because we didn't include that let's quickly fix that this is going to be the subreddit dot name of course right here save that and now it should work about r slash and r slash react awesome and now we can see created June 11th 2023 very very nice so it's in human readable format using the M format right here from date FNS looks great nice okay and you can see on very small devices this just completely disappears it's not that necessary to clog up the entire screen with and then on larger devices it shows right here on the right hand side okay and with the formatting out of the way and by the way we can just change the DT to a to a DD tag for better semantic HTML but it doesn't really matter anyways with the formatting out of the way let's go down the D-Day tag and down the closing div and then above the DL right here and below the div this is where we're going to create one more div with a class name of flex justify Dash between a gap X of 4 and we want a padding y of three in here also with a actually let's just copy from up here we want the exact same thing so let's copy the DT and the DD elements paste them in here and then slightly change them so instead of created this is going to say members and then instead of the time and we want to display the member count inside of a div in here with a class name of text Gray 900 and we can just say member count that we have calculated above just put it in here awesome and then below that div let's say if the subreddit dot Creator ID if that matches the session dot user dot ID in that case you want to display some jsx and if it does not match we want to just display nothing null and if it matches in that case we're going to display a div with a class name of flex justify Dash send and justify between not Center justify between a gap X or four and a padding y of three and in here a let's put a P tag saying you created this community there we go and this P tag is going to get a class team of text Gray 500. like so awesome let's save the layout and let's see if this server is started let's start the server and as we can see if we take a look at this in the browser it says you created this community here now with a members count of one awesome that looks really good and let's finish up this layout and what should be the next move let's move this into a side by side by the way so when we are in the subreddits and this looks a bit weird from the CSS but we're going to worry about that later um let's think about the action that should be taken inside of a subreddit when we join a subreddit or when we visit one we should have the option to join the subreddit to make posts right we are requiring users to be members of a subreddit in order to post and therefore we want to offer them a button to either join the subreddit if they aren't or leave the subreddit if they are and to do that um we are going to display a button to them but conditionally and the condition is going to be subreddit dot Creator ID if that matches if that does not match the session.user.id and this session is optional it doesn't have to exist in that case we're gonna display some jsx and in the other case if the subredded Creator is the currently logged in user we're gonna display nothing null to them and only if they aren't we're going to display a component to them that lets them toggle the join and leave status if you own the subreddit you shouldn't be allowed to leave it's your subreddit and there can't just be separated with no one inside of it that's why we have a conditional check right here let's call this toggle the Subscribe leave toggle very straightforward naming and that is a component that does not exist yet so let's create it under components and then subscribe leave toggle.tsx and inside of the Subscribe leave toggle is going to be encapsulated a bunch of logic for example how we subscribe to Reddit how we unsubscribe from a Reddit and it all starts as a functional component we can initiate right here and the jsx is going to be super straightforward let's quickly Mark something out right here and that is going to be const is subscribed is gonna be false we're just hardcoding this for now this is going to be changed later um just so we can already finish up the jsx so we're going to return from this component in a conditional so if we are subscribed if is subscribed then we're gonna return a button in the parenthesis and else we're going to return another button the first button we're going to return we need to import from our UI UI and then slash button and then the second thing is going to be the same thing also a button the first one is going to say leave com Unity because if we are subscribed we should be able to leave and if we are not subscribed then it should say join to post inside of the subreddit awesome let's format that and now comes some styling first and then the logic for this um component the styles are going to be for both buttons a width or full a margin top of one and a margin bottom of four and if you're curious as to how I wrote in multiple places hold alt while you click and then you can do the same thing styles are the same for both buttons awesome and that's gonna be it let's import the Subscribe Leaf toggle and see what this looks like let's hit refresh and go into full screen and take a look at the button that should appear not for the person that owns the subreddit again we need to go into Incognito to see this and there it is jointer post looks awesome and it's not shown to the subreddit owner just as we intended great so inside of this subscribe leave toggle um we want first off to be able to subscribe to a subreddit and to do that let's use react query and because we want client-side interactivity like listening to a button click we need to turn this into a client component instead of the default server component because we're going to be using client apis let's say cons and the object the structure later is going to be equal to use mutation you already know what this is and we're going to make use of it again right now with the mutation function being what's submitting the data to our M API as an asynchronous action okay and inside of here first you know the drill by now let's define the payload that we want to send over the wire that's what I like to do I think it's a great approach to get a good degree of full stack type safety and this payload is going to be of type subscribe subscribe to separated payload we've already created that earlier while we were in this file so we don't need to worry about that now just for your memory this is the subreddit ID as a string that we are enforcing in this payload and now we get type safety for this subreddit ID that we need to pass in here and where the hell do we get the subreddit ID from in the Subscribe leave toggle and the answer is as a prop so we get it passed as the property subreddit ID because we can easily do that from the parent after all we know in the layout what the subreddit ID is and as the type subreddit ID this is going to be a string so we can just use it in the payload and send it along cons data is going to be equal to a weight axios import axios dot post we're going to make a post request and we're gonna make this request to a slash API slash subreddit slash subscribe endpoint and what are we gonna pass for this post request well we're gonna pass the payload that we can then expect on the API route and return the data as string we are casting this because we know that we own the API endpoint and we are going to return a string from here okay let's quickly actually should we do the error handling now no let's not do the error handing now let's work on the API Rod first so to create this endpoint let's go into our file tree and give this a bit more space and go under app API and then subreddit and create a folder because that is reflected in the URL called subscribe and create a route.ts file in here to handle the actual logic of this request now from this router TS file this is back encode we're going to export in async function that is called post because we want to handle the HTTP pulse requests using this function the type is going to be the regular request as with the other routes as well and the logic is going to be pretty similar as well we can just use a try catch Block in here where we first determine if the user is logged in concession is equal to a weight get get off session is what we called it or Little Helper remember and then if there is no session dot user in that case and the session is optional as well if there is no session.user we're going to return a new response saying unauthorized with a status code status of 401 unauthorized awesome so now because of this guard Clause we can assume there is a logged end user and we are fine to find out the body that is the await rack dot Json so we're actually converting the request into what is passed as the body and whatever it might be we want to validate it I've already explained how we do this we do this using our validator we have created in our case the subreddit subscription validator if I hover over this we see we expect a subreddit ID as a string dot Powers we can parse any data it will just fail if it doesn't match the subreddit ID so if it's successful we know we can destructure it else we're going to land in the error part that we're going to worry about later great so afterwards we can say cons and now we want to find out if the subscription already exists because if it does um then you can't subscribe again that just really doesn't make a lot of sense so we can say const subscription exists it's going to be equal to await DB dot DB dot subscription dot find First and we want to search for the first subscription where the subreddit ID matches the subreddit ID that is passed into this API endpoint and the user ID matches the session.user.id this is where we find a subscription and if we find one if subscription exists in that case we're going to return a new response saying something along the lines of you are already subscribed to this subreddit period and you can't subscribe again you silly goose what the hell are you trying with a status of 400. bad request okay and if that doesn't exist the subscription then the user is fine we can actually create the subscription using or saying await db.subscription dot create and we are mapping a subreddit ID to a user ID and we can do that using the data attribute in Prisma passing the subreddit ID and also passing the user ID and that is the session.user.id like this so we are creating this subscription for this user so they are now subscribed successfully to the separated and we can inform them of that returning a new response containing these subreddit ID that initially passed into this subreddit okay and as for the error handling we can literally go ahead and copy it from our other route.ts so in the subreddit raw.ts not the Subscribe the regular raw.ts right here we can just copy this error Behavior because it's as good in this instance as it is in the other so we can just import Z from Zod and have the same error responses I just copied one curly brace too much and for example instead of the error message we could pass something along the lines of invalid request data past if you don't if you can't parse the data successfully using our schema this is going to be returned and else just a generic error probably with a different message saying could not subscribe please try again later or something along those lines it's it's not too important because this is not actually what's shown to the user you could do that I I usually don't do that um usually I just check for these status codes awesome so we've now got or subscribed endpoint that we can use right here on the front end and now we can actually get started in doing the error handling for that as well and the success State first of the error Arrow handing is what separates Junior developers from more experienced developers it's super important and we can just say on Arrow receive the error like this as an arrow function and then handle it accordingly and we can check if the error is an instance of an axios error again the class from axios in that case we can check if there is a certain response status code so if the error.response dot status is triple equal to 401 unauthorized for example you need to log in to do this in that case let's return or log in toast invoke that and we get that from const and where we bought the destruction data is equal to use custom toast that we have created earlier very handy login tools we can just return here and if this doesn't match then um we don't know what the problem is so let's return a generic toast notification I'm saying with a title as the title and we need to import the toast that's why I don't get type safety for the title that's going to be there was a problem for the description and this is going to be something went wrong please try again and the variant you can probably imagine and we can pass if we comma separate this we will also get type safety the variant could be default or destructive we want a destructive red toast informing the user hey something really went wrong here and as for the unsuccess what should happen when or request is successful well in that case we want to refresh the current page without losing any sort of state and the way we can do that is by saying start transition this is something we get from react similar to use transition but allows users where hooks are not available and that's fine essentially we can reload without any state changes um this is going this is taking a callback function and in here we can do something very simple that we need the next JS router for because we're going to refresh the page we need access to the router by using const routers equal to use router we get that from next slash navigation and not next slash router that's important so in the start transition we can say router.refresh and invoke that awesome let's inform the user their action was successful let's return a toast notification with a title of subscribed and a description and did I forget to call description there we go a description of a template string this time you are now subscribed to and then r slash and then a dynamic value using a dollar sign and curly braces and that's going to be the subreddit name now where the hell do we get the subreddit name from we don't have access to it in here after all and so we can simply receive it as props as well subreddit name as the props and the subreddit name subreddit name is going to be of type string we can just pass it in as a prop from our layout and our layout is already complaining and that we aren't passing the data that we're expecting so let's pass the subredded ID this is going to be the subreddit we have fetched from our database dot ID and then the separated name obviously you can imagine what this is the subreddit dot name and the arrows are gone beautiful passing those into our subscribe Leaf toggle made all the arrows go away and now if we subscribe successful we can see you are now subscribed to our slash subreddit name beautiful however what's about leaving a subreddit what about um you know when we want to cancel our subscription we don't have any logic to handle that yet um but first off let's just do the joining and let's just worry about that to finish this up let's destructure something from the use mutation that is going to be the muted we used to actually invoke this mutation function and let's call it something else let's call it subscribe just a bit more verbose a bit more fitting with an is loading state of let's call this is sub loading so it's subscription loading later we will also have is unsubloading you'll see that and as for the is subscribed that we're currently just smoking out let's actually completely remove that and that's going to be the last prop we accept it's going to be is subscribed like this because we've already calculated that in the layout remember and we can also just pass it in as a prop is subscribed as a Boolean okay the layout is going to complain that's fine we can pass the is subscribed from the layout into the Subscribe Leaf toggle as is subscribed and because we have determined it up here right there great all the arrows are gone I promise that's the last prop that goes into the Subscribe leave toggle and now we can actually handle subscriptions to a subreddit let's try this to try it out let's attach the function to the button on click that's why we turn this into a client component to even make this work so on click of this button let's invoke the Subscribe function great and that's all the button needs almost we want to also pass the is loading State and that's going to be is sub loading we get from react query very convenient and let's try this out and to try this out we need a different account because we have created our slash react we can't just have a we can't just join or leave with the same account that won't work because we're not even showing the button to those people so let's log in using Google with the other account I'm going to use my wordful AI support account here and then visit slash r slash react go there now let's click join the Post open the network tab at the same time to see what happens if we are successful click join to post and hopefully we will get a 200 status there are two members now and there's a button appearing leave Community beautiful good work this looks really nice and in development there was a little bit of a delay even though it was still pretty quick when you actually built this out it's going to be substantially faster almost immediate there's almost going to no delay and it's a way smoother user experience even more so than it currently is awesome so we can see the members refreshed the button changed however if I click leave Community nothing happens yet the button has no meaning and let's fix that so what should this button do essentially a very similar function to the joining of a community so we can just take or code right here from the user mutation copy it down using shift alt and arrow down now I want to make it clear yes you could also handle this all in one API row just send a request to this and handle or search for a subscription if it doesn't exist you subscribe you subscribe and if it does exist you unsubscribe just as a toggle API Rod I prefer this approach um I think this is a bit easier um to write but you could also do one API endpoint to handle both actions we're going to create two API endpoints and two mutations to handle those you could call both approaches to be honest um I just prefer this one okay now we are gonna make a second API endpoint and that is going to be unsubscribe right here and it's going to be reflected in the method or in the function name as well unsubscribe and then here is unsub loading are we unsubscribing yeah again I mean you could also call this is unsubscribing that would also make a lot of sense let's just leave it like this for now and the payload is going to be the exact same even though the name is not too accurate anymore it doesn't matter same payload same logic same error handling and lastly same success message almost let's say unsubscribe and you are now unsubscribed from r slash subreddit name awesome we can now attach that on click to the button invoke the unsubscribe secondly enter the button for the leaving of a community we want to pass the is loading set and that is going to be is unsub loading give this just a tad more space and I think we are good to go on the button last thing missing is the actual API Rod to handle that and the logic encapsulated in there is going to be very similar um so the subscription of an API endpoint let's create a new folder called unsubscribe that is actually going to end up in the URL with a route dot TS inside of here and just paste the code from the subscription world and let's slightly modify this most of this is going to stay the exact same but for example if the subscription um does not exist in that case we want to return um you are not subscribed to this subreddit because this is a prerequisite for being able to sub so being able to cancel your subscription you need to be subscribed in the first place and if you aren't um then you know you can't unsubscribe only if you are you can unsubscribe and so to actually unsubscribe let's call awaitdb.subscription Dot and then delete instead of create and we want to delete instead of the data we want to pass the where Prisma attribute that is going to be where the user ID separated at the combination that makes up the ID of each subscription and let's disable GitHub co-pilot for this where the subreddit ID matches and the user ID is the session.user.id awesome one more thing that I am going to disable GitHub core pilot for this um and that is check if a user is the creator of the subreddit exactly so um we're gonna say con subreddit is equal to await DB DOT subreddit.find first and let's give GitHub co-pilot a good shot here and see if it can figure this out and this looks really good actually awesome okay GitHub co-pilot did a good job let's quickly walk through this and I promise this is only time we're gonna use GitHub co-pilot I just want to try this out if it could do that um so away DB DOT separated.find first where the ID is the subreddit ID and the Creator ID is the currently um logged in user and now if this subreddit exists oh and I switch to English keyboard again that's not what I want to do if the subreddit exists if subreddit in that case we're going to return a new response saying you can't answer unsubscribe from your your own subreddit it just doesn't make sense you created it either you delete the Reddit um or you stay in it but you can't unsubscribe um so status is going to be 400 um bad requests can't do it okay and if that is fine if you're not a Creator then we're actually gonna delete the subscription return the separated ID and we can say could not unsubscribe please try again later and everything else can say the exact same okay I think we are done with the Subscribe leave toggle we included the logic and the buttons everything should work just fine let's try it out so I'm on the second account we are in r slash react let's click or actually let's reload the page and let's click leave community and it should deduct a member and say join the post awesome now we are in the community members have refreshed again we can now leave the community beautiful so now let's take a look at our plan here um we can create and join subreddits yes sir we can we can put a big green tick on that we created the react subreddit and we joined it and we left it even this should also say leaving subreddits probably and the next step is going to be creating posts that is going to be a very interesting part of this build because it involves an editor so what I mean is when we visit a community we want to create a post possibly right any user wants to create a post and where are we going to do this well let's create a URL structure to reflect that possibility and we're going to allow this under and let's close out of all of these to clean up the workspace minimize everything there we go just clean up the workspace a little bit we are going to create that possibility to create your own post under the r folder under Slug and then for that slot like if you're in r slash react then we want a submit in here so under r slash react submit or r slash next JS submit this is where we want to be able to create a post and to do so let's create a page.tsx to um determine the component that should be shown on this page and initialize this as a functional component and let's um let's remove the FC approach though because we're not going to be using that because we're going to mark this component as asynchronous later and however we don't need to worry about that for now uh yeah actually no we don't okay um and because we're inside of a dynamic route in Nexus inside of the slug you know we also need access to the slug right um and the way we do that in Nexus Dynamic routes has not changed from the approach we have in this page it's the same thing we can literally copy the props like this the the slug params are passed the same way into this component just like with the other page.tsx and because we're still inside of the dynamic route so we can just paste the page props with the params of slug string because we know we will also receive them here by the way if you're unsure just Mark the props as any and log them out in the page and you will see everything that next.js passes to you and for example the params but it also says that in their documentation okay inside of here let's first fetch the subreddit so const subreddit is going to be equal to and let's close this for a bit more space it's going to be equal to and now we need an await statement so now we need to turn this into an asynchronous component just like that and we can do that because we implemented this um type approach the subreddit is going to be a weight database dot subreddit dot find First and the question now is which subreddit do we want to find and the answer is where the name of the subreddit matches the params.slug so react next.js that's the subreddit we want to fetch and if there is no subreddit like that then if there is no subreddit in that case we're going to throw a not found error we get from next slash navigation and we can return that awesome so if um the subreddit doesn't exist that the page is being requested for so if anyone typed in like slash like r slash ASD ASD and spam their keyboard and that doesn't exist they're gonna get a 404 which makes a lot of sense and if the subreddit actually exists only then are we rendering the JavaScript XML down here okay the top level div inside of here is going to get a class name of flex Flex Dash call Item stash start and gap-6 inside of there we're gonna render out the heading and the heading for this page is going to be contained in a div with a class name of border Dash B for bottom border gray 200 and a padding bottom of five inside of this they have one more div with a class name of minus margin left -2 minus margin top minus two Flex Flex Dash wrap and items Dash base line inside of the zip goes the actual H3 tag that says create post and this H3 gets a class name of margin left 2 margin top two a text of bass a font of semi bold a leading dash six and a text Gray of 900. just like that give this a bit more space so you can see easier awesome so we've got the heading and right beneath that we want to create a P tag saying in r slash and then the params.slug we know this is a valid subreddit because if not we throw the 404 so we can actually just do this instead of saying subreddit.name which you could also do it works just the same and this P tag gets a class name of margin left 2 margin top one truncate to cut it off if it's too long a text Dash small and a text Gray of 500. let's save this and check out how this looks so let's go to well we don't have a post button yet oh so we probably in the layout want a button that leads us to this page that we are writing right now that would make a whole lot of sense so let's quickly return back to the layout in the slug and add that button right at the very bottom of the page under the Subscribe leave toggle this is where we want to create a button that takes us to the submission page for this specific subreddit this is going to be a link component from next slash link that's going to say create post and inside of this link component is going to get a class name to make it look just like a button and to do that you already know how to do that with the button variants that we can import from a trusted button component it's going to make it look like a button without actually being a button for the variant we want the outline and then we can also pass a custom class name and that is going to be a width of full and a margin bottom of 6. into this link component this also has to have an href and the href4 this is going to be a um template string saying r slash and then the slug slash submit so if this is react for example then r slash react submit that is gonna lead us there awesome let's refresh and see if that shows up there it is create post let's click that and then we are on the page that we are currently creating this is what it looks like create Post in r slash react that's what we are doing right here that's what this is for we can exit out of the other stuff and let's just keep working on this page.ts x to make it look good okay right below the closing P tag let's go one closing div down two closing divs down and then enter enter with one closing div below that and now we want the actual form what the hell should we allow a user to input inside of here to create their posts whatever it is let's already think about what comes next and that is going to be the div afterwards with a class name of width of full a flex and justify Dash end so what we're doing now is creating the button that's going to submit whatever we input in the form that we're going to create here in a second so to do that let's import our button UI component and this is going to say post and this button gets a type of submit because we're going to use it as an external submission button for what we put inside of the form then a class name of with full and a form oops that needs to go separately a form of subreddit Dash pause Dash form if you're wondering what this means is if we ever declare a form somewhere in our app with this ID this button is going to act as the submission for that form no matter whether it's inside the form or not this is nothing react specific as far as I know I'm pretty sure this works with regular HTML okay as for the form that's going to be a bit more involved because we're going to use a real um what you see is what you get editor let's call this component the editor and this as I said is going to be a bit more involved um as a custom component that does not exist yet so let's go ahead and create this component um new component editor dot TSX it's already complaining this component is not defined which is fine now it is that means we can import it back in or page.tsx save that and this error will be gone okay and let me tell you this editor is going to be a very big part of our app because this is where people create posts it's it's a crucial part and let's get started in the JS let's get let's get started in the jsx um for this editor the top level div is going to get a class name of with full a padding of four a background zinc of very very light 50 a rounded Dash large a border and a border Dash zinc Dash 200. and let's give this a bit more space so you can see this easier okay inside of this diff we're going to create a form and that is not going to get an action but instead an ID and the ID is going to be the same as the button we are using as the external submit which is the sub edit post form we can just copy that from here into the editor and into the form component to be more specific and apply a class name of w fit to this form a width of fit and whenever we submit this form so on submit what should happen well um let's do nothing for now let's just put an empty function we're going to worry about the proper handling here in a second whatever it might be let's put a div inside of that form with a class name of um Pros Pros Dash Stone and then on dark we want a Pros Dash invert very convenient we can just invert that it's going to apply a bunch of styles it's not too important and you could just put a regular text color as well then in here goes something called a text area Auto size as a self-closing component now Josh are we about to create another custom component the answer is no we're not this comes from an npm package that is literally called the same thing we can say import text area Auto size from react text area Auto size which is a pre-installed dependency in your starter code which you normally have to install yourself and it does what it says on the can it's literally just in text area that automatically sizes with your input we can attach a placeholder to the text area Auto size that says title because this is going to be the title of our post and a class name that's going to be with or full resize Dash none appearance Dash none and overflow Dash hidden and let's quickly go into full screen so you won't have any trouble seeing all the class names a background of transparent a text of 5xl we're gonna make this very large and then we want a font of bold and on focus and outline of none that's all the classms I'm going to leave this for a second in case you missed anything and that text error is going to be self-closing great and I think we can work with that for now so what we want to do in this component is first off we're gonna have the title right um oh and we need to turn this into a client component so we can actually use this so let's say use client at the very top of the editor turning this into a client component because we need a ref and there we go that's the title my title for my post and you can see the text area automatically resizes with the length of the title a normal input or text area does not do that so this is really convenient and this does not add a very ugly scroll bar to the right side in the inner text editor okay now what we're gonna do is the actual what you see is what you get editor strap yourself in this is going to be exciting and let's get started in the editor um the way we're going to do that is using something called react hook form react hook form provides us with a hook that is called use form and for some reason the Imports are not working let's reload the window and react hook form is going to be really convenient with any types of form but especially in this one because it's automatically going to handle if there's any error with what we're submitting from the form um can we import it now does it get recognized it doesn't let's manually import use form react hook form just like that and now we can use it right here and what the use form takes is a generic and what we can pass into the generic is a type we want to later enforce using something called a resolver I'm going to explain it when we get there but essentially we can create a validator for client-side validation of whatever data we get when we submit the form so we can Define what data we are expecting which is exactly what we want because that way react hook form can handle any errors completely for us without having to do anything to determine or to Define that validator let's go into our validate this folder in the loop and create a post dot TS file like this inside of this validator we're going to import Z from Zod and Define a schema I've explained what a schema is before um let's export a constant post validator and this is going to client-side and server side validate the data that we submit as the post creation that's what this is for this is going to be equal to a z dot object and inside of this object we want three things first one is going to be the title and you're gonna see why this is so important here um right now the title is going to be a z dot string and normally if we just did this well a title could be anything users could completely spam our API with like 1000 character long titles but with zot with defining a schema we can enforce a minimum of three characters with a message a custom error message of title must be longer than three characters and also a DOT Max of for example a 128 so no character no title can be longer than 128 characters what we Define right here with a message of let's call this title must be at least 128 characters awesome really really good and we can also validate the server side to make sure nobody messes with or posts then we need a subreddit ID this is going to be az.string and then last thing we also want for each post is the content we are going to type this out as Z dot any and the reason we are defining this is any instead of properly typing it out is because the editor we're using has a specific format you could definitely type this out yourself if you wanted to but I don't think it's worth the effort that's why we're just gonna use a z dot any and accept any content and it's going to be in the format of the unblock editor now we can also export the type post creation request let's call it as Z dot infer and we're going to infer the type of the post value Valley data and this is going to be very useful in combination with react hook form because this type we can pass right here as the generic into or use form so we can later get actual type safety when working with a form which you normally don't have in react so that is really cool we can invoke the use form function and pass it some options as an object for example a resolver and this is going to be enforcing the post validator we have just created client side for this we're going to use the Zod resolver which comes from a separate package you need to install yourself but comes pre-installed in your starter code and that is going to be import Zod resolver as a named import from add hook form slash resolvers slash there are a bunch if you take a look at this like uh nope and type box vest yup but we want the Zod one okay after importing that a zot resolver there we go we can actually use it to client-side enforce or um post validator just pass it into the resolver and now all the errors are going to be handled for us if it if the title doesn't match these um react hook form is going to take care of us just because we pass it in here no additional checking or self needed whatsoever and as for the default values we want to pass for this form those are going to be the subreddit ID that we're gonna pass as the single prop into the editor component subreddit ID is the only thing we need access to and the subreddit ID is going to be of type string in the editor now most likely the page is going to complain yeah it should because we're not passing the subreddit ID from here and simply enough the subredded ID in the overlying page is going to be subreddit.it very simple stuff okay let's continue with the default values in the editor the default title you can probably imagine is just going to be an empty string and then the default content of the editor is just going to be null there's no additional um content that should be input by default now let's destructure some stuff from the use form for example we want the register function to register our Fields the handle submit that's going to make it very easy with react hook form to handle the submission of the form and then the form State as we specifically want the errors right in here to display them to the user if there are any errors but before worrying about the actual form submission we need some content to even make a post from so what I say we do next is initializing the editor but deferring the loading dynamically instead of blocking the rendering with the EV with the editor we're going to stream it in as it arrives and do everything dynamically for a way better user experience you're going to learn how this works right now let's define a constant and call this initialize editor and this is going to be a use callback function we get from react that makes the editor maintain or the Callback function in general it takes maintain Integrity throughout renders instead of changing this because we're going to import the editor asynchronously or we're going to load it asynchronously we need to mark this as async and then we're going to do a bunch of imports for example the main editor JS we want to import this is going to be equal to cons editor.js is equal to a weight import and we're awaiting the import off at editor JS slash editor JS you can see this is pretty heavy and this is the reason we are streaming it in instead of and blocking the render with that and we want the dot default of that okay and then we want also the header let's just copy this down once and say const header it's going to be equal to import at editor JS slash header and these are plugins that we get with editor.js it's really handy let's give this a bit more space there we go and why is this still Red by the way of course because the use callback expects a dependency array and in our case the dependency array is going to be empty so a cross re-renders this function the Callback is going to maintain its integrity and it won't do that only if something changes that's in the dependency array in our case it's always going to do that because there's nothing in the dependency array okay let's install a bunch of editor.js plugins this is pretty stupid work for example we want the embed under editor JS embed we want the um we want the table let's call this table and this needs to be uppercase I mean it doesn't need to be uppercase but I think it makes more sense um or actually does it yeah let's just leave it like that and then we want the um list from editor.js and that needs to be lowercase here let's copy this down one two three four more times and instead of the list we want the code oh code right here editor.js code instead of the um list we want the link tool next this comes from editor JS slash um link second to last is going to be the inline code this is going to be editor.js slash inline hyphen let's switch to the English keyboard again hyphen code as you can probably guess and last thing we want is the image tool which is also one of the most important in this and that comes from editor.js slash image like this great after setting up all those plugins we want to check if the editor is already initialized or not and how are we going to do that well we're going to keep track of that in a ref so constref is going to be equal to use ref we get from react regular react hook nothing extra specific and pass the editor JS type in here and initialize this user FS um just no and we can get this editor JS type by importing it as a type so import type editor Js from at editor j s slash editor JS and you could turn this into a drinking game easily the way or the amount of times I say this word is beyond ridiculous anyways we can just import the type because if we didn't we would import a massive dependency to the client don't do that we only care about the type in this case no need to actually import the whole as dependency that would be a pretty bad idea um okay and we're going to keep track of the editor in the ref later on so let's do a check if the if not ref.current that means the editor is not currently initialized that means we are good to go and we've got the green light so initialize the editor um this time so let's say cons editor is going to be a new editor JS and we're going to invoke that with some options for example the holder the div we're gonna mounted to will have an ID of editor then on ready to prevent a re-initialization of the same editor this is just a function like this we're going to say ref.current is going to be equal to the editor so we're not re-initializing this at any point that would be pretty bad as the placeholder for this editor plays and we need to call my separators to get intellisense for the placeholder we're gonna pass type here to write your post dot dot then the inline toolbar and we again need to comma separate to get intellisense the inline toolbar is going to be true we want the default data in here to be um blocks and that as an empty array so nothing is in there by default and now for the interesting part the tools the plugins we're going to be using in or editor.js this is going to take a object like so and that's passed in for example the header is going to be the header then the link tool and the link tool requires some specific customization so let's say link tool is going to be an object and what the link tool does is it will later allow us to paste links then preview the metadata in the image and so on for that link it's really convenient but takes a a bit of setup to do herself and sort the class for the link tool is going to be the link tool the plugin we have imported and then the config for this is what we need to set up manually and this takes an end point and this endpoint is going to be slash API slash link and I say we do this right now and later on we're gonna do the image uploads in the image tool right now we're going to do the link tool so just an API that um gets called from editor.js when we post a link into our editor and that needs to return the metadata for that link and we're going to host this as you can see under slash API and then slash link that's where it's going to live so let's create that folder right here under app API and then let's create a new folder called link for that with a route.ts inside of it okay um the logic is going to be rather straightforward let's export a con a async function that is going to be get and inside of here a request a rack of type request awesome and now the objective right here is we're getting an href that we first need to access so we're getting that passed by editor.js automatically then we need to fetch that href get the metadata from the page and return it to the client that's all we need to do so first off we need access to the href that editor JS automatically passes us and first let's instantiate a new URL class using the rec.url and now as for getting the href let's say Collins href and this is what um editor.js automatically passes it's going to be passed in the url.searchprems.get and then the URL that's where editor.js passes the href and if there is no href in that case we're going to return a new response saying invalid href with a status status of 400 bad request you can't do this you silly goose you have to pass an address and if we have an href then let's say Khan's dress is going to be equal to a weight axials.get the href so we're simply getting a link and making a request to that link um okay below that let's parse the HTML we get back from this res using regular Expressions so for example let's match the title of the page at second's title match is going to be equal to Res dot data dot match and we're going to match a certain regular expression that's going to be the HTML title I'm kind of gonna brush over them but again not totally I'm still going to explain them so we're matching the HTML title element and here goes a and what happened here so we're gonna have two parentheses in the middle and a DOT a star and a question mark so we don't care what's inside the title um or rather we're going to match that that's what we want that's what we want to return back man no what I wanted to say is we're gonna match whatever is inside of the title and return that as the actual title and back to the front end and then the closing title we have to escape this for regex to actually register this and then close off the title like so and if we have a title match the actual title is going to be the either type so the title is going to be if we have a title match then it's going to be the title match at index one so whatever is right here between the title elements or else it's just going to be an empty string and now we're going to do the same logic for the description so description match is going to be res dot data dot match and we're gonna put in two slashes right here and we're gonna match a meta tag with a name of description of the website and the content is going to be in quotes and we don't our regular match what is in here like so with a closing quote after that and did I do everything correctly I think I did awesome so now the actual description description is going to be if we have a description match let's copy this then it's going to be the description match at index 1 and else this is just going to be an empty string and then last thing we want to do is match the image cons image match don't worry we're almost through with the regex um analysis of the website we made a requester the image match is going to be the rest Dot data.match and in here again two slashes for regular Expressions we want to match the meta tag where the property is equal to OG image and the content is equal to in quotes and now that's what we're going to match and Dot star question mark closing parentheses and quotes okay so now the image URL is gonna be if there is an image match image match then it's going to be the image match at the index of one and else it's gonna be just an empty string as well awesome so now we have all the info that we need the title description and image URL of the website that we are parsing and we can send it back in a very specific format that editor.js expects back and that is going to be return new response and in here we're gonna Json Dot stringify and what are we going to stringify well first a success value of 1 and then secondly a meta that is an object like so with a title with the description and lastly the image which is also an object and this image takes a URL and that is going to be the image URL this is not a format I came up with but editor.js expects this to come back to it in order to work properly for the link tool and don't worry we're gonna preview all of this once we're done with the actual editor and just as a little Cliffhanger to actually stick through and worry about all the editor.js plugins next up is going to be image uploads that's going to be a huge one very very important for or app and to handle that let's say image this is going to be an object just like with the link tool it's like the image tool the class is going to be the image tool and then this takes a custom config as well and inside of this config we can pass an uploader um like a thing into the object and in here we can define a custom function that is called async upload by file and this gets passed a file of type file automatically by editor.js whenever the user tries to upload a file and this allows us to handle the uploading logic or self instead of relying on any editor.js internal process to do that and what that allows us to do is use a service that makes it very easy to upload files um to us and that service is called uploadthing.com and I want to be very transparent on why I chose upload thing this has been popular recently and I do not want to use this tool because it is a hype tool I only want to use this tool not because of who made it not because of why it's made whatever it just it makes image uploads very easy very straightforward way more so than AWS S3 now if this was my personal project for myself I would not use upload thing I would rather use S3 but S3 is not easy if you're not familiar with AWS S3 it's a cloud storage and walking through that together would add a lot of complexity to this video and would cause a lot of questions especially to people who haven't done it before so this takes a lot or so much complexity out of uploading image it makes so much sense to use it in this video again not because of who built it just because of what it does for us and we can I've already done this um I'm gonna delete my app so we can create a new one together you can only have two apps on here and click OK deleted app great let's refresh this okay if you haven't yet navigate over to uploadthing.com dashboard and click on create a new app that's gonna make image uploads really really easy and here we can just say uh Red Dot yeah let's just say about it and then hit create app and just like that we have created or um storage and we can go over to API Keys hit copy on these navigate into our DOT EnV file and maybe I'm a bit fast API Keys is right here you can quick Copy your API keys from here and paste them into your dot EnV file and they should look something like this if you've got these then uploading images is very easy we can go over to their documentation on the top right here under app router and it's going to tell us exactly what we need to do to set this up properly and it's really a matter of five minutes whereas with AWS this could easily take one or two hours and the only benefit being then you don't have the two gigabyte file limit that upload thing has which is way more than enough for your first like a thousand users or more just as you scale then you could actually think about switching to S3 anyways um I'm besides the point so what we want to do is install the upload thing we can literally grab all of their code and it tells us where to install this so this is under let's have this side by side this is under app API right here and then upload thing let's create a new folder called upload thing and then in here a folder called core.ts uh no that needs to be not a folder a file never mind let's delete that and then as a file another folder let's create the core.ts and just paste their code in here now normally again this is a pre-installed dependency for you this upload thing that comes pre-installed in your starter code for this project all done for you we can remove all of the um all of the comments they are very distracting and I can explain you what this does way better than these can so let's get rid of these delete them we can get rid of the console logs for now we don't care about those and the rest is fine for now so we can leave our core.ts just like this and then we can head over to this part which is the route dot TS this also goes into the upload thing folder under route.ts we can just paste the code they provide to us and everything works beautifully out of the box great just like that we have actually enabled file uploading if this was not easy then I don't know what is there's only one very three lines of code thing we need to do and that is Define a custom Little Helper that we can use in or editor image tool right here that's going to make file uploads possible and we're going to do that in lib and then under upload thing dot TS this is where this goes very very small thing and first we're going to import the generate reacts helpers from and this is going to come from at upload thing slash react slash hooks there we go also we want to import the type of or file router from the core so what you can see right here at the bottom of the core that we pasted in here this is the type we want to import in here from add slash app slash API slash upload thing slash core import there we go and now the only thing we need to do is export cons and then destructure the upload files from the generate react helpers that we pass or file a router as a generic tool and invoke that don't worry if you don't understand what's going on here it's fine the important thing is we have now a function that lets us upload files and it works with the actual router that we have defined right here so for example we will get type safety when using it and now uploading the images is really easy we can just say cons and then destructure the res as an array right here and because we're only going to upload one file but as an array it's going to be equal to away it's upload files that we get from the helper we have just created pass this the file we get passed by editor.js as an array and then just use the image uploader see now we get hype safety because we pass the type into it and just like that we have uploaded an image to AWS S3 or rather upload thing has done it for us we have uploaded to upload thing that was really easy and don't worry we're going to test it together here in a second if everything works let's return a editor JS specific and response that's going to be success of one and as the file we're gonna pass the URL and this is simply going to be the res dot file URL you can't tell me this was not super easy and again this is not an endorsement for this app and I'm not sponsored by them or anything I just want to be very transparent and why I think for this video this makes more sense while on your own project if you're familiar with S3 then S3 makes more sense okay let's install or let's finish up the plugins rest is going to be very easy trust me the hardest part is by far done for the list we're gonna pass the list for the code we're going to pass the code for the inline code we're going to pass the inline code for the table we're going to pass the table and lastly for the embed we're going to pass the embed all the rest of the plugins beautiful let's try this out now this won't work I can already give you a spoiler alert let's try reloading the page and okay this will work but the editor doesn't show up so let's try initializing um the editor and the question is where do we do that so first off let's keep track of whether the component is currently mounted or not and we're going to do that inside of a use state so let's keep track of set and call this is mounted this is going to be a Boolean value and default of false it's import you state from react and the question is when are we mounted well let's determine that in a use effect we also get from react this takes a callback function and we only wanted to execute when the component initially mounts therefore the empty dependency array and if the type of window is not undefined so if we are on the client side because on the server side this will be undefined and undefined needs to go in a string in that case we're going to set is mounted to true okay and now we can actually initialize the editor so in another use effect let's put this after the initialize editor function to actually invoke it in another use effect right down here this takes another callback function and this time with a dependency array that is populated with the is mounted and also with the initialize editor both of which are going to come from outside and used inside this use effect first off let's declare a function to initialize the editor let's call this init it's going to be an async function and the only thing this will do is await initialize editor and then secondly inside of a set timeout we are later going to set Focus set Focus to title because this initialize editor takes away the focus we later want the title to be automatically focused we're going to do that in a second um not for now we only want to initialize the editor for now and then if is mounted if this component is mounted successfully in that case when it com we are going to call the init function and we're gonna return something but we're gonna worry about that when we get to the set timeout okay let's try this again let's read out the page and we will get an error right here element with ID editor is missing pass correct holders ID now the question is where does this error come from well we have the holder as an editor right here this is the div that the editor is trying to mount to but this div does not exist in our current and file tree therefore under the text area Auto size let's create a self-closing div because it's only going to be a shell to actually Mount the editor to with an ID of what you have in the holder in our case editor with a class name of minimum height of let's say 500 pixels in inline angled brackets hit save and now let's try this again and as you can see there's a nice loading animation and then the editor mounts we get an awesome what you see is what you get editor that works beautifully out of the box with a list one two three four five whatever and let's try one very important thing that is uploading an image for example let's try uploading a previous thumbnail of mine we're going to see this loading animation and if everything goes through successfully then we should be able to see that the image has uploaded with upload thing let's get let's see in our upload thing um dashboard if this was actually actually successful and just go to uploadthing.com into the dashboard under Reddit what we call this we can see there is a file and if we take a look at the file we can see it's the file I have just uploaded to our editor it just works and similarly if we paste a link like for example to excaled draw.com and here let's paste a link that's what we use the link tool for if I paste this it's going to make a request two or custom endpoint and check this out it's gonna it's just gonna work that's what we just did amazing work this is really really cool and I think this is one of the best user experience editors I have ever used it just works and looks really good awesome let's go back into our editor right here and do one more thing and that is whenever we load the editor we want the title to be focused right now nothing is focused and that's a pretty bad user experience and the way we do that is by assigning a ref to that um to that text area that represents our title so let us create a separate ref for that I'm going to minimize this let's maximize the editor and let's create a reference for actually I I prefer the side by side view let's move this um into a side by side give this a bit more space like so let's create a ref for this and then also use the register handle submit and so on don't worry about that first off let's create a cons underscore title ref that's what we're going to call it as a user ref and we can tell typescript what this will be and that is an html text area element and this is going to be a null reference by default and now comes the interesting part this is where the register comes into play so by default react hook form takes the ref so if we just say dot Regis register and register this as the title then well we couldn't use a custom ref for example the underscore title ref this won't work because it says ref is specified more than once so this usage will be overwritten because react hook form already takes the ref and so it can handle like automatic focus on error for example it does all this for us under the hood but in this case it's actually kind of problematic so what we want to do in this case is share the ref with react hook form so they have access and we have access as well and the way we do that is by saying const and then destructuring the ref as the title ref and the dot rest everything else from register title and what this allows us to do is Define the ref separately for both of these so we can pass the text area Auto size a ref and assign the ref manually first of receiving the event and then secondly calling an inline function with that event first off saying title ref e so we're assigning that ref to react hook form and now it's our turn we also want this ref so we're going to assign the underscore title ref dot current to e and typescript doesn't like that typescript has a problem with this cannot assign to current because it's a read-only property which is fine there is no problem at all with this approach however typescript just doesn't like it so we can tell it to um you know stop caring um so we can just use it here's ignore again nothing wrong with this approach it all works just as intended it's just typescript not acknowledging that this is a valid approach and then we can pass everything else the entire rest we destructured from the register title into our text area Auto size great and what we can do now is after initializing the editor we can now focus it so instead of just mocking this out let's say underscore title rev dot current dot Focus there we go this is optional it doesn't have to exist could be null so we need to be careful and do some optional chaining and as the timeout we're going to set 0 and this is just gonna move this call to the end of the call stack actually going through with the focusing of the title awesome and now as for the return statement down here we want to clean up after ourselves properly so one thing we want to do is ref.current dot destroy and invoke that and secondly ref dot current is going to be undefined there we go okay so we're on initializing the editor I guess you could say really really good the last thing that's up to do here or two more things but the more important one is actually handling the submit what the hell should happen when we submit a post from our editor so first thing we want to happen is use the handle submit that react hook form provides to us and we're gonna put that um right here in the on submit so this is going to look like handle submit and then this also takes a callback function let's just mark this out as an empty function for now and but in theory we could receive the E and that is what react hook form and grants us as the data that is the benefit of react hook form at least one of them using it along with Zod because we can validate that we get this data client-side and server side by also using the same validator later on the server side awesome okay just for bookkeeping let's move this use effect down just to the other use effect I find it better if they're both together just a bit more easier to read and then let's create one more use Effect one last one and that is going to handle the arrows if anything goes wrong in the editor and in here we're gonna say um or we're gonna map over the object keys so this is where the errors comes in we get from react hook form and in here we're going to say if the object dot keys and then errors dot length so if there exists any errors we might need to be worried about then we can handle them inside of this um if statement okay A little cut there because I spilled my coffee across my entire desk and ruined my keyboard so I had to go switch off the keyboard and make another coffee okay anyways we were about to handle the arrows right so um if there are arrows we're checking by the object Keys then we want to map over them and we can do that by saying four and then const underscore key and angled brackets now and value of object dot entries and then here go the err and oops that should be entries let's give this just a bit more space like that and then here go the arrows so for each key and value of the object entries we want to handle some logic and what's the logic we want to handle well we want to return a toast notification return tops we turn tools yeah it takes some use to getting this new keyboard and we don't have to return we can just invoke the toast as is and as we know this toast takes an object and I messed something up there's a parenthesis missing there and that's probably too much down here there we go and we also need to import this tools so we can send it out the toast takes a title and that is going to be something went wrong and something should be capitalized probably and then secondly we want a description description and the description is going to be the value as an object we're casting the type right now message as a string dots and now we know there is a DOT message property just to tell typescript hey typescript you know chill out this really exists and then as the very end this is going to be comma separated and destructive and to let the user know hey something went wrong there's an error in the um form submission that you're trying to do awesome that's the error handling part done very very good and the last thing we want to do is Define what should happen when the form is submitted when the user clicks this post button right here what should happen well we want to actually create the post and submit it to the subreddit and let's do that right above the is mounted between the use effect and the is melted all right and let's get started to handle that this is going to be an async function and let's call it on submit and what this submit takes is data and this data is of the type that we are enforcing with react hook form if we look all the way at the top right here we are enforcing this type because we're using the resolver that um belongs to this type right we are we know we can expect this data else the request will fail at least client-side so we know the data will be of this type right here and therefore we can just tell typescript what the type will be of the data and handle it accordingly in the on submit first off we want to know the editor content and this is the cons let's call it blocks this is going to be the await and then ref.current Dot and now we get something called save this comes from editor.js so we can save the current state of the editor inside of this concept assign it to it and we can see if the type is going to be output data a type we get from editor.js that is what we're going to send along to the request to actually make the post the payload for that the cons payload is going to be of type post creation request as well same type as the data we're getting into this and an object obviously that's going to give us an error because multiple things are missing like the title for example this is going to be the data dot title then the content and the content is nothing else than the blocks we get from the editor to save and last thing is the subreddit ID which we when we look at the very top of the file right here are passing in as a prop as a property so we can literally just put it there inside of the payload and don't need to worry about it last thing we want to do is send that over to an API and because this is a client component and we want an amazing developer experience you know what we're going to use um for the creation of that post is going to be 10 stack query let's define it right above the on submit and let's worry about the destruction later as an empty object for now it's going to be equal to and you can probably guess either use Query or use mutation what do we want well we are submitting data therefore a use mutation from react query this is meant for mutating changing data patching data and so on as the mutation function we are going to take in three is it parameters or argument it's parameters we're going to take in three parameters and those are going to be the title the content and also the subreddit ID and we know the type is going to be post creation request and that is just a tell type script what the hell we are expecting in here let's quickly mark this as asynchronous because we're going to do an axials call in here and the axials call is going to be cons let's worry about the destruction later it's going to be axios import that dot post I'm going to make a post request to the following API endpoint under slash API slash subreddit slash post slash create that's going to be your API endpoint and for the data that we want to pass in here let's define that as a payload as well cons payload is going to be equal to an object and we can also Define the type of the payload the post creation request once again and we're gonna get an error but we can literally just take all the data that we got passed in to this function like the subreddit ID the title and the content listed inside of the payload and then send that over the wire to our API endpoint by passing the payload right here we can destructure the data if we await the axials call of course and then return that data to the use mutation so we can use it later if we need to um okay very good let's quickly handle the errors so on error if this fails for some reason and we get an error we don't even care about the error let's just return a toast notification saying as a title some oops something went wrong we want a description the description this is going to be your post was not published and please try again later and lastly the variant you can already guess what that is going to be this is going to be destructive and the reason we are not very worried about the error handling right here um you probably remember in the other cases the error handling was much more in depth and way better right so why are we not doing this here well you see the editor is only rendered on a page that a logged in user can't access in the first place and so this is really just an unknown error we know they are logged in and we know they will send the right data unless they mess with their API manually which most people probably won't do and and even if they are it still will fail just with a generic error message um so later on in the Middle where we're going to protect this route where the editor is being rendered the submit routes and to make so only logged in people can actually make a post and if they're not authenticated and try to access this page they will get automatically redirected to the login which is very convenient so that's the reason the error is not too important here and then lastly we want an unsuccess Handler this is going to be a function as well an arrow function what do we want to happen when a post has been published well we could send the user back to the home page which wouldn't really make a lot of sense we could just show a toast and that's it but then they would stay on the post creation page right here what we ideally want to happen is to send the user back to the subreddit M right here and show their post right so they get a visual confirmation of that their post is actually live and others can see it and to do that let's turn um something like the r slash my com Unity path name slash submit where the user currently is to make the post right when we are create post there is r slash react submit from here we want to turn that into we want to turn that into our slash my community without the submit but whatever this might be if that's a react we want to turn it into r slash react to forward the user to that and to do that we can say const A New Path name where we want to redirect the user it's going to be the path name well we need the current path name we don't have that but you know where you get this from and that is a very convenient hook we get in nexjs 13.4 let's quickly grab the path there let's do it up here const path name is going to be use path name we get from next slash navigation and just like that we got the path name awesome and now we can work with that down here with the path name that we now want to split into different chunks to only take selected chunks of those and to split we can simply call the split method on here we want to split at these lashes and then want to slice the array into as the start index we want 0 and as the end we want minus one and then we can join those back together at or with a slash right so that is going to be r slash react or whatever the current Community is that the user is trying to make a post in and then we can simply push that into the router by saying router.push New Path name and the router is currently not a thing but again we know where we can get the router from it's the same thing as the use path name just with a different hook cons router is going to be equal to use router from next slash navigation awesome let's scroll back down to or use mutation right here and we've pushed that into the path name which does not mean that the path is refreshed so they might be in the community but the post might not show up because it's cached next.js automatically does this for you so we also want to call the router.refresh method to show a fresh version of the current um you know of the current feed of the current community and then return a toast notification to host pass it an object with a description the description saying your post oops what did I do wrong what did I mess up description your oops your pulse has been published period awesome just let the user know and that is pretty much everything we need done one thing we still want to do is pass the on submit right here into the actual Handler the data is going to be passed automatically into the unsubmit very convenient so we don't need to worry about that and then we are done with the edit oh never mind we still want to pass the payload in the on submit um and call or react query function with that that is very simple we can just say create post with the oops create post with the payload and the reason there's still errors is because we haven't yet destructured the mutation function so quickly do this the create post and pass it the payload and then we can go ahead and destructure the mutate here which we're going to call create post to be a bit more verbose and what it actually does awesome we can save the editor and now we are really done with the editor um however there is no post creation API route and I suppose or I propose we create that next um to actually you know create our first post in this app so let's go into the app folder into the API and let's see where this goes it's go it goes into subreddit right here and then slash post slash create so let's have a post folder inside of that let's have a create folder and of course we know this needs to be a route.ts to properly handle um or post logic okay and before we keep on writing all these route.ts because they are quite similar in the code they actually contain let's just go into a different route.ts we had like the Subscribe one copy and paste the code into here into the create from the Subscribe route into the create route and just change whatever is necessary so the code is not going to be the exact same but most of the stuff is going to be very very similar and so for example we want the auth session um if there's no session.user this shouldn't work either way awesome and we also want the body and this is going to be a bit different we don't want to subscribe to a subreddit but instead we want to use the post validator instead and we want to restructure the subreddit ID the title and also the content for this to actually create the post and put it into our database great um we can check for a an existing subscription which is fine if there is no subscription then we are going to return a response saying um subscribe to post because being subscribed or joining a subreddit is a requirement to post inside of it and so we can just keep track of everyone um if there's no subscription then we want to not allow any user to do this and if they are if everything is fine then in that case we can actually go ahead and process their request by saying oh wait DB dot post dot create and what do we want to create a post with well we can pass the include select we don't want that we want to pass the data inside of the data let's pass the title we want the content we want the author ID to see who created this post this is going to be the session.user.id and then lastly we want the subreddit ID in here as well awesome if everything works out we can just return an OK status code after creating the post and the error handling is pretty much fine we can slightly adjust the error message right here saying something along the lines of code oops code not post to subreddit at this time please try oops again later period awesome very very good we can save that route.s and let's see what happens so what do we expect to happen well we are in the submit right here in the slug what do we expect to happen when we write or post the editor is going to make a post request to this API endpoint let's go into the editor and redirect us to the main feed the New Path then we have calculated and then it should refresh the router and show us our post but we are not actually showing um the posts on the page.tsx on the slug yet so for now it won't show the actual posts that's the letter do we have but let's try out if this works in theory and so let's reload the page just to get the fresh version if anything is outdated because we did restart the development server give this some time to load and then let's try creating a post my first post hello I like to post here post oops there we go click post and let's see what happens if everything works out it says your post has been published that is great and we are redirected to the main feed page um again we're not showing any posts and the CSS still looks kind of weird I'm gonna fix that later but ideally the post should show up now um if we were showing any and I think it makes sense to work on that next and that also involves the one very cool data fetching strategy that I talked about in the very beginning of the video where we load the most necessary data without making a call to our database at all and then in real time stream back in all the other stuff for performance optimization it's it's very very cool and it happens for each post when we go into the detail view but first we need to display all views and all posts in the feed right that's the first step and then we can get into the detail view in the cool data fetching strategy and utilizing redis okay so first step create the feed and let's see where we are in the map right um awesome step four done we can create posts that was a step and we have successfully created the post let's take a look at that post in the database just to make sure um we can say yarn Prisma Studio we can close out of so many of these files we don't need that we don't need that and let's see what our current database looks like we can go into the post we can see there is one and we can see our post has successfully been added my first post awesome very very nice so we can successfully create posts that is step four done next up is can I get rid of this by the way doesn't seem like I can anyways next step is displaying for posts and feed and optimizations that is going to be really cool so we're going to make the feed and then the detail post View and then we're going to add voting then creating and voting for comments the search bar and then we're done and deployed awesome so next up let's create a feed and get into it and the Post Feed is gonna contain the infinite scrolling Behavior also a very cool thing to know let's render out the Post Feed right here let's just call this component Post Feed and of course this doesn't exist yet so let's go ahead and create it in all components post feed.tsx and this is going to be responsible for um getting some posts initially displaying those pores that are fetched on the server and passed into here whenever this loads and then as the user Scrolls this is what I meant in the very beginning we are subsequently loading more posts as kind of an infinite scrolling Behavior and the way we do that I've explained that in a separate video if you got lost anywhere I really recommend watching that video to make it very clear and it's a very cool approach let's first create a functional component and then here we are going to receive um two properties which is the initial posts we're going to fetch those on the server and then the subreddit name we are currently in we can close all of this and let's move this into the background right here like that awesome and we also need to type this out the initial posts are going to be of type extended post array now the type doesn't exist yet but it it describes pretty precisely what it's going to be it's like a post but with a bunch more stuff and then the subreddit name this can be optional um only if this post is for a certain subreddit this could also this could also be on the main home page right this is any feed that we're going to display on the whole application and let's worry about the extended post type first what is that and I propose we go into a types folder that we have it's already been done in your starter code there's a types folder and let's extend one type that is the extended post because we're gonna fetch um multiple properties of the post and join them together and there is no Prisma type for that so we need to declare that ourselves in a db.d.ts file it's a typescript definition file that's what this is for and from here we can export a type extended post this is going to be the Prisma post that we get but extend it so we can say and object and we can now extend this type by other properties like the subreddit for example which is of type subreddit we get from Prisma client the votes which are of type vote array also from Prisma clients all of these are from Prisma client the author which is going to be a user from the same library and lastly is going to be the comments and this is going to be a comment array also from Prisma client great we're gonna reuse this extended Post in one or two places so we can declare it in one place and Export it wherever we need it for example in the Post Feed and yeah that's pretty much it for now we can already import the Post Feed component in our page.tsx where we render this out now this demands some posts right so stop the error like the initial post we don't have those yet but don't worry about it and we're gonna fix that awesome so inside of the impulse feed we want to handle the um infinite data scrolling whatever and let me give you a very brief rundown of how this works you see if this is the viewport of the user let's make this transparent this is the viewport right um and then let's create multiple posts again I have a full video expanding this it's really good I can recommend it let's just um give you a very quick rundown this is the viewport and this is each Post in the feed initially when the user sees this right there will be no point in having the fourth post right here so we would not want to load that but only when the user Scrolls down in their viewport that post oh yeah there we go that post should be loaded okay let's just say it's not loaded over here and the user starts out here scroll scroll scroll and then this post gets loaded as the user reaches the last at the very bottom of this post right and then he can continue scrolling or she can right this is what infinite scrolling is we're only loading more posts as necessary that is a very good idea it reduces strain on the database it improves client performance it just adds a lot of benefit to your app and that's what we're going to incorporate as a data fetching principle into our application and the way we do that is by attaching a reference a ref to the last post that is currently visible to see if it's intersecting with any other like with a with the bottom of the viewport and if it is then we're gonna fetch the next post sounds complicated maybe really isn't and the way we can do that is by first getting a ref from a library that comes pre-installed in your starter code and that is going to be cons empty object is equal to use intersection so oops intersect there we go this comes from import use intersection from a library called atmantine slash hooks it's a very convenient hook you can do this yourself using the intersection Observer if you want to but I feel like this is a bit simpler and therefore I prefer it we can invoke the use intersection and pass it two things first off is gonna be the root element and this is gonna be a Dom node reference a use ref from react however we don't have the reference yet and we want to create that so let's create a user ref cons let's call this last pulse rev this is going to be a Dom node reference to the last post currently on the screen it's going to be a use ref from react initialized as null and we can already tell typescript this is going to be an HTML element later on um to satisfy typescript and because we're using hooks in this component and we also need to declare this as a client-side component by using the use client directive at the very top now we can pass that last pulse to have as the root the last postdraft.current and as the threshold which is optional on the use intersection we can just pass one and now we can restructure two things from the use intersection the ref which is going to be what we're assigning the and post two and then lastly the entry we can check for if we are currently intersecting that or not you're going to see what that looks like right now um so let's worry about the jsx first so that example gets clear we're going to render out a unordered list and this is going to get a class name of flex Flex Dash call oops that oh am I yeah I'm an English keyboard again Flex Dash call a call Dash span Dash 2 and a space y of six then inside of this unordered list we want to map over or posts however we don't just want to map over the initial posts that doesn't make sense because depending on how far the user Scrolls right here we want to extend those posts later on and we can't do that with just the initial posts therefore let's add the functionality for infinite scrolling right now it's really not too complicated and we're going to use a hook for that that is also provided to us by tensile query the hook is called use infinite query super convenient exactly made for this use case and the first thing we're going to pass into here is the query key and you can call this anything I'm going to call this infinite um query this is to specify caching behavior and so on we're not really going to make use of that but we need to pass it nonetheless and then comes the actual data fetching function so an async function we can call like this and what we want to pass in here or what we want to receive in here is the as an object we can destructure that right away the page param and let's give this an initial value of one so we're on the first page now we want to define the query and to the API endpoint that we are using to get more posts right when this is called when we're reaching the bottom of the page now we're defining the endpoint that gives us more posts an API endpoint that fetches pulse from our database let's formulate the query for that and this is going to be cons query is equal to as a template string slash API slash posts and then as query parameters we want to pass the limit this is going to be equal to the very long name of infinite scrolling pagination results and so on that we have defined earlier and page is going to be equal to the um the template there we go the dynamic insertion of page param so first page second page and so on let's go into full screen so you can see this easier and then we're going to concatenate this with with one more thing and this is going to be if there is no so double exclamation point we're going to turn this into a Boolean actually that is not there is no we're just turning this into a Boolean the subreddit name and if there is a subreddit name in that case we're going to append a query parameter that is and as a template string again and um subreddit name is equal to the subreddit name and if there is no subreddit name remember there's an optional prop so we can't assume this exists and if it does not exist then we're passing nothing as the parameter that is the query we're going to use to make a request to our API endpoint and we can do that request by using axios axios dot get and make a request to the query nothing more needed all the logic is in the query string up here we can destructure the data from that request of course if we await that request and then return that data as the extended post array type that we have to find ourselves awesome so we know this exists and two more things we need to do for this to work properly is first off passing this a certain type of configuration we do that as this right behind this object right here we can pass a second one the options and first one we want is the get next page param this handles the logic of our next Pages the first argument the last page we don't care we can just put in underscore then secondly for the all pages we can just call this pages this is going to be a function that's going to handle the logic for us we don't want any crazy logic in here we're just going to return the pages dot length plus one nothing crazy and then lastly we can pass initial data into this use infinite query and you can probably guess what we're about to do we're going to pass these initial posts into here so 10 SEC query knows those exists as well and we can display all of them combined instead of some using State and some using and the infinite query that would just get very messy very fast so we can pass the initial data right here um by saying or passing this object with pages and this takes an array of the initial initial posts and then secondly it takes the page params and this is going to be an array just saying one awesome so initially we're on the first page this is the logic to get to the the the next page param and this is going to be passed right in here and we're defaulting this to one making a request and getting more posts back from this certain API route right here appending those to something called the data we can just destructure this this is going to be the query result sent back from our API endpoint we can get a function from the use infinite query called Fetch next page very convenient and then we can also get a Boolean is fetching um next page right here whether we are currently fetching the next page or not because we can use um or we can show loading States according to that great now let's determine very quickly the posts that we want to show by saying const posts and these are the ones we're going to actually map over it's going to be the data dot Pages dot flat map if you're wondering what a flat map is it's like a map and a flatten just at the same time it's a bit more performant I think it even says that when you hover over this because a defined callback function on each element of an array just like the map then flattens the result into a new array and somewhere it should also say this is a bit more performant not too important okay we are receiving the page right here and let's return that as well return the page and if there is no data yet if this is undefined because it will be at the very beginning um when this data has not or when this data has not yet been fetched in that case we want to render the initial posts if you don't know what this is the double question mark if this is null or undefined only then are we going to render the initial posts but if it's like an empty string which would be a 4C value that would not and be considered in the in this operator right here awesome and these posts are what we're gonna map over post start map and for each post we can show um some jsx but first let's put a function there that returns um yeah well let's handle the logic first let's just return an empty div for now that's that's fine we're going to receive the post and the index of that post that we're mapping over and let's determine how many votes each posts has because we are fetching the data with the the polls that we are expecting right here this off type extended posts it will also contain the votes of each post because we're joining those tables together while making the request for each post so let's say const votes amount this is like a one two three minus one minus two upvotes that is what we're doing right here and this is going to be the post dot votes dot reduce we can reduce them to one single value we get the accumulator and then we get like the current or let's call this vote and let's reduce this to one value and we can say if vote dot type this is a an enum nor Prisma schema this can either be up or down and we can check if this is an upvote then we want to return the accumulator plus one and if this is the Vault DOT type of down then you can probably imagine we want to return the accumulator minus one and if it's none of these which it will be because it literally can't be anything else but whatever let's return the accumulator and pass the reduce a starting value of zero so typescript also knows this is a number now it can infer that property so this is the amount of votes this post has we're currently mapping over and we can also determine if the user has already voted for this specific post by saying const um current vote is equal to the post dot votes dot find and now we're searching for um for a volt we get each vote passed into here and we can do a check if the vote dot user ID is triple equal to the session dot obsession dot user dot Eddy now this session doesn't exist yet and we are in a client component so we can't just fetch the session using our handy Little Helper because that is only for servers so one time or I think we're doing it one more time in this build but um we're rarely doing it let's fetch the session client side for this it's totally fine let's say const data let's call it session it's going to be equal to the use session hook we get from next auth slash react just a convenient Little Helper and now we get the session client side I prefer not doing this because mostly it takes a bit of loading time but in this instance where it's not really a security vulnerability or whatever um this is totally a good use case to do so we can just get the session client side and everything works awesome so that is the current Vault determined correctly and now we can handle the actual logic of displaying the posts to the user and now displaying each post separately is where the fun really begins because one post is going to involve um the the very cool data fetching technique that I teased in the beginning and where we load the most important data from redis is a super fast in-memory database and then load all the other stuff and stream it in that's what happened that is what's gonna happen in each post we can create a post as a component post.tsx and initialize that as a functional component to get started for now let's leave it at that just so we can already render out the actual post inside of our Post Feed so okay we've got the votes amount we've got the current vote and now we want to do a conditional check if the current index that we're on that we're mapping over the index of the current post is equal to the posts dot length minus one in that case we want to render out a post that has a reference attached to it if it intersects that's going to be the last post if it intersects then we are loading more posts and that is the case if this condition right here is truthy so um we are in that case we're gonna return and add a ref to the last post in the list which is this one and so we're returning an Li element with a key of post dot ID this needs a key because we're mapping and then a ref of the ref that we get from the hook we have at the very top of the file that is um right here use intersection this is where the ref comes into play We're attaching it to this Li element and then we can insert the post component that for some reason doesn't work export default post it should work the pulse component in here ah dot slash pause there we are awesome so if this condition is truthy we're gonna return this post and if it's not and we can go right here else we're going to return something else and that is simply just the post instead of um with a reference attached to it and we have to return that pulse from here awesome so right now a post doesn't really contain anything and what we can already get started and try to render out the feed the feed demands some initial posts right here from the page.tsx and the initial posts are going to be the subreddit that'd be a fetch dot posts because we included them that will be fine and for the subreddit name we can pass dynamically the subreddit dot name awesome and this gives us an error let's see type pulse oops usually with these typescript messages the end is the best and so subred or incompatible type subred or null is not assignable to type subreddit um okay so apparently this can be null no it can't so this subreddit can be null um interesting and I just figured out the reason and that is because we made this subreddit right here optional in or post so this cannot be optional of course a pulse must always be in some kind of subreddit and we need to enforce that in our database so let's say yarn Prisma DB push after making the strangers so what I did is remove these two question marks after a separated ID and subreddit these cannot be optional in our post model so we need to enforce there's always a subreddit attached to a post then we can generate or types from that and restart or okay we need to unlink apparently and then we start our typescript server let's generate the types for us to work with locally and okay it's already restarted typescript server nice that's very convenient and now we should be able to see that the arrow is gone because the subreddit is not optional on a post awesome so that is our Post Feed we've got the initial post taking a certain amount in our case that's going to be two in production that would most likely be higher and then rendering those in or and post feed without any data for now we're not using these but let's see what this looks like let's go ahead and start up the dev server and then visit a subreddit for example r slash react can close out of that give this a second to load and then see what happens let's close that close that and we get an error use session must be wrapped in a session provider okay we can do that let's go into our providers file and it seems like we need to provide a context provider and that's going to be the session provider let's put it into the query client provider that's going to be the session Provider from next auth react and wrap or application children inside of this and then hopefully this will work now I'm just going to provide context to all the children that works the arrow is gone and we can see there is one post if I created another post let's try it out we're gonna get navigated to the post page let's say post to Second post and actually post that then it should say pulse twice because we're now mapping over two posts and it does great the actual data is not shown but we can see a toast notification your post has been published that looks awesome man really really nice we can see both posts right here and we can exit out of the providers and now let's get ahead and quickly look into what the plan is and this playing Post in feed and optimizations okay so the post logic is going to be next up so instead of just rendering a plain component that has some hard-coded text inside of it like the pulse currently has and we are going to actually um you know have the votes in there have the comments in there and so on and that is going to be all in this post component let's get started with the jsx I think that's the most intuitive approach we're going to have one main div inside of the post this is going to be a costume of rounded medium BG white and Shadow inside of here goes another div with a class name of padding X of 6 a padding y4 flex and we also want oh I missed a hyphen here flux and a justify Dash between for this div awesome in here we want to display the current volts and let's quickly mark this out for now as a to do to do Post votes and because this involves a bit more logic because these are going to be streamed in later um so I want to make sure we really um dedicate some time to that and make sure you understand how that works and so let's quickly finish the jsx for the Post component and then get into it below that we're going to have a div with a class name of with zero and flex Dash one awesome inside of the stuff let's render out one more div with a class name of Max height 40 a margin top of one a text of extra small and a text Dash gray Dash 500. if you're curious as to what this component is for um it's going to be the preview for each Post in the feed component and then when you click on that you're going to get navigated to a separate page where we do all the Advanced Data fetching for that post so this is like a preview component for each post that we're doing right now um okay now we need access to this subreddit name for this pose in order to display it and in the post as you know post it in our slash react or whatever so we need that as a property the subreddit name and we know the type of this subreddit name is going to be a string so we need to pass the subreddit name into our post component let's quickly do that subreddit name and this is going to be let me check what this is going to be in my page.tsx over here this is going to be the post dot subreddit.name post Dot subreddit dot name because we joined those tables together and we know this exists great and what I mean by joining them together is in the page.tsx this is the join right so we're including certain tables joining them together so we know this exists now and we can work with it in our um post jsx now so we're gonna do a conditional check if we have this subreddit names name like this in that case we're going to render out a fragment and if we don't have that we are going to render out null nothing awesome but if we do let's go into the fragments and render out in a tag We're Not Gonna render out a traditional xjs link because if we click this we want a hard Refresh on the page and next.js Link doesn't do that so we're going to use an irregular HTML anchor tag to force a server-side hard reload um if we click it with a class name of underline you want a text zinc of 900 text Dash small and an underline Dash offset-2 awesome let's quickly format this into the a tag we're going to say r slash and then comes the subreddit name so r slash react or whatever it might be and let's just save that and see what it looks like refresh this okay already looks kind of fine we want the href to be right here Dynamic and this is going to be a template string of Slash r slash and then interpolated the M subreddit name so this is going to lead to our slash react for example or and whatever the current subredded name is and then below that let's put a span that is going to contain a certain character you can insert like a hyphen right here but I like this little this little dot you can just uh insert the dot as well or a hyphen whatever you want plus as a fancy visual separator and this gets a classmate of padding X of 1. okay great right below that we're gonna insert a span that says post it by you slash and now we want the username of the author but the question is how do we get that we don't have access to the post and therefore we don't have access to the username so to solve that let's pass the post in as a property as well to the pulse component um so the post let's also Define the type of that this is going to be the post we get from the Prisma client and we can extend that pulse type and tell typescript we also want to receive the author as the user type and secondly the votes as the vote type array so kind of like a small extended post that we are not reusing anywhere so we can just Define it in line and now we know that we can have access to the post dot author dot name because we included it in the join earlier awesome very very good so we have access to the username of whoever made that call and now yeah right that's the post buy now we want to format the time and actually um we get an error right here because we still need to pass those properties into our post component and let's do that this is going to be simply oops the post is going to be simply equal to the current post that we're mapping over we are not caring about any of the other properties if you remember this is an extended post we have more properties than what we are expecting here like the what was it it doesn't really matter to others like the subreddit for example but we don't care about them so we're not going to include them in the types um right here awesome let's save that render this out and we can see pulse it by you slash and then my name and we're going to be able to change usernames later on for now that says just my Google username which is totally fine posted by you slash word for AI support that's fine that's pretty cool and then below that we want to show um how long it's been a goal that this post has been imposted and for that we're going to use a very helpful utility function and that is format time to now that is pre-installed in your starter code if we take a look at this and why this is pre-installed it's because I don't want to type all of this out with you there's no value in it it's very stupid work and it just formats the time that um you know has been that has passed since the user has created this post I'm using these certain syntax we have right here so just now like minutes ago hours ago and so on just my preference you can change this if you want and we are replacing that Dynamic count with the actual count when we use the format Distance by the way if you're wondering deform a distance actually comes from date FN so we're extending a date library with our own code to form at the time it's very it might look a bit confusing at first the function but it's very simple we can insert a new date into this function that contains the pulse dot created ads and you're gonna see what this looks like let's refresh this um 44 minutes ago very very nice and to insert a bit of spacing between the 44 minutes you can see right here in the end of the name let's insert a JavaScript um space right here or JavaScript yeah like spacebar whatever this won't go away and it's going to insert one space right here so it looks good and uh text content what is that if I reload the page okay that was because of hot reloading um no worries there was gone awesome below that we want to show the post title right so whatever the post was named by the author and let's do that below this closing div right here so we have three more closing divs and in here let's put another a tag for a hard server reload leading us to a template string r slash or no this is going to be slash r slash and then the subreddit name put into that template string slash post slash and then the post dot ID so we're going to go into the detail view for that specific post and the reason we want a hard refresh is so the comments are refreshed if they are fetched server-side and not streamed in if that's what you choose to do if you load in the comments afterwards after loading that post you could insert a regular nexjs link component here just as well but I figured this works well for the approach we are choosing this H1 is going to get a class name of text Dash large a font semi bolt a padding y of two a leading of six and a text Gray 900 just like that awesome and it's going to contain the pulse dot title save that see what it looks like and my first post pause to beautiful very very nice we can see the pulse right here very very cool there's not too much left for the Post component um only one more custom component and that goes below this a tag there goes a div so three closing divs left let's create one div up here and this is gonna get a class name of relative of text Dash small maximum height of 40 with a full and an overflow Dash clip awesome and we also want to attach a reference to this a ref and the reason we want to attach a ref is because on the client side we want to check if this post is exhausting the maximum height and if it is we can add like a little blur to it so it indicates this post is longer than is being displayed in this post preview which is 160 pixels to do that let's add a ref to this diff right here and I call this P ref um you know for post ref and we can go to the very top of the file and declare that praf it's a const puref is oops P ref with our capitalized is going to be equal to use ref and invoke that we're gonna pass it null by default and then this is going to be the type HTML div element awesome this post ref is now appended to this div element right here with the purpose of dynamically tracking its height and we can now conditionally render inside of this div if the p-ref dot current dot and then client height is triple equal to 160 so if it exhausts the full height we want to show a blur and we can do that using a ternary so if it is then we want to show some jsx and if it's not exhausting that height if it's not like filling up the entire height then we want to render null and if it is however let's render a self-closing div that only gets a few class names this is going to be absolute bottom Dash zero a left-0 a height of 24 a width of full a background gradient gradient to top a from Dash white and a two dash transparent to dash transparent there we go let me go into a full screen view in case you missed anything and yeah that's going to be a self-closing div so we're going to apply a little gradient if we are exhausting the full height very nice okay and last thing or second to last thing we want to do in here is let's go down until there is one closing div left and then above that press enter a bunch of times so we have one closing diff below this and let's put one more div in here with a classm of background gray 50. is that index of 20 a text Dash small and now we're inserting how many comments there are for this posts um a padding X of four a padding y of four actually we can just combine this into a padding of four and on small devices want a padding X of six great in here goes a link component if you click the comments you want to go to this post ID you could also have an a tag in here works just fine by the way if you're wondering why my camera sometimes goes out it's because I have to restart it every 20 minutes because it's a DSLR that is not meant for video which kind of sucks but anyways in here goes an anchor tag that we want to do actually essentially we can just copy the one from above let's just copy this anchor tag and apply some different settings to it just copy this one it's going to go to the same href that doesn't change the class name and the content will change a bit so let's remove the H1 inside of it and apply a class name and the class name is going to be with fit a flex an items Dash Center and a gap of two then inside of the a tag we're going to render out an icon that is going to be the message Square we get from Lucid react as a self closing component with a class name of height 4 and width 4. besides that let's show the comment amount and what the hell is the comment amount like where is that coming from and this comes from a property we can pass into this post because remember when we are in the Post Feed right here we have to determined like the votes amount and the current volt and determining the comments we can also do in the parent it's very simple so we can literally just pass the amount of comments there are into the post component as a prop let's expect those as the comment AMT for amount and to find those com uh comments AMT as a number as a prop in this post component let's pass them in a second and first finish the jsx there's only one small thing missing that is the comments so like five comments for example in the Post Feed we obviously want to see how many there are and it couldn't be simpler to pass the amount of comments this is going to be comment amount is equal to the post dot comments dot length awesome so that's what we're passing into the post component let's move this into a side by side refresh this and we can see there are zero comments right now for each of these posts which makes sense because you literally um can't write comments yet so there couldn't be any awesome one thing that's missing is the post content where it's like the images or the um the text that is in each post it it doesn't exist yet and to properly handle the output of the editor we are going to use a package because it comes in blocks right doing that render or writing that renderer for that post content itself would be would just be a pain we don't need to do that it would be very time intensive there are people who have done just this and the component we're going to use to render out the content is gonna go right above the praf.current where we apply the blur and it's going to be called editor output as a self-closing component let's create this component because it's going to be a custom one as editor output.tsx and initialize it as a functional component like we did with the others and the important thing right here is that we're going to make use of a library to render out this output for us and we're going to import this dynamically um we can say write B let's say it below the Imports above the interface const output the component we're going to use to render the output is going to be equal to Dynamic which comes from next slash Dynamic right here we can dynamically import this component this is going to be async and this function is gonna oh oops what did I do messed up the syntax is going to be a white and let's close this so you can see this easier import and this comes from the editor JS react renderer package that comes pre-installed in your starter code normally you'd have to install this yourself and we want the dot default or what did I do we want the dot default version of that package awesome and important thing we want the SSR we can pass as a as the options SSR to be false because this package does not work on the server side unfortunately there was an issue on the GitHub I read through and it doesn't so importing it dynamically without SSR works just fine and fully rendering it client-side gets rid of any errors that we would have if we try to do it on the server okay then the output is going to be received as a prop let's call it content this is going to be the actual editor blocks and we already defined them as any if you want you can type them out yourself I don't see a huge value in that you can log out the values and type each one out let's just leave it as any to save time and it's really not necessary to type them out yourself awesome as the editor output actual component we're just going to make use of the output we get from the react renderer this one up here you can just put the output in here and this output receives for example the class name we can pass it off text they're small so reduce the text size the renderers because we're going to extend this with custom renderers and rares now this output component works fine out of the box for most stuff but there are two things that are not awesome out of the box it's how images are displayed and how the custom code is displayed we're going to fix both we're gonna improve performance by using the nexjs image component and to use custom renderers for the output we can say cons render rares is going to be equal to an object where the image gets its Custom Custom image image renderer and yeah no not like that and the code gets its Custom Custom called renderer this component is going to be responsible for displaying the editor content right here and for each Post in the preview and we want to apply a bit style a bit of styling to that just very brief the con style is going to be an object with the paragraph value again as an object with a font size of let's say 0.875 RAM and then secondly the line height is going to be 1.25 Ram there we go we can pass the renders into the output and we can also pass the style it's equal to style into the output and last thing we want is to pass the data into the output that is going to be the content awesome now it will still complain about something I also had this in the original project but this is a totally unnecessary Arrow we don't need to worry about it so we can just expect the error and everything will work just fine um even when suppressing the arrow because you know it's not an important error we can just omit it awesome now let's work on the um Custom Image renderer for example it's going to be a very simple function and we can even declare it right here instead of the same file function Custom Image renderer this is going to receive some data let's type this out um well let's not type this out let's type it as any and We Know by logging out that's how I did it in the um in the development originally because this package is not super compliant with typescript it does work amazingly as you can probably tell from the output but it's very practical so I wanted to use it anyways and the source of the image is going to be the data Dot file.url I just found this out by console logging it out and once we're done with this component we don't ever need to touch it again so maintainability with typescript is not a huge issue here we're going to return um a div from this element with a class name of relative with full and minimum height of 15 Ram in these angle brackets for a custom inline value I just found 15 Ram works pretty well and then we can insert the next slash image component right in here with an ALT of image I know not the best old but you can improve it if you want and a class name of object Dash contain there we go with a fill property applied to it and the source is going to be equal to the m source let's quickly comment out the custom code render and save this and see why we get an error right here probably because we need to import the editor output so I'm back in the post where we want to render out the output and we need to pass the M content into the output and this is going to be nothing else than the post dot content okay so what I've done is um pass the post content into the custom editor output component that gets then received right here as the content and uses a dynamically imported output we get from an npm package with a custom renderer applied to it just for images that's what I've done in this file okay let's try this out let's read out the page and see if it works well we don't have any images in any post so let's create a post and hello image three because it's the third post and let's just add an image in here this is going to open up on my second screen let's put this thumbnail in there of a previous video let it upload works fine my caption hit post and let's see what happens we should be redirected to the page and the first posts are loaded we haven't implemented the API endpoint yet that lets us load more posts okay so the reason my post is not showing up is because it would show up below this and we're only fetching two posts right now because the API endpoint for um infinite scrolling doesn't exist yet we haven't implemented that yet so just for now let's quickly bypass that by fetching not two because this is two under the hood and let's just fetch like 10 that will involve all the posts and without the page see what happens and just to verify that the images are loading correctly we can see Hello image 3 and now there's the blur applied to it very nice and we can see the thumbnail is actually there and everything is working as expected the image upload looks awesome it's stored securely in the cloud and I just really like how this looks and we can see when this was posted the community this was posted in and we can go to a detailed view which we haven't implemented yet we're going to do that later with a very cool data fetching approach and yeah it just works man this looks really really good awesome let's change this back to the two posts and implement the infinite scrolling later and this was just to verify that the editor output actually took the custom renders and then the custom code renderer is going to be very similar we can just do this um in the same file as well as the function custom code renderer this receives also the data we can destructure as any no need to type this out right now and we're gonna return a pre element from this with a class name of background gray of 800 rounded Dash medium and a padding of four and inside we're going to render out a code element with a class name of text Gray 100. and text Dash small and put in the data dot code right in here awesome that's the custom code renderer now we can also insert code if we wanted to enter the editor and it would look very good and that's the editor output we're not going to touch it anymore the typescript not perfect integration doesn't really matter because we know we set it up once correctly and now it will just work very very good work and now comes one of the two really neat data fetching approaches that I kept teasing so what we're gonna do is we want to display the up and down votes for each pulse right we're currently not doing that and we're going to split that component up well we could just do it inside of one component called like wait why is this called like pulsed vaults right this could be our component it would be rendered alongside each post and that would work just fine as a server or client component right we could do that however I think there's a better approach and that is splitting this up into two different versions that is one gonna be a server version and one gonna be a client version and let me tell you why we're about to do this it's a very neat data fetching approach so when we are fetching certain posts for example for example the initial post the first two or whatever you set the variable to um then we want to show the upvotes and we want to fetch those on the server because there's no real overhead in adding that join um from the server side so we can immediately display all the data that is necessary for this front page that's fine right when we're visiting a community we can do that just server side but when we click on a post we want the user experience to be really good and to show the data immediately right so we only need the most necessary data like the post title and the author name for example inside of that request when we click on the post so we're immediately there and all the other unimportant stuff that can be deferred like the comments or the votes are going to be rendered separately that is why we're splitting this into a server in the client component to allow for dynamic data fetching inside of this component allowing for one faster page loads and two and we can stream this via suspense it works super well together and we also have the option to fetch this info on a page level and pass it in later that's why it makes a lot of sense splitting this into a server and a client component and just so you understand why we're doing this so you can imagine the server component kind of like as a wrapper for the client the client component will be inside of the server component let's make this a bit larger as part of the server component but the benefit of the server is that we can stream this component and we can't just stream in the client and so the client will always be there for interactivity if you click a button that needs to be a client component right no way around it um but if you want to stream in the data for example when we go to a pulse detail page that is where the server and post volts component is going to come in first off we only need to worry about the client though and when we when we need it we're going to get into the into the server component so let's create a new folder inside of the components and call this post Dash vote and I switch back to the wrong keyboard post Dash vote and we're creating a folder because there's going to be the client and the server component in here but first let's just create the post Vault client.tsx dot t SX there we go as a functional component let's initialize this and let's declare this as a client component because we're going to do some interactivity in here like with react query so actually handle the up and down votes awesome so we are going to receive some props inside of this component this is going to be the post ID it's going to be the initial votes amount as a number and yeah I realize I'm doing this whatever and the initial vote which is optional of type vote type from Prisma client or null and the post ID is going to be of type string awesome we need to comma separate these and I kind of messed up the typescript there we go Okay so we've got the post I leave the initial votes amount and One initial vote and which is optional only if the user has already voted for this specific post that we're showing this component for we want a couple of things in here first off or later we're gonna make use of or use custom toast to show a login notification login toast if the user tries to up or download without being logged in then we want to store a state const stay um let's initialize this as the state um I've got a little snippet for it and this is going to be called votes amount set volts amount and this is going to be a number um type and I don't know why the tabulator is not working it's going to be initialized as the initial votes amount so when a user up or downvotes it should be reflected immediately as a state change for an optimistic update instead of refreshing which will take like half a second or second and really off put the user experience and then secondly we want one more set and that's going to be the M current vote and Set current volt this is going to be a use State we don't need to type out ourselves because we only want it to be inferred as the initial in Niche oops initial vault there we go so when a user just voted we also want to reflect that immediately as optimistic updates and then last hook we're going to use is Khan's previous vote if anything goes wrong during the up or down voting we want to resort to the previous vote and we can use previous from maintain Hooks and pass it the current vault right here so this will be you could do this yourself in a ref but there's a convenient hook for it so why not use it and this is going to store the previous vote of the user and now because I had some issues let's create a use effect to ensure a synchronization from the past and props to the um like with the server this is a client component we want to sync it with this with the server so we're going to say Set current volt to the initial vote because sometimes the initial vote initially comes in as undefined and is later populated if there is an initial vault as the actual value so server and client would be out of sync and that is not ideal so we're going to fix that with this use effect running whenever the initial Vault changes so just fix that and ensure a synchronization so everything works smoothly awesome then let's get started on the jsx it's going to be pretty straightforward it's going to be a div and some buttons to up and down Vault the div is going to get a class name of flex on small devices and up a flex Dash call Gap 4 small cap Dash zero padding right off six a small and up width of 20. padding bottom of four and on small devices snap a padding bottom of zero inside of your first goes the upvote button that's re let's import our button component and give this an on click Handler actually let's not give it an on-click handle yet we're gonna append that when we get to the actual data multiplication with react query first off let's pass it a size it's going to be a small Button as a variant we're going to use the ghost variant with an area label area Dash label for M screen readers and accessibility purposes of upvote so because we're only going to use an icon in this button if people with screen readers see it they couldn't or you know not see it if they're visually impaired they wouldn't know what it does unless we added the area label attribute right here the icon is going to be an arrow big up we get from Lucid react it's going to be self-closing with a class name off and we're going to use our handy CN helper function to conditionally apply this class name first of the unconditional ones are going to be height of 5 width of 5. and a text zinc of 700 and then conditionally applied are going to be a text Emerald 500 and fill Emerald 500 so we're going to fill this out in a light green color with the current volt is triple equal to up there we go awesome and then secondly we want to display the current score of the user that's going to be the second element in the stiff right below the button it's going to be a P tag with a class name of text Dash Center padding y of 2 a font Dash medium then a text of small and a text zinc 900 and this is going to say votes AMT it's the volts amount that we have calculated and lastly we can copy the upward button paste it below the P tag and turn it into a down volt button let's change the area label to do that and instead of a big Arrow up this is going to be a big arrow down and instead of the emerald we're going to use M just regular red if the current volt is equal to down awesome that's all the button that's all the logic we need that's the post volt client and we can render out the passwords in our actual post component right now let's use the post Vault client like that and we need to pass it and the initial votes amount and this is going to be the um underscore votes AMT now if you're curious where the hell this comes from well we can't really calculate it inside of this component with the data we have so we also need to receive that as a property for this post component it's called votes AMT and that's imported as underscore votes AMT and we can then receive it as a prop in this component and pass it right onto the post Vault client now that is a little bit of prop Drilling and which is fine and the volts EMT is obviously not um in typescript we need to Define that right here as underscore volts AMT so we're importing this value as the naming of this value and the type is something entirely different the type is going to be a number for typescript awesome so we can just pass it onto the postwort client this little bit of prop drilling again totally fine not an issue and the last thing I promise we're gonna receive in the post is gonna be um the current Vault this is going to be optional end of type partial Vault we don't need all the properties a vault normally has so we can Define or type partial Vault right inline up here and we're going to use a typescript utility type that is going to be pick to pick a certain value from or vault type and that is going to be the type we don't need anything else just the partial vote containing the vote type the actual one is fine we don't worry about um the post ID or the user ID we don't need those awesome and then we can also receive the current Vote for This pulse component last prop again I promise that was really it okay and now in our post volt client we can um just pass these on so for example we need the post ID we know what this is and we can just use the post.id and then lastly we want the initial vote and this is going to be the underscore or the current vote dot type awesome that works just fine and we can actually remove the underscore we can just leave it as the volts amount no reason to use the underscore anymore I left it during development I'm pretty sure but we don't need it in there okay so just votes amount current volt and pass these on to our post was post vote client awesome that means we can okay we can work with them but we first need to pass these in our impulse feed because we're now expecting more props than initially in the post component so let's pass them the um what do we have we have the comment about the post and the subred name for example we need the card Vault and it's going to be the current vote we have calculated up here we have found in the votes and then this is going to be the votes AMT and this is going to be the calculated votes AMT we have reduced down awesome let's save that and now we should be able to see that each post has a vote let's go to slash r slash react and see for yourself and here they are here are the votes they look beautiful they look really really nice we can see them zero vote zero votes and a very neat up and downward Bottom now these buttons don't do anything yet and what should they do well they should send an API request to either a vote or down with their post and store that inside of our database okay so two very crucial parts that we still haven't implemented is first off the submission of a vote so the question is when we click the vote button and let me open up my example project here on the right side and so I don't miss anything when we click the Vault button what should happen we want to make a backend call and the pause should be updated in the database with an upvote or downvote that we're keeping track of if you look at the Prisma schema as a separate entity and that is a vault right here so we want to create one of these votes essentially when we press one of the buttons and to do that we're going to use as always in this client-side logic right here react query super super handy and let's implement the voting functionality together right now okay so let's go below the user effect you can put this wherever you want in the component but let's put it right here let's say const and then we're going to worry about the destructuring later it's going to be equal to use mutation from tanza query that we can as always pass a mutation function it's going to handle the logic associated with it it's going to be an asynchronous arrow function just like this and we want to receive the type of vote we're gonna do and this is of type vote type oops vote type that we already imported this is either up or down remember this is an enum in our actual Prisma schema so in typescript it's a literal type of either up or down that we are receiving for the mutation function and then we can construct the payload to send along in our patch request that we're going to do the payload is going to be an object and we want to enforce a certain type um on this payload so that is either um well that's going to be the data that we need on the back end to actually properly handle the request and you know the drill by now let's create a validator to ensure we always pass the expected valid data because currently we don't have any validator for the voting let's go under lip and then validators and what we're going to create is a vote dot TS file in here in which we're going to handle the validation logic first off let's import Z from Zod and then we can get started in writing the actual validation logic first off we're going to export a cons post vote validator and this is going to be a z dot object and we're gonna pass the object properties so what do we need when we're validating a post Vault well we need two things the post that is being voted for and then we also need whether it's an up or downvote and the rest can be handled on the um server side right those two things post ID and the Vault type so post ID is going to be a z dot string and then the vote type is going to be a z dot enum that's going to mirror or database configuration we're going to pass an array into here with an up and also a down as the second or I guess the index array element at the first place but the second array element so we've got up and down in here awesome let's export the type from that export type post vote let's call this post vote request because this is going to be the data we're going to send along in the request to vote for post and this is going to be equal to Z dot infer a super handy utility function we can use type of post Vault validator awesome and then one more thing we want um while we're here we can also not only vote for posts but we can also vote for comments so while we're here let's also export a const and comment or actually you know what let's just copy the post validator mark it and then press shift alt and arrow down to literally copy the exact same thing however now we're going to call it um comments volt validator instead of pause sort validator this gets a comment ID instead of the pulse ID but the rest is going to stay exactly the same and then the type we can export is the comment Vault request instead of the password request and obviously we want to pass the common Vault validator and here the type of instead of the password validator then we can save that and we're going to use this post World validator request type right here to enforce the payload we're sending along in the request to the back end so we also need to import it and now we're going to get an error just like we expected awesome because we're missing two things the pulse ID and the vote type and the post ID we can literally pass as is because we're going to pass this into the password client as a prop and then secondly what we want is the vote type and we're going to get past that into the mutation function and we can also call this um volt type if we want to will that work if we comma separate them yeah that will work awesome so we can pass the positive and the Vault type inside of the payload and then send over a patch request we can say await axios import axials.patch because we're updating a resource and not creating a new one um if we're creating a new one we'll use post if we're changing or modifying an existing resource we're going to use patch to the slash API slash subreddit slash post slash vote API endpoint and what are we gonna pass to this well the payload with the post ID and the Vault type we are voting for awesome if we navigate to this API route if I do this here on my side project app API and then sub what was it subreddit post vote separated post vote let's see if this exists in our current project Source app API subreddit post well we have the create we don't have a vote yet so let's create a vault folder in or post folder right here and a route.ts file to actually handle the logic um for that API route and this API route is going to encapsulate One Piece One Central piece of the very very cool caching data logic that we're going to make use of in this application where we cache the most popular posts depending on upvotes and then we're able to fetch that data super quickly on the front end whenever somebody visits that post and let's get started in the logic for this let's export an async function called patched the HTTP verb we want to handle so it corresponds to the axials request we're making with a request of type request and now the actual logic that's going to be handled in this patch request we're going to have a try catch block for some asynchronous action inside of the try let's first get the body and that is going to be equal to rec.json that's how we get access to the um request body in the new route Handler syntax as you already know from previous API rods we've done and then we can de-structure from the post request Valley data and why is that not offering as an import let's read out the window and hopefully that will fix the automatic Imports um so we can import that did we not export that from the vote.ts export cons post vote validator oh post vote oops post vote validator and there we go we can import that and now we can say dot parse and pass at the body so whatever kind of data the body encapsulates if this is successful we know we have a pulse at the end of all type and else will else we will land in the error block we're going to do error handling here in a second let's first also get the session of the user that's going to be equal to the white get auth session from our helpers and if we don't have a session dot user in that case and the session needs to be optional right here if we don't have a user let's return and you press pause saying unauthorized with a status of 401. awesome so now we know the user is logged in and we can actually handle their request so there are different scenarios on a vote right and I want to make this very clear so first off there could be an existing vote and the user is trying to so actually let's go into excalator draw and quickly map this out so imagine three scenarios first one is um there's already a vault right the user has already voted uh yeah this this just looks horrible Jesus Christ I'm gonna try to draw an arrow here yeah that looks really bad like that and the upvote is already there and there is no downward I mean this is supposed to be a downwards error okay so the user has already voted second case oops let's mark this second case is the user has not voted yet so that is gone and no arrow is filled out the user hasn't voted and let's move this into a separate order by the way so this is step one this step two when the user clicks the upvote button it actually turns green and indicates the user has upvoted so now the user has created a vote because they haven't voted before but as the second step if that's the initial point they have voted and they click the button again then the vote should be revoked again right in that case we are deleting the votes so creating deleting or if user makes it download then we want to update that accordingly as well and that needs to be A3 so all these cases we want to handle in this API route starting with first checking if there is an existing vote in the first place and we can do that by saying cons existing vote is going to be equal to um a weight DB import that dot vote dot find First and we want to find the first vote where as an object the user ID is equal to the session dot user which we can assume exists now because of this guard Clause up here and the post ID that we get passed into this API route awesome and let's also fetch the post for later we're going to handle the existing vote logic here in a second um but we also want the post so let's say accounts post is equal to a weight DB dot pulse dot find unique in this case and we want to find the post where the ID of the post is equal to the post ID we're passing into this API route in the patch request and then we want to include certain stuff on that post to Cache it later that's why we're getting this post and that's going to be the author of true and the votes of true so we're going to make some very nice use of caching in a second with redis and for that we want to include these two properties that we fetch once and can store in our cache and then everything is going to be all right okay if there is no post to fetch then that doesn't really make sense um so the post ID is probably invalid in that case let's return a new response saying post not found this case should never happen unless somebody specifically messes with your API via Postman or another request client and artificially sends in a wrong post ID in which case we want to return a status of you can probably guess it 404 post node found resource doesn't exist okay now we want to handle the um case if there is an existing vote so if existing vote if there is a vote well first off we want to handle if the Vault type is the same as the existing fault so if the user has previously uploaded and now upwards again we want to delete the vote right it shouldn't be avoided anymore so if the existing vote DOT type is triple equal to the volt type that we're passing into this API function at this API route then we want to await DB dot uh pause dot no we want to delete a vote db.vote dot delete and we want to delete the post where as an object the user ID posts at the combination also as an object is the pulse ID and the user ID is going to be the session.user.id so whenever these two match we find a vote for that that's where we want to delete the post and after doing that we can return a response back to the client and so we can say return new response something along the lines of okay your post was registered successfully um great and that goes into this yellow bracket as well so into the if statement it needs to be included in there so in the other case right here we can assume there is an existing vote but the Vault type is not what we have already voted for in that case the votab is different so we want to update the current Vault we can do that by saying DB dot vote dot update and we want to update the post where the user ID pulse ID combination as an object and again it's the same thing as above with the post at the end user ID just copy and paste that in here and then we also need to pass some data to update right so um right below here we're going to pass the date and did I pass it in the wrong spot does it need to be here no wait so where statement ends here so data needs to go right here below the where statement separated by a comma and the data we want to update is the type and that is going to be the Vault type and that's it so we're literally just going ahead fetching the post and saying hey insert the download if this was previously in upvote or the other way around awesome and because this could have created an upvote now what we want to do is count the current votes and if they're above a certain a certain threshold that we Define ourselves like a hundred or a thousand or whatever you define as a high engagement where you want to Cache a post then we want to actually cache it first step is recounting recount the votes so if it's now above the threshold then we want to cache and so calculate if this is indeed above the threshold let's say const votes AMT is going to be equal to post.votes dot reduce and once again we get the accumulator and the current value inside of the reduce invoke a function with that and if the vote dot type is an upvote in that case we want to return the accumulator plus one and if the vote.type is a downvote down we want to return the accumulator -1 and typescript still doesn't know this is a number by the way else you just want to return the accumulator the starting value for the reduce is going to be a number so typescript can infer um that the the accumulator right here is going to be a number great and if the votes AMT the current votes amount after the upvote is larger or equal to the cash after upvotes constant and this is a pretty good idea because we're going to reuse it in two places in this route Handler let's define this centrally the cache after upvotes constant at the very top of the file and so you can always go ahead and change the so cons cache after upwards I'm gonna put this as one this is whatever you you define as a high engagement like 10 upvotes 100 upvotes for very simple testing purposes I'm going to leave this as one so if I upvote during the testing of this application then we're already going to invoke the cache and benefit from this caching Behavior okay let's go back down and let's get into the if statement and actually caching the post in redis and to do this let's first again Define the payload of whatever we want to pass so we can enforce a typescript type on that payload let's say cons cache payload is going to be equal to an object and what do we want the type of the cache payload to be well we can Define this ourself and let's do this in our types file so I propose we make a redis.d.ts file red is dot d dot yes a typescript definition file just for redis and the caching Behavior we want to implement let's export a type cached post from here and this is going to be an object and uh yeah this this is good let's export type cache post as an object and what are the properties we want to Cache when a user clicks on a post like this one right here and gets to the detail post page what is is the most important data that we need to cache and immediately show to the user and what is fine to defer what is fine to show later well I think the title is one of the most crucial aspects of all then the content should immediately be shown and also the username of who posted that and we could also cache the current vote so we can immediately show that and to do that let's define the ID as a string we always want to Cache that and everything needs an ID that's uh you know that's a given then that's cache the title as a string we want to Cache the author username as a string we want to Cache the content as any again just the the blocks that we're using um actually because we're gonna save these as Json let's put these as a string as well the content then we want the current vote and this is going to be of type vote that we get from Prisma client and this at the type so this is either up or down right so we are extracting the type volt type from Prisma we could also just put a vote type from prismaclient that would also work this is going to be um just up or down or no both achieve the exact same thing and then the created ads and this is going to be of type date that we also want to store um awesome so that is the most crucial information we want to store in redis and we can enforce that type the cached payload on or cash payload if we import it and why are we not getting the Auto Imports did I mess something up or is reloading vs code enough let's see after it re-initialized the typescript features and yeah we can't import that weird okay but that's fine that's um import type and we call it cached payload from and that should be add slash types slash redis does that work no that doesn't work oh and of course that doesn't work it's not a cached payload it's a cached post there we go of course that did not work down here um cached post there we go okay and now we're getting an error that we're missing a bunch of properties which is exactly what we want first off let's define the author username this is going to be the post dot author dot username or an empty string if that really doesn't exist but it should definitely should then the content that we're going to Cache is going to be json.stringify the post dot content so if whatever that is save it as a string we want the ID as the post not the ports are one what the hell the post dot ID we want the title of the post as the post dot title and all these values are going to be almost immediately accessible with redis speed to the user and when clicking on the detail view then we want the current vote and that is going to be the Vault type and lastly we want the created ad and you can probably guess the post.created at awesome so this is the payload we want to store in redis however how the hell do we get access to redis this is a serverless environment we can't just host thread is ourself therefore we're gonna use upstash as I mentioned in the very beginning of the video they are sponsoring this video kindly I've used them long before they sponsored the channel I use them for my personal projects that don't end up on YouTube and I generally stand behind their service and what they do and they're kindly sponsoring this video as well so let's log in with a Google account let's log in with a support account I hope I don't already have an active database on here and I don't awesome so let's create one and let's select the global type the global database it's going to be globally available and let's name this uh Reddit awesome the primary region for me is going to be Frankfurt and if you want you can select another read region um this should be where your users are at where your servers are at where everything is closest together and to achieve the best speed and also let's enable encrypted data traffic the mtls and hit create important check this and so we can avoid some issues with the connection later and click create awesome that's going to be preparing our database it's really really fast and then if we um or the the way we get started with this setup is by copying two things you can see right here it gave us an upstash rest URL an upstash rest token let's copy the rest URL hit the copy icon on here switch over to bread it and go into our environment file the redis URL goes right here under redis URL then we also need to write a secret and again these EnV values come from the dot NV dot example I have already prepared for you in the starter code okay now for the Reddit secret let's copy the upstairs rest token that's what it is and paste it in here awesome and let's put this into a string just to make sure it really gets the value like that hit save on the EnV file and Now setting up redis is really easy let's go over to our lib folder and create a new file called redis a very very simple like six line of code helper that we're gonna use and from here we're going to export a const redis is going to be equal to a new redis class we can instantiate and this red is class this needs to be uppercase right here comes from import redis from a package that comes pre-installed in your starter code I've got you is um from add up stash slash redis that's where we get this class from again we can now instantiate it with the URL of process.env.redis underscore URL exclamation point to tell typescript hey this really exists don't worry about it and a token of process.env dot um what did we call it redis underscore secret also with an exclamation point those are the two values we need to Define our redis instance and that's literally everything now we can get started with redis just as easy as that and use redis in all router TS right here to actually cache the most important stuff for each post so we can later stream in the less relevant information that can be deferred um to the um detail post View let's await redis that we import from the lib we have just created dot h set right here if you want to read up on what hset does it links to that right here we can open that and let's quickly take a look at the age set right here it says sets these specified fields to their respective values in the hash stored at Key I'm going to show you exactly what this looks like in the actual redis database you can see a bunch of examples and that's pretty much it so this is nothing that comes from upset this is regular redis upstairs just makes this available to us in the serverless environment in a very convenient and easy way um okay so as for the name we're gonna save this under this is going to be a key value pretty much and we're going to save this under post colon and then the post ID that's how we're going to fetch the data later on and then what we want to store is the cash payload right here as an object this works because this is a hash and we can just set the payload like this and it's going to be automatically put into Fields you're going to see that in a second once we cache our first post great and after doing that we can return a new response saying okay right here after the set with one closing curly brace there we are returning the response great we can now exit out of the if block if the existing Vault remember that's what we had up here and now we want to handle the case if there is no existing fault what does that mean well it means we need to create a vote we can't modify one we can't delete one we need to create a new one so if there is no existing Vault let's create one saying awaitdb.vot dot create and well what do we want to create it with with data as an object we're going to pass the type of votes type we're going to pass the user ID of session dot user dot ID and then lastly we're going to pass in the post ID awesome those three data points are what we are creating a post with and again what could happen is now the post is above or voting threshold so what we want to do again is recount the votes just like this we can copy and paste the code remember this adds no real performance overhead because in either case it's only going to be run once so either the post is existing or it doesn't so this code right here doesn't get run twice it only ever gets run once um and we can paste it right here below the vault.create and I pasted something wrong what did I mess up the if statement right here okay so we are recounting the votes with the votes amount just the same we are formulating the cache payload the same way everything else is the same and we can also just copy the um setting to the hash in redis right here and paste it under the cache payload great so now we have cached that as well if the post surpasses or set threshold and we can finally return a new response from this API Rod saying okay your vote was registered you know successfully awesome let's quickly get into the error handling and we can just go to one of the previous routes like the let's go into the Subscribe route or any other and copy the error handling from here because it's just the exact same thing paste it in here so we're checking for a z dot dot Arrow Z is something we need to import in our file import Z from Zod so we're handling two cases in our error handling one is if this is an instance of a zot error and then second one is going to be um well we probably need to change the error message let's say code not register your vote please try again awesome but they say this all four or 500 because we don't know what the um problem is great and that's the error handling done for the voting functionality and now let's see if we're actually making a request from the post vote client and the answer is no we're not and we're never even destructuring the mutate and let's call this volt because essentially it's just you know voting for a post whatever we have just done awesome let's call this and let's actually try this out the unsuccess on error doesn't we're going to do that in a second let's just try if this works and this is going to be for the button right here let's add an on click hander so whenever we click this button we want to vote and now we want to pass the Vault type and that is going to be up great I mean syntactically this looks very clean but an on click Vault up that that looks really really nice and then for this button um you can probably guess what we're about to do instead of voting up we want to vote down on this button great um okay let's do a test or should we test um the logic that follows is a bit more involved and let's just give this a shot for now and see if this works let's open up our application right here and see what happens let's open up the network tab by the way and see if the request is going to be successful and if it is that's cut that counts as a success the API Rod right here fails because it doesn't exist yet it's trying to fetch more posts because we have reached the end of the page this is fine this is what we're expecting because we haven't done that API Rod yet let's try upvoting I'm going to press upvote vote okay and we got invalid post request data passed okay that is not ideal that's not really what we want again this API totally find that it fails we expect that this one inverted pulse request data pass not ideal and let me check what the issue is oh and I think I might have just found it we are not awaiting the react.json operation this is asynchronous so that might be a problem let's save that and retry this again let's vote for post and see what happens it didn't register the request that's fine let's try this again vote it pants and now we got the okay awesome so it works exactly as we are expecting really really nice the vote has now been registered but it doesn't show and the front end yet we can't see what our current vote is and you know that's not what we want we want to see or vote so how do we do that while also maintaining optimistic updates for a great user experience optimistic updates if you don't know um those just mean um that as soon as the action is taken we are already doing what user intends to do and if it fails we will notify the user later but it probably won't fail um so in that case the user just gets a very good experience lighting fast so our servers seem way faster than they actually are it's a very cool trick and in 2023 2024 and whenever you might be watching this video that's what users are expecting it's it's crucial and to achieve that let's go into or uh there's so many so much stuff open here again let's close out of all of these let's go into our component right here under the mutation function and first off handle the on error state if anything should go wrong right what should happen well we received the error and the vote type right here for the error is it parameters or arguments for the parameters arguments is wherever we invoke that exactly okay this is going to be an arrow function and for the on error if the Vault type is triple equal to oops so up there we go in that case we want to revert the vote that the user has just done and in order to revert that in real time we are going to use the set volts amount so we are deducting one after adding one optimistically that means we need to set the votes AMT to whatever it was previously and turn this into the previous value previous minus one to immediately show the action has not worked and else we're going to set the volts AMT to whatever it was previously and previous plus one because chances are that was probably a downward before because there is only up and down okay um then we're gonna reset the current volt so the current volt represents the color of the icon right whether it's green or it's red and if the voting fails then we want to reset that color to whatever it was before making the request that is why we're keeping track of things as the previous vote so we can set it back to that so we can set the current vote to the previous Vault whatever it was before again this is just a use ref wrapper that we can use for maintain you could do this yourself easily but it's a convenient hook we can just use for this and lastly um if the error is an instance of the axials error class in that case and we can do one more check if the error dot response dot status and why am I getting an error because we need to import that if the error question mark dot the response gets a question mark dot status is triple equal to 401 and this gets a question mark because this is optional and doesn't have to exist this could be anything or undefined so a status might not exist on that and so only if this exists are we checking for the status then we're going to return a login toast that we get from our custom use custom toast up here and so in that case the user needs to be logged in to actually vote for a post and if this check is unsuccessful right then in that case we want to return a general toast notification with a title of something went wrong and we also need to import that toast from our hooks then with a description of your vote was not registered please try again and lastly a variant of this toast you can probably guess it's not going to be the default instead let's use the destructive variant for that host to notify the user hey something really went wrong here and now for the optimistic updates we're gonna handle those also within react query and that is going to be inside of the on muted so as soon as the action happens before it's resolved before it's settled before anything as soon as it happens we want this action to be executed as an error function and what we're going to pass in here is the type of vote type there we go and now what do we want to happen well first off if the current volt is triple equal to the type that we're getting into here triple equal not double equal in that case well if the user is voting the same way again if they have previously upvoted and they're now upvoting again we want to remove their vote so we're going to set the current volt to undefined then if the type is triple equal to up up there we go in that case we want to set the votes amount to whatever it was previously and now return previous minus one and also we can just copy this down else if the type is down in that case we want to set the volts amount to whatever it was previously and now previous plus one you're gonna see what this does here in a second okay and if the current volt is not equal to the type else in that case if the user is voting the opposite direction also if they have previously upvoted let's draw this if they let's get rid of all of this stuff and is there an arrow somewhere in here yeah I'm just gonna draw one they don't look amazing but it gets the point across so just imagine we have um the up and down votes here so if they have previously upvoted right then the post has a one upvote but if they now decide to change their vote instead of upvoting or down voting then we don't only want to subtract one but we want to subtract two because now that should be negative one instead of zero again right so in that case else we want to first off set the current vote to the type that is being emitted by the user up or down and then if the type is triple equal to up in that case you want to set the votes amount to whatever it was previously and now the previous plus the um current well we're going to do a conditional check and this needs to be an arrow and the current volt and if there is a current Vault if this is a truthy value then we're going to say 2 and else we're going to say one right so if they have previously voted only then are we gonna add or subtract 2 to reflect that change like the minus one or the other way if they upload it now that should be one instead of zero that's why we're adding two and or subtracting two and elsewhere I'm just doing that with one and else if the type is down in that case we're gonna set or actually we can we can also do this inline set the volts amount so whatever it was previously and now previous minus and again the same logic in parentheses the current vote and if that is existing either two or in the other case one and that's all the logic done for the unmuted awesome so let's take a look at how this works let's reload the page and okay great we can see I've already uploaded that post amazing let's try that on the second one and pay attention to what happens as soon as I click the arrow this is going to turn green with optimistic updates that's what these are for awesome it's immediately done and there's nothing to it right if I download it there's gonna be -1 upvote download upward download upvote and it just works really really well and if I click the same again it's going to go back to zero it's just removing our current vote and with this optimistic update logic really really nice this ensures an amazing user experience you can be very proud of this and use it in your future project as well this is a very very cool um point or aspect of this data fetching logic we can easily achieve with react query that's one huge benefit of um using react query awesome and I propose one thing we quickly add is because currently in the background a bunch of API requests are failing um right here trying to fetch more data but the endpoint just doesn't exist so I propose to be quickly add the fetching more posts endpoints under um what is it where are we fetching this right here in the pulse fit under slash API slash posts let's see if that path exists under slash API slash it doesn't under slash posts and we want a route.ts to handle those requests under API posts and then route.ts let me open that up in my example project right here API then posts and the raw.ts awesome the logic here is going to be rather straightforward let's export a async function called the get because we're going to make a get request to this endpoint with a request of type request coming into here and now the first thing we want to do is determine the URL as the new URL class and instantiate that with the rec.url so we can get some data from it um here in a second let's determine if the user is logged in const session is going to be equal to a way to get auth session because depending on whether the user is logged in or not we want to serve different posts right that can make a very big difference and so for example if the user is logged in we want to determine the communities they are following let's follow um communities communities um communities IDs is going to be of type string array and by default this is going to be an empty array and if they are logged in if there is a session in that case we're going to say const followed communities Community IDs is going to be equal to a weight DB import that dot subscription not subreddit I've done this so many times wrong in this in this long video is going to be dot find many and which subscriptions do we want to find well where the user ID matches the session.user.id that's where we want to look and we want to include the subreddit and that's the only thing we want to include in that request so we can see if we hover over this a subscription extended by the subreddit type recognized by typescript beautiful and now we can assign that um so followed Community IDs is going to be the followed let's just copy this and instead of typing it out it's quite long um dot map and for each subreddit we want to return the sub dot actually we could just destructure from here the subreddit there we go and then we want to return the subreddit .edu okay so essentially we have created what did I do wrong followed oh let's call this followed communities instead of the IDS and only this is going to contain the actual IDs so followed communities and these is going to contain the followed communities dot map and then the IDS so these are the entire communities and these are just the IDS that's what this split is for um great so we can now handle the data fetching logic whether the user is logged in or whether they are not um and we're going to do this instead of a try catch block first off um we are going to define a Zod schema inline we're not going to do this in a separate validator but we're gonna do this inline because we're only going to use it inside of this API rod and there is no point in making it reusable if we don't need it to be reusable this is going to be easy we import from Zod dot object and this is nothing else then for the other API endpoints where we use the validators we're just going to do it in line right now so this object is going to contain a limit as a z dot string which is going to be like a two poles or four poles or whatever you want to pass the current page as the Z dot string and we need to comma separate these and then under the page we also want the subreddit name we are currently on as a z dot string but we don't have to be on the subreddit we could be on the home page for example so this needs to be dot knowledge dot option oops and invoke the dot knowledge Dot optional awesome so we don't have to pass the subred name but if we are inside of a subreddit um in that case it makes sense to only fetch post for that subreddit and let's parse right in here and as for the subreddit name we want to parse this is going to be the URL dot searchparams dot get so whatever we pass as the query params to this API endpoint from or Post Feed again if you don't remember like this it's going to be a query param or the oops or the page it's going to be a query param so we're going to say dot get subreddit name if that exists as for the limit we're gonna parse the URL dot search params.get of limit and then lastly you can probably guess for the page we're gonna do the same thing comma separate these as url.searchpreams.get and then here we're going to say the page that is what we want to parse in here and now we can destructure because now we know if the sparse is successful we always have this data and can work with it if not we're going to land in the catch block okay and because we want to make a request and like a conditional request to Prisma we need to define a certain where Clause conditionally we can use later in Prisma we can't do this inline or maybe we could but it's not very clean to do it inline so let's do it right here let's say con or let where Clause is going to be first an empty object and this is going to stay an empty object so now we're going to do a conditional check if we have a subreddit name if we are inside of a subreddit then the where Clause is going to be an object like that where the subreddit is also an object and the name is the subreddit name so in that case if we have a sublet if we are inside of a subreddit we're only fetching posts for that specific subreddit this is going to be passed to Prisma and here in a second so it needs to match the Prisma syntax this right here and then else if right here else if we have a session and I switch to the English keyboard again oops if we have a session and so this won't matter if we have a subred name we are using this and if we have a session um in that case the where Clause is going to be an object with a subreddit also as an object there we go where the idea also as an object M is in the followed Community IDs so we are only matching subreddits that you are currently following if you are logged in if you're not logged in this won't matter and we can simply use this where Clause later in our personal statement to make it very easy and clean to fetch a data data in this API route okay so let's get into fetching the actual posts so display 4 or infinite scrolling when the user Scrolls down to the bottom of the page what should be shown after that that's what we're doing right now and the const oops const posts are going to be await DB dot pulse dot find many and now we want to pass first the tag value how many posts are we going to return and this is going to be a parse end of the limit so turning that into an actual number then we want to skip certain posts as a pagination behavior and how many do we want to skip well we can calculate this as the parse isn't passed the page minus 1 outside the parenthesis by the way minus one and then times parse in the limit that is how many posts we want to skip so we're only giving back posts that are not already shown on the page awesome and then we want to order this by as an object we can pass created at as a descending you could do ascending as well if you wanted to it's just personal preference and then we want to include certain values in here like the subreddit as true oops I just went into full screen how did I do that what the hell oh never mind subreddit as true then the votes as true we want the author as true and lastly the comments as true as well so we're doing a pretty large relational join for these posts which is fine and then for the where we're just going to put the where clause which won't throw any error because syntactically this matches the Prisma syntax awesome and that's all the data fetching Logic for the post so um let's quickly review this and we're first I'm determining if we have a logged in user then we're determining which communities they are following to show if we're on the home page um only the community posts which the user is following then we are getting the data safely from the request parsing it into a format where we can expect what happens and constructing our where Clause that we can later pass into Prisma to determine which posts right here should be fetched from the database really really nice and then we can return a new response saying Json dot stringify posts so we can make use of those um on the front end and if we have any error then we can simply copy and paste the error handling from another router TS like like the subreddit for example the actually let's let's go with the unsubscribe route let's just copy this error Behavior paste it in here if we have a zot error then we can say invalid request the data passed and otherwise let's just say could not fetch more posts they say this or 500 because we don't know what's wrong and save that awesome really really good job that's the data fetching logic done for the pulse and let's try this out let's go into our browser reload the page and let's see what happens hopefully this won't go any errors and it will show us the actual posts very very nice if I refresh this page we only get the post for our slash react and let's navigate to the home page and okay we well we don't have a feed yet but if we go into r slash react we can see there's one post there's the second post um really really nice the problem is if we are scrolling right now it doesn't fetch more posts right that's that's kind of the whole point of what we're doing here um currently it's not fetching more posts and the reason for that if we go into our Post Feed is because we're not making use of the fashion next page or the is fetching next page for that matter so we're never using these actually and the way we can make use of these is inside of a user fact to check if we are currently intersecting with the last post and only then do we want to fetch the most recent data um AKA more posts right so let's create a use effect inside of our Post Feed and inside of this user effect this takes a callback with a dependency array as the second argument and in here we're going to pass the entry and the fetch next page those are what's going to come from outside inside of the user fact so if the entry dot is intersecting the entry and this seems to be optional by the way is going to be the last post because we attach the ref to it remember um right here the last post gets a ref so the entry is going to be the last ref and if the last post is intersecting in that case we want to fetch the next page and that's it we can save that let's read off the page and see what happens um give it a second we can see my first post two my first post well that shouldn't be there why is the post there twice Aha and I just figured it out um weird that it didn't appear in my finished project I did exactly testing on this issue to ensure this won't happen um so what I decided to add is right here an order by created at desk in the page.tsx where we are fetching the subreddit posts and doing the huge join right here and so if you take a look at the order by this is what I added this this is what it looked like before it's in the page.tsx under the M slug R Slash Slug slash page.tsx this is where we are and if we add the order by created at descending then we are making sure the first two posts are not duplicated with whatever is fetched after that and the newest one is at the top and the oldest one is going to be at the bottom awesome so by doing that we avoid duplicates and post Behavior very very nice and we now get infinite scrolling Behavior and I just noticed my camera is off I'm gonna turn it back on here in a second I just want to finish my point um really really cool infinite scrolling is such a cool thing to add to your apps if you're dealing with larger amounts of data it provides a great user experience it reduces strain on your database on the client and it's just overall a very good idea to add if it makes sense for your use case all right and if we look at our timeline it seems we have already done step 6 without having fully done step 5 um which is fine and however let's do step 5 next so we can already display posts in a feed this works properly and I propose we also do the home page feed right now this one which doesn't exist yet and then we also want to do the optimizations right that's the very cool data red is caching thing um whenever you click on a post and go to the detail view where we only load the most necessary data at first and then stream in the rest um I think that's what makes sense to do next um so let's move the actually uh yeah let's move this over here and go open up Reddit too side by side here and this is a note um I left for myself so I don't forget where I left off okay um great we can save this page.tsx and if we go to the main page.tsx the app um the the entry point app the home page whatever you want to call it we can see um display either custom feed or um General feed right here and this is what I meant with the um with the feed right where if you're not logged in you're going to see a general feed so any posts that have been created or when you are logged in we want to show a custom feed um to you and the way we do that is by rendering um two components either a custom feed or a general feed based on whether you are logged in or not and to determine if you are logged in we can turn the home into an async function and fetch the session by using cons session it's going to be equal to a weight get off session user handy helper once again awesome so now we can conditionally check whether you are logged in or not and if there is a session in that case we want to return a custom feed to the user right a custom feed component and if you're not logged in we want to return a general feed to the user this is in the main page.tsx um this is where we're doing this okay and both of these don't exist but the logic in these are going to be very straightforward they're both going to render out the actual and post feed that we have done right here both are gonna render out this component just with different data and the way we're going to do that is let's create a new component instead of our components folder and let's call this um for example General feed dot TSX and the general feed is going to be and very straightforward let's say const General feed so this is going to be shown to anyone that is not logged in it's going to be an async arrow function like this and inside of here we are only going to fetch posts that's going to be the logic for it um so cons posts are going to be a weight DB database dot post dot find the many and which kind of posts do we want to show um to users who are not logged in well first off we want to order them just like the others by the created ad and descending so they match the other order and then we want to include certain properties we want to do a join and that is going to be the volts true that's going to be the author true we also want the comments as true and lastly the subreddit as true as well so these four tables we also want included in this post table and then we only want to take a certain number and we want to take um right here take the infinite scrolling pagination results the super long and config variable we have um this is going to be two at first to determine the um or to demonstrate the infinite scrolling behavior in production this should definitely be higher than two right and because if we only fetch two posts on the server side when loading that is barely even enough to fit the viewport on like desktop devices normally you'd have like I don't know 10 maybe that would be a good number and but if I did 10 it would be hard to demonstrate the um infinite scrolling because you know I could create like 20 posts yes but it will take quite some time awesome and then what we can do is just return the Post Feed like this and pass it the initial posts of posts like this so this is our general feed awesome we can export that as default export default General feed and that is all the logic done that we can now import right here um the component we have just created and that is giving us an error and general feed cannot be used as ajsx component its return type promise element is not a valid jsx element this is fine typescript is acting up once again and we can simply fix that by saying um add TS Dash expect Dash error and then whatever you want like server component now the the bad thing is this will also suppress the custom feed error um because you know if we didn't have this we don't have this component which is a valid typescript error whereas this is not a valid typescript error typescript just doesn't know about server components um very reliably just yet but we still need to create the custom feed even though we don't get an error right now and the way we do that is also in the components let's call this custom feed dot TSX and the custom feed is going to be as simple as the general feed so we can literally just copy and paste the general feed in here and give it a different name like custom feed and change the data fetching logic just a bit first off we want to determine the current session so the cons session is going to be equal to a weight and get auth session from the user and to determine which subreddits they are following and the followed communities const followed communities is going to be equal to a weight DB dot subscription dot find many because a user can be subscribed to a lot of subreddits if they want to and we want to find this where the user ID matches the session.user.id and then afterwards we want to include one thing only and that is going to be the subreddit of true awesome um so these are the communities the person is following and now we want to say um or we want to determine which posts to actually show and because this have has nothing to do with with what is being shown on the page directly we just use this and to determine which posts we want to show to the user and the cons posts are going to be oh wait db.post dot find many we want a lot of posts so short to the user right here and we want to filter these posts where the subreddit as an object name as an object is in that's something we get from Prisma and now we can pass this in Array of values and it will only return us those value and those posts where the subreddit name is in this array like for example if I pass react it would only give us react right and what we want to do is map over the followed communities.map and for each subreddit or Community we want to return or actually we can destructure these subreddit right away subreddit and we want to return the subreddit.id in here so now we are only showing posts from followed communities just like this and we want to order them by the created ads um descending just like this awesome and then we want to include just like with the general feed we can just head over copy this include section the same four tables and we want to include right here great and then also the take we only want to take so many posts and that is going to be the infinite um super long name that we have um written ourself in our lib awesome that's going to be our custom feed now we still need to import this right here and we can save everything and see if this works let me reload the home page and the feed should be updated accordingly so I'm logged in it will fetch all the posts from the subs and following which is only the react sub and because literally that's what I have created um if I were to create another sub let's create the next JS for example click create community and post something there in Dev mode this looks a bit sluggish but in reality this is really fast and let's create a post in the next JS subreddit hello from next JS and hello there awesome and let's click post that should post or um sub or or um Post in the next year I subreddit and if I now visit the home page um we're gonna work on caching later it's a bit weird but when we hard reload the page it should actually show hello from next rest right here awesome and then the posts um below that if I zoom in way more you could be able to see the infinite scrolling okay now we intersect right away and that's fine um there we go okay you just saw it there um so look at the scroll bar right now as I scroll down it's going to load more posts right now um from the infinite scrolling just wanted to demonstrate that with a few more posts and we can see our next.js post right here so we know everything works just as expected really really nice okay um we've we've done the custom feed we've done the general feed let's quickly look at our roadmap uh displaying post and feed awesome we've done the first part correctly and now what we want to do is the optimizations awesome so that's going to be a really really neat part and what I mean by these optimizations again is the caching Behavior with red is and when you click on a post that is what should happen right and so it's going to be the imposed detail view immediately or almost immediately loading and this is going to be a dynamic route as well and this Dynamic route to show the details for each post once you click on them is going to live under um what is it it's gonna live under R Slash Slug slash post slash post ID and so that's gonna be R Slash Slug slash and then let's create a post folder in the slack folder um just as a static folder and then the post ID again is angle brackets as a nested Dynamic route in here this is going to say post ID as the folder and inside of here goes a page.tsx to render all the content and for each post so if we initialize this as a functional component really quick um just so we have something there we can see if we reload this page that is going to be the detail view um for each post and this is going to be a really really cool file um and we're going to handle comments in here we're going to handle the caching in here and this is going to be a really fun part of the build um okay so let's get started in this and we all already know we are going to receive some and prams in here and the premise let's type them out in the page props and we can just do this inline because we're going to declare this as asynchronous later anyways so let's get rid of the FC and instead declare the type of the params like this with the page props and obviously that up here needs to stay and this is going to be the primes and the params have a pulse ID because that's what we put into the angled brackets right here that's what it says it needs to match this and this is going to be of type string awesome and for each post let's export two things in here that's going to be export cons Dynamic it's gonna be equal to force Dynamic to force a dynamic behavior and then export um cons fetch cache is going to be Force no store and because whenever you visit a post in detail view we want to hard reload all the information that is associated with it um such as the comments right these should always be fresh instead of stale comments from um you know that have been cached in your browser we don't really want that okay as for the um content of this component first off if we have a cash pulse let's look at a redis database let's go to upstash.com and because we've already uploaded some posts we should be able to see that we have posts in our database or cached posts um let's go to orb Reddit database into the data browser and we are able to see awesome there is a post right here with a current vote of down and I just realized storing the current vote in here doesn't make too much sense because this is dependent on the user and so we might take that out later but all the other information makes just as much sense we're storing this in a hash and that allows us to store this so beautifully in this format and awesome so we can see our post in here and whenever we now visit the detail post view on this page right here we can check if we have a cached post because if we do awesome that's going to be way faster than doing a real Prisma database query and joining four tables or whatever and together it's just way more performant um so we can say const cached post is going to be equal to and then in parentheses awaits redis Dot and we need to import redis for this to load hello can I not there we go redis Dot and then H get all that's what we want we want to get all properties from this hash and this is going to be the key that we're searching for this is our format so the post colon and then in template strings we interpolate the params.post ID or we concatenate the post ID I guess you could say and that is what we want to fetch and why is this okay we also need to mark this component as asynchronous as a server component asynchronous so we can await this action from matters and we know the type this will have and because we've written this into the database ourself so we can cast the type right here as cached post the type we enforced on the payload to store it in red is so we can enforce it and cast it right here so we know what type this is awesome and now we can say let's post because we want to determine um if we have a post or not this is going to be of type post post and it's going to be a join of the post type we get from the Prisma client and custom properties such as the votes which is going to be a vote array and we also need to import this type from Prisma client and the author which is going to be of type um user also from Prisma client and this just needs to have one e there we go and but this could also be null this entire expression so we need to wrap it in parentheses right here or null and we can initialize it as null and the reason we are declaring this post as a mutable element right here instead of as a constant is so we can do a conditional check if we have no cached post if the cache missed only then do we want to make the database call right only then do we want to get the most necessary information for this post and that is going to be the post is going to be equal to a weight DB import that dot post dot find First and which post do we want to find well where the ID of the post in the database matches the prams.post ID and then we want to include certain tables in here that's going to be the votes votes of true and also the author as true awesome so with those two included we can um you see typescript wasn't happy when one of these was missing because that's what this is for that's the joins we're doing and and if both match then um typescript is happy and lets us assign the post and if we have no post and if we have no cached post so if both are non-existent in that case we can return a not found 404 because um the user is trying to access a post which just doesn't exist in our database awesome and now we can get to a really really cool part and that is going to be streaming in the information so when we load the page right we immediately have the most or not immediately but almost immediately very very quickly through redis have maybe the pulse information this doesn't have to hit the cache but if it does we have it really fast so um for the jsx let's return a top level div without a class name and then in here another div this is going to get a class name of height full of flex Flex Dash call on small devices now we want a flex Dash row and items Dash Center on small devices and up and items Dash start and then a justify between oops between there we go awesome let's give this just a bit more space let's go into full screen so you can see the whole class name awesome and in here we want to wrap or um post vote component that is going to be first um but if we just created a post vote component in here like the one we already have the post Vault um client component this one um how would we stream this in well we could try some fancy logic with using um suspense well it doesn't work here it works down here if we put an object right here we could say suspend well it doesn't work here it works somewhere I think I'm messing something obvious up right here anyways we can definitely use suspense with react query um however it's much easier and more intuitive and if we do a post vote server component that is gonna pre-load the information on the server that we can easily stream in then so when we want to display the post volts let's create a server component for that in or post vote right here and the components folder and let's create a post vote server.tsx and what we are going to do with this password server is a pretty clever concept I am learned from versel actually in there not documentation but in a demo project I did that's where I learned this and I found this approach to be super clean and so let's create a functional component for this and this component is going to receive three props the post ID the initial votes amount and the initial vote and no I was wrong it's going to be four it's also going to be at that get data so the thought here is either um well we always pass the post ID but then either we pass the initial votes amount and the initial Vote or we pass a function to execute in the client right here in the post Vault server component to get that data or self and if we do this with the get data then we can easily use suspense with this component and display a loading state to while that get data has been executed and if we pass these it's immediately going to be accessible and we don't need to worry about streaming at all so that's the thought here it's a hybrid approach between immediately rendering and between um displaying a very nice loading state to the user um so as for the logic that goes into this component well first off let's type this out um so the post ID is going to be a string then the initial votes amount is going to be optional as a number the initial vote is going to be also optional as a vote type we get from Prisma client or no this doesn't have to exist if you didn't vote what should that be no right and then as for the get data function this is going to be optional as well and we're gonna type this out as a function that returns a promise and promise is generic so we can pass it these two angle brackets right here just like a function for typescript that's what generics are and in here we're going to pass some parentheses with a post we get from Prisma client and an object we want to extend the post with the votes as votes array we get from Prisma client or this could also be null still in the angle brackets still in the generic so this expression right here in the parenthesis could be null awesome we can save that we've properly typed everything out and now let's get started in the logic for this component and I think it's going to get very clear what we're doing once we're actually making use of suspense and in our detail post View um so first off we want to determine the session this is going to be equal to a weight get server session and the way it doesn't work because we need to declare this as a server component as a asynchronous component so let's get rid of the Declaration with FC and instead put it right here after the props so what that allows us to do is Mark this as asynchronous so we can await at the top level beautiful now let's determine two things let's underscore votes AMT is going to be a number and default to zero and then secondly let the underscore current oops I activated caps lock the current volt is going to be a vote type we get from prismarck client or no or undefined and we're going to default it to undefined as if the user has not voted yet and the reason we're declaring these as let is to mutate them right now if we have a get data function in that case we can actually execute it so const post is going to be equal to weight get data and this get data that we pass in as a prop um obviously you know it's passed an essay prop so in the parent we need to define the database called that is all um just being executed in the child component right here um so if we don't have a post um we're gonna return not found because obviously the get data function was um faulty um so no post exists in our database in that case and otherwise we're gonna um actually populate these values right now so the votes amount underscore votes AMT is going to be equal to post dot votes dot um not not find many it's going to be reduced reduce there we go with the accumulator and the current value the volts and what we're going to do in here is the logic we've done previously as well if the vote.type is triple equal to up in that case we want to return the accumulator plus one and if the vote.type is triple equal to down in that case you want to return the accumulator -1 for a download and else we will just return the accumulator but this should not happen and initially this is going to be a zero a number so we can reduce properly awesome and then as for the current vote that's no New Logic either it's going to be the underscore current vote it's going to be equal to post dot votes dot find and then we want to find by vote where the vote dot user ID is triple equal to the session.user.id awesome and why is that okay because we well this would be the actual vote right but we only care about the type of that vote so that's going to be DOT type awesome so this doesn't have to exist but it could and else if there is no get data function then that means as I mentioned earlier either we pass these two right here or they get data if we want to stream this component it's a very nice hybrid approach so now we can assume these two exist and because we didn't pass the get data so we're going to say underscore votes AMT is going to be the initial votes amount as in exclamation point because we are forced to pass that if we don't pass the get data and then the underscore current vote is going to be the initial vault awesome really really nice so if we pass a get data function we execute that and if not we pass the other values for immediate rendering it allows us both ways and then what we're going to return is just the post post vote client that's going to handle the interactivity the only point is to stream this um if we want to that's the point of this component you're going to see that here in a second so the post ID is going to be the pulse ID the initial volts amount we need to pass as a prop is the underscore volts amount and lastly the initial vote is going to be the underscore current volt in this component and we're done now we can stream in this post volt server component beautifully inside of or detail page let's navigate there app R Slash Slug slash post slash um post ID and then the page.tsx this is where we want to make use of this amp streaming and the way we do that is by using the regular as react suspense component and nothing special to it with a fallback off let's call this the post Vault Shell Shell there we go because it's just a shell for the Post votes the loading state if you will I'm like a skeleton or a loading spinner whatever you want basically while the um get data function is being executed and there's so so many things open here again while the get data function is being executed right here while that is happening this is what's going to be shown as the loading State and I decided to call it postboard shell and we can declare this inline in the same component we don't want to reuse it anywhere and so we can just say right below this component function post volt shell is going to be a very simple um jsx component returning a div and inside of this div let's or first let's give this a class name of flex item Center Flex Dash coal padding right of six and lastly a width of 20. inside of here let's render out the upvote first off that's going to be a div with a class name of dynamically button variance like this with a variant of ghost so the goal right now is to make this look as close as possible to the original vote component the vote client this one we want to mark this right it should look like this but without any interactivity because it's just the loading state so um inside of here we want to render out the arrow big up from Lucid react the icon and give this a class name of height 5 with 5 and text zinc 700 just like this awesome that's going to be let's annotate this actually it's gonna be the upvote then we want to show the score so let's annotate this as well score that's going to be the current votes amount and obviously we don't have them let's create a div in here with a class name of text Dash Center padding y of 2 a font Dash medium text of small and text zinc 900 and because this is just a loading set we don't know what the score is so let's show a beautiful loader to a loading spinner we get as an icon from Lucid react with a class name of height 3 with 3 and animate Dash spin awesome if we hover over this you can see what this does it's a very basic CSS animation and now we can copy the upvote button and easily turn this into a down volt Button as the last element for or post volt shell everything is going to stay the same just the arrow big down instead of the aerobic up icon from Lucid react but again everything else is just the same awesome so now we determined what should be shown while we are streaming in the data and that we don't immediately have accessible the not so important data like the post amount right and in here we can render out the post Vault oops post vote server component that we have just created that's the entire point to render it in a suspense with a post ID off and this is either going to be the post dot ID if the post exists right or if we have a cached post then we want the cached post dot ID awesome and now we're missing one more prop and that is the get data so to execute the get data in the postboard server we we need to pass a function that returns us and why can't I hover over this that returns us a promise of posts and votes so a join table of those two and to do that we can declare an asynchronous arrow function right here that is going to be executed just like that inside of the postwald server let's return an await of DB um dot post dot find unique and if this is something you've never done because chances are if you're a beginner you might not have you can just pass functions as props and those are being executed in the client or in the in the child that's what I mean it's it's really cool um we want to find a unique post where the ID of that post matches the prams.post ID and then secondly we want to include again we are expecting okay well this is a long typescript message but we are expecting the votes as well um for that component right so we want to include those votes um in this table awesome and did I mess something up syntactically oh I'm an English keyboard what the hell did I just do is there a missing bracket here there is and why is this still giving us an error ah so um again we have the it's return type promise element is not a valid jsx element this is this has nothing to do with the postwald server um being done incorrectly this all works it's just typescript not recognizing that this right here the asynchronous is valid Behavior with react server components because these are quite new um so we can simply ignore this error it it's um totally unnecessary and there is no issue there is no risk involved it's just typescript not recognizing that so we can say yes expect error server component and we are done awesome so what this does is it streams in if we have a cached post the cached data and no no let me let me rephrase that so when we have a cash post we're immediately gonna show that response down here below that and the not so important information like the um post votes we are still fetching from the original database and then streaming that in so let me draw this out for you when we visit the detail view of a post two things or if a couple things are going to be immediately accessible like the title right here the author it's going to be way smaller like author username and the created add date created add you know what the regular post just shows and for example also the um content content content right here whatever the post content is it's also going to be immediately accessible because we store it in our redis database like um like this the content and it's all going to be here almost immediately because redis is so fast and then things like um the votes right here are dynamically streamed in things like the comments are going to be dynamically streamed and everything that's either out of the viewport like the comments mostly are or the votes that are not super crucial to immediately be shown it's fine to rely on the relational database being a bit slower because we're streaming them in with a very nice loading animation and so we ensure a beautiful user experience on our app awesome okay so right now we're just showing the loadings and the the loading if I reload this you might be able to see very very briefly that there is a loading animation and I can make this even more clear if I add an artificial delay in the post Vault server so let's quickly say const weight is going to be equal to and we're going to use GitHub co-pilot for this awesome and let's just wait for a second if we have a get data function and so a way to wait one or let's do two thousand and so you'll have an easier time seeing this if I reload the page check out what happens we are immediately loading the page and then there's the loading spinner so the data is only being loaded after the page has rendered instead of waiting for the data to render the page which would massively slow down the um user experience if we do a big join with prison workers Prisma really isn't the most performant um awesome so we can close all of that okay GitHub copilot is disabled great really really nice we can close out of most of these we've done the feed we've done the um the the volts the streaming now let's get ahead and start it and get started with showing the actual um post details um so this is going to happen on the same page in the slug no that's going to be right here in the double Dynamic route in the nested Dynamic route where we have just worked on the suspense this is where we will render all the important details for the post and it's going to be probably simpler than you think so we want to get started below the suspense with a div this is going to get a class name of small with zero a um a width of full then we want a flex of one a background white a padding of four and a rounded Dash small so this is the actual post content that we're about to render out here um with a P tag of class name maximum height 40 with a margin top of one a truncate value so if the screen does not fit or the text is going to be cut off by saying dot dot that's what the trunk head does with a text of extra small and a text Dash gray-500 inside of here we're going to say post it by U slash and then comes the either the pause question mark because that's that doesn't have to exist post.author dot username and if this does not exist then we know there will be a cached post because otherwise um the page wouldn't even be shown right so we can assume one of these two exist and that is going to be the cached post dot author username that's why we saved it in there and then we want to show when this was posted by saying format timed now invoking that with a new date and inside of the new date we want to pass either the post dot created add or if that is null or undefined that's what these two question marks do again it's um cached post dot created add that's why we save that in the cache post let's quickly insert a JavaScript space right here and so we don't have to rely on a HTML space like this but instead this will always be shown awesome and then below the P tag let's put in H1 that's going to contain the title with a class name of text extra large XL a font Dash semi bold we want a padding y of two a leading of 6 and a text Dash gray-900 inside of here we can either display the post if that exists dot title or if that doesn't exist the cached post dot title that's why we save that great let's quickly take a look at this already and render this out side by side reload the page and we can see um the person who posted that we're gonna get into more beautiful usernames don't worry it's not going to stay like this you can actually choose a username later and when we post this and the post title from the cache by the way right very very nice let's give this a bit less space there we go okay and then below the H1 we want to display the actual um content of the post and this is going to be very easy as the editor output we have already created and we can simply pass for the content either the post if that exists dot content or if that is undefined or null we can pass the cached post dot content either one will work very very nice let's save that and see the actual post content if we reload the page whatever I typed in there I don't even remember this browser does not support document.implementation dot create HTML document interesting let me debug that okay so the problem is in the editor output and what I mentioned earlier is that this doesn't work as a server component that is in part why we're importing this as dynamically with SSR false so without server side rendering and we also want to declare this as a use client component and let's see if that works let's reload the page hopefully that's going to fix the error awesome so turn this into a client component and the error will be gone and we can actually see the text of the post or the images or whatever the post content was you know like lists links and all that works just fine great so let's navigate it to the I'm gonna navigate in my example project here back to this page so I don't miss anything and after declaring the editor output right here um we want to do another Dynamic streaming of content and that is going to be the comments right and but first off let's see where we are on our little list because I think we can check off one more point and that is this one right here so very very nice what I meant with optimizations is the dynamic caching streaming Behavior very cool we've done that and we are now displaying posts in the feed and on certain communities very good work if you're following along very very nice awesome so let's do a very quick recap we've got a detailed view we've got our home page right here showing the post if I refresh that and the um the infinite scrolling also works if I zoom in so we're not intersecting it as I scroll down you can see more posts are being loaded very nice we can see how we voted for those posts and we can go into the detail view we can create or on posts we can create our own communities we have a um complete authentication flow with signing up signing out um or feed we can create community and we're gonna get to the settings later to change your username so it doesn't look like this looks kind of weird um but we are making huge progress and there is not so much left like the comments oops the comments are left and the search bar is left and then we're ready to deploy this a really really good work I'm very excited for this cool okay um let's stream in the comments as well they're not super essential depending on how long the post is you won't even see them when you load the page so we're totally or it would be a very good idea streaming those in instead of server side loading them requiring all comments to be loaded um before the page is even rendered which would make for a pretty bad user experience um so let's stream those in dynamically as well using a suspense component from react and this suspense component has a very simple fallback and that is going to be the Loader 2 from Lucid react you could make a more elaborate one if you wanted um but I find this to be a very good good idea not too like extravagant like a very simple and all the users are going to understand what's happening if they see this with a height of 5 a width of 5 and animate Dash spin and lastly a text zinc of 500. awesome move this into the new line and then inside of the suspense we want to render out the comment section well let's just create a component and name it that comments section and this is going to be a self-closing component and well let's create that component that doesn't exist yet and let's give this a bit more space there we go let's go under components create new and then comments section dot TSX that is going to be what this is called and what we want to do first off is fetching the comments for the Post we are currently on right so let's declare this as a functional component and we are only going to receive one prop and that is going to be the pulse ID so the post ID is of type string awesome we need to pass that in from the parent and because we're going to do some asynchronous action in here let's get rid of the functional component approach all together and just declare the props like this great so now we are receiving the post ID let's first see if we are um logged in const session is going to be equal to oh wait oops a weight get auth session just like this and we also need to mark this as a synchronous so we can await at the top level and then we want to fetch the comments const comments these are the comments we're going to show right here in the jsx these are going to be a weight database import that dot comment dot find many and well which comments um do we want to find we want to find the comments where the post ID is what we receive in the properties and then secondly where the reply to ID is no now let me explain what this means what is a reply to idea well you see when we have a Common Thread let's go in here there are going to be a something called top comments right the um the comments that are immediately wrote by the user that are not replies to any other comment and then there could be reply comments that are direct replies to another and not formulated at the top level as no reply to another comment so those that is what a Common Thread is we can have multiple of these and um one common can have multiple reply comments but we only want to allow up to one nested comment because going beyond that like the actual Reddit does adds a lot of complexity that does not add value to this video and so I decided to opt for the YouTube approach which I personally find cleaner as well more readable where you have only one nested reply and then each reply that is a reply to a reply is addressed with like a you know add username blah blah blah whatever you um right in your reply right so these two are the nested replies and this is the top comment and first off what we're doing right now is only fetching the top comments by saying um where the reply to ID is null and then for each of these top level comments we want to include their replies so we can say include and this is going to be an object and we want to include the author as true we want to include the votes as through the the common votes you know we want to include the replies as an object because we want to specify something there and we only want the first level replies so we want to include once again or one last time as an object the author of that reply as true and then secondly the votes of that reply is true just like we did with the top level comments so it's the same thing I'm just fetching the top of the comments and then that comment replies great so we end up with all the comments and all the replies and we can render that out in jsx as the top level div that can stay let's give this a class name oops a class name there we go of flex Flex Dash call Gap y4 and lastly a margin top of four and inside of here let's close out of this and goes in HR it's a horizontal line and just for a bit of visual separation with a width of full a height of PX for one pixel and a margin y of six for some vertical spacing great now comes the part where we want to create comments right above the actual Common Thread there should be a button or an indicator for you to create a comment let's finish up the jsx and Mark this with an annotation for now create comment and say something like to do and we're gonna do that here in a second but first let's finish out rendering the actual comments before worrying about that as a div right below that let's give this a class name of flex Flex Dash call and GAP y of six and a margin top of four for some spacing in here go the comments we can say comments dot filter and we want to filter each comment 4 the truthy value of not comment to reply to ID so we're only mapping over or we're not mapping yet but we are about to only map over um top level comments so we can say dot map and for each top level comment let's be very very boast about this we want to render out some function we're not going to return jsx directly and because first off we want to do some logic calculations and then we can return something later I could do for now inside of the map and let's determine the votes this top level comment has by saying const top level and comment votes amount is going to be equal to top level comment dot votes dot reduce and you know the drill by now we get the accumulator and the current value the volt is what I call this and we can determine the votes from that if the vote.type is up then we're going to return accumulator plus one you already know how this works we can just copy this down change this to down and return the accumulator -1 and else we can literally just return the accumulator we've done this like five times in this video already it's the same logic and we want to start this at zero the reduce great afterwards we're gonna say const top level comment vote it's going to be the top level comment votes and no cop the top level common dot votes dot find and it's the same logic once again for each vote we want to see if the vote.user ID is triple equal to the session question mark.user.adiso optional chaining on the session because it doesn't have to exist and the user the user doesn't have to be logged in to view the comments after all and now we can actually render out the comments with these values and I mean you recognize the pattern right this is the same thing as for post and now it's the same thing as for a comment um awesome so now we are going to worry about the return what do we want to return well we are going to return a div and this needs to have a key because it's what um because it's in a map right react requires this to have a key it's going to be the top level comment Dot ID great and as for the class name for this it's gonna be flex and flex Dash call so we can list um everything beneath one another inside of here goes one more div with a class name of margin bottom two and that's literally it and then we want to render out a component for the Post comment that we can pass a bunch of stuff and then it's going to show the comment for us and instead of doing this inline let's keep this in a separate component for maintainability and call this post comment as a self-closing component now the post comment doesn't exist yet so let's quickly go ahead and create it I'm going to close all of all of this and this just to make this workspace a bit more and neat and tidy and let's create a post comment dot TSX component to handle logic and for us a functional component there we go and the comments are actually encapsulating a lot of logic like their replies the votes there's a lot you can do with a comment and especially you know the the comment reply text input showing up wherever you want to reply um it's it's a lot of logic involved but it's also um pretty cool so I propose we get started with a jsx and then get into the logic of the component as we need it in or jsx so first off this main div is going to get a class name of flex and flex Dash call and for each post comment and we want to attach a ref to this comment so we can later um see when we're clicking outside of this comment then the reply text input area and you know so when I want to reply to to let's say this comment right here right the top comment then when I click the little reply button a text area should pop up where I can input my reply click reply and then that input is gone and my reply is right here right that's how this works and this bar this box we want to disappear when you click somewhere else it will just be very distracting otherwise and to do that let's go into our post comment and attach a ref to this main div and let's call it um common ref const common ref is going to be equal to a use ref we get oops use R go away use ref we get from react and there's going to be no by default and let's type this out immediately as an HTML div element awesome because we are making use of hooks we need to turn this into a client component because this doesn't work on the server and now we can quickly attach the ref to this main Jesus Christ so this emptiv and it's called comment ref great we're going to be using that later then inside of this div let's create one more div with a class name or flex and item stash Center and inside of here goes the user Avatar because for the comment we want to show like a little icon of the user for the comment right that's what we're doing right here so let's render out the user Avatar we've already done most of the prerequisites for this comment it's really nice we can just rely on those with the user of object and instead of here as the name let's pass the comment dot of the comment oh and we don't have the comment yet so we need to receive that as a prop enter this component the actual comment that we're rendering out right and the comment is going to be of type extended comment extended comment because once again we're going to do a join to actually get the comment not only the comment but also the votes and the author for this comment but we can type this inline very easily type extended comment is going to be equal to the comment we get from Prisma the normal one and then because we're doing a join in the comment fetching you see in the comment section that's where this comes in or rather this the author and the votes we also want to include those and in this extended comment where the votes are the comment vote type array and then the author is simply a user we get from Prisma great so we can now use the extended command receive the comment as a prop and now the name is going to be the comment dot author dot name or if we don't have that we can also use no and secondly the image is going to be the comment dot author dot image or we could also use null for this if we don't have those values great and let's quickly pass a class name into the user Avatar with a height of 6 and a width of 6 as well great let's go below the user Avatar create a div with a class name of margin left 2 a flex an item slash Center and a gap X of two just a little bit of vertical and spacing applied to this with a P tag off and now we will state who wrote this comment um so as a text small as a font Dash medium we want a text Gray of what is it 900. very dark text in here and then let's say U slash so whichever user created this comment and this is going to be the comment dot author dots there we go dot username whatever that might be like uh Josh or you slash Josh that would be great and right below that let's also say when they created this and that is going to be an AP tag with a class name of maximum height of 40 trunk hate a text of extra small and a text zinc of 500 we could also do this in a Time component by the way it doesn't really matter for now let's use the format time to now invoke that with a new date and inside of this new date we're gonna pass the comment dot created at as quickly save the post comment and we can already import the post comment in or comment section like so and it expects a comment property and the comment we are expected to pass in here is going to be the top level comment so for now we're only rendering top level comments we can save the comment section and then we also need um to import the comment section in here and pass it the post ID and now the post ID for the comment section is either gonna be the post question mark dot ID if that exists or if we have a cached post we can also use the cached post.id for this and why is this still okay same arrow with the jsx element we can simply ignore that at TS expect Arrow or server component as I explained before this is not a real error and this is just a typescript not recognizing what is going on awesome and let's quickly just go to this post we can see there is a bit of spacing there but there are no comments yet but they would show up right here very very nice um so we've got actually let's continue on the post comment so for each comment we want to show let's finish that right now in the jsx so we've done the username we've done the time when this was posted and now let's move to closing divs down with one closing diff to go like this and create a P tag with the actual text of the comment let's say class name text of small and text zinc 900 the margin top of two and here goes the comment dot text awesome let's save that again we won't see anything because there is no comment yet and but we're gonna do that here in a second and then we want to actually wouldn't it make more sense to write the comment and then render it out so we can see what's happening I think that does make more sense um so instead let's quickly pivot and before finishing the post comment component so we can see what we're doing let's actually write our first comment and um we are going to do that in the um comments section where we want to render out a field that lets us reply to certain comments we have previously used a annotation for this and this is going to be an actual component now and let's call this create comment component and this doesn't exist yet so let's go ahead and quickly create that in our components as the create comment dot TSX awesome and inside of here the logic is going to be rather straightforward just one API call and a text area to render out let's create this as a functional component and let's get started with a jsx and get and do the logic as we need to this is going to return a top level div with a class name of grid a width of full and a gap of 1.5 so very involved the comment section we've got a page.tsx a comment section A create comment and a post comment but it's all going to fit very nicely together later on and so this is rendering out the comment this is writing the comment the create comment.tsx and that's what these are for awesome so in here let's have a label component and this should be a custom component we import from components UI label let's see if we have that and we don't so what we can do is let's install that component from chat CN by stopping the dev server and saying npx chat cn-ui add and this is called label and let's see if I if that works just out of my head and it does let's say Source slash components UI is where we want to install this label and that's going to set up everything for us and hopefully yeah put the label right there awesome let's change the casing though I'm not a fan of the lowercasing let's change that to Upper casing and restart the dev server so I just changed the L right here to the uppercase I just find that a bit cleaner and then we can actually import that label component from our UI awesome so with a label component we can determine a html4 and we're going to call this comment here in a second the actual text area for the comment and the label is not going to be self-closing but instead we want to put some text into it saying your comment and that's it awesome so below that let's put a div with a class name of margin top-2 just give it a bit of spacing to the comment and then here goes a text area text area and text area is also a component we import from Shad CN it just looks good it comes pre-styled so let's quickly install that as well by saying npx Shad cn-ui add text area it's going to set up everything for us um we want this in the source slash components slash UI again that set up the text area again I'm going to change the T to the uppercase I just prefer that it's a convention for react components just looks a bit better and then we can import the text area from or UI this is going to be self-closing and now we can make use of the label by just stating that the ID of the text area is going to be whatever we specify the html4 in the label form the value we want to keep track of right we need to keep track of this text area as a controlled react component to always have access to what the user typed in there and so we're going to do this in state and let's just call this input set in oh what the hell set input there we go it's going to be used State we need to import your state and this is going to be a string and start off as an empty string and now because we are making use of these um client-side things like State we need to declare this as a use client component um like that by I'm using the use client directive and now we can actually make this a control component the text area by saying value is going to be the input and then also add the on change Handler to the text area where we set the well odd change we receive the event and then set the input so the E dot Target dot value which is going to contain the string of whatever the user has typed in this text area is going to have one row by default and a placeholder of what are your thoughts question mark um so this is going to be where we actually input the comment that we want to write right below that let's add a div with a class name of margin top two flex and justify Dash end and actually how about we just import the create component already and just see what we're building in real time by moving this into a side by side probably pretty good idea let's close all of that and this is the dev server running it is just needs a bit to load okay anyways let's get started with the stiff already ah there it is what are your thoughts beautiful um okay in this div we're gonna render out one button and that's going to be a very simple component just saying post to post your comment um and the logic for the posting is going to be handled using you can probably guess it react query so if you know the drill by now we're gonna make use of the mutation hook use mutation by react query because we're going to make a patch request or a post request both works and as the mutation function we're going to pass an async arrow function into here now as for the props that we are expecting um how about we make a validator for this just like we did always um instead of the lib folder so we can either so we can both get the type and validate this on the back end to ensure safety in working with this data as to this inside of the validators and create a comment.ts comment uh yeah comment.ts validator we first need to import Z from Zod and now let's get started in writing the actual validation logic it's going to be export const comment validator it's going to be easy dot object and inside of here well what do we want inside of a comment we want the post ID where we're posting to we need the text and we need the reply to ID if this is a reply right this so that should be optional the post ID is going to be sd.string just like this then we are expecting the text also of type Z dot string and lastly also comma separated we are expecting the reply to ID either reply to ID also capitalize either as a string Z dot string or as nothing so we can mark it as optional like this and Export the type of comment request to enforce the correct payload wherever we make the request a z dot infer type of comment validator great so we can use this type and the request payload that we are defining right here once again in or react query logic so we can go ahead and say cons payload of type comment request that we have literally just created is going to be an empty object and it's going to give us an error up great because it expects three things that's going to be the pause ID the text and the reply to ID optionally and how about we just pass this into the mutation function we can destructure these three values and type this out as the comment request so whatever we are receiving for this function is what we're passing right on as the payload just destructure all of those and the error will be gone awesome now to send that payload over the wire let's use axios worry about the destructuring later with the empty object and say await axios import that dot patch and now we want to make a request to a template string that is going to be slash API slash subreddit slash post slash comment so we can comment for a certain post and what are we gonna pass to this patch request well the payload these three options right here these three values right here rather we can destructure the data from the axials request and then return that data to keep working with it awesome um let's work on that API roll text before worrying about any of the proper error handling and clean up the workspace just a little bit okay so this endpoint lives under slash API Source app API then under um subreddit right here slash post and then let's create one folder because the comment doesn't exist yet slash comment and then in here goes a route.ts to handle the actual Logic for the API requests that land under comment the logic for this April is going to be very straightforward um let's let's export a async function and call it patch because we know we're going to be making a patch request and receive the data as the request you already know um how this works from the previous API rods we've done let's create a try catch Block in here to handle any errors properly and first receive the body of the request as awaits rec.json great then let's destructure some stuff from the parsing that's going to be the comment validator we have just created dot parse and then here we're gonna pass whatever data we want like the body and if it's successful we know we get these three values pulse ID text and reply to ID next up let's get the session this is going to be a way to get off session or handy Little Helper and if there is no session dot user well you shouldn't be able um to create a comment in that case so let's return a new response saying unauthorized unauthorized with a status code of of course 401 unauthorized and if that check passes the user is fine if they are logged in so they can create the comment and we can do that by saying wait DB dot comment dot create and well what do we want to create a comment with with some data and this data is going to be the text then secondly the post ID so we're really just passing on what we get as the data into the creation right here where the text is and the post ID is we also want the author ID save that as the session.user.id and then last thing we want is going to be the reply to ID if it exists it doesn't have to exist it's optional but if this is a reply then this is going to be passed awesome and very very good as for the error handling we can literally just go into any other route and copy it from there like the creation route where we check for zot error and then return a generic error message if this is no result error oops let's import Z from Zod and to the check we can say invalid request data passed status 422 and otherwise we can say could not um create comment please try again later and that's the error handing done awesome that is going to be or route.ts for handling a comment creation and now we need to work with that in this um create comment component right here so for example what happens on error on error and we can just go ahead and copy the error handling from any other component as well so let's search for any instance of across or across or application that is not in a route.ts so that is somewhere in react query and we can literally just copy this I'm in the Subscribe Leaf toggle right now is where I'm grabbing this go over and paste it in the on error and of course we need to import the stuff for it to work for example the access error class we need to import the login to host and that is going to be const login toast that comes from the use custom toast hook we've done and we also need to import the tools notification and that's the error handing beautiful lastly we need the on success Handler on success and whenever we are successful we want to refresh the browser to do that let's use a hook cons router is going to be equal to use router we get from next slash navigation we've done this previously it's the router.refresh and then we can set the input to an empty string to kind of clear the comment beautiful so now we can actually write comments my comment those are going to be sent to the API endpoint and let's see in the network tab if everything works out let's hit post and nothing works out because we haven't implemented the actual mutation yet awesome so let's destructure the mutate from here and let's call this comment and let's also grab the is loading value from here and pass it right into or um button that we are using to post the comment so in this button we can pass on the is loading state from whatever to whatever we get from react query we can pass a disabled value of input dot length is triple equal to zero so if there is no input you shouldn't be able to pause the comment and then lastly as an on click Handler we want to invoke the comment like this and this expects some data like the post ID then secondly the text is going to be the input and lastly the reply to id M is also expected now where the hell do these come from well we we don't have access to them in this component so we need to pass them as props into the create comment the post ID and the reply to ID so we also need to go to the very top right here and adjust those two are going to be received as props the post ID and the um reply to ID like this and we can type this out post ID is going to be a string and reply to ID also a string beautiful so now the create comment works but we are not passing the correct data into the create comment such as the um such as the post ID let's see comments section let me navigate over there here in my example project and the post ID is going to be the post ID just like that and um I think the reply to idea needs to be optional because we are not always replying um to something so let's make this optional so we don't have to pass it in but if this is a reply then it's also going to be sent along in the API request and if it's undefined everything works just fine as well so let's try this let's try creating a comment my comments and let's go into the network Tab and see if that works let's hit post it's going to make the request beautiful and we get a 500 error interesting that shouldn't happen um let me quickly debug this oh and I figured out why this error occurred because it was kind of weird the comment was actually successful so we can see the comments go through but we are receiving a error because we're not returning a response from the API endpoint so let's just return it okay to indicate yes your comment has indeed been created and then let's verify we really get no error let's navigate to the network Tab and say my comment three hit post and we should see that we get a 200 response my common 3. awesome so now we can actually see the comments properly very very nice but we can't reply yet right so that's probably something we should touch on next is replying to comments and let's move this into a side by side hello can I please Jesus why doesn't there we go okay great um we actually want to reply to comments and that is going to be handled in the um comment section and we also want to extend the post comment to show the number of upvotes and the current Vault that the user has like an upvote or download if they have already voted for the comment and let's do that and first off get started in the post comment with actually I'm showing those values so let's navigate to the post comment and this is where we left off with the comment.txt and now we can keep on improving where we can actually see what we're doing with actual comments which I think makes a bit more sense than what we did before awesome and so in here let's create a div with a class name right below this P tag showing the common text with a class name of flex gap2 and items Center and inside of here go the common votes let's have this as a separate component comment votes and this is going to be a self-closing component very reminiscent of the post votes we can literally copy paste the post votes but there are going to be one or two differences so and we are going to create it as a separate component and not as one reusable component so let's create the comment votes dot TSX like this and let's go over to the post votes and this is going to be I'm pretty sure the post vote client one post vote client and let's grab all the code and copy it over to the common votes and just change the name from the post Vault client select all um I'm pressing shift Ctrl and L to select all and let's call this comment vault instead what do we receive in the comment vote is not a post ID but a comment ID let's quickly go ahead and change that okay and after changing the comment ID the rest can stay the same and now for the votes amount we want to pass the initial for the current voltage we want to pass the initial board that's all good the use effect can actually go away we don't need that in here and it was just necessary for the other one um a bit weird but we we don't need it here this just worked out of the box for some reason and then as for the payload that we want to pass along in our request to vote for a comment this is going to be the vote type for one and then also the comment ID and this is not going to be of type post vote request but instead of comment Vault request which we have already done way previous in the video along with the post-world validator you remember so both vote validators are in one file and we can just use this type the common vote request right here inside of our payload and then Force the type of this and then make a patch request to the API wrote of API subreddit post and then slash comment so we're voting for a comment of a post and that is the change we want to make here great so we have API subredded post common vote the on error is gonna be I think the exact same and the on mutated is also going to be the exact same we don't need to change anything there that logic is not changing however the jsx will change a bit because the styling is going to be horizontal instead of vertical this time so let's add a flex and GAP one at the top level div for this common votes component the button is not going to change and the votes amount is not going to change either just the alignment with the flex and the gap of one is going to change okay we still get one error why is that because the import doesn't work let's import this form add slash components give this an absolute path components UI button and get rid of the use effect I got rid of the unused Imports and sort them by pressing shift alt and all by the way that's going to sort your Imports and get rid of all the unnecessary unused ones and that is the common volts component we can now head over to our post comment import that component and uh why is it not showing us the Auto Import let's reload the window and see if that fixes the issue and hopefully then it will find the comment votes let's give this a second and nope no suggestion okay let's import these manually import command votes at the very top of the post comment file from at slash components slash and then it's comment volts that works okay now it might complain that we need to pass some props like the comment ID for example and what is the comment ID going to be um well it's pretty simple the comment ID is just the comment Dot ID just like this the initial volts amount is going to be equal to the votes amount we don't have access to that yet in this component so we're going to pass it in as a prop and then lastly the current Vote or what did we call it the initial vault is what we called it is going to be the current volt that we also don't have currently access to in the post common component so let's pass these two in volts amount and current volt into this component as props so we can receive them as the volts amount and also as the current vote and while we're here we can also get access to the impulse ID because we're going to need that as well um let's type them out the votes amount is going to be a number then the current vote is going to be either a comment vote that we get from Prisma or it can be undefined if the user has not voted for a comment and lastly the post ID very easy is going to be a string that we're going to pass into this post common component we still have an error the initial vote right here Aha and I noticed where this typescript error is coming from so take a look at this the initial vote it says type comment voter undefined right here it's not assignable to type vaultype or null or undefined and that makes sense because if we go into the common volts component and take a look at what prop we are expecting it's the volt type and but this is not what we want instead let's expect a pick value and this is a typescript utility type and we want to pick from the command vote we get from Prisma the um type value like this and why is this not working apparently we can't just declare this inline so let's say type partial vote is equal to what we have just done and now um type that as the partial oh that's because the object bracket here is missing okay now we get multiple errors in this file and and we can just remove the type on its annotation on the use mutate and the current vote now has a DOT type property like this and we want to set the current volt to in object notation the type because that expects the type literally as the name okay so two changes we did first or three first of remove the type annotation in the comment votes on the all mutate secondly add the DOT type after the current vote and then also change this into object notation those three changes and now if the current volt Dot type is triple equal to up and the current vote.type down here as well okay great that we moved the error in the common votes and now also the error for the um common volts where we are rendering or where we are calling that component awesome so now we have the common votes we're passing everything in here that we need and we can get ahead and why is there an error still here and the post comment let's see what the problem here is in the comments section comment section let's see um where is this okay for the top level comment we're getting an error on the post comment right here are we missing anything uh okay yeah we're missing a bunch of stuff like the post ID as we need to pass in here is going to be the post ID then what we are also expecting for the Post comment in the comment section is the current Vault and the current vote is what we have determined up here the top level comment vote we can just paste that in here that is the current Vote for This garment and then lastly the votes amount is what we have calculated up here in the reduce function so we can just pass that into the post comment as well as a property awesome so the arrows are gone very very nice we can save all of this and we can close some of this stuff as well and let's plow ahead in the post comment where I'm almost done in the post comments so we rendered out the um comment votes and now what we want to do is add a reply button it's because not only do we want to see the comments and the votes for the comments and but we also want to have an option to reply to each comment and we're going to do that inside of the button that's going to say reply so when we click this we want a reply to happen and um okay let's first call the variant of the button this is going to be the ghost variant and then the size is going to be extra small in the bottom and this should be very very small just very subtle um a little reply symbol and then we want to add an area Dash label so this button saying reply because or actually we already have the text in here never mind we can just render the text alongside with the icon and the icon is going to be a message square that we get from the seed react as an icon with a class name of height 4 with 4 in margin right 1.5 just a little bit of spacing to the right hand side there and that should look good let's try that and restart the server we can see nice there's a little reply button now and we can see the current amount of upvotes for this comment great very very nice however currently when we click the button nothing happens and what do we want to happen well let's add an on click Handler to this button and we want to call an inline function in here because we want two things to happen um so if the person is not signed in we are determining that we're not determining that in this component just yet and whether they are signed in or not if they aren't and try clicking the reply button they should be forwarded to the sign in page and to forward them there we need to call the router hook constralter is going to be equal to use router from next slash navigation to be able to push them to that sign in route so and of course we also need to determine the session and we can do this in client side we're not doing that a lot in this project but this is a good way to do so let's call data and name it session it's going to be equal to the use session hook we get from next auth slash react right here therefore we have access to the session on the client side which is perfect for this use case so we can determine if there is no session in that case when the spot is click the user should be um forward it to the sign in page so we can just say return router dot push and we want to push to the slash sign Dash in page and if the user is signed in then we're gonna keep track of something in state and that is going to be whether the user is replying or not let's quickly create a state up here for that in the component and let's name that state is replying this is going to be a Boolean value and starting off as false because by default nobody is replying to a comment that would make a lot of sense and we use this later on to determine whether the a little comment text box should be shown under the specific comment that we're mapping over and so we can keep track of the is replying by saying set is replying to true if the user clicks the reply button and then depending on if that state is true or not we want to show the message box to actually reply to a comment right now I'm clicking the reply button would set that state to true but nothing would happen yet nothing happens if I click the button so conditionally if the is replying replying is true then we want to render some jsx and else we want to render null just nothing should be rendered and if we are replying what do we want to happen well we want to render a div and a div gets this diff gets the classroom of grid with a full and GAP Dash 1.5 inside of here we're going to make use of our custom label component that we get from our UI Library from chat CN and this is not going to be self-closing but instead a component that encapsulates some text and that is going to be your comment awesome and you know what we can probably go ahead and go into the create common component go ahead and type that in go into the create common component and literally just grab this stuff from here and paste it way faster than doing this yourself and let's conditionally render that in the um is replying we also need to import the text area and the input we're going to keep track of in a separate state also inside of this component and let's name that state I think I just yeah let's just name it input so what we did is go to the create comment copy this whole component paste it in here under the conditional is replying that's all we did and let's keep track of the input I'm right here in state and no that should be State let's call the state input and why doesn't that work sometimes that's really weird and the input is going to be a string nevertheless and start off as an empty string and we're going to turn this text area that we have for the reply into a controlled input well it's already done automatically nice um and now as to submit this reply to the comment well first off let's save this and see how this looks the button won't do anything yet and maybe we will get an error no we don't now we get an error okay that's fine um because this is not defined yet and to do this we're going to use um react query once again with some very simple um data fetching logic let's go a bit up in our component and call the um use mutation hook right here and pass it a mutation function so what do we want to happen when we are making this request well anything inside of an async arrow function inside of this Arrow function we want to receive some properties and these properties are going to be of type comment request remember this comes from or validator we have already created where we have a pulse at the attacks and they reply to ID that is optional we're going to make use of that reply to ID now because this is going to be a reply typing that out means we can destructure these properties right away and get started in the fetching logic by first constructing the payload and the payload is going to be of the same type comment request as an object and therefore we can literally just paste in the properties we receive for the mutation function or the arguments rather we receive for the mutation function and pass that payload that long in a const empty object we can destructure later as in a weight axios import that dot patch for a patch request and we're going to send that request to slash API subreddit slash pause oh post slash comment awesome and this is um the same endpoint if you notice if I copy and paste this in here you can see this is the same endpoint it handles the same logic we do not need to write a separate endpoint for the replying of comments but rather we just change the reply to ID and the same endpoint handling the original top level comments when Helm will handle the replies as well isn't that handy we only need to pass the payload for that to happen and we can restructure the data from that axial request and return it back to the use mutation awesome let's destructure the mutate function from the use limitation and call it post comment and we also want the is loading property so we can pass that on to our button great if we take a look at the button now or I just called it um comment wait yeah let's change the on click we want this to be an inline function currently it's named comment but oops that's not what we want did I remove anything on accident here no I don't think I did okay so let's remove the current on click hander and Define a custom on click so if there is no input we want to return and the if the input.length is zero then you can't even click the button but just to make sure that you can cannot post um empty replies that wouldn't make a lot of sense and then we can call the Post comment function we have gotten from react query and pass it the post ID we can pass it the text and that's going to be the input and lastly we can pass it the reply to ID and this is going to be either the comment dot reply to ID or if there is null or undefined that's what these two question marks are for again then we're going to use the comment dot ID awesome so we're gonna default to the top level comment and if this is a reply then we're going to use that reply um okay we can save that and then we should be good to go right let's try this out we still haven't done the logic for rendering the replies yet but if I write a reply and this looks a bit weird in our current layouts I messed up the CSS somewhere yeah this does look a bit weird but there's a very easy fix for this um let's move this into a side by side so what we could do is probably just go ahead and add and wherever the flex container is containing these two um that's gonna be uh this oh Jesus this is way too small like this the HTML is very clipped where it says Flex Gap to item Center that's the diff we want to apply a wrap to um so it should be let's see this right here let's apply a flex Dash wrap to this and that should fix the visual bug at least and move these below one another and it does great so we need to add a little Flex wrap right there above the common volts um on that div to shape or to to um make these layouts below one another great so now we can write a reply hit the post button and let's see what happens nothing really happens because we're not handling the unsuccess state and if I reload the page it won't show up because we're not rendering replies just yet but I'm pretty sure the reply went through successful um so one thing we quickly want to do is offer the user a an opportunity to cancel um so inside of the post comment where we invoke the post comment down here this is the submit button for the reply and we also want a button that allows the user to cancel their request so let's say cancel inside of this button so they can abort the reply and when you click this cancel button the tab index first is going to be -1 so when you hit tab you're going to focus on the post button and not the cancel button then the variant is going to be subtle for this button and on click we can very easily abort this by saying on click set is replying to false awesome and then the cancel action so whenever we reload the page and try to reply to a comment and then click cancel you can see it aborts um very very nicely okay and let's add a little Gap to here so the buttons are spaced out a bit more awesome that looks really good we can just import the reply and now to render all the replies that doesn't really work yet so where the hell do we do that and it happens right here in the comment section where we are currently only rendering out the um top level comments right but we also want to render the replies and let's do that right down here with one closing div inside of the return statement for the map one closing div right above here let's say render replies okay and we can render this by mapping over the replies for each top level comment that we included right here in the Prisma query tour database and so we can say top level comment dot replies Dot map and each of these replies we want to sort and then we can map over them so instead of calling the map directly let's first sort and this begins with a and we get also access to the element B and then can handle the um sorting or self you can leave this out this is completely optional but what I find cool is sorting them by um the most liked you could also sort them by the time they were created that's how YouTube does it um but what if we want to show the most liked ones and then we can return the B dot votes um dot length minus the a DOT votes dot length and that's just going to show us the most rip and the most um voted for I'm not necessarily the highest votes because votes can also be like negative right but the most I guess controversial you could call it which are probably the most interesting um in that matter and then we can map over them and for each reply we want to call a function that we can return M A div from Return div right here to get rid of the error and the logic we have for each reply in determining their volts amount and their um their current vote for the reply is going to be copy pasted from up here it's the exact same logic after all this is just a comment as well it's just a bit different because this is a reply and the other are top level comments but the logic is going to be the same for both of them let's call this um reply votes amount instead and that should be AMT and instead of the top level common vote let's call this the reply Vault reply Vault just like this very nice and then we can return a div with a key because we're mapping over stuff this can be the reply dot ID just like the comment dot ID with a class name of margin left two a padding y of two a padding left of four we want a border left of Two and a border zinc of 200 and that's going to add a little bit of indentation um to these comments it's going to make it look a bit better okay now inside of the stuff let's render out the post comment component it's as I said exactly the same thing we can pass it just a different comment this time that is going to be the reply we can pass it the current vote of the reply vote we can pass it the votes amount and let's quickly format this the volts amount and this is going to be the reply votes AMT and then last thing this expects is the post ID and we know the post ID that is the post ID we can just pass it like that and now we are rendering the replies and let's see if our previous reply shows up and yes it does here it is write a reply awesome if I reply to that the reply text box is going to show up we can say reply to hit post and because we're not handling the um on success properly for the um post comment in or um in or react query yet with an on success sender and this doesn't disappear yet which is not ideal but we can see the reply right here was successful very very nice let's quickly do the um proper unsuccess handling in the post comment I promise it's very straightforward and so for example when we get an error on error we want to just send out a toast notification this is going to be an error function and we're going to return a toast notification to the user notifying them that something went wrong so as the title something went wrong then as the description we're gonna say comment wasn't posted successfully please try again and then lastly as for the variant you can imagine this is going to be destructive great that's going to be the extent of our error handling for this and then the unsuccess very very simple also an error function that we can invoke and we want to say router dot refresh and we want to say set is replying to false you could also clear the input here if you wanted to if we reload the page let's see what happens let's say reply three hit post that's gonna post or reply it appears right here and the text box disappears there is a little bit of noticeable delay here in the dev mode in in the um production version when we build this out this is going to be much faster and feel almost instant so don't worry about um loading States here this is going to be super fast in the final build version awesome really really good job really good job we can write comments top level comments we can reply to comments and let's see if we can vote for comments let's reload the page and then something went wrong your vote was not registered interesting did we not Implement that API route yet let's look into the network tab what is happening it's quite possible we didn't do that yet let's click upvote and we can see okay 404 error the upvoting API endpoint doesn't exist yet that's fine let's Implement that right now so we can actually vote for comments let's go to the comment vote oops comment vote component this is where we handle the voting and we can see we are trying to make a request to API subreddit post comment vote so under app API subreddit post comment and there is no Vault that makes sense that we get a 404 let's create a folder to receive that request and the route.ts inside of it to handle the request and let me look into my example project Here app what is it API subreddit um post API subreddit post and then the comment vote there we are the logic is going to be pretty straightforward and I think we can copy from the other router TS we have for voting route.ts vote let's head over to the route.ts for the normal post voting just copy and paste everything and then hand um go over to the route.ts for the vote for the comments and paste all the codes from or post voting now we don't need a bunch of stuff in here for example the caching we won't need that behavior so we can remove the recounting of the votes and also remove that from here we don't want to recount any votes and did I remove something that was syntactly important I think I did there we go so now we only have um this logic without any of the caching okay let's go through step by step and verify that we have everything we need and we've got the body and we want a bit of a different stuff here we don't want the pause at the end of all type to vote for a comment but instead we want to validate this with the comment vote validator we have created like this and we already get an error beautiful because now we get the comment ID instead of the impulse ID the login stuff stays exactly the same you need to be logged in and to check for an existing vote we're going to check for a comment vote instead of just a vote and obviously we can't pass in the post ID but we need to pass the comment ID in beautiful we don't need to fetch any posts right here because we are not going to do any caching here for any comments um inside of the existing vote if statement instead we are gonna say comment Vault dot delete where the comment ID matches the user ID comment ID cost here and this is not going to be user ID post ID but instead user ID comment ID this is what we changed okay beautiful we are returning the new response okay that's good and if the current vote incoming is a different type of the previous vote then we're going to handle the updating in the else statement right here and we can copy the where statement from the previous um Vault call right can we we should be able to user ID oh okay because this needs to be a comment vote instead of a regular post vote beautiful and most of the logic just stays the same we just move this part into an else statement right here and then we should be almost good to go um if there is no existing vote in that case we want to create a new one and we want to create a common vote of course with the type the user ID and instead of the post ID we need the comment ID awesome if I lost you anywhere during this we just copy and paste it and if I lost you anywhere check out the GitHub repository let's quickly go through once again we're validating the data authentication stuff stays the same and then we adjusted the existing vote and the if statement right here for the common votes instead of the post votes and the error handling stays the same um as well awesome let's try this out move this over and let's try voting for a comment I'm gonna upload the first comment and we get something was wrong your post was not registered please try again interesting let's quickly check the network tab why this is happening let's try this again and we get a 404 again that should be an easy fix did I call this votes yeah okay so this needs to be vote we could call it volts but then we would need to adjust our API call let's just call this vote in the singular so separated post comment vote that is what we're going to make the API request to reload a page and now let's try voting for a comment we can see it's pending and it has gone through successfully if we reload the page then we get a weird bug where every reply seems to be upvoted when we upvoted the main comment um okay I messed up something here that's not that that's definitely not ideal oh and I found the problem it's right here in the um comment section.tsx let's close out of most of this by the way and let's just say close others and to clean the workspace up a bit so of course we don't want to reduce and find votes in the top level comment but instead we want to do this in the reply all right so what we're currently mapping over let's save that and hopefully that will fix the error and it does okay so we can see the main comment has the upward but then the replies do not and of course they shouldn't be upvoted or shown as such because they weren't actually it was just the displaying of those um when we upload the main comment nice if I reload the page we can see it's right here it's indicated that we have uploaded this comment and when I download it says your post was not registered why doesn't it do that well let's see in the network tab Network right here let's take a look at the fetch requests vote 500 could not register your vote please try again okay let me debug that oh and it is because we are not returning from here so we are um so this is what the error was I just logged it out spent like uh 30 seconds debugging this so you can see unique constraint failed on the not available prisma.com and so in invalid invocation of the creation and that means we were trying to update and then also the create statement because we are not returning something from the else block obviously we want to stop code execution afterwards so we're going to return a new response um saying okay awesome and now let's save that are we returning a response here yes we are and let's remove the console log and try that again let's reload the page so we currently uploaded it let's download this and it should go through successfully and it does awesome so we can now successfully upvote and download comments properly um very very nice and if we refresh the page we can immediately see what we voted for on a comment man this is beautiful really really good work and I think that concludes or work on the comments let's go over you can get rid of this into our game plan creating and voting for comments yes sir that's what we have just done and verified we are done with step seven man we have come a long way often um so basic setup authentication creating joining subreddits creating posts displaying posts in a feed and optimizations for um really fast data querying using or redis Cache voting for posts creating and voting for comments man this is a real full stack application now it's taken on really good scale there are posts dynamically fetched as we scroll and now the only thing missing is the search bar and to be honest the search bar is pretty straightforward it will appear right here in the top of our navbar and allow us to search for some communities so I say oh this is my example project let me move this over here I say we get started in the nav bar and implement the search bar It's the final step of our build let's create a search bar component render it right here I navigate it to the nav where if that was too quick and then implemented a search bar component here and that search bar doesn't exist yet so let's jam it into our components under search bar dot TSX as a functional component that means we can now import it in our navbar won't get any issues there and now we can get started in actually handling the logic for the search bar and it's going to be a bit more involved but it shouldn't take longer than like 10 20 minutes I believe um because the the logic is pretty straightforward um there is react query involved in one API endpoint involved for this okay I propose we get started in the jsx and to make this a beautiful accessible pre-styled search bar let's install one UI component and that is going to be npx shared cn-ui add and there we go and the component we're going to add is called command um it's a really really nice um like foundation for search component we're going to install it under Source components slash UI that is where we want it it's going to install everything for us the command package and there we are it's installed the command under components UI and we get an error in here let's look at that in a second first let's change the C of command to B uppercase I just find that to be a bit better and we can see there's one peer component missing and that is the dialog so let's also add the dialog two or components under Source components slash UI same path as before same path is all or UI Library components let's get install the dialog for us beautiful and we're also going to change the capitalization on that once that is done loading all right this is taking way longer than it should I'm just going to stop it and terminate the batch job and I think it just installed successfully might be just a bug from the UI Library let's change the D of dialog to be an uppercase as well and we need to adjust now in our commands and because this is the uppercase dialog and yeah sometimes vs code has problems with that let's reload the window and hopefully that will fix the file import error we get right here in the command and recognize the dialog as an actual component and it seems to do that just fine awesome um great let's save those we've got the command we've got the dialog now let's get started in our search bar where we make use of the command component we have just imported from UI command awesome let's give this a reference for later the command actually let's do this when we need it let's not worry about that for now and let's give the command a class name instead and that is going to be relative rounded Dash large let's move this into a side by side again with our project like this okay relative round at large we want a border a maximum width of large and that index of 50 and an overflow of visible that's going to allow us to show these search results in overflow to the page which is what we want and then here goes the command input we also import from UI command this command input is going to get a class name of outline Dash none of Border Dash none of focus border Dash none and focus outline Dash none we're really making sure there's nothing around this and lastly a ring of zero let's give this a bit more space so you can see this easier and why is that so small there we go command input great and here we also want a placeholder value let's quickly format this placeholder is going to be search communities period period period and we can turn the command input into a self-closing component if we wanted to doesn't really make a difference but it just looks a bit cleaner let's save that move that into a side by side and we can see if we reload the page and restart the dev server with npm randev or yarn Dev and then refresh the page we should be able to see that there is an input now even though that input doesn't really do that much yet while this is loading let's actually worry about the functionality for that input and this is going to come from react query as well because we demand client-side interactivity let's turn this into a use client component and you can see there is the search bar it already looks beautiful out of the box very nice okay in here let's use react query and we can do that by saying const and let's worry about the destruction later it's going to be equal to use Query we're just going to make a cat request for the search results so no need to use a mutation we can just pass this a query function that is going to be an async error function as always from all the previous react query implementations okay let's also pass this a query key and this is going to be you can call this whatever you want I'm going to call this search query to identify this certain query against a 10 step query and then lastly an enabled value of false we only want to fetch when we actually type instead of when this component renders which it is by default and therefore the enabled and false okay next up we want to keep track of the input that the user is typing into the search bar in state and let's call this input and for whatever reason it just keeps thinking I want an Emmett snippet there which I don't want instead let's have a regular use state with input set input and an empty string by default okay to turn this into a controlled input we can simply pass it the value of or input in Kodi braces and then instead of the on change we get a certain other custom function this is called on value change where we receive the text as the parameter passed into there automatically and we can set the input to this text okay and whenever we make the search request to or endpoint then we're gonna make an axials request but if there is no input like that we are going to return an empty array so we are going to return an array of communities later so always an array of strings and if we have no input right here then there shouldn't be any search made because that doesn't really make a whole lot of sense afterwards let's make an axial request by saying cons and the objects equal to a weight xeos import that dot and then a get request to a template string Under slash API slash search and then a let's pass a query parameter using a question mark Q equals that's going to be our query right here and interpolate the input value so we're searching for whatever the user has input and passed that as a query parameter to this API endpoint we can destructure the data we get from here like so and return that to the um to react query return data however this is type this any we don't really want that instead let's cast the type as and then here goes the subreddit type we get from Prisma client or custom database type and we're going to extend that type by a underscore count value and this is going to be of type Prisma dot subreddit count output type and we can import Prisma and to do this if we take a look at what this is it says posts number subscribers number and so we can later display that to the user and then that is almost everything we need for react query we can destructure um some stuff from here for example the data and that's called that the query results so whatever comes back from our API endpoint that's called the refetch function so we can call this whenever we want instead of whenever react query wants the is fetched and we also want the is fetching to display some loading states to the user accordingly nice that's all we need from react query and now whenever the um actually let's worry about that later let's save that for now and implement this functionality so we have the input field and we can make requests to our API endpoint but we aren't showing any results yet however the API endpoint doesn't even exist yet so there would be no results to search so I propose we do that first implementing this API search endpoint to actually get some results um so under or let's give this a bit more space under or API folder and let's close all of this API we are expecting a search endpoint that doesn't exist yet so let's create a search folder search that's going to be represented in the URL with a route.ts once again to handle the logic that is associated with that endpoint awesome and inside of this search endpoint the logic is going to be very very simple let's export an async function get with a request that comes in as the request type and now we want to get the query parameter right the way we do that is by first um instantiating a new URL passing a direct.url and then we can get the query parameters very easily from that by saying const Q is going to be equal to URL dot search params.get and whatever we called it and we called it Q for query and if we have no queue no query we're going to return a new response saying invalid query with a status of M400 invalid request awesome let's give this a bit less space and now let's fetch the results const results are going to be a weight and asynchronous operation DB let's import that dot subreddit dot find many and we want to display a list of subreddits that match the user query so we need to include that in our where block where the name of the subreddit as an object starts with Q or query so like rear would look would lead to react JS for example and then afterwards we're going to include the underscore count to determine how many search results we have we're going to say underscore count true and then lastly we're only going to take 5 at maximum and now these both are a bit redundant but if you wanted to show more or have some pagination inside of here that's where the underscore count would really come in clutch and to you know see if there are next pages and so on that's why we include both here okay let's go back or actually let's just return the response return new response json.stringify and we're going to stringify the results and we are already expecting those on the front end in the search bar so we can just work with them we are getting them back as the data and then making them accessible right here in the search results for the search bar that we are not yet displaying anywhere so if the input dot length under the command input let's render this out is greater than zero in that case we're going to render some jsx and else we're going to render null in a ternary operator and what are we going to display well we're going to display a command list to the user also from our custom component this is going to get a class name of absolute to list the communities right here below the search bar and above the page that's why the absolute a background of white a top of 4 and in set X of 0 a shadow and a rounded bottom medium let's give this um just a bit more space so you can see this if you missed anything and then if we have the is fetched Boolean set to true in that case we're going to render the command empty how cool is that there's already an empty state in here and that's saying no results found period in here so if we did fetch as successfully and it is fetch we're done fetching and there is nothing then there are no results and otherwise if the end parenthesis and let's put this into curly braces for some Advanced jsx action in parentheses if the query results dot length and this is optional by the way this doesn't have to exist this can be undefined the length or zero is larger than zero in that case we're going to render some jsx and else we are going to render null and we get an error here saying property length does not exist on type probably because we forgot to cast this as an array yeah this is not an array this needs to be an array of data that we are returning and now the error should be gone great so now let's return some jsx from here and that is going to be the command list where no command group excuse me command group we get from the command is going to contain all the communities that we found and let's pass this a heading and that heading is going to be communities awesome let's save that and see what happens until now at least at search react and well no results show up yet which is not ideal but fine um let's see if the network request goes through successfully though that should happen okay never mind we're not refetching the logic and the the query yet we're not making use of this refetch function and this enabled um it's not enabled so therefore there can't be any results yet that's fine we're gonna worry about that in a second let's finish up the command group we're almost done here and let's just map over the query results that are optional because they're going to be undefined when the component hasn't mounted yet map over those and for each subreddit that we found as a result to the user query we're going to render out some jsx immediately as the command item from the same component this command item gets an on select and we get access to the event whenever this is selected and we can do some router action with this now in order to do router action we need to use the router hook cons router is equal to use router from next slash navigation so we can actually make use of that okay and whenever a command item is selected we want to push that router.push into the URL navigate the user to that and this is going to be the template string of Slash r slash or slash and then interpolate the what is it the event this is just a string that we get back right here so like react or whatever Community you select from the drop down afterwards and then let's say router dot refresh to show the user the latest posts for that subreddit okay that's going to be the on select because we're mapping over stuff right here we also need to include a key on the command item so the key is going to be the subreddit.id and then the value what's actually going to show up as the text for this um result it's going to be the subreddit.name so like react or next.js or whatever the subreddit is and to make this look pretty and not just functional let's render out a user icon in here and let's render all the users from those seed react a self-closing icon with a class name of margin right 2 a height of 4 and a width of 4 as well beautiful then let's have an anchor tag right below that and we're almost done lasting here is the anchor tag with an href of dynamic template string leading to slash r slash and then subreddit dot name so again react next.js whatever and this anchor attack is going to say r slash subreddit dot name like this great so now the question is or search results would look really good right now if we typed react but there are no search results and that is because we're never actually invoking the refetch function and the question is well when do we want to do that and the answer is we want to make a debounce request when the user hasn't typed for a certain period of time to optimize database and performance we don't want to spam the database on every keystroke but instead we want to debounce that input and it's actually pretty straightforward I've done a separate video on that once a while back but let's walk through it together it's really simple first off we're going to say const debounce request is going to be equal to a used callback so this maintains its Integrity through all three renders which is important for the debounce to not get triggered multiple times and essentially act as a delay but to properly work as a debounce this gets an empty dependency array and inside of here let's fetch the request and of course this needs to go below 10 stack query and this needs to State refetch another request okay then the actual request oh no never mind that was a request because this is going to be a separate function that we're going to create together right now the const request is going to be equal to D bounce you might wonder where the hell is that coming from and the answer is right here there's a low dash dot debalance package that comes pre-installed in your um starter code that does just this and without all the overhead of adding the entirety of low Dash it's literally just the d-bones this takes an asynchronous callback function or any callback function for that matter but for us we need an asynchronous one where we call the refetch there we go and does this need to be asynchronous I don't think this has to be asynchronous by the way because we're not awaiting that or anything which we very well could but it doesn't really matter um let's just leave it out anyways I've done it like this in my demo project but I don't think we need it all right and let's save this and the D bounce request is what we can actually spam and instantiate or M call on every keystroke along with the set input because on every keystroke this on value change is going to get called and we can just invoke the debalancer request and that is going to refetch all the information for the communities let's try this out I'm going to search for react and we can see react pops up right here beautiful very very nice this looks really good we can see next.js maybe if we want to filter for that react and we don't have anything else then it's going to show no results found that is a beautiful really convenient search bar with a debounce input that looks very nice let's quickly verify that this is actually debones by going into the network Tab and we should be able to see when I type three letters only one AOK not only one request is made So currently it's not the bonds that's not optimal so maybe the async here was actually pretty important let's reload the page and then let's try that again and only one request should be made react all because we didn't pass a Time route here for the d-bone so let's pass 300 milliseconds here that's what we need to do and now we're debouncing by 300 milliseconds so only if you stop typing for that amount a request will actually be made Let's verify that and there we go only one request is made to react and if I spam this no request is made until I'm done I'm releasing my keyboard and not typing for 300 milliseconds beautiful great and that is the project pretty much done right I think that was like a what yeah the eighth step awesome we've done the search bar we can check for communities like react and if we press those we are taken um to those communities and shown the react posts and nothing else really really nice excuse me if there are some CSS errors CSS really wasn't the focus of this video and but I tried to make it look good anyways um I'm probably not gonna fix it in this video but in the final GitHub repository I'm pretty sure these are minor changes that are going to take like five minutes and it's really not that important and as long as we've got the core functionalities all set all right very very nice the votes are working the comments are working the posts are working the communities are working oh one thing is probably we want the settings and then here we want to be able to change the um username and that is going to be a pretty straightforward step actually um so let's create a page for this this is going to live under the source and then app let's create a page for that at the same level as the others as settings and inside of here is going to live a page dot TSX for the settings awesome let's export some metadata from here export cons metadata and this is going to be an object saying title settings and then the description is going to be manage account and website settings period just to have a nicer title you know instead of localhost 3000 slash settings this is going to show or this should show What's Happening Here uh what are we doing oh right because we're not exporting a component from the page that makes sense let's reload this and now it says settings right here beautiful bit more descriptive than localhost 3000 slash settings okay let's determine the session cons session is going to be equal to a weight get auth session and you know the drill to turn this into a um it's a top level awaits compatible component we need to turn this into an asynchronous server component and we can get rid of any props because we're not going to receive any um in this component okay if we have no session dot user in that case let's redirect the user to the login page so we can simply call the redirect from next slash navigation and redirect the user to the auth options dot Pages DOT sign in or if that is not defined for some reason we can redirect them to the slash sine Dash in page awesome okay and now we can assume that the user is you know existent that they have a session and we can get started on the jsx it's going to be pretty straightforward it did with a classroom of Max with 4 XL and MX of Auto and a padding y of 12. inside of here goes another div with a class name of grid items um Dash start and gap-8 and inside of that second div goes in H1 with a class name of font Dash bold a text of 3XL and M or medium devices knob a text of 4XL stating settings just to tell the user what the hell they're about to do and below that one more div with one closing div right above that is where we are creating this with a class name of grid and gap of 10 bit more spacing inside of here goes a custom component and that is going to be our user name form that's going to allow the user to change their username which is a component that doesn't exist yet so let's go ahead and create it as the username form dot TSX and the username form is what's going to handle all the implied science client-side interaction with use client and then we also want to export an FC from here a functional component awesome okay and to make this look good let's return a form it gets no action but instead an on submit and what do we happen what do we want to happen on submit well we can use react hook form for me it's important to show you how to build proper applications you can launch the production that just work instead of just fumbling something together so we're gonna use react hook form along with a validator to properly handle the username changes to do that let's destructure something from the use form we get from react hook form and this takes some kind of generic and we can also invoke that now what the hell is the generic we want in here well we can write a custom validator for a username that defines when a username is valid and when it isn't let's go into our validators and say username.ts under lib and create or username validator let's export a const called username validator is going to be a z from Zod dot object and this object takes only a name property of Z dot string but now we can refine this and say dot minimum three at least three characters dot maximum 32 so nobody has a 7 billion letter username and we can also match this against a regular expression and that is going to be two slashes with an what is this how do I do this there we go like the I have no idea what this is called like uh Arrow up I guess and then in angled brackets we're going to put a to z large A to Z and zero to nine and an underscore that is also fine afterwards a plus and a dollar sign to conclude this this is the end this is the beginning and this is everything in between and only if this matches the username is it actually considered valid now we can work with that in or form for example we can oh let's also export the type for this export type username let's call this what did I call it in the example project I didn't call it anything but let's call this username request the follow or convention is going to be equal to Z dot infer the type of username validator there we go now we can import that type username request inside of or use form and invoke the use form with some options in here that is going to be for example the resolver and this is going to be the Zod resolver from Hook form resolve result you know the drill this is what we've done before this right here it didn't change and then here we're going to pass the username resolver username validator it's called right not resolver okay so this is going to make the submission of the form automatically fail if the data doesn't match what we expect client-side we also need to validate that server side and the default values we can pass as an object as the second pre like as the second configuration option into here and the name is going to be either the user dot username or this is going to be an empty string now you might wonder what the hell is the user where is this coming from well we don't have access to the user in this um in this form so but because we have a service side session here we can just pass it into here as the property so we can receive the user in here and the user what we worry about or what we expect is from a typescript utility type only a pick type and that is from the user from Prisma client or is it actually from prismarck client uh yes it is it's not from next auth okay just wanted to make sure we only want the ID and we also want the username and we can omit everything else okay for some reason this is giving us an error generic type pick requires two type arguments what did I mess up oh this is not comma separated but this is or separated with a pipe operator so we're only taking these two properties from the user that we expect as a prop which means we can already import the username form and pass it what we expect and that is the user as an object and this is going to get the ID of session.user.id and it's going to get the username of session.user.username or if that doesn't exist for some reason an empty string awesome great we've done that now we can switch back to our username form and actually um do the logic of the form what should happen to do that let's destructure the handle submit from react hook form and also the register and lastly the form state and we want the errors if there are any great that's all we need to destructure from react hook form and now we can actually get started in the form so we can pass it the handle submit that should happen on submit and for now let's just put an empty function here that does literally nothing and to make this page look good we're going to use I think one last UI component that is pre-built let's check if we have it already but I don't think we do and no we don't this is going to be the card I'm not sure why there is an error right now we're going to solve all the arrows together don't worry but it doesn't look very severe um so for now let's say npx Shad CN Dash UI add and it's called card it's a pre-made pre-styled component that just that makes the stuff look good we want this under Source components opponents slash UI that's where we want the card that was really fast and again let's change the capital c from the lowercase C which it which it is initially and now inside of the form we can render out the card component we have just installed but that's just going to make it look good out of the box with a card header as the first child of that card and the card header gets a card title and this is going to say your username below that it's going to say card description like that and let's say please enter a display name Ur comm4 table with period as the card description very very nice now let's go below the closed card header and add the card content in here in here there's going to go a div with a class name of relative grid and a gap of one and we also want one last event here with a class name of absolute a top of zero there we go a left of zero and then a width of 8 a height of a height of 10 there we go a grid and a place Dash item Center to Center whatever is inside of this div and this is going to be a span with a class name of um text small and text zinc 400 just saying U slash let's preview this actually let's uh what is happening right here okay it seems like we are getting an error let's restart the dev server that might be from the stale version before let's refresh and see if we get the arrow again and if we do then we're gonna fix it but hopefully we don't let's give this a bit of time and we get one error right here in our username form component whatever that is I don't see it right now but we can see please enter this display name you are comfortable with um and did I forget a class name right here no this is fine for now this is going to look better here in a second but you can already see what we're doing right here it looks pretty good out of the box let's give this a bit less space and move this over and plow right ahead awesome so below the closing diff right here let's add our custom label component from UI and this is going to encapsulate the name with a class name of sr-only we don't actually want this to show up just for screen readers and then HTML for the name field that we're about to add right now as the input right here also from our UI where we pass an ID of name that's what this label right here is for then we want to add a class name off and this is going to be with Dash 400 pixels in these angled brackets for a custom inline value and a padding left of six great let's also pass this a size of 32 and we need to register this with react hook form and to do that we can enter some curly braces with dot dot dot to spread in the register off and we also get type annotations here name beautiful so we have just registered that and all the logic is being handled by us from react hook form super convenient and the input can be self-closing there we go okay right below the input let's worry about any error handling that we might need to do so for example if there is an errors dot name we will know that from react hook form let's render out a P tag that's going to show the error this is going to get a class name of padding X1 a text of extra small a text red of 600 and this is going to say the errors Dot name Dot message if there is any then it's going to show that right here and I messed it up awesome and I think we are pretty much done right below the closing card content let's add a card footer as the last thing here in the card footer is going to contain a button also from our UI lots of UI components in here um that says change name awesome and why is there an error here already included or because we changed the um the casing for the file okay let's see this and this looks beautiful okay very very nice settings your username and we can change it right here and click change name this looks really nice but it's not functional yet this won't do anything here and so we need to create one more API endpoint to handle that and again because this is a client component we're going to do that with react query by using a use mutation Hook from react query and here we can pass the mutation function in async Arrow function that we want to handle the username change and we can receive something in here if we declare this as the username request request the type from the validator we have created we can just receive the name for the mutation function like so and formulate the payload as we always did in this video with the type of username request like so and this is going to be just an object and the name inside of it awesome let's make the request const we can already destructure this data is going to be equal to the weight axios import that dot patch and now we're actually going to make the patch requester change the username to slash API slash username and what do we want to pass of course the payload to this API endpoint then we can return the data to react query if we want awesome as for the error handling it's not going to differ from any of the other error handlings so let's search for the error response status is equal to 409 I just searched for 409 in my search bar here and let's just copy the error handling behavior and from this endpoint from the page.tsx under create and we can literally just paste it on the on error we can receive the error right here with react query we don't know what it is but if we just copy and paste the error handling behavior and we can know what it is let's import the access Arrow and the only thing we want to do is the first type of error handling we don't care about the 422 to be honest and we don't care about logging in either um let's just return a generic toast in the other case like there was an error whatever and instead of the subred already already exists we can say for example username capitalized already taken please choose a different username very nice so now we're handing the arrows and what do we want to happen on success well let's quickly do that it's very simple Logic on success there we go that's going to be an arrow function as well and then here we're just going to send out a toast notification with a description and nothing else description saying your username has been updated and then we are going to say router dot refresh and the router needs to be lowercase and that comes from the hook cons router is going to be equal to use router from next slash navigation invoke that awesome really really good that's error handling and success stay done and let's actually try out if this works okay news flash this can't work because the API we're trying to call right here with a patch request doesn't actually exist so let's quickly go ahead and create that um under or file explorer and then under app API there we are and then let's create a new folder called username that we're going to make this request to right here and a route.ts file inside of here to handle that logic inside of here let's export the HTTP verb we are expecting export async function and we are expecting patch requests to be handled by this specific function right here and we can have a try catch Block in here with some very with some logic that should be second nature to you by now cons session is going to be equal to a weight get off session to see if the user is actually logged in and if they're not if not session.user there we go then we can return a new response saying unauthorized unauthorized Thor rised what the hell on let me re redo this unauthorized there we go with a status of four or one unauthorized then let's get access to the body content I've explained that like 10 times in this video this is going to be the way it's wrecked.json again should be second nature to you by now and we can validate the incoming data by saying username validator that we have just created dot powers and pass in any content in our case the body this could literally be anything and it would just fail the parse in that case now we know that we can destructure the name otherwise we would land in the arrow block and we can check if this username is already taken before actually assigning it to a new user so const username is going to be a weight DB dot user dot find first so we're finding a user inside of our database where the username of that user matches the name that we get in from the request and if we find a username like that if username if a user exists with that exact name then we can return a new response saying username is taken and as the status code right here as the second option with the status we're going to pass four or nine there's a conflict in naming conflict so you cannot have this specific username okay and otherwise the username is not taken it's fine we're going to say update username we're actually going to let the user update it and this is going to be a very simple database call under white db.user.update and what do we want to update where the ID of that user is the session.user.id that we're validating server side and then with the data of username name name so this is the only property we're updating for that user entry and then when everything is successful we can return a new response from this API World saying okay everything was fine you change your username you are good to go and the error handling we can literally just copy again from any other API endpoint I'm just going to go to the unsubscribe endpoint under separate it unsubscribe copy the error handling paste it in here and slightly adjust it for example importing Zod let's say invalid request data passed in that case and otherwise could not update username please try again later awesome format that save that and now let's try updating username so I'm gonna say Josh and let's look into the network tab to see what is happening and if the request is successful let's Click Change name that should make an API request and it doesn't because we haven't implemented that yet did we let's give this just a bit more space and that's close out of so much of this stuff and so what we care about right now is just the username form but here we are making a patch request but we're never actually invoking the mutation so that can't work mutate is let's call this update username and let's also get the is loading value from the use mutation and let's Implement that down here in or jsx and we want to implement that where the button says change name we want to implement the is loading state for that so if it's loading then we're just going to forward that to the button that receives that as a property and then on submit of the form this button is going to act as a submission by default that is standard react Behavior or standard HTML Behavior we can receive the event the data that is going to be the name we are enforcing with the valid error with the resolver in react talk form and say update username and pass that the event and did we not call this update username update user okay let's call this update username I guess same thing a bit different naming okay fine and let's try out if this actually works so I want to rename myself to Josh let's go ahead into the network Tab and see if everything works I'm going to click change name it's going to make the request we get back a 200 your username has been updated and now my username should be Josh if we go to the feed look at the posts we have then it should say posted by wordful AI support interesting so we're not displaying that properly for each post yet that is not ideal aha so let's navigate to the post component that encapsulates this logic and let's change the post.author.name to the posted orthodont username so we actually make use of the username instead of the actual um Google name that we get right and if I do that and reload the feed then we can see awesome now it says you slash Josh and we can actually go into the settings and change this to anything we want if the username is not taken and update username accordingly and it shows up right there as possible you slash one two three whatever beautiful very very nice I think we are mostly done we've done the search bar we fixed the bug we can create a different username or we can change it we can search for communities and we can create posts we can upload posts we can comment we can reply to comments man there is so much stuff involved in this and if we build this out it's even going to be a bit more responsive than it currently is or one thing I've noticed when we click out of the search bar it should disappear right that's a very quick fix we can Implement so right now when we click away it doesn't close itself um we don't want that we want it to close itself so let's quickly navigate into the search bar and fix that because I've already prepared something for you that makes that very easy and so let's quickly assign a ref to this command let's call this command ref create the ref for that cons ref or command ref is going to be equal to use ref from react create a Dom node reference and this is going to be an HTML div element and let's ins initialize this user f as null we can now pass it to the ref and now we can use a handy hook use and called use on click outside which I've already prepared for you in your starter code pass it the command ref as the first option and then a callback function as to what should happen when we click away and when we click away we want to set the input to nothing to an empty string because therefore because we're rendering this conditionally right here if the input.length is greater than zero the input is going to disappear or the search results are going to disappear and let's quickly take a look at the hook so you kind of understand what is going on essentially we're listening to an event and if the ref we are clicking on does not contain the target so if we're clicking anything else in normal words then it's going to execute the Handler so whatever we pass in SD callback function I've already prepared this for you I don't think there's a huge benefit in typing this out yourself because I just copy pasted this as well from a website called use Hooks and let's try out if this works if we search for react and click away it closes but what it doesn't do currently is if I navigate to react then oh no it does never mind it does work properly interesting okay but if I do so using enter if I press enter on this then it's not going to close so what we can do additionally to fix that little bug to also have it close and enter is the one last thing very very quick and that is going to be inside of a use effect right here import that and callback function and as a dependency we want to have the path name so whenever the path name changes in the URL when we're navigating to a different path this should also close and to track the path name we can say const path name is going to be equal to use path name from next slash navigation and now we can make use of this whenever the path name changes we want to happen we want to execute the same callback function and close the input essentially not having a display anymore by setting the input to an empty string so now when we search for react for example press enter that's going to take us to react and it's also going to close the results beautiful very nice I think we are done everything works I'm going to fix the CSX CSS box and in the finished repo I don't think and there's a value in fixing these together like this not being shown properly and I think I did that in the actual version but it's not worth the time to debug this video is not about CSS and I'm going to fix them in the final repo if this should be of importance to you for it to look absolutely amazing everywhere then don't worry I've got you covered and I'm gonna put this into the GitHub repository if you want to check that out but mostly the CSS works really well it's responsive and for the very majority of the build and all the functionality that's really important Works beautifully out of the well out of the box like out of what we created not out of the box we did this right we made this happen we can navigate to the feed create a community like so we can change our username if we want to right here and we can also sign out if we wanted to amazing work dude if you've been following along um really really good work let's go ahead and deploy this so I'm gonna head over in my other browser to Purcell because I should already be logged in there or am I logged in here as well verselle.com let's try this out nope I'm not okay so let me navigate over to Brazil onto my actual account and do I need to blur anything here yeah I might need to blur some stuff here um to protect the privacy of my freelance clients and but let's publish this together first off I'm gonna go into GitHub and create a new repository for this that's step number one oh no step number one is probably to lint this so make sure we have no building errors so now that we're done with the project let's run yarn lint to see if there are any building errors in this project in such a large project this might be very well the case as you can see command failed with exit code 1 that is not ideal because in the Post Feed we're missing in iterator let's navigate to The Post Feed right here and in line 79 we are missing an iterator that makes sense let's use the uh what did we use here the post dot ID as the iterator that should be fine do we get any other issues yes we do in the page dot TSX under create it doesn't recognize the page as a react component so let's put this into uppercase and remove the FC we don't need that and then also export default that uppercase page great do we get any other errors no we don't let's try this again let's run yarn lint to fix any build arrows and try if that works and it went through successfully awesome there's one more step very quick we need to do in our package.json for this to deploy successfully using Prisma because if we try to deploy Prisma on versel we need something called a post install I've already done this for you normally you need to add this yourself but it's very important for me to be transparent on what the starter code does for you um and what it doesn't so the post install is something I have added for this to be deployed on the cell very easily okay let's create a new repository for this I'm going to do this on my right hand side right here and so I don't leak any personal GitHub information let's call this Reddit YouTube clone we can add a description I'm going to mark this as private and click create repository and you can do this in your own GitHub account and this is the important bit the remote origin we want to add let's navigate over to our GitHub repo and clear the screen let's add that origin like so and I've already got a remote origin because I cloned the repo from somewhere I believe we can delete that get remote remove origin hit enter and then add the new origin let's add everything git add Dot it's going to add everything that is not ignored by our git ignore file then we're going to say git commit minus m and let's say initial commit hit enter that's going to commit all or changes and now let's say git push minus U for Upstream origin Master push it to the master branch and see what happens okay Source ref spec Master does not match any does Main work no that doesn't work as well so I think we are having problems that there is already a GitHub repo in this Reddit too so to fix that I'm just literally and what I meant is a git folder right this has already been registered I'm just gonna delete the get folder and that's going to get rid of all the good stuff and fix any problems we have now we can just say git add dot again or first we need to say git init to initialize a new repository then we're going to say git or add dot to add everything then git commit initial commit again and then we can add the remote origin and get push minus U for Upstream origin Master post push this all to the master branch and now this should work properly all right we can see this pushed let's go into GitHub refresh the page and we should be able to see it right here in our GitHub repository awesome we do and now let's go over to our vessel dashboard click add new project and now we can see the project pops up right here Reddit YouTube clone we can simply click import go to environment variables because we have some and we can copy these from our DOT EnV file just hit select all Ctrl a Ctrl C and we can just paste them into the environment variables and don't have to copy every single one individually that's what a subscriber told me about and it's actually very handy and then click the big deploy button and cross our fingers nothing goes wrong with the deployment and everything works out let's give this a bit of time you can watch the build process live and hope nothing goes wrong and then I'm gonna be right back oh interesting the build failed because of an unused at TS expect error directive and I just noticed we also missed one to do honestly it's not too important and I'm going to show you the back button inside my back button what is it called the button to feed button is what I call it it's honestly a very simple button that we missed in this video um I don't think it's so important I'm gonna include it in the GitHub repo and so I don't miss it and but essentially it's just a button that takes us back to either the community or the feed depending on where we currently are just 30 lines of code if you want take a look at it but it's not very important um to do right now instead let's fix the build arrow that is much more important and what was the arrow again unused TS expect error directive let's see where that is at TS expect error and that should be right here and why is that unused that is definitely used so instead let's use ATS ignore not sure why that is storing an error it really shouldn't um and also what I find weird is that there's a spacing here maybe because they added that I'm not sure but this should um actually ignore the error and it's necessary so I'm not sure why this is throwing in a a build error but we can just add everything I'm going to commit using initial commit and probably you want a different commit message like a fix of um you know build error or whatever and then we can push to the origin Master once again and that is automatically going to rebuild the project on Brazil because Versa recognizes there has been a change is except when we have never deployed this um and yeah but it is building again but I'm not sure if it copied the environment variables oh it has okay awesome so this is going to build automatically again we can see one build error and now it's building again and I'm gonna keep you updated on this build process let's watch this in real time and then I'm gonna be right back and see if this build is going through successful and it looks like the build has gone through successfully all the pages were generated beautiful and now we can actually go to our project by clicking the link and it's deployed successfully we can see it right here very very nice we can see the other posts are loading um and because we're not signed in we don't see that we uploaded something but we can see the general let's click on a post and see what happens everything is loaded the image is right there we can add a comment if we were logged in beautiful um there are two things I want to mention right here um one thing to make your user experience a bit better is when for example clicking and create posts right here we would be able to create a post but it would also show the login well it's not because of this right and we could still create a post which is not ideal because we're logged in what would be better instead is for example if we're on the next Js I'm subreddit let's just navigate to that if we click create post right here we should be forwarded to the um login page right to the login model that we are intercepting and so what we can do very easily is add a middleware dot TS file in or Source folder and in here we can specify in the middleware.ts um a function to run before every request on the edge so let's export in async middleware we can move this into a side by side by the way with the final product there we go export async function middleware where we receive a request of type next request and in here oh by the way we need to import that type from next slash server we can verify if the user is logged in or Not by saying cons token is equal to await get token which we get from next auth slash JWT and pass in the request as is into that function and if we have no token if the user is not logged in in that case we can return a next response dot redirect to a new URL and that is going to be to slash sign Dash in and pass the rack dot next URL as the base URL for that and we can match this middleware to only run on certain paths by exporting a const config at the very bottom that has a matcher property which is an array of strings and only when these strings are matched as the URL path then this middleware function is going to run so for example we want to run on slash r slash any path so colon path star slash submits or react slash submit whatever where we just were on the editor and then we also want this to run on um slash r slash create so we're unable to do this stuff without being logged in and instead the model pops up let's go into Dev mode to demonstrate that to you let's go on to localhost 3000 we could also just push this into the repo and it won't work the same way and by just demonstrating this locally is a bit faster so let's go ahead and actually why don't I do this in the browser where this is already open let's read all the page and then sign out and see what happens let's sign out like so it's going to sign us out a bit slower in the dev version but place it blazingly fast in the build out version and now let's go to a community like next.js for example and click on create post and now beautiful this happens the model pops up instead of actually taking us to the page where we could create a post if we were logged in which doesn't make sense if we are not logged in awesome so that is protected now and one more thing one last thing for this video we've deployed everything successful everything works in the build out version for cell um sometimes does a bit of caching for you automatically and I had some problems with that as this is a highly Dynamic web app so um I'm gonna show you what I did in the actual version and that is export const dynamic um so let's go into the main page.tsx page.tsx this one right here and from here we are going to export a const dynamic and this is going to be Force Dynamic honestly I'm not a hundred percent sure this does anything but it's just one line it can't do any harm and uh I think what this does is honestly I couldn't test it reliably but when we go back to the home page it refetches all the posts instead of showing stale values and um if we search for Force Dynamic it should appear twice in our app and it does so that's just what I wanted to point out to you and what we can also do to kind of achieve the same effect as far as I know is this fetch cache right here again I'm gonna be 100 real with you I'm not sure if this actually does anything and but for me in the end the caching worked as it expect as I expected I'm not sure if it's due to these or due to something else that's in the project um but it just worked in the end that's the important part right so the posts were never stale and always fetched dynamically and showed exactly what we want awesome just push this into the repo it's going to build out and you're gonna have your version live this looks really really nice let's give this a bit more space and that's the app um if there's anything I missed I'm going to include it in the GitHub repository um I don't think so just the the back button I briefly touched on but it's honestly not super important if you're interested you can take a look at the um code for that in the guitar people and this was it this was a huge project I acknowledged that I really can't get congratulate you on learning all the technologies that you did in this video incorporate them in your future builds they're going to take you very far they're amazing Technologies and um data fetching strategies as well which with react query with the infinite scrolling it's really really neat what you can do with this awesome that's gonna be it for me again congratulations on this project and finishing it and you're one of the few who actually did it so very very nice work and um I'm gonna see you in the next one I really hope you enjoyed and yeah until the next video have a good one and bye bye
Info
Channel: Josh tried coding
Views: 62,568
Rating: undefined out of 5
Keywords: nextjs, next.js, nextjs 13, app router, nextjs app router, nextjs 13 app router, nextjs tutorial, next.js tutorial, 2023, josh tried coding, joshtriedcoding, reddit clone, react, typescript, tailwind, shadcn ui, shadcn-ui
Id: mSUKMfmLAt0
Channel Id: undefined
Length: 586min 45sec (35205 seconds)
Published: Thu Jun 15 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.