Build a Complete SaaS Platform with Next.js 13, React, Prisma, tRPC, Tailwind | Full Course 2023

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey I'm Josh a software engineer from Germany and today we are going to build literally an entire software as a service business from scratch using some of the most modern tools there are and when I say an entire business I mean it payments included software included landing and pricing page included I even went as far as making sure that when you share a link to your application even that looks good but Josh what are we even building let's take a tour this is the landing page that your users will land on chat with your documents in seconds quill is now public and quill is just the name that I gave this application on the landing page we can see a preview of the dashboard and how we can interact and use this software and a little instruction with a preview image of how we upload a file these steps right here are just supposed to make the onboarding process for new users more intuitive let's head to the start of the landing page and then navigate to the pricing page right here because this is literally an entire SAS we have multiple plans the free and the Pro Plan and we have different specs for each one for example let me zoom in so you can see this easier on the free plan you can only have a maximum length of five pages per PDF on the Pro Plan that's up to 25 and also up to 16 megabytes per file instead of the measly 4 you get on the free plan and also just some other stuff like higher quality responses and priority support all right let's zoom out and let's actually log into the application to get to the dashboard using our sponsor that is kind they make it super simple to integrate secure authentication into your app I'm gonna enter my email hit continue and we get a code to our email simply copy and paste that over of course you can also use other providers like Google for this that is no problem at all and then we are going to be redirected to our dashboard where there is currently no PDF file so let's upload one and here it says we can upload up to 4 megabytes so let's give it a try let's upload some lecture notes inside of here and if this isn't the most beautiful drag and drop file uploads you've ever seen I don't know what is the PDF has been processed and I've made sure to really get all the loading States right this is a proper full-blown application with all the details that that entails for example we can go to certain pages right here for example the third page if we hit enter however of course we shouldn't be able to go to the fifth or any text right here all these details just work in this app let's try now chatting with our PDF file this is something random I found on Google something about philosophy so for example in one sentence who was Socrates somebody that is mentioned in this PDF file and don't worry if you don't know what that is it doesn't really matter and then we get all questions based on this PDF answered when was he born for example let's try asking that and based on this PDF it should display 469 line BCE and it does but what happens if you try uploading a PDF file that is too large like one that has 23 pages in our free plan we can only upload up to five well it won't work we will get an error right here on the right hand side that this PDF is too large for our plan and that our plan only supports up to five pages okay so let's try upgrading we can simply click this upgrade button right here select the plan we want to upgrade to the Pro Plan and that's going to take us to a hosted checkout page where we can simply enter or email enter some card data I'm going to use a test card right here when the payment success we are going to be redirected and if we head over to the dashboard and try uploading a PDF now it says up to 16 megabytes so let's try that marketing lecture once again because this time as we are now a pro user this will work we are being redirected processing PDF this won't take long and now we're all set to start asking questions about this PDF file for example let's ask about the modern concept of marketing we can see on the left summarize the modern concept of marketing in bullet points because not only are we streaming in the response in real time but we can also render markdown properly and it looks really good out of the box of course we can use the page controls to Traverse this entire PDF document even go into full screen to see the entire document right here we can zoom in zoom out into the document as we please and of course because not all pages are vertically we can rotate it as we want and once we are sick and tired of our app and that everything works so smoothly we can head over to the subscription plan and actually cancel our subscription let's see what happens let's head back and now we can actually see if we head over to the manage subscription tab right here that our plan will be canceled in exactly one month from now and we could even resubscribe if we want because until then we are going to be on the Pro Plan I really hope you are as excited as I am to build this entire app together with everything that you just saw included in this single video if you are grab your code editor in a good cup of coffee and then let's sit down and start in the first steps of building this application going from very simple stuff to the more advanced stuff step by step together of course you don't have to do the entire video in one sitting that would be insane there are chapters and checkpoints so you can code in your own pace that works for you and with that said let's go out of full screen right here and let's actually get started with this build oh and one thing this is pretty fast as well just wanted to tell you okay and with that said let's get started in building this app together alright and once we are on the desktop we can actually get started with or built and we are going to do that inside of the CMD the command line in Windows or any other terminal for any OS that you're using by typing in npx create Dash next Dash app at latest that is how we create a new nexjs project we can just hit enter and wait a second for that to load that's going to ask us to install the newest version which in my case is 13.5.2 and I'm gonna hit yes now this app let's call it quill that's what I name my says but feel free to be creative give it any name that you want in this case let's hit enter yes we want typescript we do want that that's really really useful as you're gonna see later we want eslint to make the building process a bit easier and to ensure a bit better code quality we are gonna use Tailwind for styling hell yeah we do want a source directory I find that gives our entire project a better structure so I'm going to hit yes there as well we do want the new app router there is a lot of really cool new features in there that I want to show you and then lastly for the defaults import ads we're going to hit no and that's gonna run the next JS installation 4 set up everything I believe it's gonna use npm yeah it states it's using npm right there which is not ideal I'm going to be using a different package manager for this video but of course you can definitely follow along just using npm if you're familiar with that I'm going to be using pnpm because the caching is a bit better there it's generally a bit more friendly for disk space that's why I like using it and it seems like or CMD might be done and it was done it turns out I had just installed it in the wrong directory because I wasn't on the desktop all right that's where I moved it now so we can see the into the file we have just created in my case that's going to be quill and then we can type in code dot to open this project in Visual Studio code that's going to open up right here and this is going to be the main default next.js project and I'm probably gonna say this zoom level right here that we can just start up if we want to let's say PM PM Dev and let's see if everything initialized correctly we can see in the CMD for the new nexjs 13.5 how that looks how it started up and we can also see if I move over this window that or project initialized correctly awesome that means we can actually get started in building out this entire project step by step together and we're going to start let me give you a rough outline on where we're going to start right here um with this process so this is how I generally Built My SAS apps right first off we have the landing page and the navigation make it look good so users can already visit your landing page and navigate around while the functionality might not even be there yet in this project because we're going to be building everything in the entire project we're gonna do auth next because the authentication is the basis for everything that is to follow right everything in here depends on the authentication that's why we're doing that second then the main functionality of the app what is the purpose of this app in our case that's going to be chatting to PDFs but again a lot of transferable knowledge that is not specific to AI or chatting to a PDF at all that's the beauty of this project and then last velocity the payment integration right so that premium users have more advantages they can upload larger files and so on that's what usually comes last when all the features are already there because then the integration of the payment is going to be very easy and the launch we're going to deploy our project to be live on the web and it's just a very rough outline just four steps on how we're gonna build this project step by step together and as I said we are going to start on the landing page and with the navigation inside of vs code let's go right here into the page.tsx that is the main page inside of our app folder that is generated by an extra S4s and we can get rid of all this stuff right here I do have a very handy hotkey for this um it's Ctrl M which is not default in vs code but it selects everything inside of this main tag that I've selected for us and if you want to do that yourself you can go into file preferences then it is key keyboard shortcuts and this is emit balance outward I believe it's called exactly so it's this right here emit balance outward you can set a key binding and whenever you press that key binding the entire HTML tag is automatically going to be selected for you it's really really handy all right so let's finally get started on the landing page by the way we can get rid of all the unused Imports by pressing shift alt and O that's how we can get rid of those and then let's get started on the landing page one thing we're gonna do which we're gonna use as the first tag the top level element on the landing page is create a component together let's do that inside of the source folder let's create a new folder called components and inside of this components folder we're going to create a new file called Max with wrapper.tsx typescript XML that's what the ending is for right here and we're going to say cons Max with wrapper is going to be equal just a regular Arrow function and also export that as the default export default Max with wrapper and I'm going to disable GitHub copilot it is making us a bit faster but you might not have it so that might just be a bit unfair if I use it um in here and the only purpose of this Max with wrapper is for it to be reusable we can reuse it on all pages and then the spacing on the left and right hand side of our page like here for example left hand side right hand side across all pages is always going to be the same that's what this Max with wrapper is for this will take in props we can destructure those right away that's going to be a class name and also that's gonna be children now we also need to declare the typescript type for this because we are working in typescript we can do that using a colon right after the destructuring syntax just like this and then also instead of an object we can declare each type for the class name and for the children separately what that means in practice is for example the class name will be optional that's why this question mark is here and of type string and then the children oops and we need to go into the next line the children are a type we get from react and that is react node if you've never worked with children don't worry you're gonna see what this does and this react no type for some reason won't let me Auto Import whenever that happens when you cannot automatically import we can just press shift Ctrl and P and hit developer reload window and then 99 of cases that's actually going to work and now it should be oops and now we should be able to import this from react beautiful and this component is going to be super super straightforward we are going to export a div from here and this div is going to get a class name now what is the class name going to be it's going to be MX Auto so we are spacing it equally on the x-axis a width of 4 which is equivalent to the 100 width using Tailwind by the way um if you want to hover over the Tailwind classes and also see what the underlying CSS is there's an extension for that called let me show you it's right here on the left hand side Tailwind CSS intellisense that's what I'm using to get this hovering okay in here we're going to say Max width of screen Dash XL if we hover over this it's going to be 1280 pixels at the maximum a padding X of 2.5 on the left and right hand side and then on medium devices and up we want a padding X of 20. just a bit more and inside of this div right here we are going to render out the children that means we can just wrap every page we have inside of this div and also we want to return learn this div from this component else this would be invalid jsx syntax and we can get rid of this now the Keen eyes among you might have noticed that we are not using the class name anywhere and that's not ideal because what we want to do is being able if we use this Max with wrapper for example in this component where we're going to need it just like this we want to be able to pass in a of course the children that's what the children are it's just everything that is between these two tags those in our case right here the div is going to be passed in as the children of type react node then rendered out inside of the stiff so we're just wrapping the div in the page.tsx inside of this div right here and we could also see that in the browser but I think the the point is clear but what we also want to do is pass in a class name like for example background red 500 and have that applied to this top level div to make it way more usable now the question is how do we do that because we don't want to overwrite this class name so instead what we're going to do is Define a custom utility function that's going to be really straightforward but incredibly helpful and we're gonna do that in our lib folder a lib folder is Gen really going into the source folder let's call it lib and then here let's create a file called utils dot TSO utility functions we can just reuse across the entire application let me show you how to declare this and then let me show you what this does so let's export a function called CN which stands for class names and this is going to take a bunch of inputs we can use the spread Syntax for that so everything that is passed into this function no matter how many arguments we can just receive as one and this is going to be of type class value array we're going to see what that is later but that is a type we don't have access to We Can't import this and the reason is this comes from a package this package is called clsx and also while we're at it let's install we need to say install by the way I'm using pnpm you can use npm or yarn or bun it doesn't matter let's install clsx and also Tailwind Dash merge while we're added to Define this super super helpful function right here once these packages installed by the way while they are installing we can already plow ahead with this function we can return something called TW and not twinkle star okay those aren't done yet we want TW merge in here in here we're gonna pass in c l s x again this app seems a bit abstract right now but you're gonna see what this does it's super super useful and then here we're gonna pass the inputs and once the dependencies are done oh I'm just realizing because an extra s uses npm by default we have a package log I'm gonna get rid of that and also delete all the not modules and reinstall them using pmpm awesome they're gone and now let's reinstall them using pnpm which is just you know you only need to do this if you're using yarn pnpm and not npm and let's give this a hot second to reinstall all the note modules that's why I use pnpm it's super fast and it reuses you can see reuse 290 packages instead of downloading them again like npm would for every project that you make so it just reuses packages and now we can install clsx and Tailwind merge so what this does I've done a separate video for this actually I if I don't forget it I'm gonna link this video that explains what this function does and how I like working with Tailwind essentially the TW merge if you have something like um padding X of 2 and padding y of two in Tailwind you could just merge them into padding too right these are the same the left hand side is the same as the right hand side and that's what Tailwind merge does for us and then clsx helps with emerging conflicts and so if we have the same value twice until when like a for example background green 500 and a background red 500 normally entail when the problem is the order doesn't matter in detail when CSS file that is generated for you um red might be before green therefore green will always be rendered because it's later in the CSS as file so the order in here wouldn't actually matter but with clsx Intel with merge it will so it behaves more as we expect that's the bottom line just um it's just way easier to work with and also the class value type we can get from CL clsx and that's all we need to do for this utility um I I know that was a bit abstract but let me show you how we can use this so for the class name we can now merge the incoming class name together using or CN function that means we can merge the class name we always want applied by default as the first argument and merge that with the incoming class name where we are reusing this component essentially turning it into a highly reusable component which is the entire purpose of this right and now this background red 500 will actually be applied so if we restart our Dev server there we go we are on our localhost and because there's nothing in here let's just say hello world because there was nothing there nothing would show up but now we can see if the background is applied as well as the padding and the maximum width that is what the max width wrapper is exactly meant for and we can see it works beautifully so this class is now merged with all the classes that we have inside of the max with wrapper awesome and what that means is that we can now actually get started in building out the landing page more productively okay for the max width wrapper we're gonna pass a class name of March and bottom 12 a margin top of 28 to give the top a bit more space on small devices and up let's close this down to have more space we're going to say margin top 40. we're going to give it a flex which is nothing else than a display Flex in regular CSS a flex Dash column to align the items vertically below one another and items Dash Center a Justified Dash Center this is for vertical alignment and this for horizontal but because we're using Flex column it's inverted right so the justify and item Center invert when we're using Flex column but because both are centered it you know it doesn't really make a difference then also we want the text to be centered um inside of here awesome inside of here let's create a div with a class name of MX Auto we want a margin bottom of four we want to flex a maximum width of fit so it doesn't take up more space than it absolutely needs to an item stash Center a justify Dash Center and we want a space X of 2 which is also going to horizontally space out the items This is Gonna Be by far the longest class name I promise an overflow hidden to not show anything that is you know overflowing from this div around it for a border border Dash gray Dash 200 background of white a padding X of seven a padding y of two let's apply a shadow of medium a backdrop Dash blur and I think the last three classes yep are going to be a transition Dash or a hover colon border array 300 so if you hover over this element you're going to see that here in a second and the border is going to change and then on Hover we want a background the white of Slash 50 so 50 transparency okay Jesus Christ you have my word this was by far the longest class name if you got lost anywhere um either always you can always refer to the um GitHub repository um right here it's probably going to be named different this was just my um my development stuff so it's kind of messy I'm gonna re I'm gonna redo the GitHub repository or I'm gonna go through left to right and you can pause if you got lost anywhere I promise this was the longest class name and then we can just put a P tag in here with a class name of text small a font Dash semi bold and a text oops text Gray of 700 saying something along the lines of quill is now public so people can now use it okay enough coding and let's see what we did essentially we did this right here let's move this into a side by side give it a bit less space and also drag this over so we have it side by side awesome so we created like a little banner up here that's going to be above the main headline now the thing is this looks really weird let's be honest there's a black background and the reason for this background is that we haven't adjusted or layout yet that is where we can do all the changes for example we can force a light mode by passing a class name of a light into the HTML element and we're going to do some CSS adjustments here in a second too to make this look better and so quickly let's continue in the layout and then we're done with that and don't really need to touch it anymore later and in the body we're going to apply a up and the class names already applied right here but we won't adjust it so we're going to make use of our CN our class name helper function we're going to make use of that a lot it's super useful I'm telling you and then here we want a minimum height of screen so every page is at least 100 view height units high we want a font Dash sense we want NT aliased there we go and that's just gonna apply these two CSS properties right here and lastly we want a grainy effect that's going to look super cool involves some custom CSS I'm going to show you that here in a second and we want to merge this by using a comma with or inter.classm or font class name that will be applied and we can just easily merge that together awesome um the children are going to be rendered and that's going to be it for now now to make this right hand side not look completely horrible why isn't the side by side working by the way there we go let's adjust our globals.css this is where we need to go and this is where we can define a custom color scheme that we want to use and to make this project look good by default there's something really neat we can use that's quite new let's go to ui.chatcn.com for that that's ui.shatcn.com we're going to land on this page right here and this is the UI Library we're going to use for this project it's really good if you've never worked with it you're gonna really enjoy this and this has a feature called themes and I've went with the blue theme essentially you can choose any color right here that you want your app to use if you don't want to go for the blue that's totally fine chose any other one I'm gonna go with blue once again and we can just click the copy code that's going to let us copy some CSS values we can just go ahead and paste into our project by replacing everything except the three Tailwind directives at the very top so we can just paste that in we have the root and the dark mode hit save and if we go back to app okay it's already looking better now we can see there's a shadow the background is white as it should be and we can get started in configuring or Tailwind config that's gonna be one thing we can do once at the beginning of the project right now for example and then we're never gonna have to touch it again luckily so let's get started configuring or um Tailwind config as for the content we don't need to touch that that just means the files the Tailwind is applied to and since it already works and we don't need to worry about that however in the theme let's get rid of the default stuff and instead let's start with the container the container is going to get a center of true we also want a padding off as a string tool RAM and then for the container lastly we want these screens property as an object the 2XL oops the 2XL value we want to set that to 1400 pixels which allows the container to go a bit wider that's pretty neat we can put a comma here comma here and I think it will do that automatically if we use spread your format yeah we don't need to worry about that and then we can extend all the Tailwind stuff that we want with our custom values that's the way more exciting stuff right here for example the max width we are going to extend that to 8xl normally Tailwind only goes up to 7xl we can go up to 8xl that's going to be later useful for the dashboard that requires a bit more width and in here we're going to say 1 400 0 8 PX for pixels then besides the max width we want to adjust the colors okay and we're going to start with the border the border is going to get an hsl value and that is going to be our variable or css variable from the globals.css file and the variable is dash dash border and this dash dash border also needs to go inside of a VAR in parentheses so hsl VAR dash dash border that's what we need right here and we can just copy and paste this down using shift alt and arrow down now that's going to give us an error and we can mark the Border press Ctrl D to select all occurrences or these two occurrences of the border and change it to input for example and there needs to be a comma here of course we can do the same thing again next up for the ring so for each one we're just defining the color it should be and later on we don't need to touch this file again and again we do it once and then our project just looks good out of the box we're going to do the same thing for the background hsl VAR Dash Dash background and the same thing for the oops let's select both again and I need to change back to the German keyboard for the Ctrl Z to work and do the same thing for the foreground as well awesome next up we can use the primary across or application this is going to be something custom we can Define right here and the default value we can say in caps default in the primary so if we ever use Color dash primary Tailwind is going to know what we mean we can use the hsl and then here goes a VAR again with a value of dash dash primary and then two closing parentheses so essentially what we're doing right now in this file is linking Tailwind to our globals.css variables so it always looks good out of the box that's what that's what we're doing right now and for the foreground we want also the hsl we can just copy and paste this right here and instead of the primary we want the dash dash primary Dash 4 ground there we go and we can just copy and paste this going on from here so we're a bit quicker let's paste this down using again shift alt and arrow down and instead of the primary we want a secondary oops and that needs to be a y and why did I change the keyboard again and again if you want to mark all of them use Ctrl D that's how we do this and change this to secondary okay we're going to paste this down one more time for destructive and then one more time for something called muted which is another color we're going to use later and we can already do this three more times like that and then instead of for muted we want the accent second to last is going to be the pop over and the last one is going to be the card and that's it I promise and there's a bit more stuff we need to do in our Tailwind config but that's the most tedious um most I mean let's let's just be real here this is I'm kind of stupid work right just some setup work it's not too fun I know that but the fun part trust me it's Gonna Come this is just so a project looks good out of the box and it's it's really worth it okay then after this after the yellow bracket right here or two above the plugins we can Define or border radiuses border radius and then here we want three things for example the large that's going to be VAR again with a dash dash radius there we go let's put a comma and we want two more the medium is going to be a VAR dash dash radius minus and we can wrap this entire thing in something called calc for calculate so we can calculate the radius minus two pixels so a bit less border radius that we're I'm calculating right here and the same thing with four pixels minus four pixels for these small border radius and then the second to last I think yeah we're almost done is going to be the keyframes we only want two different keyframes that's one gonna be accordion Dash uh down for the accordion down this is going to be an object with a from property just like this and we want to go from a height of zero and then next line two also in an object a height of and in here we're going to also use a variable they're super handy as you can see with no hard coded values we can change it all dynamically very handy this is going to be a dash dash Radix this is a kind of UI library that we're using under the hood by using chat CN Dash accordion Dash content Dash height and by the way if you got lost anywhere again to check the GitHub repository but I think this is not um too hard and then we can just copy the accordion down down and change this to the accordion up so we have two different animations and the only thing we're going to change is literally the two to the from so we can take the two cut that paste it in the from and take the original from and paste it in the two so we're just I'm reverting the animation awesome and then I problems were almost done one more thing we want to do and that is add an animation with two two curly braces left we're gonna say animation it should give you intellisense if not you're at the wrong point it can get a bit confusing with the curly brace I get that with the accordion up and accordion down let's start with the accordion up that is going to be an accordion Dash up it's going to take 0.2 seconds by default and we want an ease out and what that means is we can now use this inside of our code and call this animation um just like that it's super super simple and the same for the accordion Dash down we want to use the accordion down animation just like this they need to be comma separated and and that should be easier that should be ease out there we go that's the proper wording and then last thing I promise we're almost done we're gonna write no more lines of code in here we're just gonna install two plugins that are gonna make a live or live a lot easier that's going to be pnpm install we want one Tailwind css-animate it's a super lightweight animation library that almost adds no overhead to our project but lets us beautifully animate stuff by adding a single keyword so for example we can add something like um animate Dash in and it's going to know what to do that's what this plugin is for and then we want at Tailwind CSS slash typography so later in the dashboard make the text look good okay once those are installed we can simply go into our Tailwind config and require these so inside of the plugins array we can require tell wind css-animate it should give you intellisense on these and also we want to require the at Tailwind CSS slash typography and I promise we are done oh are we oh one last thing and the dark mode I promise very fast is going to be class okay that's it now we're already done with the um tailbone config I'm not sure why it's giving us an error right here it's a Reload or window it should know what Tailwind CSS is and once those features are initialized that should work and why is this giving us an error I'm not sure but apparently it expects a string so let's just wrap this in a string and there we go that's our Tailwind config done great so let's finish up the landing page all of our app is going to look good by default now congratulations that was by far the most um you know the you know the mo the least fun setup part now let's get started in the landing page right below this diff is going to be in H1 it's going to get a class name of Maximum with 4XL a text of 5xl a font Dash bold on medium devices and up we want a text of 6 XL so make the text a bit larger and to be precise that's going to be 60 pixels and lastly we want on very large devices and up a text of 7xl to make it very legible and here we're going to say chat with your let's all open up a span element so we can style the text in here separately which is going to be documents and let's make these documents um blue so let's say a class name of text blue 600 for the documents and then after the span let's continue writing in seconds period let's save that and you're going to see what happens right here on the localhost if I started that up if it's not started you won't be able to see it and the beautiful thing about nexjs 15 by the way is that it is much faster um so the local development is much faster and we're going to see our text show up right here beautiful okay right below this H1 we're going to create a P tag with a class name of margin top 5. we want to give it a maximum width of Pros which now works um it's kind of a really nice width for text we don't really need to think about let's say with a text zinc of 700 property and also a small text Dash XL so on small devices on top we want the text to be a bit larger because it's going to be text to base by default and here we're going to say quill allows you to have conversations with any PDF documents period simply upload your file and start asking questions asking questions right away period you can again put whatever you want and here it is totally up to you I am just going to go with what I think fits this application awesome and right below that we won't have a button that takes us to the dashboard like a big call to action right that's what we always want on a landing page um if you take a look at any landing page on the internet they have a big button that lets you do just that and we're gonna do that as well and we're going to use the next JS link element for that which lets us patch pass an href this href can link to any page or case we want to link to the dashboard for users to get started because if they are not logged in that's actually going to take them to the login page as the Target on this link element let's pass an underscore blank so this opens in a new tab that's what we want and inside of the link let's say get started and also one thing we can do is include like a little arrow right if I just save this this will look really bad for now trust me this is going to look good in a second we also want a small arrow to the right hand side just to indicate hey just click this button get started it's really easy and for the icon library for this entire project we're going to use pnpm install Lucid Dash react it's a super nice small lightweight icon library that has super good looking icons you can just use let's start backup or Dev server again that's Lucid Dash react and we can simply import the icon from here let's import the arrow right from oops now we need a closing tag here from Lucid Dash react and that means we can just use the icon as if it was any react component and even give it a class name of margin left 2 to have a bit of spacing from the get started text we want a height of 5 and a width of 5 on this Arrow right okay that's trust me not going to look much better because the button is still unstyled but the icon is going to be right there um but in a new line that's not really what we want so the question the million dollar question is how do how do we make this button look good essentially right there's a really neat trick we can use for that and that is using UI library to do this I mentioned earlier we're going to use something called chat CN for this and chat CN provides something called a button component opponent if you if you've never worked with chat CNN before it's a super reusable Library it doesn't abstract anything away from you it gives you the entire code and you can even install this manually this is the code we're going to install right now but we're not just going to copy paste it it's much easier than that actually and the way we can do this install this button and make our link right here look good is by saying npx Shad cn-ui at latest in or CMD right it's a CLI we can just use to install components and we're going to say add button and hit enter now because this is the first time we're using the CLI in this project it will ask us um to initialize this project so we can say npx Shad cm-ui at latest in net and we can go through the initialization process once and then um just by doing like adding a button or any other component that chat CN has we can just install that very easily later on we do want typescript yes it's going to ask us a bunch of questions just follow along with me here we're going to go with a default style we're going to choose zinc as our base color this could be anything it doesn't really make a difference I like zinc and caution here because we're using the source file or globals.css is going to be in Source let's close all of these it's going to be in Source app and then globals.css right here so Source app globals.css and hit enter would you like to use CSS variables yes we do because in fact we are already if you remember for the CSS file right here so let's definitely hit yes on that and or tailwind.config is not in fact a JS file it's going to be a TS file so let's close out of this and tell chatsy and it's tailwind.config.ts where it is and we can leave this as blank okay the ellip utils is the same thing we can just hit enter here are you using react server components yes we are because we're in extra s13 and lastly we just need to hit Y to finish this initialization process and that's it we're done what that did is it created a components.jsonforce that just saves some internal values we never need to touch this file it doesn't matter essentially what this allows us to do is choose any component from the component Library website that you can see on the left hand side and simply install it using the CLI it's incredibly easy so let's try it out let's try to add the button and that's going to install all the Styles we can then use on our landing pages close all of these by the way for the link component right here so if you take a look at the button that was just installed it's under Source component slash UI a folder that we didn't create but that was created by the um chat CN UI Library you can see this dependency doesn't seem to be installed it's squiggly red but let's see if we reload the window if that's going to be gone or if we do need to install this manually and it looks like it did install properly okay um as you can see right here the button variants um this is a really cool reusable style we can use but in order to use it we need to export these button variants and that's going to give us an error actually because we don't need to export it here it's already exported at the bottom so we are already good to go without changing anything in the button now what this allows us to do bottom line is this right here for the link we can just use the class name and this class name is going to be dynamic of button variants that means in here we can just invoke the button variance pass it some default stuff we wanted to if we don't it's going to apply all the default styles of the button so if we start back up the dev server which is really fast in nexjs 15 then you're gonna see what that does right here let's reload the page and you're gonna see the button style in action bam there it is it just works it just looks good bu to full okay now as for the size I'm not too happy with it I want it a bit larger and doing that is really easy we can pass this a configuration object inside of the button variants we can pass it a size and that is going to be of large and we can also pass it a certain class name a custom class name that we want in our case let's say a margin top of five to kind of space it from the paragraph element right here and that's a good looking button if I've ever seen one right it's functional if we hover over it it looks good but it's not really a button it's a link with button Styles we could turn this into regular button but that's the beauty even the button Styles we can use on every element that we want even if it's not originally a button okay and that's it we're done with the max with wrapper now we want some elements below that of course and those aren't gonna go in the max with wrapper so instead let's wrap this all in something called a react fragment which is not going to generate any HTML tag but it's gonna comply with react rules of only ever passing one element right so we can't put two elements next to one another that doesn't work in react so we only need to pass one and below the max with wrapper and this is going to be the let me comment that in so it's easier to see the value proposition section this is just them for visual reference for us as developers to know what this does this doesn't have any functionality and here we're going to create a div and this div is just going to stay a div it's not going to do much in here we're gonna create another div with a class name of relative and isolate inside of this relative isolated let's create one more div and this is going to be kind of the decoration element of the page you're gonna see what that looks like in a second it's going to be a beautiful gradient in the background of the page very subtle but a very nice detail that I really enjoy I'm just gonna get a class name of pointer events none because we don't want to block the mouse from being able to do anything um because this is purely decorational right it doesn't have any other purpose it's going to be absolute and inset X of 0 a minus top minus 40 to give it a bit of um spacing from the top of her page we want a minus Z minus 10 right here after that we want a transform Dash GPU and I think we're almost done we want an overflow let me go into full screen so you can see all the classes at one time we want an overflow Dash hidden a blur Dex Dash 3XL and on small devices and up a minus top of minus 80 to give it a bit more space on um you know larger devices awesome inside of this div let's open that up we're going to create one more div and by the way a really good practice that I want you to encourage to follow is whenever you have purely decorational elements like this div will be for example we can add something called an area Dash hidden and that is going to be a string of true that is for screen readers because this is purely decorational people with for example visual disabilities won't care about it therefore we can hide it on their devices making their navigation through our website much easier so if you have purely decorational elements use the area hidden off true to just make some people's lives easier inside of here we're going to use a class name for this diff we just created inside of here that's going to be relative it's going to be left and then a custom value we can put this in the angled brackets we can just calculate a custom value by using the calculate and then in parentheses we can calculate a 50 percent minus 11 REM okay then we want an aspect ratio on this div which is going to be a one one five five also in angled brackets by the way is a custom value because this is not a default Tailwind CSS thing I want a 1155 slash six seven eight and that's just essentially bottom line gonna make it look good you're going to see that here in a second the gradient and a width off and here goes 36 points one two five Ram and this is the only element that's going to have kind of like weird styling again purely decorational oops I didn't want to go into full screen mode after setting the width we're just going to pass it a minus translate minus x minus one and a half give it a rotation rotate off and then here goes 30 deg for degrees a custom degree value a background gradient two and then top right TR that's how we can apply this entailment it's going to apply some CSS under the hood for us and this gradient we want to go from a custom value of hashtag a hex value ff80 B5 just like this so Dash also a custom value hashtag nine zero eight nine f c then a closing angled bracket and we're almost done we want a lower pass right now this would be way too much with an opacity of let's say let's say 30 would look good on small devices and up we want a left value left Dash a custom value in angle brackets once again and calculate this using the 50 minus 30 REM and then a closing angle bracket also a closing parenthesis for the calc and last class name I promise is on small devices now we want a certain width and that is going to be custom with W Dash 72.1875 Ram kind of weird styling not gonna lie these are kind of very specific weird values but that's because this is purely for decoration won't have any effect on our layout um that's why this is totally fine right now let's see what happens once we save this you can see the gradient is here it's very soft let me increase the opacity so you can see it let's say opacity one you can see it's here it's on our page it looks really nice but also to be real with you just looks like one giant blob and that's not very pretty so what I propose we do is add a custom path to this div and we can do that by using the style and by the way we can even turn this into a self-closing div so instead of um having a closing diff down there we can just make the self closing so we have one div two div three div and then three closing divs that's the structure okay and to give this div a path we can do that via the style property this is going to take an object with a clip path and this clip path and sort of typing it out there's going to be something called a paste list for this video which I'm preparing while I'm making this video so as we do this there are certain things that you just want to paste in like this for example um you don't want to type all these percentages out so I'm going to make a separate list you can just go in there as you follow along with the video and always at the correct points I'm going to mention when you can paste some values this is totally fine we can just save ourselves the hassle of typing out this clip path which we probably don't want to do and just like that there's a certain path to this gradient as you can see now that looks much better and if we turn down the opacity of course this is too much and we're going to see that it's looking really really good so let's change this back to opacity 30 and it's going to be opacity 0.3 and that's a very subtle nice looking gradient in the background which is a very nice tensional detail that your users are probably gonna appreciate beautiful below this div the self closing diff and then one more div down let's hit enter enter so we have two closing divs to go and inside of here let's create a new div it's not going to get any class name it's purely as a you know wrapper that's what we're using it for and another div in here with a class name of MX Auto a maximum width of 6xl a padding X of 6 and we also want on large devices and up a padding X of 8. just give it a bit more space you know let us breathe on large devices and one more div in here this is gonna encapsulate the image here in a second this is going to get a classroom of margin top 16. we want a flow Dash root on here and on small device and up you want image a a margin top of 24 and by the way this is always um this is um 6 REM which equals 96 pixels so about 100 let's open this up and create a wrapper div around or image the image is going to go right in here and the wrapper div is going to get some class names like minus Mt margin on top of two minus or no that's going to be minus M of 2 a negative margin of 2 a rounded Dash XL a background gray of 900 but slash 5 with a five percent transparency very very light a padding of two a ring of one want a ring Dash inset and by the way if you don't know any of these classes or any one of these classes always just feel free to hover over these to see what CSS it applies under the hood that's how I really learned CSS using Tailwind that's one of the best tips I can give we want a ring gray 900 slash 10 very light transparency again on large devices now we want a minus M minus margin minus four and negative 4 margin on large devices now we want a rounded Dash 2XL and lastly on large devices now we want a padding of four so the padding is always equal to the negative margin that we give it great and as I promised in here goes the actual image so the image I'm gonna provide to you that looks good out of the box of course you could use any other image that you want but let's just don't worry about that and make it look good out of the box for now we're going to import this image from next slash image this is going to be self closing and it expects something called a source and this source is going to be a string of Slash dashboard Dash preview dot jpeg what this image also expects is something like the width this image is going to be 1364 pixels wide and also the height which is going to be 866 and let me show you how I found this out and it also in the enduring extra s versions requires an ALT tag let's say product preview just for accessibility purposes let me show you how this looks like so right now um next.js always draws all images using the Slash and then whatever comes after that from the public folder so this would be slash m dashboard preview so it will go into the public folder and search for a file called dashboard preview there is none here we can get rid of the default or cell and Nexus stuff by the way and let me show you how you can get this image to make it look good in your project so I mentioned the GitHub repository earlier you can just go in here this is the again I'm going to make a new prettier GitHub repository probably before this but you can find the file in the public folder and you can find all three files that we're going to reuse across the entire project but right now we want the dashboard Dash preview.jpg so go ahead download this file or download all three already put them into your public folder right here so you have them in there I'm just gonna drag them over from my open project right here on the left hand side it's going to be dashboard Dash preview let me drag this in you get these from the GitHub repository put them in here that's the image and by the way if you're wondering how to find out the width and height of the image it says it in vs code right here at the bottom right so you can just take a look at these numbers and these are I've already done this for you so you don't need to do this right now but just for the future and that I'd be interesting to you and this is our landing page so we want to make the image look good at all costs so we're going to give it a quality of a hundred I think by default this is 75 for next year s let's save this refresh or page and the image will um show up right there but to be honest it could look a bit better like the edges are a bit rough it looks kind of Unfinished so let's give it some class names to make it look better the image is going to get a class name of rounded Dash medium to make the edges look less rough a background awful white a padding of two on small devices and up we want a padding of eight on medium devices now we want a padding of 20 so a lot more a shadow of 2XL a ring of one and we also want to give this ring a color which is going to be ring gray 900 slash 10. so a very light transparency let's save that take a look at what that did to our image and whoa okay there's a noticeable difference the edges look much better it looks much cleaner now great that's the image all right let's move right on and below the image we're gonna have one closing div two closing div three closing div four closing div and right below that we're going to open up four closing divs right here a space space this oops this is where the next section is going to go so with two closing divs left four above it this is where we're going to continue to give our landing page that extra kick you know that very subtle detail we're gonna copy and paste or um decorator from above we can just reuse it there's not gonna be a lot of changes and just um paste it right in here there's going to be a few very subtle changes so for example instead of 11 Ram right here we're going to change this to 13 Ram the aspect ratio is going to stay the same scroll to the very end of here and then change the 30 Ram in the second to last class name this changes to something like a 36 give it a bit more and that's going to be about it awesome let's save that go back into our project and do you see this this looks really really nice with those gradients very subtly peeking out behind the image that gives the image the extra kick and if I was a visitor to your website I would find this really really beautiful and that's how you do it that's the magic sauce right here that you can reuse across all your projects you're totally free to do that awesome let's continue with no closing div or landing page and right here again for or information this is going to be the feature section this is going to Showcase what our app can do let's create a div with a class name of margin X Auto a margin bottom of 32 a margin top of 32 a maximum width of 5xl on small devices and up we want a margin top of 50 oops 56 there we go so lots of space lots of white space were able to work with here on the landing page with another div with a class name of margin bottom 12 padding X of 6 and also on large devices up a padding X of 8. and here goes one last div with a class name of margin X Auto we want to give this a maximum width of 2XL on small devices and up we want a text Dash Center to have the text in the center of the div with an H2 first element that's going to say start chatting in minutes oops in minutes let's save that see our changes right here okay it just appeared down here but doesn't look too good yet let's give it a class name of margin top two a font there should bold a text Dash 4XL a text Gray 900. and lastly on small devices and up it takes 5 XL to make it a tad larger so it looks a tad better let's format this and right below this H2 we're going to have a P tag this P tag will get a class name of margin top four a text of large and a text Gray of 600 and it's going to say chatting to your PDF files has never been easier than with quill I mean you can put in whatever you want here this is just some basic stuff that I made up and if you're building this for your own sense obviously feel free to put your text in there awesome and now we can actually get started in filling this section right here we're going to do that below the piece hack with one closing div two closing div and one closing div below to go and this is where we're going to open up the steps so again this is just for or orientation I always do this in my projects I think it's a really good idea so visually you can just really easily tell where what is as a developer and here we're going to create an ordered list this ordered list is going to get a class name of margin Y8 a space white space them out vertically of four a padding top of eight on medium device and up we want a flex and medium devices and up we want a SpaceX of 12 and on medium devices we want a space y of um zero this ordered list is going to contain An Li element and this Li element this list element is going to get a class name of medium flex one I think from now on I'm just going to say medium and large instead of medium device and up that's kind of a bit much anyways inside of this Li element comes a div with a class name of flex Flex Dash call a space y of two a border left or four a border zinc of 300 to make it pop just a bit you're going to see that here in a second a padding y of two a padding left of four and then also we want on medium devices a border left zero on medium devices want a border top two on medium devices a padding bottom 0 on medium devices a padding left of zero and lastly on medium device is a padding top of four don't worry this is the only time we're going to write this class name from here on out we're just going to copy paste it to make our life much easier and here goes a span element and let's call it step one let's just save that see what happens it shows up right here with a beautiful very subtle border here on the left hand side nice this step one is going to get a class name of text Dash small we want a font Dash medium and we also want a text below of 600 to make it pop from the rest awesome below that one more span element span and this is going to say sign up for an account that's the description or the title of the first step that we have that's going to be a text of XL and a font Dash semi bolt make it stand out from the you know kind of headline or whatever the step one is supposed to be and lastly the description for this is going to be another span with a class name of margin top two a text zinc of 700. instead of here that's right either starting out or um actually let's say either starting out with a free plan or choose or Pro Plan period let's save that and that looks good but you know what would be cool we want our users to have a very easy time upgrading to our paid plan right that's how we make money with assess it will cost us money we want to make money so how about we make this clickable take them right to the pricing page that we're going to create later to do that let's use the link element we get from nexjs link to the slash pricing slash pricing Page by passing the href let's give this a bit more space just for a second and inside of this link element let's format this we can say the Pro Plan that's going to be then underlined we can give it a class name of text blue and let's give it 600 or let's give it 700 differentiate it a bit from the step one so tax plus 700 in underline and an underlying Dash offset-2 awesome so we just made everything clickable okay and after that we're gonna go to the exiting spam then the exiting div then the exiting Li and of course we want multiple steps we're not just gonna have one so we can just press or emit balance outward hotkey from the very beginning of the video or Mark the entire Li element by hand and press shift alt and arrow down or copy and paste it down however you like if you ever heard copy and pasting code is wrong don't worry about it too much this use case is totally fine um let's call this step two and again I want to get back on that point copy pasting code is way less of an issue than you actually think this project is going to follow really good practices we're going to write very clean code but for a use case as trivial as this right here just in jsx without any functionality um making this reusable would just cost you unnecessary time and you're never gonna touch this probably anyways again so just copy and paste it save some time it's much easier um okay this is going to say upload your oops your PDF file and then we switch keyboards again just like this and then let's say we can get rid of the link in the text in the third span let's say we and then instead of using an apple straw like this this will actually throw a build error and because we're using this in HTML instead Let's Escape this using and a pause and then a colon so that's how we write proper um you know Apple straws in jsx without getting any build errors and but it's essentially going to show up as the same thing right so if we look at the page it's the same thing just in a safe way we will process your file and make it ready for you to chat with let's hit enter that's step two amazing and then let's copy and paste step three down as well it's gonna be step three step three is gonna be start asking questions and the text is going to be it and again the apostrop trick that I just showed you and apos colon to write it safely it's that simple try out quill today and then a hyphen it really takes less than a minute or any text that you prefer of course let's save that and that is step 3 done let's take a look at this in full view this looks arguably even better than in mobile view this looks absolutely beautiful it's very clear what you expect your users to do sign up upload start asking questions very straightforward process looks good on mobile looks arguably even better on desktop and in general very very nice awesome and what that also means we're almost done with our landing page last thing that's missing is a very nice image at the bottom to really convince our users to use our software and let's create that right below the ordered list so with one more closing diff to go between that and the ordered list let's create or last or one of the last divs of this landing page it's gonna get a class name of margin X also a maximum width and you know what let's not create this by hand but instead because the image is going to be very similar similar to the one on top here we can just copy and paste it once more let's go ahead and grab the code for this image it's going to be the same code let's copy this from the div that does nothing we're not going to copy that with only these three divs right here around the image and go to the very bottom of the page and paste them where we just were below the end of the ordered list and this image this time is going to have the source of not dashboard preview but instead it's going to be file Dash upload Dash preview.jpg and the alt tag is going to be let's say um uploading preview the dimensions are going to be different and I'm gonna to um give you a little challenge I showed you earlier how to find out the width and the height of the image so if you haven't yet go ahead now is your time to go into the um GitHub repository get the image the one we need right now is the file upload preview it's this one right here file upload Dash preview go ahead download it drag it into your project I'm just gonna drag mine over from the um other project that I have open right here paste in here and I want you to find out the dimensions of this image really quick I showed you how it's done earlier and try to enter them here and then I'm gonna do this in like five seconds okay so if you've done it the width and height again are in the right corner it's a very handy trick that I want you to remember it can be super useful in some occasions so this is um 1419 by 732 I just wanted to see if you remember that so 1419 by 732 there we go the quality is fine to be 100 um of course we want it to look good and all the rest is going to stay the same so if we save that why doesn't this work Aha and it does work I just had to reload the page beautiful so now there's a little preview of the uploading process as exactly as it's going to look later really really beautiful component that we're going to build there and for the uploading and that is our landing page if that is not a beautiful landing page I don't know what is and there's one step I didn't tell you yet to make this look like a lot better let me show you that it's a really easy step so do you remember in the layout when we added the grainy property this doesn't do anything yet but it can make our app look incredibly nice by just adding one line of CSS so let's go into our globals.css and let's go to the bottom below all these right here and let's do the following let's give this grainy CSS class a meeting and this meeting is also going to be in the pasting list that I'm gonna give to you and it's not in here yet because I'm writing it as I'm making this video but I will put it in the pacing list go ahead in there and it's going to be this CSS tag right here the background image URL this is a certain base64 encoded you know string and if we paste it inside of the grainy but here what that will do for us is it's going to apply a certain grainy style to our background which I personally think looks really nice try it out see if you like it I've gotten a lot of positive feedback on my production apps that use this grainy style it makes the banner up here pop a bit more try it out see if you like it I and many people do and I hope you might just too okay and no matter how good the landing page is if users can't navigate around they can't do much in our app so the next logical step that we want to do and I think I also outline this um in the very beginning the little drawing I made yep right here the landing page and navigation the nav bar is super important and it's how users navigate around so let's get started building it let's build it as a separate component we can close all of all of these previous components and let's build it inside of source components and let's create a new file in here called navbar dot TSX let's say cons navbar it's going to be equal to an arrow function and also export default navbar from this file and this is of course going to return some jsx the jsx it is going to return it's going to be a nav element so let's say nav just like this and give the class name the class name is going to be sticky so it's always at the top of her page with a height of 14. this is important because we're going to use this height later we're going to see that in a bit later it's going to take a while in the dashboard when we are going to build that this height is actually going to be pretty important so let's give it a height of 14 and inset X of 0 a top of zero so it's always at the top and a z value of let's say 30 so it's above the regular content a width of full a border Dash border bottom a border gray 200 to give it a bit of color let's give this more space and then we want a background white of 75 transparency so slash 75 a backdrop Dash blur blur Dash LG and a transition Dash all inside of the nav bar I'm gonna make this a bit larger so you have all the class stamps in one go right here in case I lost you anywhere and then let's open up that nav tag right here inside of here we're going to use an element that you are familiar with and that is going to be the max with wrapper that is what's going to give our navbar the width the appropriate width to make it look good with the rest of the app and inside of here goes another div that's it no extra configuration needed that's why we made the max with wrapper we can just use it like that if that's not handy I don't know what is let's give this a flex a height of 14 or we could also give it a height of full I guess um but let's leave it at a height of 14. an item stash Center a justify Dash between a border Dash B and let's say border Dash zinc-200 zinc 200 to give this a bit of color awesome one thing we always want in the nav bar is the logo and I made it really easy I just used some text as logo and the logo by the way always users expect this leads to the home page so let's wrap it inside of a link we get from next.js and Link back to as a string slash slash is always the homepage that's where we want to link when a user clicks the logo and give this a class name this class name is going to be flex and a z of 40 a font of oops phone Dash semi bold there we go inside of this link component I've made it very easy let's just say quill period I just kind of liked how this looks if we drag this into a side by side we can see there is no nav bar yet and that is because we haven't added it anywhere and next.js there's a component that wraps the entire application and ideally we want to put it there because the nav bar should always be shown and the component that wraps the entire application we've been there before it's this layout right here these children are all the pages inside of our app like the landing page for example these are rendered as the children and then the body wraps the entire app you can see that even if we go into the network into the dev tools right here this body wraps the entire application no matter what comes in it so that's why this layout is so useful and inside of this layout we want to pass in the nav bar we have just created we can do that above the children by saying nav bar import that from the component we have just created and hit save and just like that we can see there's a quill logo at the top in the nav bar the navbar has a very nice subtle blur to it if we scroll over stuff when we click the logo we're always taken back to the home page that is something um that is really important the logo always goes to the home page all right fantastic job let's add a little to do for us right here let's say it to do and we want to add a add mobile nav bar we're gonna do that later once we've done the authentication we're going to see why it does make too much sense to do that right now um so let's just add it to do so we remember to do it later inside of here let's create another div right below that reminder with a class name of hidden items there's Center a space X of 4 and on small devices and up a flex property okay inside of here we want to use a fragment because we don't really want to use a div and extend the Dom but we do want to Nest certain elements right below one another that's what the fragment is good for and these are going to be a couple of links that we get from next.js way up here and essentially these are going to be the options where the user can navigate like the pricing page to login or sign in that's what these links are going to be good for and do you remember a link we can just style it as if it was a button even though it's technically not a button by using the button variants just make it look good out of the box and each link let's pass that inside of the configuration object right here it's gonna be a variant of ghost we get beautiful intellisense in here just from installing the UI library and a size and that will be small okay each link also needs an href that's why typescript is mad at us right now and this first link is going to go to the slash pricing page that's where we want the direct users and of course it's going to say pricing let's save that the navbar probably won't be shown because um I'm zoomed in too much just like this but if I zoom out again we're going to do the mobile nav bar later we can see pricing shows up right here you can select it via tab it looks good very very nice that's what we're here for making stuff look good and then comes the very interesting part and that is the login link now where does this login link come from well it comes from a package it doesn't really come from ourself but instead it's going to come directly from our authentication provider we don't even need to worry about it which is kind and so we can install pnpm install npm install yarn add doesn't matter which dependency manager you're using and this is going to be at kind Dash OSS slash and then kind of Dash nextges It's seamlessly integrated with nexgs we can just install that dependency and get our login button from there let's start up our Dev server again and now we can go ahead and import this login link now it won't automatically and let us import that that's fine let's say import login oops login link from ads kind OSS slash kind of next as the dependency we have just installed and for whatever reason that doesn't work oh and that's because this comes from the slash server that's where we can get the login link from and that's all we need to do this is essentially like a link with all the logic done for us it's going to get the same class name because we want to look at make it look the same way and inside of this login link we can say sign in and refresh the page there we go we can see that next to the pricing now the sign in popped up beautiful and now next to the login link or below the login link we can use the same thing but this time instead of the login link let's use the register link from the same import Kinder OSS or kind OSS kind of Nexus server there we go and instead of sign in this is going to be get started and this button is going to get a bit of different style we want it to look more pronounced that's why we can take out the variant goes we want it to be a proper normal button still a smaller button but now it just pops out using colors and we can even put a little icon in here for example the arrow right we already know from our trusted icon Library give it a class name of margin left 1.5 a height of 5 and a width of 5 as well and that's just going to make the button a bit nicer to look on because that's our main call to action so we want most of the attention the users have on this button right here so register for our application awesome and with these two in place or navbar is pretty much done at least for the moment however when we go to sign in or get started of course we want there to be a sign in page right here not just a 404 so right now they don't need anywhere and this is where the sponsor of this video comes in kind kind allows us to set up working secure authentication within like five minutes it's super straightforward and I would really appreciate if you click the first link in the description that leads to kind.com they have a UTM parameter attached to that link and that just lets them know that you came from this video and hopefully they will want to work with me in the future again so if you want to support the video and what I do here and click the link in the description instead of just going to the website that would really help me out thank you once we're on the website let's go into full screen we can sign in and I mostly use GitHub for this so let's click on sign in and I'm gonna choose continue with GitHub and hopefully that's already gonna log me in without me having to log into GitHub separately you could use your email if you wanted to you would get a secure code sent to your email the that you would just enter afterwards or you could also use Google if you wanted to and then we are in the kind dashboard awesome and from here setting up authentication for our app is really easy now I already have some projects right here you're not gonna have any so let's navigate right here into let's let me go back let me show you this step by step when we're in the dashboard we can go up here to business and then switch business and this is where we can create a new project so business just means project in this case let's name this project quill the domain is automatically going to be generated for us this doesn't really matter let's choose one to ten and then the region for me is going to be Europe Ireland just choose whatever is closest to you or where your data is stored okay and then that's it save that's going to create the project for us and let's click on quill.kind.com right here whatever you named your application to be and that's going to open up the settings for this particular project it's going to ask us whether we want to start a project from scratch or use kind with my existing code base and let's just say we're going to start a project from scratch let's hit next because our project you know let's be real here it's pretty new and we're gonna hit next JS right here under all for the framework okay that's it next after that and that's gonna walk us through the entire setup process if you wanted social providers you could activate them right here I advise you to not do that right now you can always add them later if you'd like for now let's just stick with email and that's going to be it that's by far the easiest way to get set up with authentication and then let's hit next awesome now let's click connect right here connect your next JS code base let's hit connect right here and that should walk us through what to do exactly awesome so first off we can install a dependency this is just a little walkthrough HTTP localhost 3000 is totally fine this is where we are hosting our local development app the dependency we've already installed remember for the sign in and login link we don't need to do that and then it's going to ask us to insert a bunch of environment variables this is totally fine let's copy them right here by clicking the copy icon going back into our project let's put this into full screen and then creating a new file at the very root of our project and naming it dot EnV inside of this dot EnV file you can paste all the environment variables that kind has just given us and also one really important tip this dot EnV is not um in by default in the git ignore so what that means is if you stage this stuff right using git using whatever Version Control you're using you would accidentally push this highly sensitive EnV file up to GitHub and that's never what you want so right now let's go into the git ignore and just anywhere for example right here let's also enter.env hit save and because we've never committed this file it's going to be ignored from now on very very important to protect our personal information right here and we are almost done let's save the EnV file all the values are good as they are we don't really need to change anything for now now let's create the API endpoint we're going to use for or authentication let's copy all this code and it tells us where we need to insert this and this under Source app API auth and then kind auth so let's copy this structure into our own app let's go into our source folder then the app folder and in here we're going to create a new folder called API and inside of this API folder let's create one more folder called auth and inside of here let's do exactly what kind told us right here goes in in angle brackets kind of so let's just copy this and put it in angled brackets right here as the folder name what this is in Nexus it's a dynamic route so whatever we enter as the URL for example slash API slash auth slash and then whatever is going to be sent in as a parameter into this route right here you're gonna see what that looks like in a second and then let's copy all this code and that goes inside of a route.js or dot TS as we are using so let's create a new file inside of the kind or folder called route.ts this needs to be named route.ts that is a requirement from next.js files in the API need to be named route.ts and then here we can just copy and paste in in the code that kind has given us this is Javascript code and because we're working in typescript we will get two errors right here that they have implicitly in any type of course that's not what we want in next.js the request is always of type next request so we can just give it that type and cast it in here and the params to be honest we don't really care about them we're never gonna touch this file ever again so let's just mark them as any and don't worry about that let's hit save on the route.ts and I think we should almost be done with Authentication awesome important or SDK relies on the file existing in this location that's totally fine because we created it right there and then we can add sign up and sign in buttons we did just that as well and that should pretty much be it so let's validate if this is actually working let's start up the dev server awesome that's already started and let's move this into a side by side to see if or authentication is working to check let's set the sign in button right here and we should be navigated to a hosted sign in page so everything else is going to be taken care of for us and we are awesome you can see our name at the very top here welcome back sign in to continue and because we've never used this application before we can also hit create an account I'm going to enter my name right here Josh and then as the last name try it coding as the email I'm going to use my hello at georgeting.com and then hit register I've already went ahead and opened up my email here on the other monitor let's enter the code we got via email and hit continue and that should ideally finish the sign in sign up flow and it did very very nice and we got redirected to a URL we have specified right here the kind post login redirect URL this is where we are forwarded once we log in and this is the dashboard which obviously doesn't exist yet so we get a 404 error but this is the next page we're going to create and just as easy as that we implemented a secure working login sign up flow if that's not amazing I don't know what is and I'm really happy to have kind as the sponsor on this video again it would mean a lot if you use the link that's all I ask for you to follow along with this video that would be really cool of course you don't have to but I'd really appreciate it okay and right now let's plow ahead with the dashboard we can close or actually how about yeah no let's do the dashboard okay let's close all of all these tabs and then let's create a new a brand new page and that is going to be the dashboard okay and here let's minimize all files so we have a good structure to work with going to the app folder this is where our dashboard is going to live and let's create a new folder inside of the app folder and this folder is going to be called dashboard in next year s13 this is the way we create new pages so instead of declaring a file called dashboard.tsx like we would in nexjs12 in nexo s13 this is a folder we need to create and inside of this folder goes a file called page.dsx just like with your route.tsx this name is really important and that's because it is a name that is enforced by next.js just like with the route it needs to be named page.dsx if it was anything else like my file.dsx or whatever then in that case Nexus would not recognize this as the actual content for the page under the dashboard route okay inside of this page.tsx file that we have just created let's say const page is going to be equal to nothing else than a regular Arrow function that you can export default at the bottom beautiful inside of this page now that we've done the log and flow we can do something really cool I want to show you that is get the current login session of the user so we can say const and empty object because we're going to worry about destructuring later it's going to be equal to yet kind server session so not only can we get the current user that's logged in on the client using a hook you can also get them on the server because in Nexus 13 this is by default a server component now what does that mean a server component let's take a very quick look at that so imagine if we just returned something like a div from this component right and let me log out something like hello from the server let's save the Page by the way you don't need to follow along with this console log I just want to show you what happens right so when we load this page so that's the the dashboard page in regular react what we would expect is this console log to be shown in the browser because this is by default a client-side um page in plain react but the log is not here it's not in the console that is because this complete component this entire page is prepared on the server and only the HTML that is generated on the server is then sent back to the client that is what you see or don't see on the page because we have an empty div let's say hello and only the HTML is then sent back to the client so the actual console log is going to be on the server and if we wanted to opt into client-side behavior in Nexus 13 we would use a use client directive at the very top of the page and that would actually turn this into a client component now this is still server side rendered so the HTML can be streamed in but we will see the log on the client in that case and the only reason I'm explaining this is so you can understand why we're doing what we're going to be doing let's remove the use client because we want this to be a server component and now let's get the current user from kind because we are logged in right remember we went through the login process and but currently we don't represent any of that on the page so let's say cons and the object is going to be equal to get kind server session and from here by pressing Ctrl and spacebar we can see all the stuff we can destructure from this function and we want the get user what that means is now we can say cons user is equal to get user and invoke that and just like that we can get access to all the user properties that there are for example we could log out the user.email inside of the div and because we are logged in let's reload the page and what did I do wrong why doesn't this work oh okay never mind I just had to restart the development server and that was already it so there wasn't really a bug just restart the server and we're golden okay so that means we can now get all the properties from the user so in like five minutes we set up a working secure authentication that is really really cool and with this authentication place what that means now is we can make sure that this user that is created for us also exists in our database which is super important because this only means that user is logged in but later when you consider that we're gonna upload files right then we want to save that file for a certain user that means if you upload a file we want to save it in the database and we can't just do that with the authentication State we get from kind what that means is we need to sync our user to a database the way we can achieve that well there are two options let me show you first off let's do a check if there is no user or if the user has no ID so user.id then we're going to call something called redirect we get this not from this weird import right here that vs code is suggesting to us but instead let's import the redirect from next slash navigation so import redirect from next slash navigation okay and where do we want to redirect the user if they are not logged in well let's redirect them to something called the slash auth Dash callback now this auth callback doesn't exist yet we're going to create it together here in a second and let's also pass a query parameter we can do that by using a question mark and then pass the origin is going to be equal to dashboard that way we can navigate user right back to where they were from this auth callback page and which is really good for the user experience and then we want to check if the user is already synced to our database because here's the idea right let me show you that okay so first time user this is what happens when a user logs into app for the or for the you know the first time ever the user logs into application let's say the user is right here and then we want to do a check because by default they're not going to be in our database usually this is done through something called webhooks but if we go into kind and then let's go into the is it under details no it's not under details let's go to home settings and then right here the event hooks this is usually how you sync a user to a database through webhooks so when someone logs in then kind is going to make a request to one of all API routes let me show you how this works the user logs in they go oh that's a bit too big the user logs in through kind right that's even bigger what the hell why is this so large anyways it doesn't matter the user logs in and they get the data they need from kind like the Json web token they need to log in right so they're logged into our application now logged in and attached it's like a Json web token as the cookie we can verify that if we look into our Network tab or under the application right here we can see the cookies and then we can see the kind token right here it's like a really long token this is what the user gets back once they log in and this is the login request okay and then when the user is logged in kind it's going to send a request to or app and this looks absolutely horrible I apologize um anyways then kind sends a request to our app that we can use to sync the user to the database so kind tells our app hey a user logged in for the first time that is basically what kind tells us let's make this a bit smaller via a webhook so one of our API rods is called by kind and then we can go ahead and sync that user to our database so we can see is the user already in there and if not we're gonna create it and then send back the response from database send back the response to kind and whatnot this is the usual flow it might seem a bit complicated right now in reality it's pretty straightforward what we're gonna do is a bit different let's get rid of all this bit confusing stuff let's you know underline the headline so it's a bit more clear so when the user logs in we're always going to redirect them to the dashboard and again this is only the time when the user first logs in we're going to redirect to the dashboard and let's say redirect right up here okay so they get sent to the dashboard once they are on the dashboard that is this page we are just creating then we're gonna check is the user already in the database so we're gonna go ahead fetch some information from the DB from the database let's make this a bit low larger there we go and is user there so we're going to check is the user already present in our database and now we're going to do a check depending on whether they are or whether they're not the database is going to return the result either yes or no and if the user is already in the database then everything is fine and the user can continue to you know chat to their PDF or use or service however they want but if the user is not already in the database if the answer is no in that case they can't just chat to the PDF let's say yes chat to PDF and that is in regards to this answer right here if not right if the user is let's say no in that case we want to sync user to our database because they're not in there yet and to make this a bit more clear let's wrap all the parts in these rectangles so the user dashboard the database and let's make this colorful so it's a bit easier to tell what's going on so yes let's make this green everything is good the user can continue to use our service however they want and then this scenario no we want to sync the user to the database that is what we're about to do right now so if the user is logged in for the first time then this will be no and if they're logged in for any other time like the second third whatever time then they are going to be synced to the database already so only for the first time they log in this red arrow is going to be true and we want to sync the user to the database and we're going to do that on a page that is called auth callback and what this entire process is for is we're dealing with something that is called eventual consistency known in software development which means the user is not directly editor database but often through something called a webhook or this process right here so if they are not synced to our database and what happened to the arrow then we're gonna Jesus what is going on with this Arrow then we're going to send them to a page called auth callback and if they are then you know nothing happens it doesn't really matter from the auth Callback this is where we're going to sync the user to our database so let's connect an arrow to itself right here just like this sync user to database this is where it's going to happen inside of the old callback and then the user can safely be sent back you know right here to the dashboard to go into the Green Arrow right here and this entire process exists because we need to have a user in the database to assign files and chat messages and so on to them this is in all apps right this is not just for this app this is a very common thing and all that logic happens inside of the auth Callback so just as an example let's walk through a user process right now together so this is even more clear okay the first time a user sees our application right imagine you see this page for the first time and try signing in um or registering that's what that's what you will do on the first time you register to our application so you are right here and now you register and you're sent to the dashboard right here you are logging in for the first time so if we ask the database is the user there the database is going to say no let's say no right here that means we are going to get sent to this path right here we're not in the database yet because we're a user for the first time that means we're going to get sent to a page called auth callback and or app is going to sync this account this first time user account tool or database in the background and then send us back to the dashboard where we can just continue to use the app as normal and when we get back for the second third fourth time it doesn't really matter we are the user we log in you know the next day for example we want to use the app again we're back on the page click the sign in this time then we're gonna get navigated as always to the dashboard this always happens but the check is the user there from the database it's going to return Yes because remember we've already been there we've already been synced so we can just continue to use the app as normal and won't even notice this is a really nice approach to sync the database to the application state of the login State we get from kind and that's precisely what we're about to do with the auth Callback page we have created right here awesome now to make this work to make the auth Callback page work obviously we need to create it and we can do that by creating a folder inside of the app folder called auth Dash callback create that folder instead of here create a page.tsx once again this is a pattern that you're going to see a lot in the entire application creating a folder for the page name that's going to show up in the URL and then creating a page for the actual content on that URL inside of the auth Callback file let's create the page for the content so cons page is going to be equal to an arrow function and we also want to export default page at the very bottom to make this component usable and the only purpose this page has is to sync the logged in user and make sure they are also in the database okay in this page we can first say Collins router is going to be equal to use router a hook we get from very important next slash navigation not next slash router that only works in xjs12 we want to get this from next slash navigation and invoke that the reason we do this is because remember when we go back to the dashboard we are passing the origin as a query parameter so we can easily send the user back to where they were and not disrupt their flow throughout the application to get access to this origin dashboard we can use the router and let's say cons search params is going to be equal to use search params like this that's how we get access to that value and the origin that we're passing const origin is going to be equal to the search params dot get and we can simply get whatever we call this so in our case that's going to be origin that's the value we want to get and then the equals the dashboard or whatever it's equal to will be saved as the origin const right here awesome and on this page one of the most fundamental setup steps for the entire application is going to take place and one of the most exciting and most useful tools we're going to use in this application and we're also going to use on this page and that is called trpc if you're not familiar with trpc let me give you a very quick rundown of what this does right so in regular nexjs without trpc we are doing the following when we write API rods let's separate them when we call and let's sub separate this with a dotted line just like so into front and back end so front end on the left hand side and then back end on the right hand side right here and that's regular next year S without trpc the tool we're going to use in this application when we make a request from the for from the front end we're going to make a fetch request to get some data from our API from the back end that's going to send a request over to our back end or backend it's going to process the request with whatever business logic we want and then send back a response to our front end and this response is basically going to be saved as the fetch result if we await that operation right so technically it will look something like this right here the main problem is if we're working in typescript the fetch result will be any so let me show you what this means let's say const API response is going to be equal to a weight Fetch and then whatever or API Rod is like slash API slash whatever right it doesn't really matter whether we have an actual API endpoint in there right now or not and also it doesn't really matter if the await syntax is correct the point is the API response is not typesafe so if we say cons data is equal to API response dot Json or that's what we need to await there we go then the data is going to be any in typescript that's really bad we don't get any intellisense on what this type is so we don't know what our backend return to us as the data and we need to check it and it's really unmaintainable and plain not just fun to write and trpc solves this problem if we make a request to an API endpoint the type will be exactly what we returned from that endpoint so for example let's just assume right here this is the slash API slash what oops what ever code right here in the comments okay let's just assume this is the API if we send back if we return an object from this API and this needs to be a longer comment so this works if we return an object with the name of let's say John all right then trpc knows what is going to be returned from this API and the data instead of any is automatically casted as that type like for example name String right so we get automatic type safety through front and back and something we would never get within regular neck stress that is why this tool is so powerful that was a bit abstract I realized that but it's going to be very very clear once we set up trpc now that's the worst thing right trpc is an amazing tool we get full stack type safety so if we copy it over this entire process it would look something like this if we make a request to the back end then you type so for example the name String object that we return from the back end let's just make it blue to indicate the type is then automatically let's make this a bit wider a bit smaller there we go it's then automatically going to be sent back and we know what type it will be on the front end again the worst thing about trpc is the setup it is actually a bit tedious it takes like five minutes but once we set it up you're gonna realize why this tool is so incredibly powerful and I'm always a big fan of just learning by doing so let's say in Google trpc app router setup and that's going to link us to a page by trpc um right here set up with next JS I'm also going to link this page in the description so you can just click that and land on this page directly don't need to search for it yourself and there are some dependencies we need to install to make this work which are quite a bunch but it's really really worth it you're going to see that let's copy all these dependencies I'm going to use pnpm to do this just choose your package manager let's navigate back into our app stop the dev server paste all the dependencies hit enter and let it install now we're gonna see what this dependencies gonna do um each one right we're going to take a look at that so for example trpz server trpc slash client prpc slash react query trpc slash next tan seg react query and salt a schema validation Library we're going to take a look at what each one does right here when we set this up with those dependencies installed let's create a new component we're going to do that inside of app let me close or minif and minimize all the open folders and inside of our components folder let's create a new component called providers.tsx this providers component that's initialized it cons providers it's going to be equal to an arrow function and also export that as default export default providers great and this providers needs to be a client component because we're going to make use of state and context really important that we mark it as a use client a client-side component in Nexus 13 syntax using this directive at the very top of the file above the Imports and all else and turn this into a client component this providers component is going to be a pretty simple component actually but it's going to make trpc work in our app first off let's say const and then the query client in angled brackets is going to be equal to use state we get that from react and this takes in a callback function which we're going to return a new query client from now this query client apparently the Auto Imports don't work remember what I told you you can always reload the window after you've just installed some dependencies and mostly that will fix the Auto Imports so new query client and okay apparently it still doesn't work so let's import the query client from and that's going to be from at 10 stack slash react Dash query second thing we want is the trpc client because trpc is a very thin wrapper around react query so we're going to do the pretty much same thing the cons trpc client is going to be equal to use state in this state will also take a callback function and inside of this callback function we need to call something that doesn't exist in our app yet and to fix that we need to create an instance of trpc where we are going to do that is instead of our app folder let's create a new folder and call it underscore prpc the underscore makes nexjs recognize that this is not an actual route you can navigate to because remember the auth Callback dashboard and so on these are actual Pages you can navigate to through the URL with the underscore this won't be possible because we don't want that to happen inside of this underscore trpc in our app folder let's create a new file called client dot TS inside of this file let's import a type and that is create trpc and that's not just a type that's a function create trpc react and we're going to import that from at trpc slash react Dash query that's where we get this from and we're going to export a oops export a const trpc from here which is going to be equal to the create trpc react we have just imported and we can just pass an empty object as the initialization um you know data into this function now one very very important thing is missing in this file and that is we need to pass the type of our main router in trpc as a generic into this function I know okay that sounds a bit abstract what do I mean by that the thing is in trpc if we go to the documentation the way we declare API routes is just like this let's just try it all together and you're gonna see what this is so instead of declaring a route in Nexus 13 like an API Rod this is just a bit different syntax but it's the same thing as an API Rod just with the benefit of type safety between front and back end so trpc says we should do this in a server slash index.ts honestly I'm not a huge fan of that I'm just gonna do that in a trpc folder I think it's a bit easier so let's go into our source folder and then let's create a new folder called trpc inside of here inside of this trpc folder we just created let's create an index.ts and this is where we're going to paste the code we just copied from the trpc quick start right this code right here I'm going to paste that in this file now that's going to throw an error because it tries to import a router from dot slash trpc which obviously doesn't exist if we look at the folder so let's create that then the same folder let's create a trpc.ts file and inside of this file we can also just copy some code from the trpc website that is this code up here from the server slash trpc.ts that is the code that goes inside of this file right here we can just save this for now we're gonna work on this file a bit later but right now it's not too important we just want to get things up um up and running really fast so let's see if the arrow is gone it is we can import the router and all the logic will now be or API logic will now be in this file now the client is still giving us an error because the thing is it expects a type that is actually what's going to give us type safety across our entire application and right now it just doesn't exist so the create trpc react expects the type from or router right this right here to know which API rods exists in our app and also which data they return without this type trpc won't know what type to send back from the back end to the front end so we get automatic type safety that is why we need to pass this type this app router type inside of this generic right here let's just import it from add slash trpc the arrow is going to be gone and that's it that's the file already done and that means we can save this save this and save all of them and go back into our providers because for the trpc client we need to call a trpc method and now we have a trpc client we can just import and call the dot create client in here inside of the create client we're going to pass an object with a links property now these links are in Array and the only element we're going to pass in here is something called an HTTP batch link we get from trpc slash client we can also pass that some configuration like the URL for example that's going to be slash API slash trpc right here and we can prefix this with HTTP colon localhost 3000 or whatever Port you're running this on slash API slash trpc and that's pretty much all we need to do now from this component we need to return something and that is going to be the trpc let's import that we've already done that right here at the top and this needs to be a component by the way and the trpc dot provider that actually lets us use trpc throughout our entire application in any component that we want to use it in that is what this provider is for this takes in a client which is nothing else than the trpc client from right up here you can paste that in there and a query client which is nothing else than this query client right here this is not going to be a self-closing component but instead we're going to render out the children that we're going to get into this component right here so essentially we're going to wrap our entire app with this and render out the app right here as the children to receive these children in this component let's say children right here and destructure them in the properties of this component and this is going to be a re I'm going to show you a really cool trick if you only receive children in a component you get a type from react and that is props with children you can just import that from react and all the typescript errors are going to be gone you could do this yourself obviously you could type this out as you know the children are going to be of type react node if you want to it would achieve the exact same thing but why do that if you just can do it with this syntax where the children are already included in this type it's just a really neat trick we can use and just like that we've got trpc set up and one little thing we also want to do is you remember when I said trpc it was just a thin wrapper around the react query right it's just a typesafe wrapper around react query to be able to use react query independently of trpc if we ever want to and spoiler alert we will want to then we can also pass the query client Provider from react query this is absolutely nothing to do with trpc this is just so we can use both amazing tools independent of each other if we ever need to and this takes the query client as the client and that's literally it we're done with the providers can save this and we almost never need to touch this file again now the last thing we want to do is take a look at this right it's an HTTP batch link to a certain URL called API trpc that all the requests from trpc are going to be Center but if we take a look at our API we only have an auth route this route doesn't exist yet there is no slash API slash trpc and that's not ideal and the trpc quick start I believe has something to tell us about that is it in here um it's not but if you type in the app router into trpc I'll be honest I'm not a huge fan of the search functionality then you're going to find the next JS adapter page and this is what's important for us so this allows us to use trpc with nexjs13 which it wasn't possible before but with this adapter we now can I'm going to link the page just in case you don't find this as well so you can copy and paste the code from right here this tells us where we need to create this route let's move this into a side by side and in or file system let's go into the where do we need to go under app API we have that folder under app API this is where we are let's create a new folder in here called trpc inside of this trpc again a angled brackets a dynamic catch-all folder that we can create in here and then lastly a route.ts inside of this angled brackets folder route.ts awesome and this is the API row that will handle all the logic which we never need to touch we just copy this code paste it in here now the only thing we need to fix is that import at the very top let's get rid of this and see if the Auto Imports will work they won't let's reload the window and then see if they do or else we can always import this manually if we need to let's see does it work now now it still doesn't work so let's import the app router and this will come from at slash trpc and it seems like we're not exporting anything from there let's go into full screen and let's see what happens so it should be exported from add slash trpc that means the index file in the trpc folder and are we not exporting it from here no we're not let's say export cons app router now right here it says export type router type signature not the router itself that's totally fine because the only place we're using this is also in the back end this is server code so it's totally fine to export and use right here and as for the create context let's just remove the three dots we won't need it it's a option you can use if you want additional stuff in your roads but we don't need that right now so let's just remove the dots close that and that should be the entire trpc setup done I know it was a bit tedious I'll be real with you that's my least favorite part by far about trpc but let's see if this finally works there were a lot of files involved in this and let's go into the trpc.ts and there's something called a public procedure now what this public procedure means it essentially allows us to create an API endpoint that anyone regardless of whether they are authenticated or not can call it's like a public API we can use so for example let's create a new API route called test we just want to see if trpc is working and this test is going to be a public procedure so anyone can call this we're going to import it from dot slash krpc and then we have two options either create a query or a mutation in this app we're going to build we're going to make use extensive use of both they're both very useful the queries are mainly for get requests so purely for getting data and the mutations are for like post patch delete requests for modifying data let's just have a query we can call to see if trpc is working and each query takes a callback function in which we can Define the logic that should what happened inside of this API endpoint because this right here is nothing else than a regular API endpoint now if you've ever used nexjs13 you know the way we return return data from an API Rod is through this syntax right here return a new response and this response can be anything that we want to return if it's Json we can use json.stringify and send back whatever we want to return with trpc the syntax is much easier we can literally just return a string let's say hello from this procedure and then if we did the setup correctly which I hope everything should work with type safety in mind so let's go to the auth Callback page where we wanted to implement this functionality in the first place let's go in here and just see if trpc is working so we can say const and then worry about the destructuring later is going to be equal to trpc that we can now import Dot and now the magic happens we can see our API endpoint that we have just created trpc knows about it we can call it from here.test Dot and then we have a bunch of options what we want is the use Query because we have defined this as a query right here if this was a mutation trpc would immediately know and throw an error because now we would have to use the use limitation so that is why the type safety is super nice and also let me show you what happens when we invoke this and restructure the data from here let's get rid of the example I made a second ago this data is typesafe we can see constata string or undefined whereas before in the non-trpc approach let's sell let's call it data too this was not typesafe at all it's any so that is why trpc is so much nicer we get full type safety the data is a string and if I change this API response to a number well this would be a number how cool is that and the reason this is undefined is because this is a client query so when the page loads then this is going to be fired and until the data gets back from the API the data is going to be undefined but we can handle that very easily by saying something like is loading for example that is the full beauty of trpc it's going to be super useful across the entire application and I'm so happy to show you this and how it works so do you remember the purpose of this auth callback it was to make sure that a user is synced to our database so we can get rid of the test right here and instead let's declare a API route for the auth Callback let's call it auth callback in this off callback is going to be a public procedure again this just means nothing else then everybody can call it regardless if they're logged in or not and then here this is going to be a query because we're just going to make a get request to our database to see if the user is in there or not in our diagram let me see where we did this that would be this part right here this is where we currently are we want to see if the user is there yes or no and if they're not we're going to create the user in the database the way we can check the user if it's there is first we need to find the user ID with kind that is really easy we can say Collins get user is equal to get kind server session and then the user cons user is going to be equal to invoking that the structured function from above the get user and now if the user is not here with an ID or the user has no email user.email in that case we're gonna throw a new trpc error that is a really nice utility that trpc gives us we can just pass it a code of something like any of the codes we can see right here and we're gonna go for the unauthorized so there we go and now we want to check if the user is in the database that is the sole purpose of this API Rod but you might have noticed well we don't have a database we can check yet so the the gist of this will be we check if there is user in the database if not we're going to create them and if there is then we're just going to return something like um return like a success of true for example and with the success value we then know on the front end that there is a user like this in our database and can send them to the dashboard where they belong so let's try that on the front end and using the auth Callback API wrote with full type safety we can use the use Query just like this inside of this use Query the first argument is going to be undefined because we don't expect any data any request data like a post request body for example we don't have any of that and then in something called an unsuccess callback which technically is deprecated but this is one of the only places where it actually makes sense to use it we can destructure the data we get back from the API response so this data we get access to in the on success callback either by just calling it data like so and we can see the success property or we could destructure the success right away and use it in or unsuccess callback so for example if we are successful if the user is sync into our database let's just comment that user is synced to a database in that case we can navigate them back to wherever they came from using the origin right and that's why we have the router at the very top we can call the router.push and we can push if there is an origin if that is passed in it doesn't have to be this could be null as well oops it could be either a string or it could be no the origin if we have an origin then that's what the question mark is for this is called a ternary operator then we're going to navigate the user to slash and then in a template string the origin so for example slash dashboard if they came from the dashboard or if there is no origin then we're always going to send them to the dashboard because nothing else was specified and right now is the time to actually create a database I've talked about it a lot is the user synced to a database or not and whatever but we don't even have a database so what we're going to use to interact with our database is going to be Prisma we can simply go into our CMD or command line and type npx Prisma in it that's gonna run a bunch of code for us and essentially generate a new folder called Prisma in our project with something called a schema dot Prisma and what it's also going to do if we go into our environment variables you can see a database URL variable has been added to our EnV file this is a very very sensitive information that you should never reveal to anyone else that is why we also put the EnV in the git ignore and because this essentially allows access to all all of our data this database URL we're going to get from our database provider in our case that's going to be Planet scale so let's navigate over to planetscale.com this is what I use personally for my projects I really enjoy it we're going to sign in using GitHub let's go over to GitHub there we go and then we can either start with an empty database which is what we're gonna do or import from in existing which we don't want to do let's give this a bit more space and click Start A or create a new database that's what we're gonna do and the database name is gonna be quill I'm gonna choose a region that is closest to me and that is going to be Frankfurt right here and we're going to choose the free plan right here oh we don't care about the rest and let's click create the database we're going to create one for a Prisma now that it really matters but it will just know what the password should look like that it generates and then click create password right here awesome the stuff up here is not too important what matters way more is that connection string we can see further down that's going to be the database URL right here we can simply copy and paste into our EnV file now one thing I'd like to mention is that planet scale has recently implemented a feature where you need a credit card to host a database on their platform if you don't have a credit card you can always use an alternative database one I would recommend in that case is something like neon the I think it's called neon dot Tech or something is it neon detect yeah it's a postgres database it's not going to be SQL what we're going to be using but with prism you write one syntax for pretty much all of them anyways so the underlying database doesn't really matter and as far as I know neon doesn't require a credit card to set up so you need to change this actually you don't need to change this if you use neon because we're going to be using MySQL we're going to select that it if you're using neon then you don't even need to change it it's going to be postgres and then you can follow along basically as normal as long as you have the database URL and connect your database to Prisma all the rest is going to be the exact same but I just really like Planet scale for my personal projects now one thing that is special about planet scale is that they don't support foreign key constraints and what that means is we have to use a certain relation mode and that is Prisma if you're in postgres chances are you're not going to have to worry about this but all relation mode needs to be set to Prisma right here so we can use Planet scale without any problems and then let's create the most important database model and that's going to be the user so if a user is you know logged into our app this is the database model we want to synchronize them to each user in our app gets an ID that is of type string and this is going to be an ad ID and add unique then each user will also want to save their email the email is going to be string and this needs to be unique as well now the reason we are not giving this ID a default but instead just a unique it's because this ID actually comes from our authentication provider in our case kind so we're going to use the same idea that is the entire purpose right we have the same ID matches kind user ID as in kind as in the database they're going to be the same thing and while we're at it we can already add some properties we're gonna need way later for example the stripe customer ID that is basically one of the last things we're going to do in this build but while we're already here we can already set this up this is going to be a string optional because not every user is going to be a customer this is only for customers this needs to be at unique and we can map this to a certain value and that is the name oops the name right in here and this is going to be the stripe underscore cost oops customer underscore ID then the strike type subscription idea it's also something stripe or payment processor later on will generate for us this is going to be an optional string as well with an add unique property and this is going to be mapped to a name of the stripe underscore subscription underscore ID we want a stripe price ID this is also as always going to be an optional string and mapped to the name of stripe underscore price underscore ID and I did a little type hold there and lastly last of those four is going to be the stripe current period and because we always want to know how long a user has left in their premium in their Pro Plan and so we can adjust their quota accordingly this is going to be of type date time this time and we're gonna map this at map to a name of stripe underscore current underscore period underscore and there we go and that's just some information for later we will need we can save the Prisma file and let's see if we set up our database correctly and to check it we can say in or command line npx Prisma DB push and what npx Prisma DB push does for us is we're going to push the schema we have written in our local file up into the database and then force it in the cloud so we can run that and let's see what happens running generate and this means we set up our database correctly the EnV file contains the database URL and that is pretty much all we need to do now to get access to these types locally currently we don't have that we also need to run npx Prisma generate and that's gonna generate the typescript types for us so we can use them in our application and we can just reload or window finally it's a habit I've developed with Prisma sometimes the generation is enough but sometimes typescript won't recognize them until we reload the window if you're wondering how I just did that again it's shift control and P I think by default in vs code to do the window refreshing awesome and what that now means is we've set up all the database and we can finish up our auth callback route now to check if a user's in the database we can say const DB user for database user is going to be equal to an operation we need to await and to be able to await something we can simply Mark the Callback function of the query this one right here as asynchronous that allows us to use the await syntax in here and now we need access to the database right now we have the schema and we've generated everything correctly but there's no DB or database option we can call yet and in order to do that let me comment this out for a second so we don't get any errors let's go into our source folder then create a new folder called DB right here inside of here let's create a new file called index.ts a very simple file we pretty much never need to touch again now inside of this file we have two options either write the Prisma code or self it's like 18 lines of code or what I literally always do I did it once and then I copy and paste this code across all my projects that use prismand I recommend you do the same because it doesn't really make sense to write this yourself so what we're gonna do is um go to the copy paste list that I'm going to provide to you and copy the Prisma instantiation part it's again like 18 lines of code it begins at import Prisma client and NZ export const DB I never write this myself either you can just copy and paste it inside of this file essentially what it does is it makes sure we have a Singleton a single cached instance of Prisma throughout our entire application it doesn't do more you don't need to understand it it really doesn't matter this file we're never gonna touch it again the only thing that's important is that we now have access to our database that we can use in our code so go ahead copy paste it in don't need to understand it too much and then we can continue in or auth callback and actually finish it up so now we can import or DB or database that we can just use we're going to say away db.user and you can probably guess what we're about to do we want to find a user in our database so we're going to say away db.user dot find first that's how we can find a user and where that is where we can add a condition the ID of the user is going to be the same as the user dot ID and this user refers to the logged in user and this user refers to the user in the database there are two separate things right and now we want to make sure that user that's logged in is also in the database and if they are not if there is no DB user that means this is the first time the user is using the application remember now we're at this red part no there are not in the database so we need to sync them here in a second what that means for us is now we need to create user in DB if we need to create the user and to do that we're going to call awaitdb.user.create and then here for the data that we want to create the user with it's going to be one the ID and the ID is going to be equal to the user.id because remember the ID in the database the left hand side matches the kind ID the authentication provider id same thing so that's what we're going to use to create the user and then as for the email we're going to use the user.email just like this and we can just use these values because earlier we made sure they exist using something called a guard Clause that will just throw an error if they don't exist and that's our entire Logic for the auth Callback done let's head back over to the auth Callback page we can get rid of all of the rest we don't need it for now and then let's finish up this page right here if there's an error on error something also deprecated but it does make a lot of sense use it in this case right here we get access to the error because the possibility of an error is definitely there after all we are throwing one um not always but sometimes in or back end right here so there could be an error and we need to handle it accordingly for this to be a you know good high quality application we can just throw arrows and not handle them that would be really really bad so if the error.data.code we can get access to the trpc error code is triple equal to and now let's see what it should be we can even open this up side by side to make it a bit easier we can now see if the code right here is unauthorized that's what we can check in our app unauthorized just like this and if it is unauthorized in that case we're gonna say a router dot push and we're going to push the slash sine Dash in oops sign in route right here so this means they are not authenticated therefore they should be authenticated let's force them to do so we are also going to add something called a retry property and we can move this back over to have more space in it and this retry is going to be true so if there is any error we're just going to send the request again until the route returns something successful and the retry delay we can pass in here is going to be 500 which is milliseconds so every half a second we are checking if the user is synchronized to our database to ensure everything works correctly awesome and we don't even need any data from this we can remove all of that because this query runs on page load we don't need to call it anywhere we don't need to invoke it this automatically will run once the page is loaded and displayed to the user what we do need to do is return some jsx from here for a good user experience so for example we're going to return a div with a class name of with full a margin top of 24 flex and justify Center for horizontal alignment inside of this div one more div with a class name of flex Flex Dash column and items Dash Center and a gap of two inside of here we're going to import an icon from Lucid Dev or Lucille react that is called loader 2. it's a beautiful loading spinner you're going to see what it looks like here in a second let's give it a class name of height 8 with 8 and animate Dash spin property that's just gonna make it spin and a text zinc of 800. below this loader let's add an H3 and this is going to say setting up oops up your account dot dot so the user knows exactly what's going on right now why they are waiting on this page usually this page is finished within like a second of loading but still users always want to know what's going on for the best possible user experience and this H3 is going to get a font the oops a font there's semi-bought class name and a text-xl and lastly a little paragraph right below the H3 saying you will be redirected automatically period so the user knows exactly what's going on let's try this out let's move this into a side by side just like this we can close out of a lot of this stuff just like so and let's go to the slash auth Dash callback route let's hit enter and is the dev server started no it's not should definitely start that up and then let's see what happens when we navigate to the auth Callback let's even go into full screen so we can see the network requests that trpc makes for us automatically under the hood until our account is synchronized to our database and of course we get an error this will not mark this as a client component no we didn't okay so this is a client-side library this will execute client side and also we're making use of hooks right like use search params and use router what that means is we need to declare this as a client component by saying use client at the very top of the file we can then reload the page and I mark this as a synchronous earlier to make an example that doesn't work in client components and so you probably didn't even copy that if you did remove the asynchronous before the page and then we should finally be up and running aha not quite we forgot to do one very little thing and that is the providers adding those to the layout we declare the provider you remember that was this file right here the providers where we had the trpc and the query client to make it you know usable from the client across our entire application but as this is a context provider we also need to wrap our app in it and we do that in the main layout.tsx at the top level of our app this layout right here we can just import the provider's component and wrap our body in this component that's all we need to do and the entire body is going to be passed as the children right here into the providers and rendered out wrapped in or context providers and now we should finally be good to go that means we can now navigate to the auth dash callback route and see what happens it said setting up your account and it was really fast probably faster than you could see and that's perfect users don't want to wait very long and you can see the successful response we got back from trpc indicating that the operation was successful and what that also means is now we synced our user to our database let's verify that let's open up the console open up a new tab and let's say npx Prisma Studio it's going to open up like a database you know Studio we can just copy the link go into our browser paste it in it's hosted on Port 5555 and we will be able to see one user that means everything was successful and this user has been synced to our database we have the idea that matches the kind ID we have the email of the user and then we have some stripe data which is obviously null because this user is not a paying customer at the moment beautiful really really good job now one thing we want to do is navigate into our dashboard page.tsx under Source app dashboard and the reason we're doing this is every time a user comes into app they are always redirected to the dashboard so if we notice in the dashboard that there is no user in the database for this logged in user then we're gonna redirect them to the auth Callback to make this synchronous you know to sync them to the database so inside of the dashboard page we can say const DB User it's going to be equal to a weight DB dot user dot find First and we're going to do the exact same thing where the ID in the database equals the user.id of the logged in user and to be able to make this a weight we need to mark this Pages asynchronous we can do that in next.js server components and if there is no DB user if this user is not synced to the database yet then we're going to send them over to the auth Callback to make that sync happen what you just saw right so we're going to call the redirect and essentially redirect them to the exact same thing if if they weren't authenticated and just like that we made sure the user is logged in we made sure the user is linked to our database and now we can actually show them the jsx for logged in authenticated users for the dashboard and we're going to do that inside of a separate component called dashboard which is going to be a client component where we can have something like State and so on and yeah this is a server component so we don't do it here let's turn this into full screen and create this new dashboard component under Source components let's create a new file called dashboard.tsx and let's say you know cards dashboard is going to be equal to an error function and Export default this dashboard component right here and for now just to see if everything works let's return a div saying hello world from this dashboard component and import it in our page.tsx which comes from add slash components slash dashboard to close all of this go into a side by side opening this up on the right hand side and we can see if the user is synced to the database if the user is authenticated then they can actually see the dashboard and users that visit your app for like the second third fourth time won't even be sent to the auth Callback so in here by this Clause we can make sure that only if the user does not exist in the database the first time they log in they're sent to the auth Callback and see the you know your account is being created message and if you are logged in and already using the app you will never see that message again and you know not be sent there every time you log into the app because that would be pretty annoying okay great and with all this logic and place right with this logic right here on the server component we can ensure that whenever the dashboard is rendered all the data is as we expected and what that means is now we can safely proceed to write the entire dashboard component and the main thing we want in the dashboard is going to be inside of a main tag just like this inside of here let's give this main tag a class name of mx-auto a maximum width of 7xl and on medium devices and up a padding of 10. inside of this main tag and let's give just a bit more space like this let's create a div and let's also think about what this should even entail right what do we want on the dashboard because it's going to be a really big part of our application after all so let's list some requirements for example requirements what should the dashboard be able to do feature wise right what's the purpose we want to list all files a user has right the dashboard should be the place where there's a list and it's we can even do a little visual representation say Visual and this is kind of how the dashboard should look like right we have something like my files and let's make a little wrapper around this so we can tell this is supposed to be a screen I really like doing this for my personal projects just to get an idea of what the final product um or the final component should look like so let's say my files up here there should be a button up here that says like uh upload PDF or something along those lines and that button should be very easily recognizable oops up loads PDF there we go that button should be really easy visible to users so let's give it a bit more stroke width and let's also make it blue for example right here there's going to be that button and then we also want to list all the files a user has so and this is going to be a PDF file this is going to be the next PDF file let's make them just a tad smaller and that is about the dashboard visually right that's how it should look like and for each PDF we also want um as a requirement of course to delete it so let's say delete files and what I think is most useful for that is adding like a little icon let's say that's like a trash can icon to each of these PDFs so for each file you have uploaded to our app you can also have the option to delete it from the dashboard right away I think that's a really good idea very user friendly to have it right there in the dashboard and also of course we should be able to create new files right that's a requirement as well create files or um rather than create let's use upload because this is where we also upload new PDF files so that's the requirements and that's pretty much all we need for the dashboard and that's kind of how it should look like and I think if you build your own projects doing a requirement and visual section like this is a really good idea so you get a good grasp of what you're actually trying to build instead of just getting to build and then figuring out stuff on the way this generally saves me a lot of time so I think this is a good idea and with that in mind let's get started actually building the dashboard right now this is our dashboard this is not what it should look like right we don't just want a hello world and so let's get started in this div right here this div is going to get a class name of margin top eight Flex Flex Dash call items Dash start justify Dash between and let's give this just a tad more space there we go a gap of four a border Dash B for bottom a border gray 200 a padding bottom of five on small devices it's gonna be a flex Dash row so we're switching up the flex column we don't want that on natural devices on small devices we want an items Dash Center and on small devices a gap Dash zero there we go then we can open up this diff with a really long class name we just gave and insert an H1 tag in here this H1 tag is going to represent let me go over here this my files right here that's what we're currently building so let's say my files in this H1 tag and this is going to get a class name of margin bottom three a font there's a bold a text-5xl and a text Gray of 900. great and save that let's see how this looks in our dashboard there we go beautiful we can see my files just like that very very nice let's give it a bit less space again and then go right below the H1 this is where the button should live right the upload button so my files and the upload PDF button are on the same level that means this right here is where the upload button will go and that's where we're gonna create it however we're not just gonna put a regular you know HTML button in here that's not what we're gonna do instead we want an entire model to pop up the up and the the upload model when we click that button right so what makes sense is to declare this as a separate component instead let's call this upload button of course this component doesn't exist yet we're going to create it in a second and this can easily be a self-closing jsx tag we are not going to pass any children in here now again this upload button doesn't exist so let's create it under all files let's go into components create a new file called upload button dot TSX and that is where all the logic not only for the button itself but also for the model that will open up when you click that button to upload your files will live and as you might have noticed in the preview at the very start of this video this is one of the most beautiful components in our entire app so let's declare this component cons upload button there's going to be nothing else than irregular Arrow function let's also export this as the default export default upload button oops upload and the B needs to be capitalized there we go beautiful this needs to be a client component because we want some interactivity like when we click the button a model opens we cannot do that on the server therefore we're going to do it on the client instead and here we're gonna declare some State and this state is going to be called is open and also set is open this is just a react convention to have the value and then set and whatever the value was like is open set is open there we go this is going to be equal to a use State we get from react import that right away and this state is going to be a false value by default there we go what I like to do I think it looks a bit better is to also explicitly type it we don't need to do that typescript already knows this is a Boolean from the value but I just like to do it anyways you don't have to do it if you don't like the syntax okay from this component we're going to return something and that is going to be the model But Here Comes the trick We're not gonna do the model or self there's a really easy command we can execute in or CMD to install the modal component that is going to be MPX chat cm-ui at latest which is the UI Library we're using for this project and we're going to say add dialog and hit enter npx Shad C and dash UI at latest add dialog and just like that a beautiful accessible dialogue component was added into our app that we can use right away dialog and we can simply import it from UI dialog where this file is automatically put Now to turn this into a controlled component we can simply pass the open State and this is going to be is open and then on open change it probably also need to pass if we pass this first one we can simply pass a callback function that receives the visible attribute as the first um you know parameter into this function into the Callback function in here this is of type Boolean and then for that we want to execute an arrow function M right here inline we can do this inline because it's only like two lines of code and the code in here is going to be if not visible so if this evaluates to true and we want the opposite right here in the if statement then we're going to set is open to that V value right here and just like that we turn this dialog into a controlled component that we can open and close using this state right here by default this is not controlled and we only control it through something called the dialog trigger which we're gonna add right below and this dialog trigger receives one property and that is as child what this as child does is essentially by default this is a button component but we want the trigger to be our custom button and not a button that the dialog is inherently therefore we can pass the S child making this button instead not pre-wrapped in one more button because otherwise you know this would be a button and this would also be a button leading to invalid HTML because you can't have a button inside of another button instead of this button let's say upload PDF and that is the one that's actually going to show up on our page so let's save this let's head over into our dashboard and import this custom component we have just created and just like that we can see the upload PDF button right here and one thing I just noticed is that technically this should be blue and not this you know darker color so what I think we actually accidentally did is not use the correct theme let's go to ui.chatcn.com themes select the blue one and then hit copy code and let's copy all this code right here this is how we determine the color scheme of our entire application we can just copy this go with whatever color you want and then paste it into our globals.css and we've already done that but it seems to not be the correct values let's save that and hopefully that will have fixed it yes now they are the correct blue that we want for our app beautiful because this is now a controlled component we also need to add an on click event to this dialog trigger because by default it's uncontrolled and this will work but because we made it controlled right now this would do nothing if we click on the button so instead let's pass it a callback function and set is open to True whenever we click on the dialog trigger beautiful and then why is there no space here between the my files and upload PDF technically the button should be on the very right hand side of the screen oops didn't mean to do that so I probably misspelled something right here yes I did it's supposed to be justify between and just like that the button is in fact on the right beautiful and currently when we press this button the dialog would open but there is nothing in the dialog so we can't see it for that let's add the dialogue content inside of our upload button we also get that from UI dialog and not the Red X UI dialog we want it from our custom component and in here let's say example content just to see if it works let's reload the Page hit upload PDF and we can see a beautiful model opens up that we didn't need to do anything for that is accessible we can close it using the Escape key using the little closing thing that is on the top right here and it just works out of the box that is the beauty of the UI library that we're using under the hood it's accessible it looks good and I use it for all my projects at this point beautiful let's leave the upload button at this date for a second with the example content and right now finish up the dashboard on displaying all the files that the user has right because we've not done this part at the moment only this up here so let's finish up displaying all the files a user has and then implement the beautiful PDF uploading functionality so the way we're going to do that is below the closing div and above the closing main we're going to open up some new space and basically we need to determine all the files a user has to display them right here so let's do a quick comment on here display all user files however currently there is not even a file a user can have because usually we would store that in our database but right now we only have a user in our database and not a file someone could upload so that will be the logical next step we add a file model that we can add each time a user uploads a PDF and this model let's name it file right here in our schema.prisma is where we declare our database stuff also let's give it a bit more space there we go also it needs an ID this ID will always be of type string with an ads ID and we can give it a default condition resistant unique identifier just like this so that means Prisma will generate the ID automatically needs a name this is going to be a string a user and this is going to be a user just like this with an ad relation or actually I think Prisma will do this automatically let's let's leave this out for now because if we don't have to type this we won't each file will get an upload status this is going to be a certain enum so what that means is for each state a file upload could be in we need to declare a certain state for it let's do it above the file model go right here and say enum upload status and what status could there be for a file upload in my case or in our case right here for this app there are four stages there's pending we also want processing we want a failed State and we want a success state so basically we're depicting everything that can go right or wrong during a file upload that we want to save right here as the upload status so now we can use that as a custom model right here and give it a default value of pending and we get beautiful type safety right here as well okay each file will have a URL later we're going to need this URL to load the PDF into or app and this URL is going to be a string we want a key that's how we identify this certain file through or uploading provider through our file hosting service that we're going to use this is going to be of type string as well and we need a created L property which is a date time with at default and by default it's going to be right now and we also want an updated ad which is also going to be a date time and it's going to be at updated at something Prisma already knows we don't need to do it ourselves awesome the thing is right now a file has nothing to do with a user and we want to tell Prisma that each user can have multiple files the way we can do that is by going into the user model and then adding a file in uppercase or not uppercase but capitalized and this is going to be a file array so that means each user can have multiple files in Array of files and now just by formatting this page using prettier oops I didn't mean to do that Prisma is automatically going to add this relation right here for us which is really really convenient beautiful now we have files we have the file functionality we can save or Prisma schema and always when we do local changes in this file right here we need to push them into the database and generate our local types from them so npx Prisma DB push so we push these changes we just did in the file into our Cloud database and it says operation not permitted and that is always because we still have a running Dev server so let's go into our Dev server stop it for a hot second and retry this command this time it will work and then we also need to generate our Prisma types so npx Prisma generate after running npx Prisma DB push to generate our local typescript types right here so we can work with them and Prisma knows what a file is awesome after running that we can close out of that console for now let's restart the dev server by saying pmpm Dev npm run Dev or yarn Dev whatever you're using and that was basically all we need to do for now now we have files and now we can display all user files in this dashboard page right here and to do that we're going to use the full power of trpc so let's create a new API endpoint in trpc therefore let's go to the index.ts in trpc this is or you know main router where we can Define all the API endpoints we want we could also split this up in separate routers so there's less code in this router and but I think for now this is totally cool and let's create a new API endpoint called get user files and the purpose of this get user files is as the name States we pass in a user ID and we get back all the files that this user owns however this should not be a public procedure you remember what this means it means anyone can query this API endpoint that's not what we want we only want logged in users to be able to call this API therefore let's define something called an auth procedure someone only that is authenticated will be able to call this procedure and the way we do that is by defining a custom middleware inside of our trpc.ts file in the file Tree in case you're lost it's in the trpc folder and then the trpc file right here now let's give this middleware a name I always go for is off so is the user authenticated or not and this is going to be equal to middleware which is something we can also get from this T object we also get the relative from so we can just say cons middleware is going to be equal to T Dot middleware and that's it so the is auth is equal to middleware and this takes in a callback function and conveniently into this callback function we get something called ops passed from trpc and we also want to mark this as asynchronous great right now this is giving us a squiggly red underline because we're not returning anything from here which trpc expects us to but one step after another we're gonna do that in a second first off let's declare the business logic of our middleware and in here we want to make sure a user is authenticated and the way we can do that with kind or authentication provider is by using the get kind server session right here and destruction the get user function from it so now we can say cons user is equal to get user and invoke that now this user will contain either the user object or it will be undefined or null or any other falsy value so what we can do is insert a guard Clause if we have no user or if there is no user.id in that case we're gonna throw a new trpc error right here and the code we can pass in in a configuration object the code right here is going to be unauthorized the user is not able to call this API endpoint if they're not logged in and to get rid of this Arrow finally we're going to return the options dot next so the next action after this middleware which is obviously the API rod in our case right here this will be executed next or another middleware if we decided to chain them which We're not gonna do right here and this options dot next takes a context well this context allows us to do is pass any value from this middleware directly into or API row that uses this private procedure let me show you what this means and why this is so useful for example we can attach the user ID this is going to be the user.id and also we can just pass the user right away and there's a reason why we passed these separately so let me show you first what the context does we can get access to it in any API Rod that now uses um this middleware now we need a procedure from it so let's export a const private procedure it's going to be equal to and then we're going to call the regular T we have right here and then we can say dot procedure dot use and then here past the is of middleware that means when somebody calls this procedure then it makes sure to run through this middleware beforehand to ensure whatever business logic is in here is run before this API endpoint is called and what this allows us to do is destructure the context in or API Rod so for example in the private procedure that we want the get user files to be we want this to be a query as well it's like a get request for all the files one user has it takes a callback function as always and now we're able to destructure the context right here so what that means is we can now destructure from the context and that is precisely what we have passed into the context right here so that's going to be the user and the user ID if I were to add like a name of um you know John into water passing next then that would also be included in the context and why doesn't this work let's see if it works if we just reload the window because technically uh it should be right here let's see there we are okay it was just a typescript error now we can see the name is also included in the context so whatever we pass into the middleware context right here is then going to be accessible to us inside of the context from our API endpoint which is super useful in our case for this get user files endpoint the only thing we need is the user ID from the context and let me quickly show you why we pass those separately if we get the user and then say ID is equal to user.id in that case typescript doesn't know that there is definitely a user that there is logged in we know it but typescript doesn't and that's why you know instead of just having this operates right here to tell typescript hey don't worry it's definitely a string instead we can just pass the user ID as it is and make our life a bit easier so that's why we pass these two separately right here okay and then in the get user files route what we're going to do is return the result of this operation await DB dot file dot find many and of course we have to turn this into an asynchronous function just by adding the async right up here and what do we want to find well or where do we want to find rather we want to find where the user ID matches this user ID right here because for each file we store which user it belongs to and this is now the full beauty of trpc if we go back to the dashboard and say const worry about the destruction later it's going to be equal to trpc dot we can already get the user files from here and then the use Query you can use on that we get automatic type safety on the data trpc knows the type of the datum that we are returning from this API endpoint I cannot tell you how useful this is and how much time and nerves it saves US during the development of this application it's really cool you can use it in all the applications from going forward trpc doesn't support every use case for example it only supports Json values and we're going to see that later but for 95 percent of use cases or even 99 trpc is insanely useful and you can always go back to regular API rods if trpc should not support what you're trying to do right here so the data we get on the front end is automatically going to be queried as the page loads and let's call it files because that's what they are and so we're going to rename it from data to file so it's a bit more fitting and then display all the files as per or list or or requirements right here on the page the way we're gonna do that is right here if we have files and the files.length oops dot length there we go is not equal to zero so we if if we have more than um or equal okay let me put it a different way if we have one file or more then we're gonna render out this first block of jsx right here and else if we have no files or files it's undefined then we want to do another check and that is if this route is currently loading and we can easily get that state by just destructuring it from trpc because after all just a very thin wrapper around react query which has this feature so else if we are loading then we want to display some kind of loading State and else if we are not loading and there are no files in that case we should display in empty State like there there are no files yet or whatever so let's just put an empty div right here let's put a empty div right here we're going to worry about that in a second and let's put an empty div in here as well to get rid of the syntax errors great let's start with the empty State because that's going to be the first day that we're actually going to need so let's go to the last case that we're checking for and let's give this div a class name that is going to be margin top 16 Flex Flex Dash call and items Dash Center and a gap of two just like that let's give it a bit more space inside of here we're going to use the um ghost icon from Lucid react as a self-closing icon and give it a class name of height 8 with 8 a text oops text zinc of 800 right below this icon we're gonna have an H3 saying pretty empty around here this H3 is going to get a class name of font Dash semi bold the text of XL and right beneath that a paragraph element saying let's and again we're not going to use this apples draft but instead the and a pause and then colon to make it safe and not throw any build arrows let's upload your first PDF period and that's or empty State done right there let's take a look at if this works and how it looks like let's reload the page and of course I suspect oh no the dev server is back up great and it's giving us an error maybe one of these should be marked as a client entry that's probably because we forgot to mark this as a client component we are doing this because we're using a client-side utility right here that's why this needs to be marked as a client component or dashboard great and this or empty State because we have no files we just searched the entire database we get back that we have no files there is the empty State beautiful now let's add a loading State because right now if we know this there is just nothing and then the empty State pops up that's not ideal we want a real loading State and the way we're going to do that is by adding one very lightweight dependency and that is the react loading skeleton so navigate into your CMD and let's add a new dependency npm install pnpm install doesn't matter react Dash loading Dash skeleton let's hit enter on that that's going to install a dependency for us and what was that bad gateway 402 it seems like npm is having some problems right now but or dependency was installed successfully awesome and what that allows us to do is to import a skeleton right here in our dashboard it's like a loading state that looks good by default and we get this from oops from react loading skeleton just like this okay um right now to make sure this loading skeleton actually works and the CSS is imported that it needs to work let's quickly navigate into our layout.tsx the root layout and we need to import a CSS file right here it also States this in the documentation for react loading skeleton but let's just say import and this is going to be react loading skeleton slash dist slash skeleton dot CSS make sure to import that in the root layout so we can always in every page just use this dependency without having to import the CSS right there and what that also means is we can now already use the skeleton in or dashboard and we're going to make use of it right here in the um is loading so instead of the div we have right here oops let's replace this with the skeleton just like this it's going to be self closing with a height of 100 a class name of m y minus 2 a margin on the um top and bottom of two and then a um count we're gonna pass in as a separate prop that is going to be three just like this and we should be able to see a beautiful loading state in our app assuming of course that the dev server was running so let's quickly start that up and see um how the loading state is looking and it should look really really nice and clean right there it's barely seeable you can see the CSS pop-up right there and in your app um right now if you added the skeleton it looks really nice as a loading set and then the empty state pops up right here beautiful but what if the user actually has files currently we're just showing an empty div that's not in the expectation of the user so instead what we're going to do is create a UL an unordered list element right here and this unordered list we want to show all the files in so it's going to wrap all file elements just like this let's give it a class name of margin top 8. a grid a grid Dash calls dash one a gap of six we want a divide of Y A divide zinc and let's give this just a tad more space for the longer class name a divide zinc 200 medium grid calls 2 and large grid calls three so I have three columns on large devices and up and inside of this UL we can actually map over our files okay so for the file let's sort them first and then map over them so we can say files.sort and the Sorting in JavaScript always gets an A and A B value in the Callback right here that we can then use to execute a m function on and what we want to return is the new date the B dot created ad and we want to get the time for that so new date B dot created at dot get time minus the new date of the a DOT created at dot get time and invoke that there we go and why is that giving us an error ah okay don't worry about the error yet that is because we're not rendering out any valid react element from it yet that's what we're gonna do right now so let's map over these sorted files we could also sort them from the database right away it doesn't really matter and then for each file we're mapping over let's display some jsx right here the topmost element is going to be an Li element because inside of a list goes a list element we are using semantically correct HTML in this build of course and first thing we're going to add is a key every time we map over something we need a key for react to know the Integrity of this list element and this is just going to be the file.d anything that's unique to this Li element and this Li is also going to get a class name that is going to be call span one dash one there we go a divide Dash y a divide gray of 200 rounded Dash large a background of white and let's give this more space there we go we're very background of white a shadow a transition and a hover Shadow Dash LG so when we go over it with a mouse there's a little beautiful Shadow added with a transition property awesome inside of this list element we want to display a couple things like the name and so on let's just hard code this for now so we can already see what this looks like let's go into here and okay we we have no files yet so I guess we're we're gonna have to wait with taking a look at what this looks like in a second but once we add our first file you're gonna see wow this looks beautiful man instead of here inside of this list element we're going to add a link from next slash link and the reason is if we click on an element we obviously want to be taken to the page where we can view the details like view the PDF file and ask the file questions and so on that's why we want the link element right here leading to an href off a template ring so we can dynamically insert values and this is going to be slash dashboard slash and then the file dot ID so later on we're going to be using Dynamic next.js routes to make this happen inside of this link or actually rather in this link let's give it a class name of flex Flex Dash call and gap-2 give it a bit more space so you can see the entire class name there we go inside of this link we're going to add a div and this div is going to to get a costume of padding top of six you want a padding X of six we want Flex a width of full to make it take up all the space it possibly can and items Dash Center we want a Justified Dash between and lastly a space X of 6. inside of the zip let's add a little decoration element this is going to be a self-closing div just for some decoration and color this diff is going to get a class name of height of 10 a width of 10 a flex oops a flex shrink of zero then we want a rounded Dash full so it's going to be a circle a background gradient two and then R for right side from Dash cyan Dash 502 Dash blue dash 500 this right here is how we make Radiance beautiful gradients with Tailwind CSS by the way we can save that already and I really want to show you what this looks like in the browser so how about we just add a mocked a fake file so or account npx Prisma studio so and we can actually see what we're building here in the dashboard I think that's way more fun so let's open up the Prisma Studio in our browser by pasting in the localhost 5555 let's go to files and just create a new one so let's expand this just a bit click add record the name is going to be mocked the upload status doesn't matter yet the URL is going to be mocked the key is going to be mocked and all other stuff is generated automatically for us just so we have something to show for awesome so if we go back to our page reload it then we should be able to see the file right why doesn't this work did we forget to add the we probably forgot to add the yeah okay the user ID the user is not linked currently to this file so let's select or user and can we just hit enter on this yes we can so now we linked the file to the user and now we should be able to see it right here in our dashboard right now there is no title or anything but the blue dot in the card for this file is already there so now we can actually see what we are working on which is very nice and we can go below this decorator div and continue our work right here let's create a new div in here with a class name of flex-1 so it will take up all the space it can within the flex container and truncate that will cut off names if they are too long so they will still look good even if it's like a really long name and you make the window very small it's still going to look good with truncate in the earth let's create one more div with a class name of flex and items Dash Center and also a SpaceX of 3 and lastly in here in H3 element this is going to contain the file dot name just like this and get a class name of truncate as well a text of large a font of medium just to make it pop a bit and a text of zinc 900. beautiful let's format it hit save and we can see the mocked file name show up right here this would be the actual file name of our PDF later so you know lecture notes.pdf or whatnot and that's pretty much it now below the link tag right here let's create a new div element with a class name off padding X of 6 a margin top of four a grid a grid calls three for three columns a place oops Place Dash items Dash Center a padding y of two a gap of six as well a text of extra small these are just going to be some details for the file we are currently creating let's give this a tad more space so you can see this easier let's actually go into full screen for this and a text zinc of 500 just like this and here just some additional details for the file for example when it was created so let's create one more div in here with a class name of flex and items Dash Center and a gap of Two And A Plus icon in here we get from Lucid react this is going to be plus you can just declare this as a self-closing component with a class name of height four oops h-4 for height four width of four inside of this div we want to display when this file was uploaded right the way we can do that is typically through the file dot created at just like this but the the problem is this is like a really weird timestamp that looks super weird if you just left it like this um so we want to format this timestamp to actually you know look good and the way we are going to achieve that is to a very lightweight dependency that is called pmpm install npm ad or whatever and the dependency is called Date Dash F and S for functions just some utility functions we can use on dates because working with dates in JavaScript if you've ever done it you know it's an absolute headache and this Library makes it really enjoyable let me show you so inside of these curly braces for a dynamic value that's called The Format we get from the dependency we have just installed let's go to the very top import the format function from date Dash FNS the dependency we've just installed go back down to this little information section right here right below the plus and now we can call the new date and pass the file dot created add in here now we're still going to get an error because the format also expects as the second argument the format we want it in so you can see expected two to three arguments but got one that's obviously not enough so as the second one we're gonna pass the format we want this in and for us that's going to be mmm on all caps for the month as a human readable name and then YY for the complete year and just like that by saving that and then restarting the devs server you're gonna see this looks so much better now because instead of this super ugly string right here 2023.09 whatever this is which looks horrible let's reload the page and it's just going to say September 2023 how cool is that it looks so much cleaner awesome after this first div with a plus in the format let's create a second one and then here we're gonna say message Square which is an icon we get from Lucid react and this is going to be useful for later when we add the messaging functionality to a PDF file for now let's just give it a class name of height 4 and width 4 and um normally in here we would display how many messages the user has exchanged with this PDF file for now we're just going to say mocked and that's fine and last thing we want is for users to be able to delete this file right if we take a look at or requirements for this page we want a little icon right here in the top and the bottom right that lets users delete this file first off let's save this and take a look at okay why does this look so weird because we forgot to give this div a class name and also because I messed up the position of this diff it should be inside of the div with the padding X of 6 and not outside of it and this div also gets a class name the same class name we applied to the first div we can just go ahead grab that class M from the first div go into our second div and paste it in here and that's looking much much better awesome with the mocked and now as the third element we want a little button that lets users delete this message right here and the way we're gonna do that is we're going to use our button or UI button and inside of here we're just going to display a trash icon we get from Lucid react with a class name of height four with four and this trash icon can be self-closing if we just save this it would look really weird it wouldn't really look like a destructive action like a um you know it should be red probably right and it should also be smaller this is a really really big button so let's pass the size of small into it that's already looking a bit better and let's pass a class name of with full I just think that looks much better than having the small button it now extends all the space it can within this three column grid that we have going on here and as for the variant of this button we can pass in the destructive and not the defaulty destructive to make it red now I personally think that this red is a bit too much it looks too vibrant you know it takes away from the blue and the name and so on it looks very flashy it really draws your eye to it that's why I think it's a good idea to just or globals.css just a bit to make this button look a bit more balanced therefore let's navigate into our globals.css and for the destructive values that we have right here the dash dash or hyphen hyphen destructive and the destructive foreground these are the only values we're going to change to custom values so follow me here the dash dash destructive is gonna be zero then 86 percent and 97 as the last value awesome and we can also add like a little annotation right here custom value just so we know later that okay this is what we change right here and then for the destructive foreground let's change this to zero then 74 and lastly 42 percent as well and we can simply copy the little disclaimer we have from up here that this is also a custom value if we save this you can see it turned around pretty much and it looks so much more balanced much more subtle it is there if you're looking for it but it doesn't draw the eye too much I just think this is a much more balanced approach to building this beautiful dashboard right here and we can close out of the globals.css we don't need them anymore let's finish up this button and let's actually give it some functionality because with trpc there's nothing more enjoyable than building API rods so in order to use trpc by the way we can close out of a lot of files right let's go into our index.ts for trpc where we declare all the API rods that we have in our application again you can split them you don't have to have them all in one router we're going to do that later when it comes to the PDF files you're going to see that but for now just jam it in here this is totally cool okay for the delete file delete file API row that we're gonna um declare in here we also want a private procedure very important not anyone should be able to delete any file you need to be logged in for this to work very very important and then this is going to require an input this is the first time we're using this this is like a um a post body for example if you make a post request you need a body to it and this is nothing more than the input and we can use a schema validation Library called Zod to validate right away what we get back um or what we get input here on the server side the way we can do that is by saying Z and this is just the letter Z and we can import at the very top of the file Z from Zod just like this now if you've never worked with zot before don't worry I'm going to show you exactly what this does for us let's call the Z dot object and this actually you know takes an object with an ID property and that is going to be a z Dot string so what we just did right is essentially we said whenever you call this API endpoint as the post request body you need to pass in an object and this object needs to have an ID property and this ID needs to be of type string this is how we can enforce types at runtime because typescript is purely at build time but this works at runtime and if we don't pass data that exactly matches this right here the API Rod is going to throw an error it's not going to let you call it which is amazing for us of course the input alone is not enough for this API or to work we also need the business logic and we are going to do that inside of a mutation by chaining it to the inputs of privateprocedure dot input dot mutation and inside of this mutation this takes a callback function by the way we can declare the API route Logic for this API endpoint first off we need the user ID and as you already know we can destructure the context because this is a private procedure the user ID is automatically passed in here because that's what we did in the middleware and we can destructure the user ID right here from the context in the very beginning of the business logic there we go and then let's find a file that matches certain criteria so const file is going to be equal to a weight and again to be able to use the weight right here we have to turn this into an asynchronous API endpoint now we can call the await and we can say DB dot file dot find First and we want to find the first file where the ID of the file matches the input dot ID the oops the okay we also need to restructure the input that's how we get access to it so not only the context but also the input and the input automatically has the type of what we declared up here in the input if I or if you or if we whatever added like a z dot string for a name property for example that would immediately be reflected in this type as well that is incredible developer experience if you ask me so now we can make sure the input dot ID matches the ID of the file in our database also very very important if we left it at this this would be extremely insecure because anyone could pass any idea and we would find that file and later delete it we also need to make sure the file has the user ID of the user that is calling this API endpoint and those need to be comma separated so not only should we pass an ID but we are only searching for files that the currently logged in user owns because a user should not be able to delete files for any other user right that would be horrible in terms of security and if there is no such file that the user owns and is trying to delete at this point then we're going to throw a new trpc error just like this and as a code we're going to pass not found because there is no file that this user owns that has the requested ID and if that is successful the user is trying to delete one of their own files then we're going to say awaitdb dot file dot delete there we go and we are going to delete the file where oops where the ID matches the input dot ID and we can also pass the user ID right here oh it doesn't even take that okay awesome and after deleting that file we can just return the file that we have found we don't really need to return anything but I prefer just doing it we are not going to really need it on the front end anyways but it is a good idea to return the data from the backend awesome so what that means is now we can actually have the delete functionality from the front end and call the back end let me show you how this works this is the first time we're using an API endpoint not as a get request but as a post request right this is the first time we're making use of this mutation which is for changing mutating data that's what it's for and then the query is for getting data so let me show you how this works the way we can call this action from the front end is by destructuring something from the trpc this is the same thing as with the query for now dot we can call the function we have just created which is delete file and then dot use mutation and invoke that now from this function we can destructure something called mutate which is just how we can call this action right here so when we invoke the mutation anywhere from our component then this code is going to be run or a request is going to be made to our API endpoint and running this code right here so let me show you how this looks in detail first off let's rename the mutator delete file oftentimes I found it always matches this right here when we're working with limitations because that's you know what the action is we're not mutating anything I mean technically we are but it's just a better idea to also give it a name that you know makes sense for us as developers and when do we want to delete this file well we want to delete this file when we press this button to delete a file right here so let's add an on click event to this button and whenever we click it we want to say delete file and invoke that and already you can see this gives us an error because it expects an ID property of the file we want to delete and this is nothing else than the file we're mapping over dot idea okay we can save that and technically that will already work let's try it out let's hit the trash icon right here and you know technically that already worked if we reload the page it's going to be gone but we can't expect our users to know that they won't reload the page they get no feedback this works technically but it's not um an implementation that is perfect it could be better so let's make it better let's add a loading set and let's also let the user know that their action is successful and reload the page after they have deleted their file so first off let's go back into our Prisma schema it might already be open there we go and let's quickly create a another file let's add a record let's go into full screen for this just so we have something to work with let's say name is mocked URL is mocked key is going to be mocked and then let's link the user which is gonna be me right here in your case that's going to be you hit enter and that's going to link the user to the file beautiful let's give it a bit less space go back into here and there we can see beautiful or mocked file once again now I want to show you a really really cool trick so whenever we delete the file we want it to be gone right it we shouldn't have to reload the page for the file to be gone and the empty status show now when we click the button we want it to just be gone so the user doesn't have to reload let me show you how we can do this in the use mutation we can use a callback called on success so when the route has been successful and the file has been deleted from our database we can work or we can execute any code that we want and the code we are going to execute let me show you let's up here above the get user files.query let's say cons utils is going to be equal to trpc dot use context and what this allows us to do this utils is to invalidate this query right here essentially forcing it to refresh the data because this is how we get the data and if we can force it to invalidate then it's going to get the data again and find hey the mocked file is not there anymore so I'm gonna remove it from the page without the user having to reload the entire page let me show you how this works we can call the utils dot get user files whichever API endpoint we want to address and then call the dot invalidate method on it and already what this does is when I click this trash icon we don't have any loading States yet but you can see once the API is successful the data is automatically refreshed without us having to reload the page beautiful let's quickly add that back in once again let's go into our Prisma Studio add a record and let me very quickly just throw something together here URL key and Link the user like that hit enter there we go so we have something to work with again move this into a side by side and there is a file once again okay let's finish up the beautiful user experience in here by adding a loading State and the thing is if we just try to add a loading set like this it wouldn't work one there would be a naming conflict and then secondly right we want to only display a loading State for the one file that we are currently deleting imagine if there were um four files right we wouldn't want to show a loading state for each one of them if any like imagine if I press delete on this file right not all of them should have a loading set but only this file should have a loading State instead of the little trash icon right here so we can't really use this because this is going to be true if this function is called at all right for any icon so instead we need to know which file specifically is currently being deleted and to achieve that we're going to make use of State so at the very top of the file let's declare a constant and call it currently deleting file and also set currently deleting file by react convention this is going to be equal to use State we can get that from react give it a bit more space just like so and this is going to be of type string or null we can pass it as a generic into the U State and by default this state is going to be null just like this so it's going to be defined but it's not going to have a value and then whenever the mutation right here is called we want to add that file we are currently deleting to this currently deleting file state just like with the unsuccess there are more callbacks we can use to make exactly this happen like the on mutate that is called whenever we click the button right away not just when the API Rod has resolved successfully so right away we can destructure the ID from here because it's passed in automatically by a trpc into this on mutated callback and we can set the currently deleting file to this ID right that's the beauty and lastly on the unsettled whenever either an error is thrown or the unsuccess or the you know API resolved successfully whatever it is as long as it's done somehow we can call the on success or it's automatically being called for us and we can set the currently deleting file to null so whether there's an error or not the loading State should always stop at that point right and lastly we need to determine if this file that we're mapping over should be loading right now or not and doing that is really really easy so we're going to conditionally render the icon right here currently we're only showing the trash icon but if the currently deleting file is triple equal to the file that we're mapping over dot ID if we are deleting this file then that's why we have a question mark we want to show some jsx and else we want to show the trash icon so if it is the current data deleting file then we want to show the loading State the way we do that is by showing a loader 2 icon we get from Lucid react just like we did before with a class name of height 4 with 4 and animate Dash spin so it spins around a bit let's see if that works let's add a second file in our Prisma Studio let's go in here let's give it a bit more space and let's add a second file let's say second copy and paste that you don't need to follow along I just want to show you that this works and I didn't want to set it to that there we go and also link the use it to it so now we should have two files showing up in our dashboard let's see if that works it does beautiful and now I'm going to click delete on this one and see what happens the loading State should only be on this one and then whenever the API Rod has resolved successfully that file is gone and everything looks so smooth so beautifully with an A1 user experience if you're following along a really really good job grasping this concept of giving the user a good experience just like this is going to take you very very far and you'll be able to reuse it across all your projects not just this one we're building but that's the main point of this video right I want you to learn good stuff that you can reuse across all your projects to make them much better and congratulations that is the dashboard done that's our dashboard functionality it looks really clean and we can even click on these items now when I do click on this file it's going to forward us to a domain that is slash dashboard slash nid and of course there's a 404 because that page doesn't exist in or app yet and the goal for this is to be the main interface where a user interacts with their PDF file where the user can view the PDF file on the left hand side and chat to it on the right hand side this is what this page should be and to learn how we can work with Dynamic routes in nexjs let's close all of all of this and let me show you how this works so the URL let's take a look at this let's copy it over and then let's go into excalator draw right here paste it in and let's dissect what this means let's go into full screen so this is the URL we're currently forwarded to right so this is or let's make this colorful this is or host this doesn't really matter then this is the dashboard path this is important for when I'm going to show you how this works in a second and then this last part right here is going to be passed automatically into the Page by an extra s is something called the params okay so this value this last part right here is what we need in order to fetch the file from our database once we navigate to the page right so when we land on this page we get this certain ID because this is nothing else than a file ID and remember when we forward it to the page right here from our dashboard and you know slash uh Slash dashboard slash and then should be file ID right here this is the link we just clicked so we get forwarded and the file ID is passed into this page so we need to retrieve it when we're on the page from the URL and make a database request with this file ID to get the contents for the file to display on this page and the way we can do that in next us let me show you the dashboard right here is important because this needs to happen in the dashboard route so under app dashboard this is where the magic will happen in here we can create a new folder and give it an angled bracket for a dynamic route and call it file ID just like this in angled brackets and as always you know the drill this needs to have a page.tsx in which we determine the content that will be shown on this page let's say cons page is going to be equal to nothing else than a plain Arrow function and Export that as the default from this page right here now on this page how do we get access to this file ID which is what we need to make a database call right the steps are retrieve the file ID and then secondly make database call to get all the details like the file URL we need to later render out the PDF but how do we get the file ID on this page well the answer is it's automatically passed into the page for us by next.js and the only thing we need to do is destructure it we need to destructure the params from this page and declare a type of what these params are let's call this page props for example we could call this anything we want but they are the properties of the page so let's call them page props and let's create an interface for them interface page props this is a type that Nexus just gives us it's the params type as an object and in here as the key goes whatever we put in the angled brackets for this file so we can call it anything inside of the angled brackets I could call it mjawn in angle brackets but it needs to match whatever is right here as long as these two values are the same you are golden so in our case that's the angle brackets file ID and this will always be of type string just like this and that's pretty much step one done so what that means is now we can de-structure the file ID from the params and that's how we get the file ID on the page just to show you that this works let's return a div and render out the file ID return a div and then here we're going to put the file ID just like that if I go back to the page then we should be able to see the file ID correctly right here beautiful and what that also means is now we can get the details for this file from our database first off however we need to make sure the user is logged in so first off let's get the user const and you know the drill by now how this works constantly get user is going to be equal oops gonna be equal to get kind server session and invoke that and to get the actual user object we can just call the get user function and invoke it great if there is no user on this page if they're not logged in or if there is no user.id in that case you want to redirect the user and it seems yeah okay the Auto Imports do work we want to redirect them we get that from next slash navigation and we want to redirect them to the slash auth Dash callback and the origin is going to be equal to dashboard slash and then the file ID we get passed in here as a param and of course for this templating syntax to work you need to wrap this in template strings instead of plain um you know quotes that's how we can actually get the file ID to work beautiful so if the user is not logged in um and lands on this page somehow then we can redirect them to the auth Callback and if they have an account then they're going to be redirected back automatically because we passed the origin perfect what that means though is we can actually make our database call after ensuring we have a user so the const file the file details for this page are going to be equal to a weight and to be able to use the await syntax we can just make this page asynchronous because we're using an extra s13 server components by default in this app we want to await a database operation DB imported dot file dot find First and we want to find the first file where the ID of the file matches the file ID that is passed in as a param and also where the user ID matches the user dot idea that is why we have this card Clause up here if we didn't have this I could view your files and you could view my files that wouldn't be good right we don't want that to happen so a user can only view their own files that's why we have this line right here and if there is no file then we can return something called not found which also next slash navigation gives to us and what this does is essentially it throws a 404 error let me show you what this does if true not found then we're always going to throw this for four four four this patch could not be found beautiful but of course we only want this to happen if there is no file and in our case there is a file because the ID does exist in the database beautiful and now we can actually get started in the jsx we want the display on this page and to have a clear goal in mind for this jsx let's once again switch over to our drawing app and kind of list the requirements and the visual you can just copy this over from the dashboard paste it in here and then file view and let's just call it that so it's going to be the detail view for one certain file and also in the dashboard okay let's get rid of the requirements and let's get rid of this visual stuff because it is going to look a bit different and let's first off list the requirements right a user should be able to view their PDF I think that's the first one right the PDF should be shown on the left hand side so just like this and this will be the PDF then we want to have some options like PDF options for example that's going to be up here with um rotating your PDF and let's make this colorful so we can tell it belongs together just like this and like rotating the PDF zooming switching to the next page and so on we want that at the very top of the PDF viewer we also want them to be able to chat to a PDF that's the main functionality of our app that's super important and that will happen right here on the right hand side let's make this blue so we can tell what it belongs to this is going to be the chat and also inside of here needs to be an input field just like this they can use to type in their text and then the chat messages are going to show um up here this is for the chat messages great are there any other requirements for this page we want like a full screen view that's more of a detail we're going to add then we don't need to list it explicitly here but this is pretty much the gist of how things are going to look it's a two column layout with a PDF viewer on the left side and the chat on the right side and what probably makes sense is to have the chat take up a bit less space and the PDF to take up a bit more space and Visually because the PDF likely is pretty small the text and it just makes it easier to read if it's a bit larger so like a 60 40 split or something like that awesome and with a basic idea of what we want to build we can save a lot of time in the actual building process so let's move this over give it a bit more space move this into a side by side there we go and move this into view just a tad now we can get started on the jsx of what we want to build okay the first top level div we can remove the file a theme that's not what we to display on the dashboard is going to get a class name of Flex one a Justified Dash between a flex a flex Dash call and a height and a calculated height so we're going to put it in angled brackets for a custom value that's how we do it in Tailwind with a calc in here this is a regular CSS thing we can just use the calc to calculate a custom value and what we are going to calculate is going to be 100 VH for view height units that make up the entire screen minus 3.5 RAM and these 3.5 Ram don't come from anywhere this is to offset the nav bar height so everything looks really really good even with the navbar inside of here we're going to create one more div with a class name of MX Auto to center it on the horizontal axis automatically a width of full a maximum width of 8xl we want this um this viewer to take up a lot of space and that's why we made the custom 8xl value in detail when config we can just call it like that a row and on large we want a flex property and lastly on XL devices a padding X of 2 for this um you know visual representation that we have right here okay first off we want to build the left sidebar and the main wrapper so as for the left bar right the vendor this is what I refer to when I mean the left side it's the entire PDF renderer with all the like options that come along with it let's define the layout for that first so let's say as a common just for us so we can easily tell what's going on let's say left side and that's it we can just tell what's going on and a div in here this div is going to get a costume of flex-1 and on extra large devices and up we want a flex property on here inside of here goes another div with a class name of padding X of four a padding y of six we want on small devices a padding X of 6 we want on large devices a padding left of eight and on extra large devices now we want a flex of one and last one on extra large devices and now we want a padding left of six inside of here comes the main content for the PDF viewer which is going to be a custom component and we can already add it in let's call it PDF renderer this is a component that doesn't exist yet so let's quickly create it in or source and then under components let's create a new file called PDF renderer.tsx which is nothing else than a arrow function cons PDF renderer is going to be an arrow function let's export that as the default PDF renderer and let's just return a div saying PDF renderer from here just for now so we can see everything works and import it back in or page.tsx so we can already have this show up and then let's go down one closing div two closing div enter enter with two closing divs left and then here is where the right hand side goes so we're just defining the top level layout for this entire page right now we did this the left side now we're gonna do the right side the chat right here and this is going to live inside of a div with a class name off shrink Dash zero to not let this shrink any further a flex Dash and then as a custom value again an angled brackets that's how we do that in Tailwind CSS is 0.75 right here a border Dash t for top a border Dash gray Dash 200 on large devices we want a width of 96 that's something we can pass in the Tailwind accepts let's give it a bit more space just for a second right here two more properties we want on large devices a border Dash L and we also want our large devices a border top of zero and that's it inside of here let's give this a bit less space once again inside of here comes a wrapper component and this wrapper component is going to prove super useful here in a bit you're going to see that essentially it wraps both the input right here and the messages which is a separate component and you'll see it makes a lot of sense to have one component to bundle these two together and let's call it chat wrapper again a component that doesn't exist yet so let's go into our file system right here and under components a new file called chat wrapper dot TSX and that is going to make working with our chat much much easier this again the chat wrapper is nothing more than an arrow function so cons chat wrapper and also export it as the default at the very bottom chat wrapper just like this for now let's just return a div from here saying chat wrapper and import it back in or page to make sure everything works as expected it's imported and actually look at our page let's give it a bit less space and we can see all right this already looks good on mobile devices it's stacked below one another and on desktop devices if I zoom out or if I make it full screen we can see there's a left side and there's a right side they take up a lot of space which looks good visually and the chat has a bit less space that would be that is what the flex 0.75 is for it has a bit less space than the PDF renderer just as we expected or wanted it to be right here in or visual layout and that is the top level layout for or dashboard page beautifully done and what that allows us is to not care about the layout while we build out these individual components the PDF renderer and the chat one by one as a modular approach to building this dashboard out together okay and I say we do the PDF renderer first the thing is PDFs are not like images you can't just use like an image tag or a picture tag and as the source use a PDF that really just doesn't work PDFs are done much different and the thing is there are even libraries like the one we're gonna use it's called PDF Dash react or react PDF the other way around this is what it's called this library right here which specializes on rendering PDFs and it's super useful as you're gonna see so let's install it this is going to be the basis 4 or PDF renderer here on the left hand side so what we are going to install let's open up the terminal right here we can stop the dev server and we are going to install a dependency npm install npm installed yarn add doesn't matter that is called react Dash PDF it's not the one you can see here on the right hand side that's something different we want react Dash PDF hit enter that's going to install the dependency and that is going to be the core Foundation that we're going to use for displaying the PDF here on the left hand side or on the top side if we are on mobile because this is fully responsive awesome let's start up back the dev server by typing in pmpm Dev and then let's get started building a prototype of displaying a file right here on the page so for the PDF renderer what we are going to return at the top level is a div element with a class name of with full we want a BG white a rounded oops surrounded Dash MD for medium a shadow we want Flex a flex Dash call and items Dash Center there we go let's give it a bit more space so you can see the entire class name and in here we are going to create one more div with a class name of height 14 and very briefly what this diff will represent let's go to excaledraw.com right here the stuff that we're creating with a height of 14 is going to be this um yellow or orange element right here the top bar that allows us to have some you know custom functionality like going into full screen of the PDF rotating the PDF it's these PDF options we can see it in yellow right here on the left hand side that's what we're building in this div right here let's give it a width of full a border of B for bottom a border Dash zinc Dash 200 Flex items Dash Center so this is horizontal um so this is vertically aligned a justify Dash between so the items are spaced out left and right side that's what the justify between does for us here and lastly a padding X of two just to give it a bit more space um to breathe okay in here one last div I promised with a class name of flex items Dash Center and we want a gap of 1.5 beautiful okay so this is going to be the top bar let's just say top bar right here in the PDF renderer and save all of our files and let's see what happens did we start up the server yes we did so technically it should be right here and it is we can see the top bar that is later going to be you know it's going to contain the I'm going up and down my page we are going to have the full screen button right here on the right hand side and also the zooming right that's also going to be in this top bar and to be honest I just realized we we do have files in our database technically but we can't even upload you know normal PDFs yet so display here in the PDF renderer so I think it makes more sense to build the upload button first actually before we try to render out any PDF right here in the detail view in the dashboard right we don't even have files to display it um so let's quickly revisit or upload button right we left it at example content and let's fix this let's build one of the cleanest upload buttons you've ever seen with a progress bar the turn screen when the upload is done and all the beautiful stuff you saw in the very beginning of the video in the teaser right that's what we're gonna do right now so one Library we can use for this is called netstop or Dev server and go into our terminal and say pnpm install and this library is for dragging and dropping right when this model opens when we're in the dashboard right let's go into the into the dashboard just like this oh we stopped the the dev server I want to show you what this does though so I'm going to start it back up you don't have to you don't have to follow along this is just so I can show you what this is for let's let the dev server load for a second and I'm going to click on upload PDF right we want a drag and drop area right here where people can take a file drop it into because that's really user friendly we don't want them to have to click in here and then select their file from the desktop that's just really annoying and one library that makes this super easy handles all the edge cases and dragging and dropping for us that is called react drop zone so we're gonna install pnpm install react Dash Drop Zone just like this hit enter and that's gonna handle as I said the dragging and dropping in a very very clean API let me show you this so after installing this dependency what we can do is at the very top of the file of the upload button let's import the drop zone as a default import by the way not as a named Import in this object syntax we're not destructuring anything from the react Dash Drop Zone we have just installed and this drops on let's put it right in here but I think what makes life a bit easier is actually not putting all the HTML directly in here but instead what we can do is have like a custom you know upload Drop Zone we can call it a custom component which we're also going to Define in this file but it does make the jsx of the upload button more readable if we separate this right here into a separate component to handle all the dragging and dropping logic and separate it from the actual model you know this can be a self closing component that is totally fine and let's declare it right above here so the const upload drops on what we call it down here is nothing else than a regular Arrow function as always it's just a regular component and this needs to return some jsx so let's just return it for example and that's going to get rid of all the errors in here beautiful now this is going to handle all the logic and at the very top level we are going to return the Drop Zone we got from react Dash drops on let's give this a bit more space so we can see what's happening on the left and right hand side there we go we're going to return at the very top level the Drop Zone and it's not going to be self-closing and this drops on we'll get a multiple something we can pass that this Library supports of false because we only want to accept one single file when people drop it in right here now the cool thing about the drop zone is we can destructure some stuff right away by opening up an object right here inside of your goals parentheses and this is basically just a callback function we can use to render out some more jsx so for example a div at the top level right but what the Drop Zone allows us to do is to de-structure some stuff right in here if we see what we could destructure well it's a bunch of stuff but what we care about is the get root props and also the get input props and lastly the accepted files that we can get access to so when somebody dropped a file into our Drop Zone it's gonna be right here in the accepted files and we can work with it however we want and the get root props and get improv props this is just important for rendering out the top level div so what we want to do and this div right here is just pasted some props that the Drop Zone gives to us so we're going to spread in the get root props result by invoking this function and the div can just take that and we're golden right that's just gonna make the Drop Zone work this div also the top level diff inside of the drop zone is going to get a class name and that class name is going to be border this is what's going to make our Drop Zone look good in the end a height of 64. a margin of 4 a border Dash dashed oops Dash there we go want a border Dash gray-300 and lastly a rounded of large let's just see what this already looks like let's save the file and we probably yup we need to start back up Dev server let's do that reload the dashboard to see what we're even building here on the left hand side that's always a good idea let's let this load and then click the upload PDF button okay the drop zone is slowly starting to look better and better we have a nice thin outline around the content let's give this a bit more space so we can see more of the class names and format this there we go and inside of this div we're going to open up one more div with a class name of flex item stash Center we want a justify Dash Center a height of full and a width of full as well beautiful now inside of this div let's open it up comes not another diff but this time a label tag in HTML and this is going to be the html4 Drop Zone Dash file and this label is also going to get a class name and this class name is going to be Flex Flex Dash call items Dash Center a justify oops justify Dash Center a width of full a height of full a rounded Dash LG for large we also want a cursor pointer let's add that cursor Dash pointer a background gray of 50 and lastly in the hover State we can use the hover pseudo element or whatever it's called for this so hover colon that's how we Define the hover State and Tailwind is going to be a BG gray of 100. beautiful then let's open up this label right here and inside of this label we're gonna create a div element let's say div with a class name of flex Flex Dash call items Dash Center justify their Center pt-5 a little bit of padding on the top and a PB of six so a little bit more padding on the bottom all right and not only does it look good but if we click it it actually opens up the file explorer to select a file it does it on my right side monitor so you won't be able to see it but it will do the same for you and we should also be able to drag something into here and to check if this was successful there is something called on drop on the drop zone so the accepted files oops accepted files are something we can receive in the Callback whenever this on drop is called so when we drop in a file and let's just console log the accepted file for now just to make sure that when we drag and drop something into here let's move this into a proper side by side it actually works let's go into the console right here and let's drag and drop in a PDF file and we can see the file is logged out in the console the lecture notes.pdf that just dropped in with a date a last modified date a name that's going to be imported later a type we know this is a PDF that is also important so we can later validate that only PDFs can be uploaded to our server and a size of the file which is also important because premium and free users will have different file sizes they are allowed to upload beautiful so we know the job Zone works very very nice and it also looks pretty good already we can make it look a bit better let's do that let's work on the visual design of the Drop Zone the functionality is mostly done but we can make it look way better with a cloud icon in the div element right here which is going to be self-closing in this Cloud let's give it a class name of height 6. let's also give it a width of 6 a text zinc 500 and lastly a margin bottom of two just space it out from the content below A P tag will go below that with a class name of mb-2 a text of small and a text zinc of 700 right here this PM is going to contain a span element right here with a class name of font Dash semi bold to make the text Pop a bit more and inside of this span element we're going to say click to upload and then outside of the span right afterwards we're going to say or drag and drop let's save that see how it looks okay that already looks pretty good and lastly right below this P tag let's add one last P tag and this one is going to say PDF and then in parentheses up to 4 MB and by the way this 4 MB were later gonna dynamically change depending on whether the user is a pro user or not because um Pro users will be able to upload 16 megabytes while free users can only use four and this p-tag is also going to get a class name of text Dash XS for extra small and a text zinc of 500. there we go let's save that see how it looks beautiful it's very subtle right here below the main text that we want to draw the eyes to very very nice okay now if there are files dropped into here currently let's try this again I'm going to drag a PDF file in here nothing happens there is no user feedback the file is there remember it's in the console logs and we get access to it in the on drop and in the accepted files right here but there is no user feedback so let's fix that let's give the user some feedback and that will happen right here above the label whenever we have accepted files and also the accepted files at the index of zero because it is an array exists because we only ever accept one file we can only check for the first index that is totally fine and render out some jsx if this exists if not in that case we're going to render out no we don't want anything else by the way this is a better idea in jsx generally than using this syntax right here technically it achieves the same thing but sometimes it can lead to a bug when this evaluates to true and you get a 1 in the end like a just a one on the page and it looks really weird and to completely avoid this bug altogether we can always use a ternary operator and render out null and therefore we never get like a visual bug it rarely happens to be honest but let's just not even risk it in here we're going to use a div element with a class name of Maximum with extra small a BG of white Flex item stash Center a rounded oops surrounded Dash MD medium and overflow Dash hidden let's give it an outline a outline of and then in custom brackets because this is going to be a custom value one PX to make it very thin then a outline Dash zinc Dash 200 a divide oops divide Dash X and A divide zinc 200 there we go inside of this div we have just created with this really long class name let's open it up let's also format this file there we go open up the div and inside of this div let's create one more div like this and this will be the preview of the file right when we drag in a file this will be evaluated to true the statement right here and this jsx will be rendered inside of this div let's give it a class name of padding X of three a padding y of two an H of 4 a grid and a place items Dash Center beautiful inside of here goes a file icon so we can tell the user hey there is actually a file you've uploaded it did work so the user knows what's up that's going to get a height of four a width of four and a text blew of 500. it's already preview this so I did drag in a file so we should see this right here and it looks really nice beautiful we can make it look even better by also displaying the file name let's do that below this div right here in another div and let's give this a class name of paddingx of 3 a padding y of two a height of full a text of small and a truncate again this truncate is important if we had like a really long file name let's say lorem 10 to generate a bit of text if we just save that you know you see the three dots at the end right here and by the way if you wonder how I just did this if you type in lorem and then a number and hit tab in vs code it's automatically going to generate some lorem ipsum for you if you didn't know if we save this this should represent a really long name and you can see what the trunk head does it adds three little dots at the end which is a really really helpful tip and you can use in any of your projects but of course we don't want to render out lorem ipsum we want to render up the file name and the way we can do that is by rendering out the accepted files at the index of zero because we only ever accept one file dot name that's the file name and you can see the lecture underscore notes.pdf beautiful let's try this again let's close the upload model open it again and let me drag in a file the lecture notes you can see the preview is right here very nice now of course there is no upload yet this is only the preview this is purely on the client side on the user's machine right but of course we want to upload the file to our servers and for that we're going to use a service that makes file uploading really really easy for now we're just going to use mock data to finish up the jsx instead of the upload drop zone so one thing we want to do is display a loading state right when somebody drags in a file there should of course be a loading State and we're gonna do this using use state in react a hook we can use to enforce UI updates and this is going to be called is uploading and by react Convention of course also set is uploading as the setter for this state inside of here this is always just going to be a Boolean and just because I like it syntactically I'm also going to give it a Boolean generic right after that you said again you don't have to do this typescript knows this I just find it a bit more intuitive especially when it comes to handling like multiple States as you often have like null MSD start and then you assign a value later which is Boolean then you need this generic because else typescript would always think this is a Boolean which is of course not true for now let's leave it as true and the reason we're setting it to true we're going to set it to false by default later it's just so we can um you know mock the loading set and see what we're actually building so the state let's leave it as true and then go back down toward jsx right below the conditional jsx where we render out the file preview this is where we're going to display the loading state so if we have is uploading if that is true then we're going to display some jsx and else if this is false again we're going to display no so nothing at all is shown on the page the jsx we are going to display whenever we are uploading is going to be a div element with a class name of with full a margin top of 4 a maximum width of extra small Xs and an MX of Auto so this is centered horizontally inside of here we want to display a progress bar and luckily the UI Library we're using to build this entire thing has a beautiful progress bar we can use for this let me show you let's go down to progress right here there it is it animates between different values it looks really really beautiful out of the box and this is what we're gonna use so to install this is as easy as going into our CMD let's clear this and saying pnpm or let's actually use npx for this npx chat CN Dash UI at latest add and then progress that's going to add all the progress bar it knows where to put it it knows where to name it all based on our components.json that the UI Library initialization Step At the very beginning did for us this components are Json right here so we don't need to worry about anything and just using that command is going to install all the progress component for us awesome let's start back up the dev server and then inside of this is uploading we can now use the progress we get from dot slash UI progress beautiful this can be self-closing we don't need anything as a child in this progress and we can pass it a value let's leave this value and why doesn't this work let's reload the window to make this work because we should be able to pass a value in here and just for marking purposes let's leave the value at 50 so it will be half full and also give it a class name of h-1 a very tiny height a width or full and a background zinc of 200. awesome let's save that go back into our model and because the is uploading evaluates to True always because we put the state into true we should be able to see the progress bar right now and we do which is really really nice now if the upload progress is at 100 if we are done uploading in that case we want to redirect the user to the certain file dashboard view right the detail view with the PDF render in the chat interface that we just got started building a second ago right this is where they should land and in order to let the user know that they are being forwarded let's keep track of the upload progress and if it is at 100 in that case we want to display a redirecting message and actually redirect the user right so let's go to the very top of the file and let's keep track of the uploading progress as state of course because we want UI updates to go along with it if not we could just use a ref in the state let's call it uploads upload progress and also Again by convention set upload progress and there we go and this is going to be equal to U State and for the default value of this we're going to use 0 and just because I like it again I'm going to pass a number generic into here to say to typescript this won't ever be anything different than a number now there's one small problem we need to solve with this upload progress and that is the service we're going to use for file uploading is not going to give us the actual upload progress so it won't tell us what the progress is at it will only tell us when we are done and just to have the progress bar skip from 0 to 100 always you know is not the best user experience so this progress bar is purely for user experience anyways and what is a really good idea if you don't have access to the actual progress is to make use of something called a determinate progress bar let me show you what this does so a determinate progress bar determinate progress bar you use this when you don't have access to the actual progress and the way this works is Imagine This is the entire progress bar right and then we want to show the progress in blue right in there and let's give this a background of you know blue as well there we go this is the actual progress and the black thing is the entire progress bar when you don't have access to the actual progress one thing a determinate progress bar does is essentially you can Define how it skips the progress you can do this yourself so for example in five percent increments is what we are going to use it will proceed proceed proceed and then whenever we're done throughout this process we get the message that we're done it will skip to a hundred right so the user gets immediate feedback and also the correct State when we're done uploading the file which is amazing for user experience it doesn't just skip from zero to a hundred when we're done that is not amazing for user experience and the thing is if we went to a hundred before we got the call that we're done that wouldn't be a good user experience either because the user would obviously think the file about is done why am I not being redirected so what we are going to do while we are not sure that the file is fully uploaded we're going to skip ahead in five percent progress increments just like this whenever we get the call then it skips to 100 but if we don't get the call in time then we are going to stay at 95 so the user gets progress feedback up until this point in which they know oh we're almost done okay this is beautiful and then whenever we're done this is going to skip to Android so we are never at a hundred percent while the file is not fully uploaded but the user does get immediate and accurate feedback and that is the beauty of a determined progress bar that we are going to use in our application we can just open up our app again go into this and let me show you how this works in code so for the start simulated progress function that's what we're going to name it start simulated progress just to give the user immediate and good feedback this is going to be an error function right here under the upload progress State first off we're going to set the upload progress to zero just to reset it if there should be any upload progress from a previous upload then in here we're gonna determine an interval let's say cards interval is going to be equal to set interval which is a JavaScript function which takes in a callback function just like this and also in interval as the second argument so we can pass a 500 a 500 millisecond interval right here and also for the body of this function the logic that should be run in here let's set the upload progress and when setting is dead we can always get access to the previous value in the Callback function as the first argument or parameter I always mix those up let's say pre progress and have it as that and as for the logic inside of the state Setter right here well we want to check if the previous Prime progress is greater or equal to 95 in that case we are going to clear the interval and pass in the interval so essentially we are saying don't progress anymore we are invalidating this whole thing so we won't update the progress past 95 but we're not sure the file is uploaded and we're going to return the previous progress from here which is going to be 95 and else we are going to return the previous progress plus five oops plus five there we go because we want to increment in five percent steps if you wanted bigger steps you could always change this to larger numbers I think 5 is a really good idea for good user feedback and that's pretty much it last thing we do is now return the interval from this start simulated progress function so we can call it anywhere and the question is where do we want to call this and the answer is of course we want this to start the progress bar to load as soon as you drag and drop in a file for an instant Snappy user feedback so we can easily utilize the on drop function that the Drop Zone provides to us and here first let's set the is uploading to true so we can then later use this set is uploading value to conditionally render the progress bar which we're already doing right here right so this is what we're going to do in the on drop and then we can get the progress interval const progress oops progress interval right here is going to be equal to the start simulated progress beautiful so we are starting the progress bar up then just in theory conceptually we want to handle the file uploading right here just as a comment for us so we know what's um left to do right here and then once the file uploading is done then we want to clear this interval again so we're done with the upload we can clear the interval of progress interval just like this at the very end of the on drop and then we can set the upload progress to 100 to tell the user hey we're done uploading beautiful okay let's see if this works let's actually close down the model and open it again and I just noticed the value is still always at 50 so let's change that let's make it Dynamic and react to the current upload progress so the value is going to be the upload progress State we have declared at the very top okay let's save this that should bring it to zero beautiful let's open back up the model and now let's try to drag in any PDF file I'm going to drag in my lecture notes and you can see okay that immediately updated because this process right here doesn't take very long right it's immediate but if we await it a certain timeout let's make this asynchronous and that's a weight oops await a new promise we get the resolve right here we're just gonna mock some delay so we're just gonna pretend the API to upload takes a bit which it will let's set a timeout and this timeout takes a resolve callback and we want like a you know let's give it a 1 500 millisecond timeout that's going to make the upload take a bit just as a mocked set let's drag in the PDF and you're gonna see it skips in um in five percent increments until we upload let's say this was extremely long this upload let's just mock that in case the you know API was very slow for some reason let's drag in the file we can see still the user progress is immediate and it will never go past 95 unless the file is actually properly uploaded so let's give it a moment and it was uploaded successfully beautiful so even with 15 000 seconds it's gonna stay right there until the file is uploaded per perfect okay so we can get rid of this mocked delay this is where the actual file uploading logic will go and so how do we upload files that is a big question in web development because usually you would use something like AWS S3 to upload files this is what many services use it's a cloud storage let's go in here mine is on German don't worry about it I'm just going to show you what this is about it's like a very scalable pretty cheap solution to store data in the cloud the main problem with it is it's not very intuitive for beginners to set up and you also need need a credit card to set this up and as a video I don't think it makes too much sense to do this yes I will do it personally to be honest but we can save ourselves a lot of time just using a managed service that wraps S3 and makes file uploading really really easy inside of our application it will take like a couple of minutes and we don't have to worry about anything with S3 because just from experience a lot of questions especially in beginners arise when working with S3 for that reason we're going to use a managed service that wraps S3 called uploadthing.com it's a tool developed by a fellow YouTuber here and he's called Theo I'm going to link this page in the description as well and I want to be very clear with you as to why we're using this we are not using this because some other cool YouTuber made this okay we're not using this because it's some new hype tag that we could use for application no and this is not sponsored of course anyways by this tool no I genuinely believe this is a really fast and easy way to get set up with file uploading and up until like 500 to 1000 users this is totally cool to use because essentially it just wraps S3 and we have some free projects in here and we can use up to two gigabytes of file storage for completely free and for starting and for this tutorial that is more than enough if you have like 500 000 users yes I would actually go with something like S3 big because it is more scalable and cheaper than upload thing is but to get set up I really enjoy this tool I think it's a great idea and it will provide a lot of value to us in this build right here so what we are going to do on this upload thing page is creating a new app chances are you won't have any so let's walk through this process completely together step by step as the app name I'm going to enter a quill if we already had a deployed version of our app we bought entered here the app URL however we don't have it so we're just gonna click create app right here and that's pretty much all it takes to get set up with upload thing now we want to incorporate it in our code and actually have files uploaded so they show up here in our dashboard right to upload files the way we can do that is through the documentation let's navigate over here and it's really simple let's navigate to the next setup to the app router right here and they give us all the code we need and I'm gonna walk you through what this means as well so just like with the kind authentication you may remember we had to create a certain API route and with upload thing that is no different let's create a new folder under API upload thing core dot TS so in our app let's actually let's not close all of that right now let's go into Source app API let's create a new folder called upload thing and it is important it's called this for later because it depends on this naming just like our authentication provider inside of this upload theme folder let's create a new file called core dot TS and in this file we can just go ahead and copy and paste the code that the upload thing documentation provides us here on the right hand side we can paste that in we are going to get a bunch of Errors because we haven't installed the npm package yet and that is the upload thing dependency we need from up here we haven't installed that yet so let's open up our terminal stop the dev server for a second and say pnpm install upload thing and and hit enter beautiful that's gonna install the dependency we can start back up the development server and let's reload the window so the error should be gone in the core.ts and we can already go ahead and read further in the documentation while the slots so the next thing we need is an API Rod under the upload thing folder we have already created here on the left hand side and then a route dot TS this is so nexjs actually knows how to handle a request to this API because currently the core.ts doesn't mean anything this is just for or logic but the route Handler goes always in a route.ts this doesn't change even with upload thing if you were confused as to what the core.ts does now we still need a route.s we can just copy the code from here paste it in here and this is one of the files again we never need to touch beautiful we can save the route.ts and there's some optional stuff in the documentation right here that we are not going to worry about um at all what we still need to do is create an API key and did I just miss it in the documentation I'm not sure but we definitely need a uploading API key so let's go ahead and where do we generate the API key let's go into the dashboard and see if we get it here into our project I named mine quill then yeah right here under API Keys we get a secret key and an upload thing app ID so that's here on the left hand side under API keys and let's just copy these values head over to our EnV file we put our sensitive information in open up a new line and paste in or secret as well as or app ID and that's automatically going to work with upload thing beautiful now this core.ts let me quickly walk you through what the hell this is because this looks really messy if you just paste it from the documentation let's remove all of the comments and let me walk you through this step by step okay we can get rid of this the middleware get rid of that rid of that and also of this and we can get rid of all of this in the on upload complete as well so we just have this the auth we don't care about it we're going to implement our own auth in a second and let's just return an empty object from the middleware so this is a lot more readable now okay so as a basic overview what this um core.ts does is essentially the middleware will run when somebody has requested to upload a file from the um client right so let me show you what this looks like the uploading process right here so there is the user and the user makes a request to upload a file their PDF file let's move this over here that request will first be passed through the MW the middleware what we can see right here on the left hand side this is the Middle where I'm talking about let's move this into a side by side so we have a bit more space this request will first be passed into the middleware in here we should make sure the user is authenticated and if they're not then they won't be able to upload a file they have to be authenticated for it if they are authenticated then beautiful they are allowed to upload their file so the server and then once the upload is done there will be a callback function via a web hook that is the on upload complete we can see here on the left hand side so that will be called by upload Thing Once the file upload is complete so um the Callback off or let's just call it on upload complete and this is where we will add the quota to the user for example that they uploaded a file and we will do a lot of logic in this on upload complete and you will see that later this is really really important for our build that this gets called and that's what these two are for right as a basic rundown one runs before the request one runs after the file that's uploaded successfully and we can also see um that we only allow file uploads up to four megabytes we're going to customize this later so Pro users can actually upload larger files like um 16 megabytes or basically anything you want right this is totally up for you to choose M but 4 and 16 I think is a pretty um solid you know idea just leaving it at that okay with that said We Now understand what the core.ts does for us and we can now actually get started in uploading or file to upload thing and now very briefly for the middleware right we want to make sure that only authenticated users are able to upload files and the way we can do that is well you basically know how to do this by now we can destructure something from the get kind server session and what we will destructure is the get user to make sure the user is logged in and to get the user we say const users equal to get user invoke that function and if there is no user or oops not at or if there is no user dot ID in that case we are going to throw a new error and that is going to be unauthorized and just by throwing this error here on the left hand side upload thing will know that this user should not be able to upload a file you can just throw and that's totally cool and then from whatever we return we will get access to that in the on upload complete as the metadata already typed out for us in typescript so what we want to pass in here is the user ID and that's going to be the user.id and if I now hover over the metadata you can see if the user ID will also be there in the on upload complete and with that in place we should be all set for actual file uploads to work we can definitely leave the on upload complete empty for now so nothing really will happen once we upload the file but we should be set to start the final uploading process from our upload button where we want to upload let's open up the model and in here let's define the logic we need so for example we need a custom upload thing hook and this Hook is not exported by upload thing in the package because we still need to do some manual work for it for example uploading needs to know this type right here and it can't just infer that type in the package therefore it expects us to write this hook or self but it does provide the code we need for it somewhere we just have to find it in the documentation let's see if it's in here no it doesn't seem to be on this page where is that okay probably right here under API reference and then react we can see the code for it let's scroll down a bit and here it is beautiful so this is the generate react helpers we need right here okay so let's copy this generate react helpers and let's create a new file inside of the directory under source and then let's go under lib and create a new file in here called uploadthing.ts and the reason we are creating this in lib is because basically we are preparing a library to be used inside of our project that's what the lib folder is always for in here we're gonna see we are importing something from ads upload thing slash react which is also a dependency we haven't installed yet it is separate from the upload thing and we also need to install this one it's basically a toolkit for Apple thing to use with react and once that is upload or once that is downloaded the package we can start back up the development server and this syntax is not something we use in or App instead we use an at from add app API upload thing core this is where we get the type from it's this type at the very bottom of the core.ts we are importing here and also if we read out the window then the import error for the first import should be gone ideally and that's already all we need to do we don't even need access to the upload files we can get rid of that and let's save the upload thing lib file go back to our upload button and now we can actually try out if the uploading Works how we are going to do that is by destructuring something from the use upload thing hook we have just created and this takes in a string off or uploader let's quickly rename this from image uploader to a PDF uploader because that is more fitting so let's go into the core.ts and rename this to PDF uploader let's save that core.ts head over back to our upload button and now we can simply pass in the PDF uploader for the use uploading Hook from this what we are going to restructure is the start upload function we can now always call and if we hover over this we can see it returns a promise of upload file response or undefined so it's just an action we can await and where do we want to wait this action well of course in the on drop whenever a file is dropped we want to upload it immediately and the way we can do that is by saying corn stress is going to be equal to a weight start upload and what do we want to upload well the accepted file right here beautiful if there is no response however if not rest then we want to show an error message to the user and of course we could just alert this in the window or whatever but that always looks really bad we got console log it but no user is going to look in the console therefore we want a toast notification and or UI library or trust the UI library from chat CN makes those notifications really really easy for example these are the tools notifications in the bottom right right here that's how they will look like and they even have a destructive variant so like a Red Tools notifications that something went wrong and installing these is actually super straightforward so what we need to do is just copy this command or type it in yourself if you're not on this page right now and that is npx shared cnui at latest ad to host you can just paste that into or um CMD or we can use npx for that let's use npx what am I doing here let's remove all of this let's clear the screen and now let's say npx Shad cn-ui at latest add toast hit enter that's going to install all the stuff we need and also walk us through how to set up the tools notifications which is as easy as adding one single jsx element to or root layout so once that is done installing let's head over to our root layout and in here anywhere I'm gonna do it above the nav bar I'm gonna put the toaster element we get from our custom component we have literally just installed together beautiful and that's all we need now what we can do in the upload button is get the function we need to send out a toast notification or to display it and we can simply destructure this from the use toast hook that we get by installing this toast component so the function we want is the toast right here that allows us to call it anywhere like in the if not rest part right here so in that case we can just return a toast notification and this toast always takes some props for example you can see right here there's a title there's a description of the toast and that's what we want to pass in here as the title we are going to pass something went wrong and then as for the description we want to pass please try again later and as the variant we can pass in this is going to be this truck this truck Tiff there we go that was hard to spell and we're not getting any intellisense right now so let's quickly reload the window and hopefully that will fix all that error so if we typed in something wrong in here then it will actually tell us nice now we get beautiful intellisense and we handled the case where there's no response from the upload um to the server great let's open up or up here on the right hand side just so we have something nice to look at and now we can actually get the response so the const and then in angle brackets because we are destructuring from an array directly the file response we can call this anything we want is equal to the rest so we are essentially taking the first array element of this response which we can because this is of type array right and in here we can get the key and the key is generated by upload Thing by the service we use for uploading the cons key is going to be equal to the file response dot key and this is how we can later identify it against our database and now even though it says the key is always a string we just want to make sure this key always exists so if there is no key for any reason it doesn't even matter we want to also return a tose notification so let's just copy and paste it from up here from the not rest case paste it in the not key case and we don't even need to change anything up let's just leave it as that so we can always make sure we definitely have a key proceeding because this key is absolutely essential because we now need to pull or server to see if this image is uploaded successfully because we don't really know because the thing is even though this function could be awaited and it was successful no errors whatsoever this still doesn't automatically mean that our file is actually in our database and uploaded to the uploading servers so we need to do a polling approach to check if it is actually in the database so what polling is essentially let me tell you so in the polling approach we're going to use let me show you how this works we have or service or app which is named quill on the left hand side and then we also have the API or API on the right hand side and what polling is essentially the principles essentially every you know like 500 milliseconds we're asking is the file there and or API will tell us if it's there or not by checking against the database and if it's there we're going to get a success response in the app and if it's not there then we are going to keep polling until the app is there which generally only takes a few milliseconds but it is asynchronous so we need this polling approach to handle it so essentially we're asking the API until we get a successful response and we are sure that the image is there in our database and actually uploaded successfully to the upload thing servers and this porting approach is actually really easy to implement let me show you exactly how so because we're going to use an API route for this right here on the right hand side let's create it in our index file for trpc and this route we're going to create together right now let's call it up here get file because we get the info of a file we want this to be a private procedure so only logged in users can call it and this needs an input so essentially again the post request body is what this input is and as for what the input is this is going to be a z dot object I've explained earlier what this does essentially we are making sure that the input we get is of this certain schema and we want a key property which is a z dot string you remember earlier when I mentioned that the key is important for checking against our database well this is where we need it and as for the actual business logic we can chain a mutation to this and to Define what should happen in this API route the mutation takes a callback function in which we can handle the business logic right here but before we do that we also let's add a comma first so we can get syntax formatting right here and before we get into the business logic in the mutation let's destructure the context and also the input from it and turn this into an asynchronous function just by adding the async keyword in front of it beautiful the thing we need is the user ID and that will come from the context in this API route now we need to check if the file is in our database because that's the entire purpose of this app we want to make sure that the database is synced to upload thing right and the way we can check if the file is in the database is by saying const file is going to be equal to a weight DB dot file dot find First and we want to find a file that matches the key we pass in to this post request or this uh you know whatever request this is just a mutation and also that is owned by the user the way we do that is by using the where Clause with Prisma so we can check for certain conditions of the file and the key should be the input dot key and of course it should only be searching for files that is currently logged in user that's why we have the private procedure owns I shouldn't be able to check you know your file status and you should be able to check my file status security wise that would probably not be the best idea not a huge threat either because you know this information is not really sensitive but you know if we can avoid it I think we should okay and if there is no file in the database yet um because of the asynchronous nature that is not unlikely to happen we are going to throw a new there we go trpc error and this error is going to be with the code of not found so essentially a 404 error we're sending right here and else if there is a file that's what we're going to return to the front end so what we essentially just did is finish this entire part right here um okay and that doesn't look really good what we essentially did is finish this entire part and I didn't want to do that either Jesus finish this entire part in the API rod that's a really really good step this is completely done now and now we want to shift over to the left hand side the front end side and that handles the polling right that makes the request so this API route right here on the right hand side so let's worry about this part the front end how do we Implement that let's go over to our upload button because that is where the magic will happen and by the way we can close out of all the other files um and just worry about the upload button for now so how do we handle the polling in here how do we make sure that the database is synchronized with the um uploading servers how we can do that is in a on success callback we get from trpc import that dot and we can already see the API route we have just created the get file and then a use mutation because this is a mutation and this mutation always gets an on success callback in which we can handle the case that there is a file now and once we are sure that there is a file let's go to our trpc index um so it's essentially in this case there was a file found we are returning it back to the front end in that case we can simply push that file into the URL and redirect the user to the detail view so once the upload is done we want to redirect the user and that is what we can do in this on success callback right here to do that we are going to need access to the router the cons router is going to be equal to use router and again very important get it from next slash navigation and not next slash router because that's for an extra S12 and in here we can simply say router dot push to redirect the user to a template string so we can dynamically interpolate values to slash dashboard slash and then the file.id dynamically and we of course also need to receive the file in the Callback right here as the first argument beautiful so now we are redirecting the user if there is a file however what if there isn't a file yet which is not uncommon because right away this probably will be undefined and then like half a second later it will actually um return the file right but let's handle the case where there is no file essentially we want to poll until there is a file and with trpc I'm doing that is actually really easy you can simply pass it a retry property of true which means it will retry indefinitely until we get a successful response which is exactly what we want and also we can pass it a retry delay of any delay that we want I think 500 milliseconds half a second is a pretty good idea and the only thing that's left to do now is trigger this in the first place this won't run on render because this is a mutation we explicitly need to call the mutate function to make this start polling so let's give it a name let's say start polling and we can simply destructure it from the mutation the mutate function right here let's copy the start polling now the million dollar question is where do we start this polling and the answer is as the very last step of the on drop because when the file upload is successful we awaited it we have all the properties we need there's no Edge error case or whatever then in that case as the very last step we want to start polling and of course this expects the key of the file we have just uploaded to start porting the database if it is in there great we can save this upload button and one final thing we need to do to make this uploading functionality work is also pass the get input props to an element right here in the model we haven't done that yet and that's a really easy fix let's go in the jsx right here below the is uploading conditional check and let's simply adds an invisible input right here the type is going to be of file the ID is going to be the Drop Zone oops Drop Zone Dash file the class name is going to be hidden so users won't even see that it's there the important thing is that we spread in dot dot the get input props from above just like that let's give it a bit more space and with that invisible input we are all set now let's try out if this actually works right that's the most exciting part let's give it a bit more space let's read out the page and of course start back up the dev server after adding the toast I forgot to do that let's reload the page and hopefully the file uploading will actually work the file should ideally show up right here in or uploading dashboard and also in our database uh and just as I said that it should also show up in our database I realized it can't show up in your database we don't we don't have a function anywhere that adds the file to our database and that is exactly what the on upload complete is for when the file upload is complete this is where we handle the logic of adding stuff to our database so for example you can say um you know we can just add the file using Prisma right here for example let's say cons created file is going to be equal to a weight DB imported database dot file dot create and now we need to pass the data that we want to create the file with so let's pass in the data the key is going to be the file dot key because the beautiful thing is we get access to the file that was uploaded right here inside of the on upload complete which is really really convenient the name is going to be the file dot name just like this the user ID we want to save with this file so we link the user to their file is the metadata dot user ID that we got passed in by the middleware so this beautifully comes together right now as the URL okay now it's going to get a bit interesting as the URL we're going to use a little trick because the thing is while I was developing this app usually the file also comes with a URL but I had the issue that sometimes this just times out which it shouldn't but for some reason it did I talked to the developers and they are notified about this issue but a really simple and always working fix that we can use is to just directly get this image from AWS S3 which uploading uses under the hood so as for the URL let's type in HTTPS colon slash slash then upload thing Dash prod for production dot S3 3 dot us-west dash 2 dot Amazon aws.com slash and then dynamically insert the file.key this is always where this file is hosted this won't change don't worry about it and this is just as good as the file.url with the only difference is that this always just works so this is a really good idea okay and last thing as the upload status we can just insert um processing because we will be processing it further and then later add either a failure or success state to it and now comes the big reveal does it work does it not work the dev server should be up and running and with that what we expect to happen now is that when we drag in a file here the loading power will work and it will also add the file both to our upload thing dashboard which is right here under files and also to our database that's the goal right now so let's see if it works the big reveal I'm going to drag in the file oh and it says something went wrong try again later okay that's something we will have to debug yeah that's that's not ideal why does this happen let's investigate let's go over to the uh on drop and take a look and there we go it literally tells us what the problem is right here in our CMD Arrow file type PDF not allowed and that is because in the core.ts in upload thing we can Define exactly which file types we even want users um to be able to upload and currently we only have an image selected however there's a bunch of file types we could allow and of course the one we want to go for is PDF users should only be allowed to upload PDFs and with that changed um in our core.ts the dev server is reloaded let's try that again let's click this field and let me open any PDF file here on my right hand side let's go with the lecture notes drag them in here and let's see if this is working the status does seem to working the progress bar and on the left hand side we can see we've successfully simulated callback for file the polling seemed to work because we were redirected to the detail view to the dashboard slash and then the file ID with the on success callback we have at the very top of the upload button this one right here navigated us to The mdtel View that is what I call Beautiful let's check in the uploading dashboard if the file is actually here and it is awesome we can see the file size if I give it a bit more space right here about a hundred kilobytes which is totally fine it's under or file limit of four megabytes and also what we should see is the file in our database let's open up a new terminal right here and type npx Prisma Studio that's going to open up our database um you know interface we can interact with to see what data we have in our database let's open it up and it should show the lecture notes.pdf with a status of processing and the URL right here everything works congratulations I hope it does for you too if there's any questions if I lost you anywhere on the way um either consult the GitHub repository or go to the Discord if you have a specific question or error right here um we do have a gist a a Discord channel for this project where you can ask questions and get answers always um but I hope it works for you it works really really nice we get the image in the dashboard and in the database which is perfect now we're almost done with the upload Button as you can see it does redirect us and everything works um let's go back to the dashboard for a second because one thing I've noticed is that this bar is always shown this progress bar and it shouldn't always be shown so it should only be shown when we are in fact uploading and that is because if you remember we set the state of its uploading M to True by default just to mock it out of course we don't want that and um I think we are good to go uh one important but small I mean one small but really really important very nice thing we can add right here is in the is uploading conditional check below the progress bar in the div of it we can do something really really neat and actually it goes into the same div where the progress is in inside of here so we can do a check if the upload progress is triple equal to 100 if we are done uploading then we want to show a redirect message to the user to let them know exactly what is happening for the best possible user experience let's give this div a class name and this class name is going to be Flex a gap of one and by the way we can already complete the ternary if this condition right here is not true in that case of course you know the drill by now we want to render out null okay so the flex Gap one we want items Dash Center let's give it a bit more space so you can see the full class name here a justify Dash Center a text of small a text zinc of 700 then we want to give it a text Center let's go at the end here text Dash Center and lastly a padding top of two and inside of this diff we have just created right here let's oops let's open that up inside of here goes a little spinner icon we want to display to the user and that is once again the Loader 2 icon we get from the seed react and the class name we're going to give to this loader is going to be H3 a width of three and an animate spin property so it spins and we're just gonna say redirecting period period period right below that and that is about to wrap up or upload button let's move this into a proper side by side and give it a proper final test let's read out the page and see if everything works correctly we can already delete all the files in here let's upload a new PDF I'm gonna drag in the lecture notes and let's see if everything works correctly the status bar is updating nice it's at 100 it says redirecting now the final detail we can add I admit it's a bit nitpicky but I do think it makes the user experience much better when the file upload is at 100 we can change it to turn from blue to green and users will really appreciate that small detail okay and the way we can do that is by by slightly modifying this progress bar that we have in our app so let's navigate into this UI component that was installed for us by the UI Library we are using under components uiprogress.tsx this is where we are and one thing we can do is accept a custom property in this component and that can be the indicator color so whether it's blue or green right so inside of the props that we are destructuring at the very top right here don't worry if you don't really understand what's going on if you're not too familiar with typescript um don't worry about the top here we're going to walk through it step by step together inside of the destruction right here let's add one property and that is going to be the indicator color so we can change it from any component where we call the progress and let's create a type for this indicator color so the type progress props the properties we pass into this component are going to be equal to react Dot and then component props without ref because the ref is declared separately and then in here comes a generic we pass that in these I'm not sure how these are called but the HTML kind of brackets the not angled but like the triangle brackets I guess and in here we're going to pass the type of progress primitive dot root okay and we're gonna extend this generic with an and sign and then a custom object so we're taking the base interface of this and extending this by the custom prop we want to have in this component which is going to be the indicator color and this is going to be optional that's why we have a question mark and of type string just like this and now this type is currently unused and to actually use it we can simply replace this part right here with our progress props so essentially we took what was here and extended it by a custom indicator color and by doing that the error is going to be gone beautiful and now we can actually use the indicator color um in our app and where we want to do that is right here with the progress primitive.indicator now if you don't know where these Primitives come from they come from Red X UI which is an unstyled react UI library that shared CN or UI Library just Builds on top of and you see if we want to overwrite the color of the progress bar this BG primary right here is what we want to override and to do that we can do something really cool with our custom CN class name function we can simply use it and apply all the styles that were there already by default so nothing really changes unless we merge it with our custom indicator color so only if we pass that then the style is going to be overwritten or else everything is going to stay as default and nothing changes so it will look always good but if we want we do have the option to overwrite the indicator color beautiful and let's actually do that let's switch over to our upload button and overwrite the indicator color the way we can do that is by passing the indicator color prop which we can now and this is going to be dynamic so if the upload progress is triple equal to 100 if we are done uploading then we want to pass a background green oops of 500 and else we can just pass an empty string because again all the defaults are already handled in the progress bar and we're not going to overwrite anything this way beautiful let's save that and we are officially done with our upload progress let's reload the page to get a fresh version of our build click upload PDF and you can even pause the video and test this for yourself everything should work perfectly right now and let's try it together let's drag in a PDF file once again the lecture notes are super handy for this and let's try out if this works you can see the progress bar it turned green and it said redirecting if that is not a really clean beautiful UI interaction I really don't know what is okay and with that out of the way we can actually get started in the dashboard because we have everything we need we have the file uploaded it's in upload thing it's in or they in our database exactly so now we can actually get started in building out the PDF renderer and render the actual PDF right here okay to do that let's close all the windows let's give it just a tad less space let's go into the PDF renderer right here and let's get started building this out so do you remember we were at the top bar and this is where we left off okay and if we go back to how this should look um it was somewhere on this page let's zoom out there it is um so the PDF options like um you know rotating the PDF and zooming in and whatnot only makes sense if we are already displaying the PDF so maybe I got a bit ahead of myself here and we can't really see the result of rotating the PDF if we're not rendering it up rendering it out and beforehand so I say we work on that first we actually um render out the PDF and then we get into the you know options at the very top so let's get it let's do it let's go with one closing div below because this is gonna go right below the um the diff with a height of 14 this is where we want to start developing right now let's create a new div down here and give it a class name of flex dash one to take up as much space in the flex container as possible a width of 4 and a maximum height of screen so it takes up at maximum 100 view height units and never goes um above that so it doesn't take up more space than the actual screen okay inside of here let's create a new div and we're gonna need this diff for later we're not going to give it a class name right now but this div is going to be very very useful here in a second you're gonna see that inside of here let's create the renderer what's going to display the PDF file actually and we do that in something called a document in this document we get from the PDF Library we have installed so let's go to the very top of the page right here to the Imports and import something as a named import a document from and the library we're going to use for this is react Dash PDF which we've already installed okay now very important if we go to the react PDF npm page you're going to see a certain CSS that we also need to install in our project or well not really install but just import into our app and we will find that somewhere on this page let's look for it and okay we still need to do this we have to remember that we also need to import a worker but let's search for these CSS first should be summer on this page let's say dot CSS there we are okay so support for annotations um it just makes the PDFs look better and handle some edge cases so we do want to import two things in here and so navigate to the documentation just copy and paste them or if you want I can also put them in the copy paste list um so you if you have that open on your other monitor you can just copy it from there The annotation layer and the text layer.css go ahead copy and paste them into your PDF render for some edge cases to work and then next thing we want to do is to import a worker so what does that mean the thing is a PDF is very very different to just an image we can't just render it out we actually need something called a worker in order to render it but in order to understand what the worker does for us I say we don't import it right now and instead just finish up the document so you'll see why we need it instead of just doing it and then you're going to be like oh why did we do this no I want to show you why it's so necessary okay so let's finish the document and try it let's give it a class name this document of maximum height of full then let's render out the pages inside of this document we do this by importing the page also from react PDF this goes right inside of the document and this page um can actually be self-closing we don't need any children inside of here and this page takes in a page not index but a page number and let's just leave it at one for now just hard code it now this document takes something called a file and then here we need to pass the URL of the file that we want to render out which we already have in our database but we don't have this URL in this component so where does this file URL come from well if we take a look at no what it what did I just do nope if we take a look at the page.tsx inside of the dynamic route of file ID we can see at the very top we have access to the file here and we are rendering out the PDF renderer as a child so what that means is we can just use it as a prop let's receive the URL inside of the PDF renderer and we can also declare a type for this URL let's call it something along the lines of PDF renderer props and declare that interface up here interface PDF renderer props and in here declare all the custom properties that we receive which is only the URL as a string so now we expect the URL and we can pass it already into the file that the document takes however we still need to remember to pass it from the parrot so the URL in here is going to be equal to the file.url that we are fetching from the database let's save that and see what happens okay so we can see module parse failed unexpected character and then some weird character we don't know it says you may need an appropriate loader to handle this file type currently no loaders are configured to process this file this is why we need the worker and this is also why we need to go into the next dot config and change one small tiny detail up as I mentioned earlier PDFs are nothing like images we cannot just render them instead we need a custom webpack config inside of our next config and don't worry it sounds complicated it really isn't we can just use the web pack right here and that is a function in which we can receive the config and then also destructure some things like the build ID for example the dev the is server the default loaders just follow along with me here we're going to do it together it's only like three or four lines of code and then lastly the web pack we can destructure and in this function right here inside of the body we are going to say config dot resolve.alias dot canvas is equal to false let's go ahead and copy that and secondly the config.resolve.ads.encoding is also going to be equal to false and then lastly we're going to return the config just like this and that's all we need to do for webpack to work with this let's save that and see if the error message changes and it doesn't it will take a bit to reload but we still get the same error message and the reason is we haven't imported the worker yet now how do we get this worker to work as you can see here on the right hand side fail to load PDF file and it just doesn't work and this is precisely what I wanted to show you we need the worker now we could just type this out it's one line of code but how about we just go into the copy paste list and go to the PDF renderer section where you might already be and just copy this line of code that helps us import the worker we need to properly render out this PDF file we can just paste it in here and this expects something called the pdfjs Josh where do we get this from well we can simply also import it from react PDF just like this let's save this and see if our file is being displayed correctly so let's give it a shot it says loading PDF okay and interestingly enough it still says failed to load PDF file um let's see let's do some debugging here together let's see if we get any error we don't get any in the console let's see if we get any in the client console let's open it up up inspect and go to the console right here okay um server responded with a status of 404 not found oh and without even taking a minute to debug I think I already know where the issue is and that is this of course needs to be a client component we forgot to add the use client directive at the very top and I hope this is going to be the issue let's reload this page and let's see there the PDF is okay that was the issue of course we can't just render this on the server this needs to be client-side for the worker to work um so just add the use client directive at the very top and there it is that is our PDF file really really nice awesome and with that out of the way let's add some quality of life changes to this um document so one thing for example we can define a custom loading state which is really good we don't really want the what does it say loading PDF dot dot to show we want a custom nice looking loading set of course the way we do that is by using this loading right here and this takes in some custom jsx like a div we're going to pass in with a class name of flex and justify Dash Center in here comes a loader 2 or trusty spinner icon we get from Lucid react with a class name of my 24 give it a bit of a margin on the top and bottom a height of six a width of six and animate Dash spin because else it wouldn't even spin great if we have an error any error by loading this we want to notify the user and we can do that with the on load error so if anything goes wrong we will be informed and what do we want to do well we want to render out a toast notification the way we can do that because this is a client component we can easily destructure the toast function from the use toast hook we have in our custom UI component we installed using chat CN and just call this toast notification message and say something along the lines of title um let's say Arrow or loading PDF as the description we are going to pass please try again later and lastly as the variant we want this to be red to indicate something went wrong so we are going to use destructive as the variant and now you might say Josh why do we even bother with the loading state in the on error right and it is because these make all the difference users care about these tiny details they will notice if there's just an ugly you know loading PDF dot or if there's a really nice loading spinner and of course I need to save this to demonstrate it to you that loading spinner makes all the difference it looks so much better and we handle all the edge cases with the um on error users will thank you trust me okay great and with that out of the way we can actually get started with the top bar like for rotating the PDF file and so on but one thing I want to fix beforehand is do you see how this looks when we make this page really wide then of course we want the PDF to fill out the entire space there's just a huge wide you know blob here that is not used and that looks really bad we could just make the PDF way larger it doesn't really fit the page very nice and there's a fix we have for that we can use so let's give the code um more space once again and there's a really nice Library we can install and that is why do you remember earlier when I said this div is going to be important now it is going to become important to make this PDF fit to the page let's install one very lightweight tiny dependency and that dependency is called react npm install react resize detector there we go hit enter it's called react resize Dash detector install that I'm using version 9.1.0 let's run back up the dev server and now I'm going to show you what this does for us because it's really lightweight it's a very simple functionality but it's going to make the PDF look so much better we are going to destructure something from a hook called use resize resize detector and invoke that now this hook the Auto Imports of course don't work let's reload the window and see if they do work and if they don't we're going to import it just manually from the package we have just installed let's give it a second for the window to reload and of course it doesn't work okay whatever let's import this I believe it is a named import called use resize detector right here and this comes from the package we've just installed react resize detector just like this and there are two things you want to destructure from here which is the width and also the ref element we can use to assign to any uh element and we're going to use the div where I said this is going to be important later this div is exactly where we are going to assign this ref and what that means now because we linked this ref to this hook we now get the width of this element saved inside of this with constant up here which is super useful because this width we can now use down here in the page to determine the width so we can give the page exactly the width it needs to take up on the screen and we can pass in the width and prop into this page and if we have the width if it is defined then we are going to use that and else we are going to use one because this could be undefined right here that's why we do this ternary operator check let's hit save and let's see what happens on the page if we start to pack up the dev server it is running let's read all the page and see what happens ideally this p PDF should take up exactly the space it is um you know it can take up and it looks so much better this way now we can still scroll on the page which is not ideal we're gonna fix that later but the PDF now fits to the screen and that looks really really good awesome let's give it a bit less space and the code a bit more space because there's some stuff on the page we still want to do but first off I say we continue on the top bar it looks really boring with just saying top bar so I think it makes a lot of sense to continue this top bar first where we have functionality like you know zooming in and rotating the page awesome so the first element we want instead of the top bar text is going to be a button so let's say button is going to be your custom UI button from the UI library and we're going to give it a label area Dash label I'm going to show you in a second why we do this and let's call it previous page and inside of this button we are gonna have a Chevron down icon from Lucid react and give that a class name of height 4 with 4. now the reason we use this area label is because the only thing in the button is an icon and if you have a visual impairment for example and use a screen reader an icon doesn't tell you anything on what the button does people with a screen reader can't tell what this button is for therefore we need to add a label to tell those people what this button even does of course if you can see the icon then you might know what it does if you can't see it you won't know and that's why we have the icon if we save that we're going to notice the button is blue that's a bit over the top we don't want that let's instead give it a variant of ghost so it's way more subtle very Like Only If You hover over it you're actually gonna see it it looks way nicer and then inside of this button we want to update the page in a second however first let's even tell the user what the current page is so this will be the button to navigate down a page and in a second we're going to create a button to navigate up a page let me show you what this means below the button let's create a div with a class name of flex items Dash Center and a gap of 1.5 and inside of this diff we are gonna use a input component however that's nothing we have in our project and it is pretty much like the button it looks good by default but it is nothing more than a styled input to do this we are going to use orc UI Library let's say pnpm or actually let's use npx for this is a bit easier npx Shad CN UI at latest add input this is a accessible input that looks good by default in mattress or color scheme we have defined in the app we only need to run the CLI command it's already done and that's all we need to do in order to be able to import this input component right here it is going to be self-closing we are going to add a bunch of properties in here in a second first off let's add a class name and this class name is going to be width of 12 and a height of 8. there we go let's save that let's start back up our Dev server so we can actually see what we are even doing here and save that give that a second to load we can close out of some pages in here too there we go and we can see a really nice page input field right here which we're later going to use to be able to input like a custom page like a 5 hit enter and that's going to take us to the page in the PDF that we want to go to and next to this input we of course want to display the user the how many if page that is so for example three out of and then like the entire PDF has five pages for example how do we do that how do we know how many pages this PDF has well let's first off create a P tag right below the input and give it a class name of text zinc 700 a text of small and a SpaceX of one inside of this P tag let's create a span that says slash that is going to show up on the right side of the input right here and one last span element in here and then here we go the number of pages let's put a five in here for now that's where the number of pages will go but where do we know the number of pages well let's save that in a state so right up here let's say const let's call it num pages and also by convention set num pages is going to be equal to a u State we get from react and by default this num pages should have a value of undefined right we don't know how many pages we have but once we render the document it's going to tell us how many pages we have and we are going to save that as a number like five pages 10 pages and so on so what we can do to get the amount of pages in here correctly is go way down to the document and this document takes something called the on load success not just or not on load success there we go this receives a callback function and from this callback function we can directly destructure some values and the one we care about is the num Pages we get from the document and the only thing we're going to do is to set the num pages to that num Pages we get from the rendering of the document perfect so that means we now have the correct value of the number of pages inside of the state and therefore we can now use it right here in the um span element that we just created so in the span element let's insert a dynamic value and this is going to be the num Pages we could leave it at this right this will work it will show three pages which is correct this PDF has three pages but one thing I want to point you to is while this is loading pay attention to what this three becomes it's just a slash and then an empty value and for a loading State we can do better that's why we can add a question mark question mark So if this evaluates to null or undefined which it will during the loading state in that case we can just render an X so instead of showing nothing we can show an X right here which as a loading State I think is a lot cleaner awesome that's how we can display the number of pages now all that's left to do is copy down the button let's copy this go below the div containing the input and the P tag right here with two closing divs to go afterwards and then paste the button in here once again and this button is going to receive a different area label that's going to be the next page and instead of the Chevron down we want a Chevron up we also get from Lucid react let's save that and that looks much better however there is no functionality yet this doesn't really do anything it doesn't change the page of our PDF when we click it to keep track of the page that we are currently on we're also going to do this in state so let's call this Cur page for current page and also set color page you know the drill by now and this is going to be a use state which contains nothing else than a number and by default we want to be on the first page so we're going to pass in a one and just because I like it once again I'm going to say number right here awesome okay so the current page is going to be one by default and now to synchronize the page that is shown in the document to this state number right here in the current page let's copy this value per page and go way down to where we render out the page and this takes in a page number prop right here oh we already passed it that's why we don't get intellisense we hard coded it one but this needs to be the curve page state that we have have up above and now using these buttons to increment our decrement the page is easy as cake the way we do that is let's go up to the button and add an on click listener so what should happen when we press this button right here the thing we're gonna do for the previous page is we're going to set the Cur page and we receive the previous value in the Callback as the first argument or parameter whatever it might be and we're gonna set a conditional or we're going to do a conditional check in here if the previous value minus 1 is larger than one then this is a totally fine action right in that case we're going to return the previous -1 but what we don't want to allow is to go below the first page right there shouldn't be a zero or minus one page that doesn't make any sense so if the previous value -1 is not larger than one it is an invalid an invalid value and we want to set it to one instead so you cannot go below one that's the entire and purpose behind what we're doing here on the button and let's also just disable this button disabled if the current page per page is lower or equal to one in that case we shouldn't even be able to click the button so right now it would be disabled because we're on the first page beautiful a very similar thing we're gonna do for the second button this is also going to get an on click event right here with a callback and in this button we're going to handle a very similar logic just the opposite way around so we're also going to set the current page and receive the previous value however if the previous Value Plus one in this case is larger than the num of pages in that case you want to set it to the number of pages so the upper limit like three in this example because it would be an invalid value else we just want to let the user increment this now we do get an error here type undefined is not assignable to type number which means that the num Pages could be undefined right in this case let's insert an exclamation point or tell type script we always know that this will not be undefined don't worry about it and if we left it at this it could be undefined and we might get a really serious error right here however what we're going to do is to disable this button if the value is undefined so the disabled set of this button is going to be if num pages is triple equal to undefined or if the current page is triple equal to the number of pages we have in total so that means we can always know that num pages is going to be defined when this on click fires and just like that we implemented the page functionality where we can switch up and down between pages in or PDF renderer beautiful what we can't just do is with this field right here it doesn't display the current um the current PD F page if we enter a number and hit enter it doesn't do anything that I can just type anything in here and that's not a deal of course if we type in a 2 here and hit enter we want to go to the second page and if I type in something invalid here which doesn't make sense and doesn't represent a page on the PDF we want to let the user know that this is an invalid value and they can't go um to that page and to make this happen the logic of this right to check if it is a valid page and so on there is a really really nice dependency we can use and that is called pmpm install react hook form let's hit enter if you've never worked with react hook form before you're definitely missing out and you'll be really happy to have it learned in this video I use it in almost all my applications where there's any input logic involved it is super useful in making sure that the input that the user gives you is valid and makes sense let me show you exactly how this works at the very top of a file where we Define these states and so on let's destructure something const empty object is going to be equal to a hook called use form and this use form hook comes from react hook form the package we have just installed let's import use form from react hook form there we go and this use form is nothing else than a function we can pass an object and the question is what goes inside of this object and to you know find out what goes inside of that object let's first think about which input values are acceptable right it should be a number the only thing that's valid for this input is a number between 1 and the upper limit of how many pages we have so for example one two and three in this case everything else like text or A5 should be considered invalid and the way we Define that in code is through a schema validation Library let's call this custom page validator cons custom page validator is equal and we're going to use our schema validation Library called Zod to um depict this logic so this is going to be a z dot object and the value we want inside of this object to allow is the page value we can call this anything we want I think page makes a lot of sense and this is going to be a z dot string and it's not going to be a number because when we input something in an input field it's by default going to be a string and not a number that's why this is also a string and we can now refine this value by chaining the dot refine we can get access to the value the user has entered as the first argument of the Callback function that the refine takes and what we want to return from here is we want to convert this input which is a string to a number so we can say number and wrap it in there and we're gonna do a check if it is larger than zero well and this is by the way what the user has input so if I type in a 5 here the number right here is going to be 5. and we're going to do a check if the number is larger than than zero and if the number and pass in the num once again is smaller or equal to the num Pages we can just assert that this exists as well in that case and in that case only this result is going to be considered valid because the number is between 0 and the upper bound if it's not this is going to be false and the user will get a notification a you know a red outline around this field that this is an invalid value like if I typed in text we shouldn't be able to do that of course and to Now link this with this use form to use it in our application and have the logic actually apply to the field first off what we need to do is to get the type from this and with like the typescript type and with um Zod that's really easy let's call it t for type custom page validator right below this is going to be equal to and now we can infer the typescript type from this Zod type by saying Z dot infer and pass in the type of custom page validator just like this and that will turn this entire thing into a regular typescript type that we can now use for our use form right down here and pass in and this type as a generic so let's copy it the T T Custom page validator and pass it into the use form in this generic syntax right here so the use form knows what kind of data we are expecting the user to type in inside of this use form we can now pass default values and because we passed in this generic we now get beautiful intellisense on the default values we know we have a page property this page is going to be a default string because again an input is always a string and let's put in a one by default because that's how the user starts out okay and now to link this form and the logic that we have for the input field to this custom validator to make sure that the input the user makes is acceptable we need something called a resolver to link these two together because right now they are not linked together at runtime only through the typescript type but we can't enforce any logic through the typescript type so to link these together we need to install one more package and that is called pnpm installed at hook form slash resolvers that's at hook form slash resolvers go ahead and install that using your favorite package manager you can start back up the dev server and then let's go to the very top of our file and import the Zod resolver we can use to link the form library to the schema validation Library that's going to be the import zot resolver from the package we have just installed at hook forms resolvers slash Zod that is where we get it from and we can simply use this to now connect or form by passing in an option inside of the use form and let me switch back to a German keyboard right here we can simply pass the resolver inside of the use form and this is going to be the Zod resolver wrapping or custom schema validation type right up here now what you might encounter depending on when you're following along with this video is that this gives you an error and this is actually a known bug you didn't do anything wrong this is just a bug that is talked about on GitHub one one very easy fix we can do to get rid of the spark is to install a different version of Zod so follow along with me here go into your CMD and install a version of zot pnpm install Zod at and this is going to be 3.21.4 and that version everything works um this is just a known bug that gets fixed in that version so just rolling back one minor release apparently they accidentally introduced a bug in the newest version which might already be gone to be honest when you're following along with this video depending on when that is anyways the bug will be gone we successfully linked or form validation library to the schema validation library and now we can actually you know make use of this use form there are a couple of things we want to destructure from this use form namely the register function and if you've ever worked with react inputs you know normally to make them controlled we have like a value and an on change in the input and the register takes care of all of that for us under the hood we can just spread in dot the result of the register operation and this takes a string and because we're using this with type with typescript we get beautiful intellisense on what we can register this input as that does all the stuff for us like the value and the on change and stuff and we don't need to worry about any of that let's also destructure something called the handle submit from this um use4 we also while we're here can destructure deform State and what we want from that are the errors so we can get them right away like this in the syntax and lastly the set value we're going to need that a bit later and to synchronize this input state to the current page properly okay with that out of the way let's worry about the handle submit first where do we use that and this goes inside of the input right here so inside of the inpos we can say when we press enter we want the page to apply so if I type in A2 press enter of course we want to be taken to the second page to make this happen we're going to add an on key down listener on the input we can receive the event as the first argument inside of this callback function and if the E dot key is triple equal to enter if we're pressing enter in that case we want to handle the submit so we can say handle submit and let's create a custom function called handle page submit to handle that and also remember to invoke this don't forget these two parentheses at the end to actually call and invoke this function now this handle page submit doesn't exist yet so we need to create it let's do it way up here and let's create the const handle page submit to handle the page submitting logic this will receive a page and this page is going to be of type T Custom page validator custom page validator the type we have defined above and inside of this function all right here inside of the handle page submit we want to do two things first off we want to set the current page the curve page to the number of the page that we get right because this function will only ever be called if the input is legit and legit meaning it passes this check right here so if the number is between 1 and 3 only in that case this function will be run or else the user will get an error message which is automatically handled by the form validation Library we don't even need to worry about that okay so we can assume that this is a correct input right here so we can also set the value the input value right here of the page that we get beautiful intellisense for to the string of page the number we get passed into here like a one two three whatever and we're going to set the input value to that value right here as a string again because inputs are always a string now to give the user a really nice feedback when they type in something wrong let's go down to the input right here and lightly change the class name so instead of the hard coded width of 12 and height of 8 we're going to use a dynamic value and use our CN helper function to achieve this we're going to pass in what we had before as the you know the default Styles right here that are always applied and if there are errors and the errors are for the page input which this right here is because we registered it as such if we have errors for this input which is our only input then we are going to apply a class name and this class name is going to be outline red 5 hundred let's save that input some bogus that doesn't work and hit enter and unfortunately it doesn't work okay and once again after like two minutes of debugging I found out that um if we actually just needed to restart the dev server and that was the fix wow um okay never mind let me get rid of the two console logs I inserted here and just make sure you restart your Dev server for this to work and also one thing we want to do is change the outline that wasn't the outline but instead let's do a Focus dash visible and then a ring bear red Dash 500 let's give it a bit more space that tends to look better and because the whole thing is going to be red in that case so we're going to start out on page one if we enter page two and hit enter that's going to take us to page two and if we enter some bogus that is not a page then this is going to turn a beautiful red to indicate that this is an invalid value perfect this is exactly what we want okay beautiful the only thing that's left to add in the top bar right here in the orange part now is the zooming and the rotation right so how about we add the zooming first let's give this a bit more space and let's go down a tad below the button right here and below the closing diff below it remember this closing diff right here is still for the top bar you want to stay inside of it but begin a new item in here with creating a div and let's give that div a class name of Space X of 2. inside of this div we want a drop down menu to handle the zooming States right there should be like a selection of a hundred percent 150 200 and so on on how far you want to zoom in and we are going to create this using a drop down menu a beautiful accessible one is already provided to us by our UI Library let's install that it's npx shuts the end-ui at latest add and this is called drop down uh is it menu um let's go to ui.chat cn.com to check let's search for the drop down menu and see the command to install it yes it's in fact drop down Dash menu all right that's npx chat cn-ui at latest ad drop down hyphen menu hit enter on that that's going to do all the workforce to install a beautiful accessible drop down menu it's going to take a while and because it does involve air dependency let's hit pnpm Dev to restart our Dev server and navigate back to or page now we can already make use of this drop down menu it's from our custom component not from Radix that um that is unstyled and would look a bit weird we want the styled one we have just installed together inside of this drop down menu goes a drop down menu trigger in which we can Define the button we want to open up the drop down menu remember to not have two buttons we need to pass the S child property because we want to use our custom Button as the trigger let's use the button in here and in this button we want a little icon a search icon we get from lucidreact this is going to be self-closing and let's give the search icon a class name of height of 4 and a width of 4. great let's give the button an area Dash label because we only have an icon inside of it let's say zoom in here and as for the button variant we want a ghost to not make it stick out too much let's save that already see what we have on the Page by reloading the page and we did start up the dev server I hope yes we did and we can see the little icon that is right here great it doesn't do anything yet because we haven't defined the content of the drop down menu also really quick let's give the button a class name and that is going to be Gap 1.5 and also give this entire thing a bit more space okay now we get to the part of what should be inside of the drop down menu first off let's create a drop down oops drop down menu content right here we also get that from our UI Library below the trigger but still inside of the drop down menu and for each item we have in here we can simply State the drop down menu and you can probably guess it drop down menu item as easy as that also not from Radix but from our custom UI component let's say for example a hundred percent save that and when we now open up this drop down it looks beautiful it's fully accessible with keyboard and we get the 100 right here now this currently doesn't have any effect when we select a value it does literally nothing and for it to have an effect on the UI we obviously need to save it in a state so let's say the const and let's call this scale and set scale ads or States up here is going to be equal to use State now the question is which scale do we want to start at now just like with the current page the default value we're going to use is A1 and just because I like it once again we're gonna pass in the explicit typescript type which you don't need to but I think it does look a bit better okay then we can now work with the scale to take an effect on the PDF so what we want to do right here down for the drop down menu where we just were right first off we want to display which scale we are currently at to the user for that let's go below the search icon and insert a dynamic value right here which is the scale times a hundred right here and percent for that and right below that or right next to it we can add an icon that indicates that this is a drop down menu because if we left it at this this looks good but nobody would probably care to click it because they wouldn't know what it does so let's add a Chevron down icon right next to the scale in percent and this is going to be self-clausing this icon will get a class name of height 3 a width of 3 and an opacity of 50. so it looks a bit more subtle so we can tell it's a drop down menu if we pay attention to it and it looks really really good now for the items what do we want the item to do well on select is something we get for each item and when this item is selected we can simply set the scale to 1 for the 100 let's copy this down using shift alt and arrow down and you can Define the next option this can be anything you want I'm gonna use a hundred and fifty percent as the next step and the scale should be 1.5 in that case let's copy this down another time this time this is going to be 200 percent and the scale is going to be set to 2 of course and that's the one last one for people that want to zoom in really really far and that is going to be 250 for very small PDFs and that will of course scale to 2.5 great we can now select these values in our drop down and they work but the PDF is not scaled accordingly it doesn't have an effect on the PDF yet and the way we can do that is we can also pass this into the page at the very bottom and this takes a property a prop that is called scale and we can simply pass it the state that we're keeping track of and that way we are actually going to be zoomed in right here to the PDF however as you might notice oh what happened right here as you might notice it doesn't really work yet localhost there we go if we go into here it works by default but if we select a different scale well everything crashes that's not ideal let's go back and let's fix that issue and the first thing we're going to fix is let's go up a bit above the document and the ref or the div wrapping this document one thing we want to do is you remember when we click this and scale it up it suddenly becomes huge and takes up the entire screen of course that's not what we want instead we want scroll bars to be able to scroll around the PDF but not suddenly make it take up more space on the screen more than it is designed to take up and one really really handy package we can use for that is called pnpm install simple bar Dash react go ahead and install this and what this does for us let me show you let's Let It install and then start back up or Dev server and then go to the very top of the file right here to our inputs what we can now import is the simple bar as a default import from simple bar the package we have just installed simple bar Dash react just like this now let's go back a bit right here to our document just like here and insert that simple bar package we have just installed this is not going to be self closing because we're gonna wrap this div inside of it I'm going to use my emit balance outward hotkey right here to select the div and wrap it inside of the simple bar and we are talking about the div with the ref by the way that's where we currently are okay this simple bar takes a auto height property and this is going to be and why am I on English keyboard again this is going to be false by default and also it takes a class name and we're going to pass a class name of maximum height and then a dynamic value that's why we use these angled brackets once again with tailwind and what we're going to insert here is a calc we're going to calculate the value of 100 view height minus 10 REM just like this and hit save on that and now what you're going to notice is once we scale this up and let's reload the page beforehand and I hope the dev server is running it seems like it let's let this refresh and then zoom in a bit let's go to 200 okay and something is seriously wrong here it shouldn't be that way with the simple bar give me a minute I'm gonna do some debugging and I'm gonna be right back okay and after some time of debugging I found again this is a very simple fix but one thing we forgot is do you remember when we added the simple bar right here we just did that and the purpose of this is to ensure that the PDF never gets bigger um than it can be because that's what's crashing the application the PDF is just becoming way too big for the page when we increase the scale because this only happens when we increase the scale so to fix that we are going to go into our layout or root layout and import the CSS that simple Bar Needs in order to display this correctly so right below the react loading skeleton import we can say import simple bar Dash react slash dist for distributables and then the simple bar dot Min dot CSS that's what we need to import okay let's navigate back and let's try this again and we can see Luckily everything works now we can close out of the console and we zoomed in successfully and we get these beautiful bars which indicate hey we can now zoom into the PDF and scroll around as we want to beautiful let's go back into our application into the PDF render I'm gonna change this back to the scale you probably still have it in here so the page always scales and I'm going to remove this debugging step as well okay let's save that and we now expect this to work properly by default it's going to be rendered at a hundred percent now the PDF is really large this is going to change when we develop the chat wrapper don't worry about it for now and we can actually zoom into the PDF as much as we want to and scroll around view to full Okay now what's left to do is the rotation and the full screen both of which are really intuitive because you already know the principles we are going to use for them so first off let's do the rotation let's move this into a side by side and of course the rotation Y is the side by side not working and also goes into a state because we want UI updates to go with it so let's go into your PDF render we can close all of the other Pages for now by the way and let's save the current rotation inside of a state as well so calls rotation and set Rotation by convention is going to be equal to use State and this of course also will take a number starting at zero I'm going to add the number generic in here as well now to have a button to actually rotate this around let's go ahead and add that button to do that we are going to go below or drop down menu right below this one still inside of the div for it though because that's the div that's on the right hand side here we want it in there and then here we're going to create a button right here this button is going to have an area label because again we're just going to have an icon inside of it so people with a screen reader can tell what this is for the area label there we go and let's say rotate 90 degrees there we go and inside of this button the icon we are going to use is the wrote rotate CW for clock wise and that is going to be self closing with a class name of height 4 with 4. perfect this button is going to be a variant of ghosts so it doesn't pop out too much let's save that and we're going to be able to see the button right here beautiful however we also want this button to do something right now it doesn't do anything so on click we are going to change the rotation for that we can call the set rotation to change the state this gets the previous value inside of the Callback and what we want to return from here is of course the previous Value Plus 90 so we are rotating this um clockwise if I now press the button nothing happens because we haven't linked the state to the page yet and to do that it's really easy let's go down to the page and pass the rotate as the rotation state that we have and hit save now when we click the button it will actually rotate the PDF which is exactly what we expect to happen when we click the rotation button really really good work the last thing we want on this page is a button to view this PDF in full screen because to be honest sometimes this text will be really small especially when it will have a bit less space due to the chat wrapper being on the right hand side here right if we're not zoomed in sometimes it could be a bit hard to see so let's move this to the right side and get started creating that full screen button right below this button right here let's call it PDF full screen this is going to be a custom component which doesn't exist yet not let's give the vs code a bit more space and create that component under or components folder in Source a new file called PDF full screen dot TSX and the functionality is actually going to be pretty straightforward let's first create the component Collins PDF full screen is going to be nothing else than an arrow function that we export as the default at the very bottom of the page export PDF full screen beautiful now inside of this component we want to keep track of the open State and to do that we're going to use react State it's going to be equal to use state from react and by default it should not be open right the full screen view should be closed by default unless the user presses the button to open it so let's call the state is open and also set is open just by convention and we can already get started with the jsx in this component this is going to return a dialog one we have from our custom UI library right here it's not going to be self-closing and this dialog will take an open property because we're going to make it controlled you remember that from earlier it's the same thing now we can pass in the is open to the open and then on open change once again we receive the visibility as the V Let's just call it that and then if the if it's not true so if not V if it's invisible then we're going to set the is open to that visibility okay so essentially kind of like a toggle right and inside of this dialog we can once again make use of the very useful dialogue trigger also from our custom component not from Radix and pass it the S child property as usual because we want a custom trigger which is going to be a button we get from our UI button this button is gonna say nothing it's just going to be an icon inside of here and that is the expand icon we get from Lucid Dash react you can simply pass it a class name that is going to be height of 4 and width of 4 and because we just have an icon in the button once again we need to pass an area label well technically we don't need to pass it but it is a really good idea to make your app more accessible to people and let's say full screen inside of the area label so visually impaired people know what this does the button has a variant and that is going to be ghost and let's give it a class name of Gap minus 1.5 as well great you can already save this and let's import the PDF full screen inside of our PDF renderer component so we can already see it showing up right here and there it is beautiful if we click this nothing happens yet because there is no dialog content there is nothing to show in the model that pops open right so let's change that instead of here let's define the dialogue content this is what's going to show up in the pop-up and let's give it a class name of Maximum width of 7xl and a width of full so it will actually take up that 7xl or in pixels that would be 1280 if we just hover over this it says it right there beautiful inside of this dialog content let's once again make use of the simple bar and to not let it overflow and have some nice scroll bars we can use to Traverse the PDF file this symbol bar is not going to be self-closing either it will receive children with an auto height of force there we go and we want a class name on the simple bar and this is going to be a maximum height off and then in custom angle brackets because we want a custom value in here we're going to calculate 100 view height minus 10 REM once again let's give this a bit more space there we go and also we want to pass a margin top of six okay inside of the simple bar let's just straight up copy what we have in the PDF render because the PDF rendering logic at the bottom line is going to be exactly the same stuff so let's head back over to our PDF render and then for the ref and the document let's copy this entire element without the simple bar to be honest we could have just copied that as well um never mind let's just copy the div and the document inside of it and paste it right into the simple bar tag in the PDF full screen now of course that's going to give us an error because you know we still need to make the Imports in this component and the Loader 2 we also need to import as well as the toast remember the toast is not a direct import but rather it's a destructured version of the use toast hook we can get from right there that's how we get the toast function now there's some stuff still missing like the num Pages for example so we can literally go again to the PDF renderer take the num page State at the very top of the file right here copy this line over into our PDF full screen to fix that error let's move it up to the um is open state so we have those together beautiful and that was the current page we don't want the current page we want the num pages that was an accident let's replace that we want the num Pages there we go okay let's see what is still missing the ref is still missing for the div so we can literally go ahead and also copy and paste this line with the user resize detector you can just copy that from the PDF renderer head into our PDF full screen and paste it here so we have the width the ref and we also need to import this Hook from react resize detector let's see what is still missing we have the page we still need to import that from react PDF beautiful we need the URL and this is going to be a property we receive into this PDF full screen component we don't need to fetch it separately of course we can but there's no point because we have access to this in the PDF render we can simply pass it as a prop into the PDF full screen so how we do this is let's call it file URL and destructure it right away and this is going to be of type PDF full screen props in interface that doesn't exist yet so let's Create It Right Above It interface and that's copy the name paste it right here and the only thing we receive in this component is the final URL which is of type string and it's not optional we always want this property and that is exactly what we can now pass into the document for the PDF file right just pass in the file URL and that also means we will get an error in the parent component right here because we don't pass currently the file URL and this is nothing else than the file and no that's not the file it needs to be the URL that we get in this component in the PDF renderer beautiful that means the arrow will now be gone in the PDF render and the only thing that's left is three errors in the PDF full screen however we don't care about the scale or the rotation in this component we can just get rid of those the only thing we do care about is the page number however we don't want pagination like we have on the right hand side here in this component instead we want to display all the pages how do we do that so instead of displaying one page with a page number what we can do is map over every page that we have and the way we do that is by saying new array and we can pass in as the amount of array entries that we want to have the num pages so for each number um we create one array entry that we can now map over first let's fill them with zero and now let's map over them so for each array entry we don't care about the first value but we do care about the index so if we don't care about the value a convention is to just use an underscore for it which just means we're not going to use it and we only care about the index as the second argument for this callback function and what we are going to return from this is a page element like we already had with the only difference is now we do need a key as the key we're going to use the index and as the page number we're going to use the index bot plus one because the pages start at index 1 and an array starts at index 0. so we need to add one for this to be you know page one two three four five and so on beautiful let's save that page and one thing I just noticed while I'm demoing this is that this one does not update while we navigate through the pages and that is because let's switch quickly back over to the PDF render and fix this really quick it's like two lines of code we can do um so for the buttons that we use these buttons right here currently we're only updating the current page but what we also need to do is update the value of this input right here we're not changing this currently which we need to do for this to work properly so what we can do is set the value right here that's how we can modify this value right here and we want to change it for the page input which is the only one we have and what do we want to change it to well we can convert the current page minus one to a string there we go and now if we use this button let's reload the page and try this out well it will work if we use it on both buttons Now it only works in One Direction which is not ideal but we can simply copy this line of code go back to the other button right down here and do the same logic except instead of the -1 we're now going to add a plus one to it and save that let's try if this works it's going to start at one and when we navigate up and down it's going to work and if we enter a custom number it's also going to work and if we enter some bogus great it doesn't work perfect great and one of the last things we need to do for the PDF full screen to work properly is at an event listener to the button right currently the button does nothing if we click it no full screen model opens which is not good so to fix that let's go into the button of the PDF full screen and add an on click event in this click because we turn this into a controlled component we want to set the is open value to true and that's the only thing we need to do in here perfect let's oh actually wait a second this doesn't go on the button this goes on the dialogue trigger honestly it doesn't really matter it would do the same thing but let's put it on the trigger I think that's where it belongs okay let's make this a bit larger and see if this actually works let's press the button and this does open up our full PDF with all pages in a beautiful scrollable model that's exactly what we want this is fully accessible we can close it using the Escape key or the mouse it looks really good out of the box and it has a loading animation with that little spinner and it loads really fast perfect really really good work let's close out of this and I think we are almost done or are we even completely done with this whole PDF viewer let's reload it and see so we can browse through the pages that's looking good we can zoom into the pages and one very tiny detail but it does make a difference in the user experience that we could do is when we Zoom there could be a little flicker a little delay in the rendering of this page and to not have that flicker when we Zoom we can actually do a little trick and that is show the last page until the new page is rendered therefore we avoid any Flicker and it just looks better to the end user and the way we do that is pretty straightforward let's head over to our PDF renderer and quickly implement this for a bit cleaner animations this especially gets visible when we slow down our CPU here's a little trick you can do we can go into the tabs right here and then under performance we can manually throttle or CPU and this might not work because I'm so zoomed in let's zoom out just a tad so for people with slower devices especially let's enable some CPU throttling we can do that right here and then let's reload the page and try zooming in directly right away you can see everything is a lot slower and that's the entire point this is way slower than it would really be in reality for most people but this way we can do some debugging on slower devices so if we zoom in right now you can see there's some lag there's some white space before the page is rendered that is what I mean and we can completely get rid of this doing a little trick that I want to show you right now let's disable the throttling because we won't see it very much on Fast devices but to make it look good on all device is no matter how fast or slow they are let's get ahead and fix this so the way we're going to do that is by conditionally rendering this page and keeping the alt page while the new one is loading so for that we need to determine two things we need a loading State and the scale of which the page is currently rendered let's already insert those values right here to conditionally render this first page let's call them is loading and also the rendered scale and if these both are truthy then we are going to render this first page and else we are going to render null and of course these two values don't exist yet so let's create them and what exactly do they mean well let's go to the very top of the file and declare those values let's go right here to our states and we need one more State and that will be the const rendered scale and here we're going to keep track of the scale of the page that is currently rendered and set rendered scale there we go this is going to be a use State and by default this is going to be null and in here it's now actually important that we pass in the generic um for typescript to know this could be a number as well as null so we can later change it to a number if we want to and then the is loading set is very simple we can simply determine it and derive it from this rendered scale so if the const rendered oops let's call it is loading and that is going to be equal to if the rendered scale there we go is not equal to the scale right if we change the scale this value gets updated and if there is a mismatch between the rendered and the scale then we are currently in the process of rendering a new page and therefore our loading that's why we can derive this from these two State values right here and really neat little trick now with this is loading value let's go way down to our Pages again and determine what to show if we are loading and for that let's create a new page and we can simply copy and paste this one down and change it up just a bit but that will change us some you know manual work first off in this page we just copy down below this conditional check we'll receive a class name something the other one doesn't have and this class name is going to be a CN because we're going to conditionally apply a class name and that's going to be if we are loading if it's loading then we're going to apply a CSS hidden property and else we're not going to apply anything just an empty string the width is going to be the same the rotation is going to be the same and let's also add a loading state to this page right here again this is just plain jsx we can create a div in here just like so give this div a class name that's going to be flex and justify their Center and inside of this div let's create or trust the Loader 2 icon from Lucid react and give it a class name of my 24 a height of 6 a width of 6 and an animate Dash spin property so it spins around in circles beautiful one really important change we need to make to this is to update the rendered scale once this page renders now how do we do that when do we know that this page has rendered well the react PDF Library provides the helper for that that's on render success and this takes a callback function in which we can just say that the rendered scale set rendered scale is now the scale therefore the loading is finished we are displaying the new page great and one final thing we need to do is to give these Pages a certain identity and we can do that using the regular key attribute in react and the key we're going to use for the second page is going to be a combination let's say add and then this is called a composite key we're going to use the scale to give this page a certain Integrity which needs to differ from this upper page right here to not get any flickering during the resizing so we're going to add another key right here it's going to be a composite key so a key consisting of something plus another thing that's all it means it's a fancy word for it and this needs to be the rendered scale so the key for this add plus a rendered scale and the key for the regular page down here the add plus the regular scale not the rendered scale all right so let's hard reload the page with those changes and see if this works if you go to a hundred fifty percent 200 percent 250 we can see it looks really good it zooms just as we expected to and there is no flicker are in between which is exactly what we want beautiful okay so this is the entire PDF renderer done we can rotate the file we can even go into a full screen mode if we want to and view all the pages we have in the PDF in a beautiful model beautiful and with that done we can finally move on to one of my favorite parts of this entire build and that is going to be the chat wrapper that's going to encapsulate all the chatting logic this one right here with all the messages with a chat input the AI logic and everything that's connected to it what are you going to learn in this section for example optimistic updates now why are optimistic updates such a big deal and what does it even mean so an optimistic update is when I type in a message right here and hit enter right then we need some time even though it's not much to submit that message for example to the database and to generate the response to the message right from the AI and the thing is with optimistic updates as soon as I type in that message right here and hit enter it is immediately put into the chat even though it is not in the database yet but for the user feedback that is amazing because they get insanely fast basically immediate feedback which they won't get when we have the message hit enter then wait for the API raw to finish and then once that is done loading the message gets put into the chat for the user that just feels slow and in 2023 2024 whenever you're watching this video people just expect instant updates that's just how it is and it's a really good idea to incorporate this into your app I'm going to show you how in one of my favorite ways you're gonna love it and then next to the optimistic updates what we're gonna do is something called infinite queries now what that means is if we have certain messages in this chat right message here message here message here message here and let's put one on top as well whenever we navigate to this page and just imagine there are a hundred messages in this chat if we don't have infinite queries we will be loading a hundred messages even though only five are visible on the screen right now and all the other ones you know what be on top we wouldn't even see them but we would still load them if we didn't have infinite queries which is a pretty bad idea because there could be technically an unlimited amount of messages above like 5 000 for each PDF file that you know loading would put a lot of strain on our database and also mainly the problem the client they would have to display all the messages even if they didn't need them that is why we're gonna make use of infinite queries when you visit the page we're only gonna load the last 5 or 10 or 20 whatever you like in the app where building the limit is going to be like 10 I think we set it to um the last 10 messages and if the user Scrolls up the chat only then are we loading more messages for the user to display it looks seamless but it significantly improves performance and makes a lot of sense in this use case and all the other apps that you might be building in the future so learn it here once and then apply it throughout your apps that you're building it's really really cool and those are my two favorite features about this section of course it all looks good and we're streaming in the AI response in real time one more cool feature I forgot to mention and I say we get started right away and the Cornerstone of this all we've already set and that is going to be the chat wrapper that is rendered down here now what is the purpose of the chat wrapper why do we need this well it serves three purposes first off it handles all the loading States for Us loading states are a super significant part of a good user experience and we will deal with them centrally in one component instead of having to deal with them each in one of the input or the messages component then secondly we need context if we input something into this input field we also need to know about it in the messages component you're going to see why that is so useful and lastly for the layouting we can Define the layout in one component and then just put the children into there and it's gonna look really really good let me show you how we're gonna do this first off in the chat wrapper let's begin by creating a div element and this is going to get a class name on of a relative a minimum height of full a background zinc of 50 and let's give this way more space to work with there we go a flex a divide of Y A divide zinc of 200 you want a flex Dash call let's go a bit to the right side a justify Dash between and lastly a gap of two inside of this div right here we're going to create one more div with a class name of flex-1 a justify Dash between a flex Flex Dash call and a margin bottom of 28 so quite a bit okay and now comes the thing in here we want to display the messages and these messages will live in their own component that doesn't exist yet so let's create it it goes right here and let's create the component under Source components and then a new file called message oops messages dot TSX and I messed that up it should be messages right here in the chat wrapper as well in this component the messages const messages are nothing else than a regular Arrow function once again let's export that as the default export default messages from this component so that we can already import it back in the chat wrapper and have it work here this doesn't return any jsx at the moment that's why we're getting an error so let's just return a basic div from these messages so we can already use them and get the layout thing done before we dive into the messages component further right below the messages we're going to render out the chat input these are going to be separate components we have the input right at the bottom here and then the messages um what you can see on the top here these are going to be separate so let's declare the chat input right here as another component that doesn't exist yet same thing as with the messages let's go over and what we can actually do is kind of separate our concerns here so because the chat will involve about three to four components what we can do and which makes a lot of sense here is create another folder and let's name it chat and all components that are related to this chat will go into this chat folder so for example that's going to be the chat wrapper let's move that into there that's going to be also the messages right here let's move it into there and now in here we can also create the new file for the chat input.tsx let's just go ahead and copy over the messages spoilerplate into the chat input so we're a bit quicker and don't need to write it from scratch and then let's just replace the messages with the name of chat input and save this file now we can already import this in our chat wrapper okay after importing the chat input let's save this component now what I teased earlier one really important function of this chat wrapper is to handle loading States and what are these loading States going to depend on well do you know if we go to the core.ts I'm just going to show you this no need to follow along we are creating a file with the upload status of processing and this is essentially a loading State because what will happen is that we are gonna do a bunch of logic right here in the on upload complete below this so this won't be as easy as just adding a single file but later on we will also need to Index this file into a vector database that's going to be really cool you're going to see what I mean by that in a bit but essentially bottom line is this operation will take a bit and for this loading state to be handled properly that is exactly what the chat wrapper is for let me show you how this will work let's go into the index.ts under trpc because we do need a router for this a new API endpoint choose any spot you like right here in the index and let's create a new API endpoint called get file upload status right here this is also going to be a private procedure so you need to be logged in to call it with a certain post body as the inputs you know how this works by now this is going to be a z dot object and the data we expect for this API Rod is going to be a file ID and this is going to be of type Z dot string so the file we want to search for and SD mutation as the business logic right here as always this mutation takes a callback function just like this and let's format this after putting a comma here so it looks a bit cleaner okay as for the business logic in this API Rod what we want is to find a file and check for the status of that file upload so how we do that in practice is let's say cons file is equal to await DB dot file and again to be able to use the await we need to mark this mutation as asynchronous just like this remove the extra parentheses and then let's find the first file that matches our criteria so where do we want to find this file essentially where the ID is the input dot you know file ID that we expect here but enable to in order to be able to use the input we do need to import it right here from the input and while we're at it also import the context so now we can check where the file ID equals the input dot file ID we pass as the body to this request and where the user ID matches the context dot user ID which is you know nothing else that the logged in user beautiful after we found that file we can return the status of it so for example if we don't have a file if it doesn't exist in the database then we are going to return a status of pending and we are not gonna just leave it at this what we also want to do is return this status as a constant this is just for typescript to know that pending is one of the valid States or files can be in because if you remember what the enum is the upload status pending is one of them and in order for typescript to recognize that what we're passing here is in fact a valid attribute if we left away the as const it would be any string but we want it to be exactly the pending string that's what the as cons is for and let's return an object where the status of this object is going to be the file dot upload status perfect we can save this API route and now go back into our chat wrapper and actually make use of it in another polling approach so you might know how this works by now let's import trpc we already see this get file upload status right here and can use the use oh and this needs to be a use Query by the way because we're making a get request no need to have this as a mutation let's change this back to a query and now we can make use of the use Query right here on the front end because we wanted to trigger as soon as the page loads and as for the file ID that this API Rod expects well we don't have access to it in the chat wrapper how do we get access to it well really simple we can just accept it as a prop into this component the file ID let's name it chat wrapper props you know how this works by now and we can just Define the props as an interface chat wrapper oops wrapper props way up here and for each thing that we destructure we need to define the type of it in our case this is just going to be a string that we accept and pass right into this use Query okay just like this the polling wouldn't quite work what we also want to do is to configure a refetch inter Bell just like this now this refetch interval passes in the data so whatever we return from the API Rod is going to be accessible right here in the data for example the status that we send back from the API and this is really important because now we can Define that we want to poll until the file has a certain status so for example if the data dot status is in the success state or if the data dot status is in the failed state that both means whether it's success or fail doesn't matter both of them mean that the final upload is technically done it's resolved whether it's successful failed doesn't matter so in that case you want to stop polling because there is no longer a loading State we have a definite result and if it's not then it's either pending or processing so in that case we want to keep polling in a 500 millisecond delay all right and by the way one reason we get this error right here is because we're not making use of an implicit return but instead are just passing a function block that is not what we want let's remove the curly braces alternatively what you could do is just return the statement as well but I think it looks a bit cleaner oops if we just get rid of these curly braces and do something called an implicit return where this is implicitly directly returned from the function beautiful and now if we destructure the data and the is loading property we have everything we need to get set up and running for beautiful loading States in our app right here so the first thing we want to do is handle the is loading loading set if we don't have a status yet so not success failed processing or pending if we don't have any of these yet but at the very start when this page is first rendered then the is loading is going to be true when we don't have the result of the database API call yet that's the is loading and in that case we are going to return some jsx a beautiful loading state to be precise so the top level diff we're going to return from here gets a class name of relative of minimum height full a background zinc of 50. we want Flex it divide Dash Y and divide zinc of 200 a flex Dash call can we give this a bit more space yes we can there we go a justify Dash between and lastly a gap of two beautiful inside of this div let's create one more div with a class name of flex-1 flex justify Dash Center items Dash Center Flex Dash call and lastly a margin bottom of 28. let's open up this div and create one last div I promise with a class name of flex Flex Dash call we want an item stash Center and a gap of two in here as well beautiful and in here let's put our loader two we are gonna put this you're gonna see we're gonna change it up later in the other loading states in the is loading we're going to use a loading spinner just like this with a class name of height 8 a width of 8 a text of blue 500 and an animate Dash spin to make the circle turn around in circles an H3 element with a class name of font Dash semi-bolt a text of extra large saying loading Dot there we go and lastly to finish up this loading State a paragraph element with a class name of text zinc 500 and text Dash small and in here we're going to say we and then what I mentioned earlier the and apos and colon for the save HTML right here we're preparing your PDF period okay let's take a look at what this looks like let's change it to true the is loading so it will always be truthy save all the files and also we need to turn this into a client component and because we are making use of trpc in the component so save that turn it into a client component let's move this into a proper side by side there we go and reload the page so there we go that was our loading State again that was really really fast but if we expand this and take a look at the right side this right here is our loading set and what we can also do just for decoration is already render out the chat input inside of this component as well the thing is this chat input is purely for decoration right we want to make it very apparent to people that this is a chat but they shouldn't be able to interact with it while this entire thing is loading right so that's why we also want to pass in is disabled property to the chat input that of course we need to accept first in the chat input so let's navigate over into the chat input and accept as a prop and is disabled property and we can just call this the chat input props for example go to the very top of course declare that interface interface chat input props just like this and then for each thing that we're destructuring actually is disabled which could be optional we wanted to care that this is a Boolean value right now this doesn't do anything but what it does allow us to do is already use the functionality as if it was already functioning from our doting states in the chat wrapper so we can already pass the is disabled um flag right here even though it doesn't do anything we can already syntactically clear everything in the chat wrapper perfect let's return to the is loading instead of the is true to only show this conditionally as I if I save that it will obviously remove the loading State here from the side beautiful let's replace the true with the is loading again so we're not constantly mocking this loading State and next up let's handle the processing state so if the oops if the data dot status that we're receiving in this component is processing triple equal processing there we go in that case we're going to return a very similar component to the is loading and we can just copy and paste the code we have up here again using our emit balance outward to Mark the entire element with just one button press and paste it right here in the processing step now we do want to change some things up like for example loading let's change it to processing PDF and as for the P tag content let's say this one and then for the apostrop we're again going to use the same tactic won't take long period okay the Loader 2 can stay the same consistently that's good and by the way this is stuck in a loading because of our database state of the file we're going to fix that up later let's first complete the loading sets here on the left hand side so we handle d is loading we handled the processing now what we need to do is handle the failed State now when is the state going to be failed for example if you try to upload more pages than your quota allows if you are a free user you shouldn't be able to upload PDFs larger than five pages and if you do well you're gonna see this failed state so let's define it if the data.status is triple equal to failed well in that case what are we going to return we're still going to return a variant of the loading state from before so we can copy and paste it right in here however there are going to be small differences for example the icon is not going to be a loader 2 but instead an X Circle just like this and the text is not going to be blue but instead let's make it red and of course we don't want this x to spin around as for the H3 we can say too many pages in PDF dot dot or just leave them away it doesn't matter and then as for the P tag well we want to inform the user what's wrong so how many pages their plan supports for example your and then let's add a span to make this a bit bold so let's give this span a class name of font Dash medium your and then for example the free plan or let's just enter free right here and then after the span say plan supports up to five pages periods or pages per PDF I think is a bit more clear there we go and let's see this in action let's mark this out you don't need to follow along with this let's just take a look at that loading state right here and also of course to invoke this let's quickly go into our database right here oh it's still open great let's open that up and change the state of the file from processing to fail let's remove one of the files go into the other and change the upload status to failed there we go to mark that it did not work and then we should be able to see in this loading State well we're not seeing anything right now let's quickly navigate on the dashboard and see why that is where are we on the wrong file well it's loading right now and now we should see that the error status here that we have too many pages per PDF and it does pop up really quickly but it doesn't show permanently and that's not ideal that is not what we want so if we can see anything in here okay interesting so we get a Zod error that means whatever we pass in to this API route to or Index right here is invalid in our case the file ID and that's what trpc is complaining about here with the trpc client error interesting so let's see how we can fix this let's do very quickly some debugging together and if it's a more complex issue I will debug on my own and then tell you the solution but I don't think this is going to be a very um you know complicated solution so let's quickly log out the file ID in the chat wrapper and take a look at what happens let's reload the page and we should be able to see okay the file ID is undefined that's not ideal let's clear the console and see as to why this might be undefined where we are rendering out the chat wrapper and we are doing that right here and of course we also get an error right there that literally tells us that we're doing it wrong let's get rid of the console that's a bit distracting and that we forgot to pass in the file ID from the page right here interesting okay so this file ID needs to be equal to the file that we are fetching in this page.tsx dot ID and problem solved let's try this again let's reload the page loading PDF and then we see too many pages in PDF your free plan supports up to five pages per PDF of course this is just mocked this PDF only has three pages so we shouldn't see that error in any you know real setting but now we know what it looks like and that it works perfect now one thing we want to do is when we're displaying a loading set we want always to offer the user an option to get out of the you know in quotes bad State and what that means in practice for example for this state right here let's offer them an option to go right back to the dashboard right because they have nothing more to do on this page so let's display a link right here we get from next slash link and this is not going to be self-closing in fact in here we're going to say back that takes them back to the home page and also a little icon a Chevron left icon we get from our blue seed react icon library with a class name of height 3 with 3 and margin right 1.5 there we go this link needs an href and this is going to go to the slash dashboard there we go and also a class name to give it nicer button Styles these are going to be dynamic and you already know how this works by now we can pass in the button variants to apply the default button Styles however we don't want the default Styles as the variant we want the secondary Canary there we go select that and as the class name we want a margin top of four to space it out a bit from this P tag element right here awesome let's save that see how it looks we got the back button very subtle but it is there for users to just click to head back right to the dashboard perfect very very nice let's go back into the file so we can see the loading State here on the right hand side and that's it that's the loading States all done we're handling three different cases for the arrow we're handling the processing and we're also handling the is loading State really really nice anything else basically means that the file upload is either still in progress or it was successful and if it is successful of course we actually want to display the chat and all messages inside of it so we can get rid of the or true because that will always evaluate the true and just leave it at the data status is equal to failed and then render out all the good stuff in the other case so in order to see this let's go back into the Prisma studio and quickly change this to success to pretend that everything went well which normally it always should and then we can see that this is still empty because essentially we are rendering out two empty components the messages and the chat input and what I think makes more sense to do first is the chat input because without it there won't be any messages to display in the first place so let's start in the chat input let's start by giving the top level diff in here a class name and also give this entire thing a bit more space like that this stiff class name is going to be absolute we want a bottom Dash zero a left Dash zero and a width of four inside of here goes a form and this form is not going to get in action instead it's just gonna get a class name and that is going to be MX of two a flex a flex Dash a row a gap of three or medium devices in MX of four on medium devices and the last element a margin bottom of six you know this syntax right here then on large devices in MX of Auto on large devices a maximum width of 2XL and on extra large devices XL we're going to give it a maximum width of 3XL let's give this a tad more space there we go inside of here let's create another div with a class name of relative this is going to be Flex a height of full a flex dash one to take up as much space in the flex container as it possibly can and items Dash stretch and lastly on medium devices a flex Dash call to enforce a vertical layout okay in here let's create one more div with a class name of also relative Flex Flex Dash call a width of full if Lex Dash grow and a padding of four perfect inside of here one more div with just one class name I promise and that's going to be a relative and inside of this div we want the input element right let's quickly take a look at our drawing right here how this should look like this is the component we're currently developing this little chat input and of course we need a area where you can type in your text and that's not going to be a regular HTML input but instead we're going to use a text area for that so we can type in multiple lines which is way better of an idea for long questions in the chat than a plain input is so how we're going to do that is let's go in here and let's say PNP or let's use npx for this npx Shad cn-ui at latest to add another UI component to our app and that's going to be add text area and hit enter that's going to add a accessible good looking text area to our app that we can simply go ahead and use right here in our chat input so let's start up back the dev server go in between this div with a class name of relative and insert a text area that we just not test area text area that we just installed into or app beautiful as the placeholder let's pass it enter your question Dot and then let's give it a rose oops that needs to go as a prop into the text area a rows of one a Max rows of four whoops not in the array syntax of four and now you might be wondering hey Josh the text area doesn't take a prop that is Max rows and you'd be right because we are not going to be using the default text area that is installed into our components that's the beauty of Shad CN as a UI Library we can customize all the code that we want because one thing this text area right here lacks as it's just a default text area when we enter multiple rows and try to type a multiple row message it won't grow with the message it will add like a weird scrolling bar and it's going to look really really awful so to counter that let's add a very lightweight dependency npm install pnpm install doesn't matter and it's called react text area Dash Auto size hit enter on that and it's literally just a react text area that automatically sizes with your input that's all it does it's super lightweight and we can already go ahead and import the text area Auto size there we go from react text area Auto size and what we can also import with just for typescript is the type so comma and then let's also get the text area Auto size props type to use in this component let's give this all a tad more space format the file there we go and the only thing we're going to do is switch out the text area to the text area also size and of course we also need to tell typescript that hey this is fine so let's just copy the text area Auto size props and replace the text area props right here and just like that all the arrows are gone perfect let's also very quickly remove the minimum height of 80 pixels right here we won't need that and we can leave the rest as it is and save the text area and now you can see now it does accept a Max rules of four property amazing and we can continue right here one cool property that you might not know about that you can pass to pretty much any input or text area in react is the autofocus and what that will do is when you load the page the cursor the you know the type will automatically be inside of the input by default it's a really neat little trick doesn't require much at all just that one property okay let's add a class name to this text area and that's going to be a resize Dash none to make the little icon in the bottom right disappear because it doesn't look good a padding rate of 12 a text Dash bass a padding y of three a scroll bar Dash thumb Dash blue a scroll bar Dash thumb Dash around it a scroll bar Dash Track Dash blue dash light lighter actually a scroll bar Dash W-2 and a scrolling Dash touch now these aren't Tailwind properties these are custom CSS properties that we don't have in our app yet so let's quickly go ahead and add them in or globals.css right here and because this is not a CSS tutorial what I suggest you do is just go into the copy paste list and grab all the CSS styles from here under the scroll bar CSS section and paste them right here beneath ukraini that way we don't need to type this out itself I even forgot some comments in here I'm gonna remove them in your paste list there we go and just by doing that we have a nice looking scroll bar and don't really need to care about the Styles this is not any advanced CSS very stupid work and we can just save some time by copy and pasting that awesome so let's continue in the chat input right below this text area is where we are going to create a button right here and that's going to be the sending button and in here we're just going to have an icon and that is going to be the send icon from Lucid react we can just go ahead import that and give it a class name of height 4. and also a width of four now because we only have a you know icon in here let's also at an area label and this is going to be send a message there we go let's already preview what this looks like here on the right hand side let's go into a side by side there we go and let's see what this looks like well it doesn't look like anything because the dev server is not on okay let's restart the dev server and now reload the page and take a look at how we are tracking with the input beautiful looks kind of weird the button is not where it should be so let's quickly fix that let's give the button a class name of absolute a bottom of 1.5 and also a write of a custom value of 8 pixels in these angled brackets 8 PX there we go let's save that and hopefully the input will look better now the button is exactly where it should be beautiful now for the actual functionality and that is not actually going to be done in the chat input let's quickly close all the others and let me quickly show you what we're about to do so the chat input let's quickly copy these two blue components right here these are gonna be the messages right here and this is the input we have just created however these are separate components and in order for those two to communicate with each other well they are technically in react how react sees it on the same level so in react we can only pass data from above but we cannot pass data from a component to another component on the same level unless we use something called context so what that means is if we wrap both these components in something called react context then we can pass data from one component to the other that means we can write a message in the input message right here that is going to be sent to the context and processed right here so for example we are sending this message to an API and storing it in the database that's what's happening in the context and once that is done the messages are going to refresh their data or get the data from the context to show and that is essentially how we can communicate between two components on the same level using react context a really really useful thing that we are going to make use of right now and that really isn't too complicated so to make use of this let's go into the chat and create a new file called chat context.tsx this technically isn't a component and we're still putting it under components it's not a big deal because it belongs to the chat we're not going to use it anywhere else of course you could also make a separate folder for this as you know context if you wanted to I think this is a totally fine way to go about things I'm just co-locating it with the other chat stuff what we need from this file the chat context let's export a Content called chat context this is what we're later going to consume in our components and this is going to be equal to the create context we get from react using this we can create or context let's give it an option and first even creating context in react we always need to define the fallback values and in this context we're going to have four things first off a function to add a message and by default this is just going to be an empty function that literally does nothing then the message we are currently adding is going to be an empty string by default again these are the fallback values these don't represent the you know the actual function we're going to use later then the handle input change is another function we want and by default again this is going to be an anti-function that literally does about nothing and lastly also separated by a Karma we want the is loading State and this is going to be false by default this is pretty much just like react State just a bit more of them really nothing too complicated and now because we're in typescript let's also Define the type of what we want this to be so let's name it type stream response there we go at the very top or we could also name it you know context type or it doesn't really matter and let's quickly copy in the values from here paste them in the type so we don't have to type out everything ourselves and this just makes it a bit easier for example the add message will return void nothing will be returned by that the message is going to be a string type the handle input change is also going to be well this is going to be a bit more elaborate so for example in the Callback function in here we're gonna receive an event and this event will be of type react dot change event just follow along with me here and let's pass a generic into this change event of an html text area element there we go now what is this type well essentially whenever an event gets triggered in our chat input because this is a text area this type comes out as the event that's why it's this type right here and this returns also void nothing specific and instead of just false we want the is loading to be a Boolean because it can also be true while the loading state is true now let's export the chat contacts provider and that's essentially what allows us to wrap our components it's nothing else than another jsx component we can use to literally wrap the other components to do that let's export a const chat context provider and this is going to be an arrow function that also receives some properties namely these properties are the file ID and also we want some children to um you know wrap our components let's define the type for that the interface let's just call it props and these are gonna be for the file ID a string that we are required to pass into the provider and then for the children these are going to be of type react node and just import that of course we also need to assign this type to the destructured props right here in the chat context provider and that allows us to actually get started with the logic because again the chat context is going to be responsible for all the logic involved in sending and receiving messages so it's going to be a bit longer component but to be honest one of the most fun and one where we're going to learn a lot about modern web development the first thing we want in here is some State and that state is going to be a message State and a set message just like this it's equal to use State a react hook we can simply import and this is going to be an empty string by default and just because I like it again I'm going to pass in the string generic in here also for later we can also destructure the toast notification cons toast is equal to use toast in case anything goes wrong in sending over the messages to our API now the most important thing is that we need a mutation that allows us to send over a message to an API endpoint and this is going to be the only time throughout the entire application that we are not going to use trpc for an API Rod we are however going to use react query by using the use mutation which we can these are independent tools trpc is like a typesafe wrapper but we can always Resort back to the regular react query for this and the reason we're not using trpc is because we want to stream back a response from the API to this client side right here and and in trpc that doesn't work it only works for Json now there might be some experimental version of trpc where it might work but this is a much more stable and solid approach in my opinion and because we're not using trpc we need to pass a mutation function this is asynchronous as an arrow function something we've never had to do before but need to do now and let's make a fetch request in here to our API endpoint the const response is going to be equal to a weight Fetch and this fetch request is going to go to an endpoint under slash API slash message just like this and of course this is not going to be a get request but instead a pause request so as the method let's pass post and for the body let's pass Json Dot stringify and then here we're going to say the file ID and also the message that we have as a state in this context component and if the response is not okay something we can check if something is is wrong with the response we can simply throw a new error and let's say for example fail to send message and that's later gonna call an on error where we're going to handle um the error State and display a user-friendly message don't worry about it for now and let's return the response dot body back from this mutation function so we can use it later in the on success now from this use mutation we want to destructure the function that allows us to actually call the mutation and let's call it send message now if we go to the very bottom of this chat contacts provider that we're creating we still need to return some values and that is the jsx of this component this is going to be the chat context dot provider and we can immediately pre-fill all the values so that we don't need to pass them later and of course in here we want to render out the children because we're wrapping our components in this provider later this provider takes a value in this value it takes an object of the values that we have to find way at the top using typescript for example a function called add message then a function called message and we get beautiful intellisense on what exactly this takes like the handle input change and lastly the is loading which we don't have defined yet the only thing we currently have is the message let's quickly add the add message function as well because it's really simple right above here the const add message is going to be equal to an error function and when we call that we're going to say send message and invoke that with our message that we're saving in state in order to be able to do this to pass the message into the function like that we also need to receive it in the mutation function we can just destructure it in here in the asynchronous mutation function and also tell typescript what the type is the message is going to be of type string it's whatever we typed into the input field and there's nothing more to it and just like that we have the add message that we can return from the context provider now for the handle input change let's define that the const handle input change is going to be well first off it receives an event and as we know from earlier this event let's go to the very top and just copy the a bit longer type this one right here the react dot change event this is what we want to copy and by the way I just saw that we are not making use of the stream response so let's do that and pass it as a generic into the create context so the context knows um what we're working with and typescript isn't mad at us anyways let's copy the type from the event from up here the text area element and paste it right in here for the event type that we are receiving for the handle input change and the only thing we're going to do in the handle input change is setting the message to the E dot Target dot value just like this and now the only thing that is left is something called is loading and we're going to keep track of this is loading in a separate State way at the top where we also keep track of the messages so the const is loading and the set is loading we're both going to be equal to use State and this is going to be a Boolean value of false by default and again just because I like it pass in the generic so typescript knows what's up we're not going to worry about the set is loading for now what we do want to worry about first is creating this endpoint because it doesn't exist in our app yet now this endpoint will live under or API folder let's close out of all of these under app API let's create a new folder that's how we create a new API endpoint that is named message and in here goes a route dot TS to handle all the requests that are made to this API endpoint and the only HTTP verb we're going to export from here is the post HTTP verb so we can only make post requests from this or to this endpoint this is going to be an asynchronous error function because we're going to do some awaiting in here and in xjs these always receive the request as the type of next request by default that is automatically passed in by next.js now this is the endpoint for asking a question to a PDF file that's what's containing all the logic first off we need access to the body you remember when we passed in the body as Json the file ID in the message well we need that content here in the pulse route so let's say Khan's body is equal to a weight rack dot Json just like this this is how we get access to the pulse request body in nexjs13 and we also want to make sure that the user is authenticated do you know how this works by now we can destructure from the get kind server session the get user and the user is nothing else than get user okay now let's destructure the ID from the user and let's call it a bit differently so we don't get a naming conflict later let's call it user ID instead of just the ID and if we have no user ID then in that case we're going to return a new response saying unauthorized unauthorized with a status code we can pass as an object and that's going to be a 401 status just like this okay great now we need access to the body content and as you remember that's the file at the end the message how do we get access to that and this is where thought or schema validation library is going to come in super super handy to make sure we always have the data that we expect in this API Rod let's create something called a validator let's do that under the lib folder let's create a new folder and let's call it validators inside of this folder let's create a new file called send message validator.ts and let me show you how this works first off we need to import Z from zot in order to create this validator and now we are defining the schema in which the data should always be that we receive in this endpoint else the API will throw an error so we can enforce that we are always past this data let's export a constant called send message validator and this is going to be equal to a z dot object and in here we can simply State the properties that we always want when you call this API endpoint in the post request body we are expecting a file ID of type Z dot string and also we are expecting a message of type Z dot string so whatever we as the user input into this field right here is going to be the message and that's already it that's the data we are expecting in the pulse request body and now we can make use of it in the route the way we do that is by saying const will worry about the destruction later is going to be equal to send message to validator Dot and now we get a method called powers and we want to parse the body so the body can technically be anything even typescript knows that and tells us hey anyone can make any request with any data to this API endpoint however when this parsing is successful we always know that we have a file ID and a message of type string else again the API route will automatically throw an error for us which is beautiful what that allows us to do is to continue working with this data because we know it now exists and it matches our expectations now this data we're going to use to find a file from our database and this file is going to be equal to a weight DB import or database dot file dot find First and the first file we want to find where the ID is the file ID we passed in and also where the user ID matches the user ID so we're only looking for files that the currently logged in user owns and not for anything else and if we have no such file that matches the file at the end is owned by the user in that case we're going to return again a new response we can copy that from up here paste it down here but instead of unauthorized let's say not found and change the status to an HTTP 404 not found error just like that awesome and if there is a file that the user owns that they want to add a message to well let's do that in the database let's say a weight DB Dot message dot create and now you might notice hey Josh we don't even have a message yet what is that we have files again here but we don't have a message so that's a model the last Model we still haven't implemented in our database yet let's do it it's really not too complex a message model let's create it right here model message at the very bottom will get a couple of things like for example an ID that is going to be of type string by default it's going to be an ad ID and we can generate a Collision resistant uid by default for each message that is created each message will have a text as a string and this is going to be an at DB dot text oops dot text in uppercase or in capitalized that's how we can make sure we can allocate a bit more space to each text then a user and user idea We're Not Gonna worry about that because Prisma will do that automatically for us linking a message to a user that wrote it let's add a is user message property because we need to differentiate between user messages and AI answers to the messages so this is going to be a Boolean a created ad and updated ad that we can literally just copy and paste from the file and lastly we're going to link it to a file as well so each message is owned by a user and it is in you know it is asked to a certain file and the way we can let Prisma do all the heavy lifting is by going up to the user model right here and below the file let's add a message because each user is able to send a bunch of messages it's going to be a message array you know you can send 5 000 messages if that's what you want and simply by hitting our prettier formatting shortcut Prisma is automatically going to ensure that this is correctly referenced in the message and we want to do the same thing for the file let's do it right below the key that each file could also have certain messages attached to it and this is also going to be a message array once again if we format Prisma is going to do the heavy lifting for us and add these um connections right here to the message and that is already it we can save the message and as always when we do local changes in Prisma we need to say npx Prisma DB push to push those changes up into our Cloud database to reflect these local changes and then also we need to say npx prism up generate to generate the local typescript types even though to be honest I'm not even sure we need to do that in the newest Prisma anymore but just to be safe let's do it let's start back up our development server so we get live insights as to if everything is working and now we can see the arrow on the message is finally gone in or API roll amazing that means we can now actually create this message and we also need to pass some data for this message to be created for example the text that we want in the message and this is the message that we pass in the post request body to this endpoint the is user message is going to be true because a user requested this API route so it will always be true in that case let's also add the user ID and the file ID for this message to be created great so now the message is in the database however what is the next step inside of this API Rod well this is going to be like a question you have about the PDF about anything that's in there no matter the PDF usually this app is meant for asking questions so who was Plato or who was Socrates when you take a look at this PDF right here just some random I found somewhere on the internet okay and to answer this question we are going to use a large language model this gets to the AI part of the application that is going to be rather small but it does make up the core functionality of the app however again the importance for this video for this SAS is all the stuff around AI so even if you're not a huge AI fan it is only a small part so here's how this is going to work there's a really important concept I want to explain to you and that is called semantic querying or a semantic query now what is that so for every text you can possibly imagine any text like for example the dog is brown right this right here this sentence every text using AI language models it's really not complicated by the way can be turned into a vector and this Vector is nothing else than a JavaScript array of 1536 numbers so for example 0.5 minus 0.7 and you know 1.2 I'm not sure if it goes above one it doesn't really matter and about 1 500 more of those so dot dot so this text is represented by a really long array of numbers called a vector so the semantic meaning that this sentence conveys means this Vector in the space and we can now find the semantic similarity between two sentences like for example the cat is brown it's gonna have a pretty similar Vector if you consider where it is in the room so the dog is brown maybe it's gonna be like a 0.55 or minus 0.71 because cat is different than dog but the um there's an animal and it's brown it's the same thing in both sentences so the vector is going to be very similar however when we have a sentence that has nothing to do with the other sentences like for example Germany is a country or whatever it has nothing to do with these previous sentences therefore the vector it will have in the room is going to be not this one it's going to be completely different it might be minus five and uh in this slot it's going to be 6 and so on and so on you get the idea so we can see how similar certain sentences are using a vector in the room that's how we can quantify the meaning of a sentence and that is called a semantic query we're gonna do so where this principle comes into play is when we index a PDF file for each message that we want answered in the chat we are gonna index the entire PDF first and then based on the question we can find the parts of the PDF in text that are most relevant to that question by their similarity right there's a certain product we can calculate mathematically it's called I think cosine product and that calculates the closest vectors to one another so if we ask a question we will get the parts from the PDF document that are in meaning closest to it so for example if I asked which color was the cat and we got exactly one result from the vector database we're going to use to answer this question using a large language model which one do you think we should pass to the large language model as context to answer that question of course it would be the left one we want the large language model to know that the dog is brown and okay if the question was which color was the dog to use this part of context to answer that question and obviously not this part because Germany is a country you know it does nothing to answer the original question so we only want to get the pieces of the PDF later that are closest to the question and the way we do that even though this does sound pretty complicated trust me it's only a few lines of code it's actually really straightforward and the basis to be able to answer a question here in this API Rod is going to be beforehand indexing the entire PDF file so now we can just search for the parts of the PDF file in this API Rod that are closest to the question to the message that the user sent so in the foundation we are going to do that in the core.ts in upload thing when we upload a PDF file right away we are going to index it so that we when we ask a question later it's already indexed and we can just search for the parts that are most similar to the question awesome and the way we're going to Index this is through something called a vector database this is not sponsored or anything we're going to use pinecone.io for this it's a vector database we can use very easily let's go ahead to app.pinecone.io and log in you could use any other service even Super Bass I think null supports this I'm gonna log in using my GitHub account and then we are going to create something called an index together and an index essentially just stores a bunch of vectors that's all it does so let's create a new index together here on the dashboard in Pinecone let's click create index let's name it quill the one you've already saw was for the demo app I already built and for the dimensions we're going to enter 1536 and this is actually important and this number is because we're going to use open AI to generate the semantic vector and they always consist of 1536 array numbers so 1536 of like 0.5 minus 0.7 and so on and so on the metric we're going to use is cosine we're going to leave everything else as standard and just click create index and that's also okay index will fail to create the index exceeds the project quarter of one pods okay so it seems like I do need to delete my old pod that's totally fine let's delete that chat dock My Demo project so we can open up a new one this says terminating and now we can create a new one again let's name it quill 1536 and let's leave everything else as the rest and hit create index awesome that's going to initialize our index and there are um there's an API key section you can see here on the left we are going to need an API key to interact with this index programmatically and to store all the pages of the PDF in their meaning into this Vector store so let's create a new API key I'm going to name it quill hit create key and then just copy this value right here so just follow along with me copy the API key value let's head over to our project into the dot EnV file and in this EnV file what we want to save this value as is going to be the pine cone underscore API underscore key and that's going to be equal to this value we just copied from Pinecone perfect okay we can go ahead save this EnV we won't need it anymore and after or index has been created right here let's see if it is awesome it's done initializing perfect that means we can actually get started to use it in or app right now and to do that we are going to create a custom file under the lib folder just like with the database for example that we had a separate file to initialize that in the same thing will go for Pinecone or whatever other tool for Vector database you're using and this will go under the lip right here and let's name it pine cone dot TS and to get started with Pinecone I don't think you even need a credit card or anything it should be really really easy um to get set up in there there is a package that Pinecone can provide to us to make this process much easier and that package is called at Pine Cone Dash database slash Pinecone there we go and by the way this is a really cool project using AI if you're not a fan of AI I totally understand that you could Implement another functionality entirely yourself all the rest of the video that's the cool thing is still going to stay as relevant because it has nothing to do with AI still the pricing model and everything else we're going to implement together and trpc and infinite queries and whatnot and it's all going to stay relevant even if you don't use AI but of course I encourage you to follow along with me here I think this is a really cool project and there are apps that do justice that have literally hundreds of thousands of users and that's pretty cool that's an indicator that people actually want something like this okay first off let's import the Pinecone oops pine cone client from the dependency we have just installed which is at pinecon Dash database slash Pinecone and from here we're going to say export cons get Pinecone client this is going to be an asynchronous function we're going to use to ship this Pinecone client across our app the cons client oops const client is going to be equal to a new pine cone client and let's invoke that and why is this marked as deprecated wow okay I just Googled it and it seems like as I made this project they literally updated their API to a completely different version but the good thing is they made it simpler just from the documentation I got this little code snippet right here where we can oops literally just import Pinecone database slash Pinecone and then export const Pinecone as a new pine cone and that's going to do all the work for us nice this class that we are instantiating here should take an API key this API key is going to be our process.env DOT Pinecone underscore API underscore key and we can tell typescript yes this definitely exists by using this exclamation point and the environment is going to be whatever environment you created your index in it's says that right here it's U.S east 1 gcp for Google Cloud platform we can just input that right here Us East 1 dash gcp and chances are for you it's gonna be the exact same thing awesome let's format this file the Pinecone and we are done here we don't need to touch this again assuming of course that the new implementation works I'm just doing it with you right now and because this is all new to me too but it shouldn't be a huge change and I think this is going to work out beautifully okay now back to the core.ts where we want to index our entire file there um let's first initialize a try catch block right here below the created file and let's catch the error if there is any and handle it later first off we need the PDF file we did create one for database what we do need it in memory in this API Rod right here to index it and also see how many pages it has to achieve that we are going to copy this URL right here that we are also using to store in the database and to get this file we can simply make a fetch request to this URL so the const response is going to be a weight Fetch and literally just paste in the URL right here what that's going to do for us is now we have the URL in memory and we can use it to generate some pages we want to index in our Vector store from them first off we need them as a blob object we need a PDF as a blob to be able to index it so let's say cons blob is equal to await response and there's a smart or a really handy not smart method we can call and that is dot blob we can just invoke okay to now Index this we have basically two options either we could do it all by hand of course that would work but it would also take really long instead what I opted for and what I think makes a lot of sense is installing one little package that is called Lang chain and land chain is becoming incredibly popular let's go to npm over here and let me show you let's enter Lang chain right here and I think that's um literally the name yep here it is typescript version and it's been getting a lot of popularity it didn't even exist a while ago but as AI app screws sold it land chain because it does making it does make working with AI apps a lot easier let me show you how exactly this works first off we want to load this PDF file into memory and the way we can do that is with a loader let's say cons loader is equal to a new PDF loader and this PDF loader if we reload the window hopefully the automatic Imports will work chances are they still might not let's see if we get the automatic import which would be from length chain let's see no we don't get it okay so let's go to the very top of the file and do the import yourself then let's import the PDF loader from and this is going to be from length chain slash document underscore loaders slash FS slash PDF and I just didn't come up with this this is just from the Lang chain documentation and it seems like it doesn't recognize that we have just installed Lang chain so let's see what could be wrong with that oh I didn't run the command okay of course the automatic Imports don't work if I don't run the installation command let's give it a second and then hopefully the automatic Imports will resolve correctly okay so it seems like the import is correct we do need to pass the blob into this PDF loader to work correctly and now we have loaded the PDF into memory and can actually work with it so for example we can extract the page level text of the PDF let's say const page level docs just means the page content is going to be in that document okay and now the amount of pages const Pages AMT is very easy to extract from this it's just the page level docs dot length so each document each element in the array is going to be one actual page of the PDF document that is uploaded and later we can use this to check if you're on the pro or on the fleet plan and for example if you try to upload above five pages of the PDF on the free plan we will throw an error right here for now let's just not worry about it let's instead vectorize and index the entire document instead so we don't care about page lengths for now we're going to do that later in the stripe integration okay so the way we can do this is first we need access to or index let's say const pine cone index is going to be equal to Pine Cone and this should be index by the way and let's see if we can just import this from lib it seems like we can and this is going to be dot index and we want the index we just created and we named it quill remember in the Pinecone dashboard right here this name is what we need to enter for the index right here because that's the name we gave to our index okay then we want something called embeddics const embeddings and by the way if you have questions about any of these steps there's always the land chain link chain JS documentation to explain all of this where it can literally also ask AI about something and it's going to answer based on the documentation so if you don't understand anything in here and if you feel I didn't do a good enough job at explaining this and always feel free to check the Lang chain documentation in case you don't understand anything at any point okay these embeddings we use to generate the vector from the text that's what we are using them for and this works using a new open Ai embeddings and of course the automatic import doesn't work either so let's go to the very top of the file and just do the import or self and this is going to come from import and then open aim beddings this will come from Land chain slash embeddings embeddings slash open AI there we go and the reason we're using the open AI ones is because it's just really easy to get set up you could use other services to take the text and turn them into a vector but the open AI models work very reliably for this and in here we can pass a configuration object that takes something called an open AI API key to get this key let's quickly head over to openai.com if you've never had an account or worked with open AI don't worry it's completely free you get like a starter credit of 18 or whatever and when you first sign up and that's more than enough for these embeddings These are incredibly cheap you can embed like 3 000 pages of a PDF for like two cents or whatever they're incredibly cheap so it should be really easy for you to follow along I'm gonna log in with Google here on the right hand side monitor beautiful let me move this over and now we want to navigate to the view API key section where we can create a new one I'm just going to remove my last used one that is the furthest away this was May 15th we can probably get rid of that revoke key and because you can only have five let's create a new one let's name it quill and I'm going to hit create secret key right here that's true that's going to generate or open AI key we can already move this window to the left that is literally all we need from open Ai and let's go into our EnV file and save it as the open AI underscore API underscore key it's going to be equal to the value we have just gotten from open AI awesome and what this now allows us to do we can go into our core.ts and just pass this environment variable into the open AI API key like so as process.env.openai underscore API underscore key beautiful okay and with that out of the way the originally pretty you know complicated sounding process that we had right here of turning text into vectors is now going to be super easy in fact it takes three or okay it's gonna be four lines of code I admit but still that's really really easy so follow along with me here and all the work is going to be done for us it's it's really cool let's say a weight and then pine cone store which is something we get also from Land chain and the automatic Imports don't seem to be working today anyways let's just do it ourselves I hope they work for you as you're following along the Pinecone store is Gonna Come From Lang chain slash Vector store course slash Pinecone like that the import is going to resolve beautiful let's quickly save that and now the pine cone store that we want down here this is going to come from documents and what we're going to pass into here is the page level docs from right up here that we loaded from the PDF we can just paste them in there as they are the embeddings as the second argument so tell Pinecone or to tell land chain rather how to generate the vectors from the text using these open AI models as the embeddings right here and lastly we can give it a configuration object containing the pine cone index and also containing the namespace in which we want to create these vectors this is like a you know we can save a vector to a certain namespace in our case that's going to be the file ID so when we query by a file ID we can get all the vectors for that certain file so let's use the created file Dot idea as the namespace okay and it seems like here on the left hand side we do get a little type error and I wonder if this is just a typescript error so for now let's say at TS Dash ignore in my example code I didn't have this because this Pine conversion is pretty new but I wonder if this is just a typescript error if it isn't and an actual problem in runtime then we're going to fix it together using the alt um implementation anyways we're going to fix it don't worry about it and then let's at the very end of the file add the database call to update the file to a successful upload set because that is already all we need to do right this is a very small AI part as I mentioned and focus on all the you know transferable knowledge so the DB dot file that update is what we want at the very bottom right here let's update the file we have created and uploaded and synced to our database with the data of upload status and turn it into a success because we process the file successfully and we want to update the data where the ID of the file matches the created file.id so we are going to change the file we have created at the very top of this code of this API route right here that is automatically called buy upload thing when we upload a file and if there is any error on the way in that case we're also going to await a db.file.update and as you can probably guess what we're about to do well let's copy the data from the update call right above paste it in this update but instead of the success we are gonna insert a failed so something went wrong there was a problem during the uploading and that is already all we need to do in the core.ts isn't that beautiful that's all the AI stuff and database called we need to process a file and to prepare it to ask questions against it that we are going to use our router TS on the left hand side here for the first off before we even get ahead of ourselves let's just try out if this works so let's go back to the slash dashboard by the way later we're also going to fix the navbar to have this dashboard right there and when we are logged in we could even do that right now but let's focus on this functionality first but let's not get ahead of ourselves let's go back to the dashboard and test if the indexing actually works we can close out of most of these windows and we just want to stay in Pinecone so let's try uploading a PDF ah and it's good that we're doing this test because we're getting an error and that is can't resolve this and that and whatever and that is because there's a peer dependency that comes with Lang chain that we did not install yet and that is called PDF Dash parse and we need to install this because not everybody that uses slang chain also will use this parsing of PDF so it doesn't make sense to include by default so it is an opt-in package that we still need to install for langshan to work properly but I think it's worth it judging by the amount of work it saves Us by just these few lines of code and that's um already all we need to implement so let's go over to the dashboard let's reload the page and then hopefully now everything will work correctly we can click the button where we can upload a PDF file and let me select my trusty lecture notes drag them in there and ideally what should happen is that we should see the upload thing Dev server is now running in our console it should say that the Callback was simulated successfully here in a second let's see while this is loading processing PDF this core.ts is loading that's why we added the loading State and once this is done and has resolved successfully we should see that right here in the console okay and after some debugging I finally made it happen successfully simulated callback for file and whatever so it seems like the problem really is the new Pinecone implementation so when I go back this was our previous code and this is the old implementation that I used in my code where I know this just works so I'm going to include this in your copy and paste list so you don't need to type this out yourself let's say Pinecone um client creation or whatever I'm going to call it and I'm going to put that code right there so you can copy that code paste it into your pinecone.ts file and simply initialize the Pinecone client before using it in the index you remember when we had the little at TS ignore here well it seems like this wasn't just a typescript error but we didn't get any error in the console or whatnot so we are going to use this implementation I used in preparing this project I know this works reliably that's why we're going to use it and no need to type out everything yourself just add this line that wasn't there before and everything else will just work beautifully if we save this core.ts now we can see the file was indexed successfully successfully simulated callback what you just saw and also if we check in Pinecone in the quill Index right here we should be able to see that we have some namespaces right here it matches the file ID and since our PDF has three pages there are now three vectors in this namespace that means for each page of the PDF we now converted it into a vector like imagine every sentence here was one PDF file we turned it into a vector and that's what is stored in Pinecone and will now allow us in the route.ts here where we send and retrieve messages to see which page of the PDF is most relevant to the question that the user is asking retrieve that page for context and send it to the large language model together and the principle is going to be exactly the same so first we want to back to rise the message the incoming user message that's going to be step one and that's really straightforward we can use the same embeddings as we did in the core.ts so we're going to use the exact same code to generate a vector which is essentially the exact same thing as an embedding those you know those words are interchangeable you can also import the open AI embeddings here and wow look what's working you know the automatic Imports fantastic and now in this router TS we need pretty much the same code as in here we're going to get the client and the index from Pinecone and so we can just copy and paste that bit from the core over as well paste it right here in our route import the get Pine con client and what we can now do is search or vector store for the most relevant PDF page to this message that we just create a you know Vector from and the way we do that is by saying const Vector store oops store is going to be equal to a weight Pinecone store import that dot from existing index we've got it right up here so we can create or Pinecone store from it with the embeddings we have just created and this takes a configuration object with the Pinecone index and also the namespace very important that we search in the correct namespace is going to be the file dot ID and that's what's going to allow us to search for the most relevant page of the PDF to answer this message so to actually you know get the results from Pinecone which we haven't done yet we can say cons results is going to be equal to a weight Vector store dot similarity search really really convenient and in here we can pass in the message and also we can pass in how many results or PDF Pages we want to get back for example we want the four closest results to our message that should ensure we get pretty good results and then later what we can do if the user is a pro user a paying user then we could for example add more messages for better contextual results awesome now to pass these messages into the large language model itself would be good but what also makes a lot of sense if there is a chat history we want to see the previous messages of the user and the way we can just search for the previous messages that were exchanged in this chat with this particular PDF file well we can just ask our database because that is where we store them let's say Khan's previous message is going to be equal to a weight a DB Dot message dot find many and which messages do we want to find where the file ID mattress or file ID we want to order them by the created ad in an ascending order and lastly how many do we want to take I just opted for six this is a pretty arbitrary number but I thought yeah the last six messages make sense to take you can obviously choose more or less depending on what you like I think six makes a lot of sense and now comes the part where we send them to open AI for you know the large language model to answer the messages and open AI expects a very certain format of these messages to be in so what we can do is say const formatted formatted messages that's pre-format them are going to be equal to previous messages and we're gonna map over them now for each and this should be previous messages oops messages like that as a plural and for each message MSG we map over we want to return an object implicitly like directly that's why we're wrapping this in parentheses right here where the role is going to be the message dot is user message and if it is a user message then we want the role to be user as const and else we want the role to be assistant and that should be with a space as cost and the reason we use s-cons right here is else in a second here we would get a typescript error that's why the S cons is so helpful and the content of this message is going to be the message dot text that was exchanged and now these formatted messages are actually ready to be sent to open AI for a response and the way we can do that is pretty straightforward the response from the large language model let's save that as const response is going to be equal to a weight open Ai and that would be a really nice utility to have that we can just call and we can really easily create this utility just ourself and let's do it in the lib folder because we are preparing a library to be used in our project and let's call it open AI dot TS and in here let's just export const open AI it's going to be equal to new open AI which is something we get from the open AI package makes it really easy to interact with their API let's quickly install it pnpm install open AI just like this and we only need one very tiny thing from this package and that is this open AI thing right here we can import that as a default import open oops open AI from open AI just like this you can close all of that and this takes in a configuration object once we instant instantiate it which just takes the API key let's pass in or process.env.openai underscore API underscore key that we have in our environment variable right here this one is what we want to pass in great and that's literally all we need to do we can already call that utility now from or route dot TS and the API has become so much better to work with in typescript let me show you how this works we can call the open AI dot chat dot completions dot create in here for the model we are going to use the GPT 3.5 Dash turbo because it's really fast and cheap as the temperature we're going to use zero as the stream we're going to say true because we are going to stream back this response to the front end in real time okay you're gonna see what it looks like it's it makes a much better user experience and now as for the messages that have been exchanged in this chat and by the way we also need to add a comma up here perfect all the arrows are gone these messages will serve one purpose and that is we want the previous messages that were exchanged in this chat with the bot so if we go back to the example we had earlier we want to attach all the messages that were already exchanged previously so if you're referencing something from a couple messages ago the AI will know what you mean and then also they need to be in a very specific um format now doing this all herself would take a bit so therefore I propose we just go into the copy paste list and copy the messages where all the prompt is already written out that's not a really you know crucial part to know it just teaches you how to prompt and not how to code so I don't think it's worth spending our time on now we are going to get an error here because I forgot to name the formatted messages um how I named them here and then all is going to work so just go ahead into the copy paste let's paste in the messages just like so and change this to the formatted previous messages is what I called it and just like that we saved ourselves a bunch of time we can skip all the formatting and essentially we just have a system prompt to tell the AI that it should just use the context to answer the question we attach the previous conversation in a you know specific format a specific context these are the PDF pages that the AI will use to answer the user question and then finally the question and the user has asked to the large language model and this is what we can now return as a real time stream back to the client and the way we can do that is of course by hand now that would be quite a bit of work I actually did it all by hand a few projects ago and it takes like 10 minutes to do but it's really not the most fun work but there's a really cool new package that versel recently published an official Versa package and that is called just Ai and that is what I propose we use it has gained a lot of popularity recently as you can tell in the downloads and one little side note by the way one thing I've noticed in many other people I've noticed there was a huge dip in all npm package downloads recently this has nothing to do with the video or anything just an interesting thing I noticed no idea why that happens I don't think anybody really does and but this is a cool new package we're gonna use to make streaming response in real time a lot easier so we're going to say npm or pnpm install AI as easy as that and then we're going to make use of one specific function that the AI and package offers us and that is going to be once that is installed let's go to the very top of the file and import something that's going to be the open AI stream and also the streaming text response from AI there we go and that needs to be open AI stream just by importing these two values from AI right here we can go back to the very bottom of the page right here and this right here is the main reason we can't use trpc but instead need a custom route so what we're going to do is say cons stream the stream we want to respond M or send back to the client in real time it's going to be an open AI stream what we've just imported at the very top this takes in a response there we go and also an object and we can pass this a callback function for when the stream is complete so this is going to stream the AI response in real time back to the client and once that is done we get something called a on completion callback which is super super handy in this completion callback this takes in the completion the um you know fully done string of the response so after sending everything back in chunks to the client we get back the complete response as one long sentence basically as the completion and can put that as the message into our database so db.message dot create and we want to create it with certain data and that is going to be as the text the completion then as the is user message we're gonna pass false and also the file ID and the user ID to link those um two together so we know the user we know the file this message was asked to and that's literally all we need to do now final line of code in this entire file is returning return the stream to the client and consume it in real time as the AI model is literally just generating the output that's going to be return new streaming text response and we can simply pass in and why am I on the English keyboard again simply pass in here the stream just like this beautiful let's save that route.ts and now essentially we are all set to stream a response in real time back to the client okay awesome welcome back for me it's the next day that's why there was a little cut there and I left myself a comment right here on what we were about to do so we just returned the stream from the API route and now we want to accept that message on the front end to be able to display it right here in the chat messages however the first thing we have to do to even see the message and send the message from this chat input we can see right here is go into that chat input and what did I do here I don't want to print that go into the chat input and actually wire up everything we need for that to work currently we only have a form here that would be sent however that's not really what we want instead we want to call the send message we get from the context which is not open right now let's go into the context this function right here the add message is what we are going to use to send a message over to this route.ts API endpoint and we still haven't wired that up in the chat input so what we can do is go inside of the chat input and destructure some stuff from the use context and in here we are going to pass or chat context that we are exporting perfect and in order to be able to do this we also need to wrap every component that uses this context in something called the context provider which is nothing else than the component we are exporting from our chat context to use this this is also what the chat wrapper is amazing for because both components that will use this context are already in here so we don't have um any work we can just import the chat context provider at the very top of our jsx inside of the chat wrapper and wrap the entire component inside of it now this chat contacts provider also requires one thing and that is the file ID and this is nothing else then let's call it file ID which we already have access to inside of this component if we look at the very top we get the file ID passing as a property inside of this component so we can just pass it right on in the chat context provider down here perfect save the chat wrapper we can now exit out of it we won't need it right now and go back into our chat input now we can actually make use of the context and the structure before things we need from there which is the add message the handle input change the is loading and lastly also the message and now we can actually you know wire up the input to send along the message to our API endpoint and also Implement a really cool functionality to make this more keyboard accessible and that is when we press enter inside of the chat input right here we want the message to send instead of opening up a new line As we currently are and to do that let's go on to the text area and check if we are pressing enter we can do that inside of an on key down Handler just like this and in here we receive the event the key down event and now we can do a check if the E dot key is triple equal to enter and we are not pressing the E dot shift key because enter and shift is the shortcut for opening a new line and we want to keep that of course only if we're pressing enter and not shift at the same time then we're gonna first prevent the default Behavior by calling e dot prevent default then we're going to say add message and the last thing we want to do in here is shift the Focus right back into this text area so it will again be focused right after sending the message in case the user wants to send a second one right away that's a really good idea and the way we can do this is through something called a ref and let's call this a text area ref right above or return statement and this is going to be a use a ref hook we can get from react and if we're in typescript we want to pass an HTML text area element right in here and by default this ref will be null and now let's assign this ref2 or text area let's call it ref is going to be equal to text area ref what we have just created right up here and just by doing that we can now go ahead and in the on key down actually programmatically set the focus inside of this text area by saying text area ref dot current dot focus and we can just invoke that make sure the current is a question mark because technically this could be null as we are initializing the ref as null perfect okay now there's gonna be two things missing in this text area first off that's gonna be the on change we need to turn this into a controlled input and we already have the function that handles that and this is nothing else then or handle input change we've already declared in the context and lastly we want to pass the value to turn this into a complete controlled input and that is going to be equal to the message we also get from the context perfect and last thing we haven't used from the context yet is the is loading State now where are we going to make use of that the answer is right here in this button to control the disabled state so this button should be disabled if we are loading and in this case loading just means that the message is currently being sent to the API or the AI is currently answering the question in that case we won't be able to send a second question or we can disable the button if we say the is disabled what we will receive as a prop inside of this component is true so if either this or this is true the button is going to be disabled okay lastly we want this button to be of type submit and then we want an on click event on this button so when this button is pressed in that case we want to also add the message the function we get from our context and lastly we also want to set the focus programmatically so we can simply copy and paste this from up here the text area.current.focus paste it down here in the on click and that's already it we can save this button component and now let's actually try out if orchat is working let's go into full screen into the inspect and let's open up the network Tab and here and see what's up so let's enter our question that's going to be let's say who was Plato because again this is just a random PDF I found online and it's something about philosophy and one name that is mentioned here is Plato So let's just ask who was Plato and hit enter okay apparently the text area is not cleared yet that's fine but let's see if we get a response and we do and the beautiful thing is that this response is actually being streamed in as you could see with this waterfall diagram it only appeared completely as the entire response was already there but there's another indicator we can use to see that this is in fact actually being streamed in let's try this again let's send a message in one sentence who was Socrates another you know name that is mentioned inside of this random PDF let's hit enter and pay attention to the size of the request that we are going to make unfortunately I can't zoom in much more but pay attention to the size that the response will have let's hit enter and this message pay attention to the size right here you can see it increasing it's now 816 bytes and it was increasing because the message is being streamed in as we get it so we know that this definitely works which is awesome we know we now get the AI response in real time and that also means we are now all set to actually display it in the chat messages window right down here so currently we can ask questions and we do get the answer in the network tab but of course we want to show all the messages and that were sent previously right in here and the way we can do that is inside of course the messages component that currently is literally just an empty div inside of here we want to get all the messages that we have to display and to render them alt one by one inside of the chat window the way we do that is by of course first fetching these messages from the database to know that we always have the correct data and the tool we are going to use for that is going to be trpc however we don't even have an endpoint to handle that yet so let's create a new API rod in trpc that lets us fetch all the messages inside of or um you know for this file that were sent to this file and the way we do that is let's create a new endpoint called get file messages because that describes the purpose pretty well and this is going to be a private procedure meaning you need to be logged in to do this as the input the pulse request body we are going to be expecting an object inside of this object we need three things and let me show you why we need these three state these three things there we go first thing is going to be a limit this limit is going to be a z DOT number and that's already insert a comma here to get rid of that syntax error this is going to be a z DOT number and as a minimum so dot Min on this number we want one and this this number can also be a maximum of 100 but we can also say dot knowledge which means we do not have to pass it it is in fact optional then we need something called a cursor and a cursor determines later on in the infinite query that we are creating right now which means first off on page load we are rendering only the last like 10 messages or 20 whatever you want you can customize this and then as we scroll up more messages are loaded and to know when we should start searching for new messages right so if we're loading the first 10 then we want the next 10 and to determine how to find the next 10 that is exactly what the cursor is for this cursor is going to be a z dot string and it can also be knowledge which means we do not have to pass it but the last thing we do have to pass is the file ID that is going to be a z dot string so we can know which file we should fetch the messages for and this is going to be a query this query will take a callback function just like this and I did a little typo here we don't want to wrap the stuff right here inside of a literal object syntax but we want this to be a z dot object and in here we can Define all the properties we have in the input awesome okay now let's get started in the query and in here we're going to fetch all the messages we want to display in this chat window in order to be able to do that we need to destructure both the context and the input right here and can now get started in the logic for getting the messages first off we need the user ID so that we can later ensure that only the files that is owned that are owned by this user um are going to be fetched so we can just destructure that from the context then we want to get the file ID and the cursor from the input just destructure those right away and we can now Define something called a limit and this is going to be either the input dot limit if it is passed which it doesn't have to be and if it is not passed well in that case let's define a central query limit and usually this is done through something called a config file and where we are going to create this file is under or Source folder let's open that up let's create a new folder called config and in here just go some constants that you know Steer the entire logic of your app that you can reuse let me show you how this works so for example let's create a new file called infinite Dash query.ts in which go all the configuration constants to determine the logic of our infinite queries for example or infinite underscore query underscore limit that we are creating right now and let's set this to 10. that means only the last 10 messages will be loaded once this page loads and then when you scroll up again 10 messages will be loaded you could obviously set this much higher but with 10 and we can actually see this happening instead of having to you know write 50 messages before we could see it in action so okay let's copy this as the fallback value for the limit in this API rod and just paste it in here import it there we go so either we pass in the limit directly or if we don't then we're always going to have a fallback value we can use now let's first find the file the file is going to be equal to a weight and view to be able to use a weight we need to turn this into an asynchronous function DB dot file dot find First and we want to find the first file where the ID of the file matches the passed in file file ID and of course you should know the drill by now that is only owned by the user so we can't get anyone else's file and if we don't have that file in that case we're gonna throw a new trpc error and in this crpc error the codes we're gonna pass back is the not found because there is no file with this ID for this user okay and now it's time to actually fetch the messages that we want to show for this file that were sent in the past um from the user and these are going to be cons messages is going to be equal to a weight DB dot messages or message is what we call it dot find many and now in here goes some more elaborate Logic for example we only want to take the messages of limit so we will never take more than the limit but we also want to take this plus one and this plus one is going to be important here in a second to determine later if we scroll up from where we want to fetch the next messages so take this for example and let's just say we fetched the five last messages and didn't we have the component in here yeah let's just copy this out and duplicate it so I can show you let's say the constant we have which we set to 10 just a minute ago was five right so we fetched the five last messages well now we are gonna take from the database to the last six so this will also be included on top but not necessarily displayed but it's going to act as the cursor so when we scroll up right we now know with this cursor in place which are the next messages we need to fetch right up here so the database knows what's happening because these messages should not be fetched again they're already in there there's literally no point of getting those again from the database and that is what the cursor is for it tells us which next mesh messages we need to fetch from the database so we're going to take the limit plus 1 and this one extra element is going to be our cursor we want to get these messages where the file ID you know just matches the file ID in this API route we want to order these messages by their date of creation so created ad and we want them in a descending order then the cursor that I just explained to you is going to be either if we pass in the cursor into this API Rod then that's going to be the ID of cursor and the idea just how we identify the cursor in the database and if we don't pass in the cursor then it is going to be undefined which is fine as well and lastly we are going to select only a custom properties from this for example the ID we want for each message we want the is user message so let's say true there as well we want the created ad property and lastly we want the text that is in the message so that's going to show up in the chat window as the text content last thing we want to do in here is determine the logic for the next cursor so as we scroll up now we need to determine which message is the cursor and to do that let's say let next cursor and let because we're going to mutate this here in a second and this is going to be of type of cursor or undefined and let's initialize this as undefined so it doesn't really exist yet now if the messages dot length is larger than the limit so if we have more messages in the database well in that case let's say the const next item that we're going to start fetching from is going to be the messages dot pop right here if you don't know what the pop is let's hover over it it oh it Jesus it removes the last element from an array and returns it so or plus one what I explained here a second ago that's what we get as the next item right now and let's assign that to the next cursor and we have defined right above this if statement that's going to be equal to next item this is going to be an entire message but we only care about the ID property of it and finally we can now return from this API Rod both the messages and the next cursor um so we know where to start fetching if we scroll up later awesome and that is already all the logic for the infinite query done on the back end part the only challenge now will be integrating it on the front end but let me tell you even though it is one of the most elaborate parts of this build especially with the optimistic updates we're going to do it's also the one we're gonna learn the most and it's gonna be really fun as well so let's get started in displaying the messages in our component right here the way we do that is by destructuring something from trpc and then the API route we have just created which is the get file messages and lastly we want to use and not use Query this time but use infinite query so we can scroll up and get more messages later in here we're gonna pass an object and there goes the file ID in here which obviously we don't have inside of this component so we will need to receive the file IDs props and let's call these the messages prop so just the component name plus props of course we also need to Define them let's create an interface at the very top the messages props there we go and inside of here only the file ID of string is going to be passed as a prop in here that means we now have access to the file ID for trpc and as for the limit we are going to pass the infinite oops the infinite query limit config constant that we just created together and of course those need to become separated awesome as the second configuration option in another object just like this we are going to pass a function called the get next page param so what should happen as we scroll up how do we get the next page well first off we get access to the last page and with this last page we can simply return the last page in the next cursor so react query knows what's up by the way this needs to be optional with a question mark right here so react query what we're using under the hood with trpc um knows where to start fetching the next cursor and lastly as we are getting new data we still want to keep the previous data to avoid any data flashes so the data in the chat will only be refreshed as we already have the new data and not before that perfect so what this now means we can actually get started and display the messages in the chat window right here for this the top level div let's give it a class name and this is going to be Flex a maximum height of and now a custom value in angle brackets that we are going to calculate like this and this is going to be 100 view height minus 3.5 REM minus 7 Rem there we go awesome then for the Border we're going to give it a border zinc of 200 flex-1 Flex call reverse there we go a gap of four a padding of three an overflow y of Auto is scroll bar Dash thumb Dash blue a scroll bar Dash thumb Dash rounded a scroll bar Dash Track Dash blue dash lighter and almost last the scroll bar W-2 and lastly a scrolling Dash touch there we go I promise that was by far the longest class name let's open up this div right here and inside of this div we want to map over each message and you know show them one by one in the chat window however one trick we're also already gonna apply for later is that we want to create a combined messages constant what this means is if we send a message later we also let me move this into a side by side want to display a loading State message and in order to achieve that way easier let's create something called combined messages that is going to combine the loading message to all the other messages in our um chat so essentially it's just going to be a array and inside of this array if we have a certain condition set to true we want to display a loading message and else we just want to display all the other messages so first off let's mock this loading State let's spread in something and in here in parentheses we're going to check for now let's just mark this as true later we're going to insert a dynamic constant in here and if this is true in that case we want to also render out a loading message and else if this is not true then we're just going to spread in an empty array essentially meaning we are adding nothing to the messages the loading message will give us an error because it doesn't exist yet that's fine and lastly let's spread in all the other messages that we got from up here first off to do that we need to de-structure the data from here and while we're at it we can also destructure the is loading and the fetch next page function for later let's give this a bit more space so we can see better and the actual messages to extract them from the data you can see these are not the messages directly but the messages are inside of the data that we get back in something wrapped called infinite data with a next cursor however we just care about the messages and to extract those from the data let's say cons messages is going to be equal to data dot Pages dot flat map and what a flat map is essentially we could use a regular map for this and let me show you what this does and why we need the flat map so for each page that we get access to in this callback right here let's return the page dot messages and if we used a regular map let's see what happens well the return type is going to be an array array and that's the beauty of the flat map it's basically a flattened and a map at the same time so we only get one level of array which is exactly what we need so we don't need to map around the data you know twice later on which would be um you know not very developer friendly okay and inside of here let's spread in the messages or in empty array if these are null or undefined that's what they double question mark right here is for okay now as for the loading message that doesn't exist yet so let's quickly create it and this loading message that we're going to create right up here in format needs to match the regular message so we're going to pass all the properties we have inside of a regular message and need them in this loading message for example the created add this is going to be a new date and we're going to convert the state to an ISO string just like this as for the ID of this message let's say load Dash message we can just make something up that is unique as the is user message we're gonna pass false and that's because we want the loading state to be true for the AI and not for us as a user and lastly as for the text we're gonna pass in in parentheses a span element so we're you know I'm just going to pass in some jsx instead of plain text and in this span element this is going to be a flex a height of full as the class name and items Dash Center and a justify Dash Center in the span element as well and then a loader 2 to render alt a loading State just as we did many times before with a class name of height or 4 width of 4 and animate Dash spin so it spins around in circles perfect so now we have the combined messages we spread in the loading message in a mocked true state which is fine by now we're going to change that in a second and we can actually now make use of these messages in the jsx to show them in the chat window up here so for each message let's map over them first off we need to do a little logic check that is if the combined messages even exists and if the combined messages dot length is greater than zero because only in that case can we actually map over the messages if it is we're going to render out something in parentheses and if it's not then we're going to do another logic check and that is going to be if the messages are currently loading so that's going to be the is loading and if we are loading in that case again we are going to display some jsx let's just render out an empty div here for now and we still need to complete this ternary operator so if we are not loading in that case as the colon that goes at the very end here in that case we're also going to render out a jsx element that's going to be a div and right now again this is also not a valid syntax with the just the parentheses right here so let's complete this part first let's go into full screen so if the combined messages are defined and are longer than zero it means we have at least one message and so let's map over them and display them all the way we do that is by going into these parentheses and saying combine the messages dot map and for each message that we have in our array first off we want to do some logic that's why we're not returning some jsx directly but later on we are going to return some jsx and we also need the index from what we're mapping over let me show you why this is important and why we need this index from the combined messages so the way we are going to determine when to load new messages on top is by this element right here if it is intersecting with our viewport so imagine we're scrolling or scrolling and now we want to load more messages because this element is intersecting with the top of our viewport so we want to load new messages now and the way we do that is by attaching a ref to the last element that is rendered on the screen that's why we need the index to perform a conditional check if this is currently the last element that we're rendering so if the I the index is the combined messages dot length minus one so it is the last element in that case let's return something um and let's return a message oops message custom component that doesn't exist yet but we're going to create it in a second to render out each message and else if we are not rendering out the last element then we are still going to return the same thing a message so let's say else return also a message component the only thing that's going to differ in a second is one prop that we will pass to this component right here okay um let's also do one little logic check inside of the mapping right here and that is if the next message is sent by the same person because that way we can kind of change up the CSS that will be an edge case because we always expect messages to be exchanged one after another user AI user AI but if for some reason anything goes wrong and there are two messages from the same person again we do handle edge cases in this app of course because we want to display deploy it later in that case we of course also want to make it look amazing so the const is next message same person is going to be a very simple logic check if the combined messages at the current index -1 the dot is user message is triple equal to and let's go into the next line right here the combined messages at the index of the current index that we are mapping over dot is user message and if that is true then there are two messages from the same person and we want to accommodate for that in or styling else if we are loading and there are no messages yet or they haven't been loaded in that case let's return a div just as we currently have with a class name of width of full Flex Flex Dash call and a gap of two inside of here let's render out some loading skeletons skeleton we get from react loading skeleton these are going to be self-closing and each one is going to get a height of 16 and let's copy it one two three more times so we have four skeletons in total and I just copy that again by pressing shift alt and arrow down in case you forgot and lastly if we don't have anything to show if there are no messages let's also handle that in the last ternary statement with a class name of flex-1 on the div Flex Flex Dash call items Dash Center justify their Center and a gap of two inside of here let's render out a message Square icon just like this and give it a class name of height 8 a width of 8 and a text blue of 500. below this icon we're going to render out an H3 element with a class name of fonter semi bold and a text of XL for extra large saying U and then again the safe variant of rendering out in apostrophe by and apos and then this little colon or whatever it's called your or set to inform the user that they are ready to message their PDF file right now and inside of the P tag we're going to create below that with a class name of text zinc 500 and text SM inside of here we're going to say ask your first question to get started period awesome okay now the last thing that's left to do is actually creating this message component that we are going to use to render out each message individually in the chat window okay yeah we're getting an error for this so it's about time to create this and let's create it inside of our chat folder where each component goes that is related then the chat functionality let's call it message dot TSX this is going to be an arrow function cons message is going to be in Arrow function and let's export default message from this component beautiful what we are going to return from this component we turn right here is going to be a div element at the top level and this div element is going to get a dynamic class name so as the class name let's pass in a curly brace and or trusted CN helper function that we can now use the first determine or to give it some styles that are always applied that's going to be flex and items Dash end and then we want to do a check if the if it is a user message that we are displaying in that case we want to display the message on the right hand side and if it's an AI message it should be on the left hand side of the chat and the way we do that is we need to know if this message because each message will be rendered out in one of these components is a user message so there are two things we want to pass as properties into this component and that's going to be the message object and also the is next message same person from the other component let's define the type this will be and this will be of type message props of course we also need to Define this interface let's copy the name and define the interface right above the component right here the message is going to be a certain type that we're going to create right now in a second let's already Define the type for the is next message same person by copy and pasting it in here and let's say this will be of type Boolean because either it is or Not by the same person and now this message type is going to be a bit more involved because if we take a look at the data that we get back from here each message that we have it has a text an ID a created ad and an is user message that is not in fact the entire message object we get from Prisma that has way more stuff attached to it so what we are gonna do is give each message of these a separate type and where we are going to declare this is going to be inside of our source folder and let's create a new folder called types and here everything simply and only typescript related will go in let's give this a name of message.ts and let me show you some Advanced trpc magic we can do inside of this file right here first off we need to infer the type of the data that we get back from trpc and trpc allows us to do this actually with something called a type router output where we can infer the output of any route that we have in trpc and the helper we can use for that is the infer router outputs from trpc server and we can simply pass in the type of our entire API structure the app router then the type of the messages that are returned from this um you know get file messages that is the type we need right now and we can simply infer it by using this router output Helper and pass it in Array with a name of and we get intelligence now how cool is that we want the get file messages and as the second element right here we want the messages and not the next cursor so just like that we have what trpc returns from our API endpoint and if that changes this type will automatically change with it so this is incredibly maintainable then what we want to do is if you remember we have a loading message inside of our messages and this doesn't render out a string as the text but a jsx element the loader so we need to accommodate for that first off we need to omit the original message property let's call this type omit text and this is going to be a typescript helper type a utility type you can use the omit pass in the messages and because this is an array by default but we only want one single message element we can pass a number as an array right here so we will get only one instance of it and then we need to pass the value we do want to Omit and that is going to be the text property because by default this is only a string but we want string and jsx element the way we do that is really easy let's name this type extended text and this is going to be an object with a text property of either string or for this pipe operator a jsx DOT element there we go so we now accommodate it for or loader and we can simply export these extended messages extended meaning the text has been extended by saying export type extended message is going to be equal to Omit text and let's combine that with the extended text just like this so the result will be all that the message originally has like the ID and the is user message and whatnot but the text is now going to be a string or a jsx DOT element and that is exactly this time right here we are going to use to declare our messages instead of the message props and all that's left to do now is importing this type wherever we render out the message beautiful so now we get full typescript benefits inside of this component and we can plow ahead in our conditional calcium that we were writing right here so what we want to do is pass this also an object into the CN function and we want to apply a justify Dash and property if this message is a user message by checking if the message dot is user message value is true then the justify end is going to be applied then we're going to create a icon either a user icon that will always be on the left hand side of the message or a little AI icon whatever you want it to be um so it just looks a little better right these um red things the little icons inside of each message is what we are creating right now and let's give them a class name most of these class names are going to be dynamic with or see and helper function let's use it right here again and pass a default name of relative or Flex a height of 6 a width of 6 and aspect Dash Square let's give this a bit more space there we go in items Dash Center and a justify Dash Center and then conditionally we want to pass an object where the order Dash 2 property a background blue of 600 and a rounded SM property will be applied If the message is a user message and then we want to apply an order dash one a background zinc of 800 and a rounded of small if this message is not a message dot is user message and lastly we want to also apply an invisible property if the next message is from the same person because in that case the icon should only be shown once right this look would look weird but instead we only want to display the icon once right and the way we do that is by applying this invisible if the is next message same person Boolean is true beautiful now let's open up this div and inside of here we are going to render out the logo that we want to show either for the user or for the AI feel free to use whatever icon you want let's first do a check if the message dot is use oops message dot is user message is true and here we want to pass an icon and to make life a bit easier for ourselves let's quickly go ahead and declare a separate components for or icons let's do that inside of the components folder create a new file called icons dot TSX and this component is going to be very very easy let's export a const icons and that is simply an object as for the user icon we want the user we get from Lucid react and as for the logo icon that we want well this is going to receive some props that we can't pass in and these are going to be of type Lucid props so wherever we render out this logo we can give it a different color and height and whatnot that is why we accept props right here and this is going to return the SVG element the logo will represent and instead of typing this out I say we go to or copy paste list and paste and you know copy and paste this SVG icon right into our logo icon right here of course is if you want to use a different logo that what I'm using for example your personal logo if you have one you can of course paste that right here as the SVG and make sure you spread in the props so we can style it accordingly perfect now in order to be able to use it this is very easy we can simply go ahead and import our icons component and say icons dot user right here this is going to be self closing with a class name of that is why the props are so important we can simply give it a class name of Phil zinc of 200 a text zinc of 200 a height of and then three dash or uh you know what is this called like a slash four so three fourths of height and a width of three-fourths as well okay and if this is not a user message it means it is an AI message and in that case you want to render out the icons dot logo again self-closing with a class name and this class name is going to be fill zinc of 300 this time a height of 3 4 again and a width of three-fourths as well perfect and that about finishes up the icons we want to display for each message let's give this a tad more space let's open this up and by the way we can already or should already import this message component into our messages so we can get rid of the error of course this expects some properties for example we are mapping over elements that is why we need to pass them a key this goes for everything we're mapping over and this key is going to be the message that we have for this mapping instance dot ID then for the above message let's also or we can yeah we can just do this for both messages as well why do it for both separately we need to pass the is next message same person and this is nothing else than the value we have calculated right up here instead of the concept so we can simply paste that into both messages now we are still missing something and for the above message and the blow message both that is the actual message and that is nothing else than the message instance we are currently mapping over the element of the array that we want to render out this message for so let's pass that into both as well so we will get rid of all the errors awesome let's save these messages and we have so many tabs up in wall we can close out of a lot of these let's just leave open each message okay perfect we can already see an icon popping up right here and that is our mocked loading State message from the AI that is why the logo is showing up here but the text is not there yet so of course we also want to render that out so let's go below the closing div of the logo and let's open up a new div and this is going to get a class name and this will be dynamic as well with or CN of flex Flex Dash call a space y of two a text Dash base a maximum width of MD and an MX of 2 and what we now want to dynamically apply in this class name is going to be instead of an object and that is going to be the order dash one property and the items Dash end if this is a user message if the message dot is user message and else if this is not a user message we want to apply the order Dash 2 and items Dash start of course for the same condition but in the opposite so a exclamation point if not message dot is user message just like this amazing now let's open up this div and create one more div inside of here with a class name off again this is going to be conditional depending on whether the AI sent this message or the user sent this message this is going to be a CN and by default we're going to apply a padding X of four padding y of two rounded LG and an inline Dash block property there we go as an object we're going to pass in here that is going to be the BG blue of 600 and text Dash white if this is a user message message dot is user message and we are going to apply as the color a background gray of 200 and a text Gray of 900 and there doesn't need to go a comma right here if this is not a message that is user message oops message dot is user message perfect then we want to do one more small check and that is oops what did I do with formatting here that is we're going to apply and rounded Dash BR bottom right dash none if not the is next message same person and this message is a user message message dot is user message there we go and else we're going to apply a rounded Dash bottom left dot none if the if not the is next message same person again and the message is an AI message and if not message dot is user message there we go so a negative clause in both of these last ones okay perfect then we are going to open up this div right here and we are now going to display the actual response or the user message you know whatever the text is inside of the message now inside of this if we are actually going to display the text of the message and one thing we are going to do is let's quickly go into our CMD and install pmpm install react Dash markdown and the reason we are installing this is because just by adding like two lines of code we can massively improve the output of the AI to handle markdown like bullet points and tables and whatnot and the way we do that is by going now to the very top of the file and we can now import react markdown from the library we have just installed react Dash markdown we can simply import this as the default import go back down inside of the div we just opened up and wrap this entire thing in react markdown and just by doing this or AI basically well the AI already knows how to handle markdown but now it can actually answer with bullet points and it's going to look really good just by adding this code let's add a class name to this react markdown and this class name is also going to be dynamic with or CN helper by default we're going to apply Pros which is a you know we get from the Tailwind CSS plugin we installed and next to this Pros we're also going to put an object for conditional styling and we're going to say text zinc of 50 If the message dot is user message perfect now this react marked on currently has a problem and it says property children is missing type blah blah blah what not what not so all that means is we forgot to pass it the message.txt as the actual content now the arrow is not going to go away this jsx tags children prop expects type string and whatever but only a single child was provided now the problem is react markdown can only accept a literal string but the text could also be a jsx DOT element which cannot be rendered in markdown therefore let's cut this element out we're going to be pasting it in a second but first off we need to do a little check but I just wanted to show you why we're doing this check so let's say type of message dot text is triple equal to and then in quotes a string and only if it is a string in that case let's open up some parentheses and paste or component and else we are just going to display the message dot text as it is because it's going to be a jsx element and we don't want to render that in mark them so this way we can make sure only a string is ever tried to render out as markdown awesome and then right below the curly braces from this conditional check above let's do another quick check and that is if the message dot ID is not equal to a loading Dash message because let's open this up inside of braces in that case we want to show the date when this message was sent if it's a loading message of course we are not going to show that date instead of the loading message so let's open up a div inside of here and also handle the other side of the ternary where we just render out no if this is in fact the loading message now of course let's also close this devop to get rid of all the errors and this div is also going to get a dynamic class name with or C and helper which is going to be text Dash XS a select Dash none a margin top of two a width of full the text Dash right let's open up this div right here and inside of this div we want to render out the data where this was created in a certain format and again we're working with dates which is pretty horrible in JavaScript by default so for the format function we are going to import that from date FNS inside of here let's create a new date and this will be created at the message at the message dot oops message Jesus Christ dot created at and the format we want this to be in is going to be in HH colon mm so hour and minutes awesome and that's already it one thing we quickly want to do is also do a little conditional check and change the color whether this is a user message or not so the way we do that is let's go into our CN right up here enter an object for some conditional checks and we are going to apply a text zinc of 500 if this is not a message.is user message and we are going to apply a text blue of 300 if this is a message oops message dot is user message perfect and that about summarizes or message now we should be able to see the loading set let's restart back up the dev server and then let's reload our page right now the only thing that should show up is the loading stat which we are um setting as always true remember from right here so it will always show as that the messages load and there is a loading State beautiful now of course this is just marked for now but we can see the loading State works just as expected and there are currently no messages in the chat window okay so how about you just go ahead and send the message and see what happens let's read out the page the loading state will likely still be there that's fine let's just say who was Plato in one sentence and let's see if this works let's hit enter okay the enter doesn't work yet let's go into our chat input and see why that might happen when we press enter the message will be added we get that from our context so I suspect that the add message doesn't really do anything yet no it did send the message to the API okay oh so it seems like the only thing that's different is that we are not clearing the input field that's why to the user it might currently seem that the message is not being sent that's fine let's see in the network tab if we got a response and we did so this was the get file messages so apparently there was an error loading the files and the the messages okay interesting we get an error let's see in the console if we get any error for this and we don't interesting I'm going to do some debugging why do we get an error on fetching the messages oh okay from the error message we can tell that we got the error because we made a bad request and that is apparently because this file ID instead of messages is undefined and yeah okay we even get a message in the chat wrapper that we still need to pass um the file ID in here okay so let's pass it let's give it the file at the and of course this is going to be the file ID from the chat wrapper let's save this and try this again and I just saw okay the network request did go through and there are or messages amazing really really good work we can see all the messages pop up right here on the right hand side that we have already sent and let's try asking a new one who was um or what oops in one sentence what was the uh you know relation between Plato and Socrates and see what happens let's hit the send button we are not going to get our message okay but if we refresh then we do get the complete answer so to me it just seems like we submitted a form at least that's what it looked like visually that we didn't want to submit and I remember we did use the form element in the chat input now it's probably just because we haven't implemented some functionality from the future but let's just change this form in the chat input to a regular div there is no benefit in having the form right here so let's just change it and avoid that behavior altogether and also remove the type submit from the chat input button and that should clear things up okay that's good let's try this again let's reload the page and see what happens hopefully this time without a form submission okay so the loading state is there the messages are there let's just say hello how are you to see what happens let's send the message there is no hard form reload awesome but we do get the message streamed in and we can see the answer right here I am an AI language model so I don't have feelings and uh whatever and we can see the message pop up here however one really important thing is still missing and that is if we ask the question then first off um there are no optimistic updates yet so when we send a message any message and I hit this button it is not immediately put into the chat window pay attention it's not even there right it doesn't pop into the chat window so to the user it seems like it wasn't even sent secondly the message is not removed from the input what if we wanted to ask another question and then thirdly the answer is not being streamed in either and the loading state is always there which it shouldn't be so right now we have to hard reload the page to see the messages that were sent and that's not ideal we want to implement something different something really really cool that is called optimistic updates what that means is as soon as we type in a message let me show you what this means an optimistic update is what this is called let's remove all of this and let's title it optimistic updates right here and back what an optimistic update means is if there is a message right here a message right here and we are typing in another message as we click the send button it is immediately going to be put in or it should rather be this way around it should immediately be put in the chat window as soon as we click the button and without even having processed through our database and whatever first right so the user immediately gets feedback that is what is an optimistic update in 2023 2024 that is what people just expect and instant feedback and if there should be any error in the message then we will put it back into the chat window and revert the optimistic updates it's no big issue right and it's no biggie at all and but that way we can provide the user immediate feedback which is a really really nice skill to have and let me show you how we can implement this and all of this stuff will happen in the chat context because this is where we process our messages where we handle loading error States and all of that so the way we can use a optimistic update with react query and trpc is first we need access to the trpc utils you know this from before right where we can get access to all the API routes for example let's call this const utils is going to be equal to trpc.use context and with this utils we can actually create the optimistic updates So Below the mutation function that we have right here let's say on mutate in this on mutate we'll get called as soon as we send the message as soon as we press this button so this is exactly where the optimistic update should happen and let's move this into a kind of side by side so it looks a bit better there we go this this is what the on mutate is for for the optimistic update and then the rollback and what dot we're going to handle later so first off in the on mutate we want to First create a backup of this message if anything goes wrong any message right here if anything goes wrong after the optimistic update we want to roll it back and put it back into the input because we're going to remove it as soon as the message is sent for immediate feedback and the way we will keep track of that message is going to be inside of a ref so we don't force a re-render let's call it const backup message it's going to be equal to use ref at the very top here we import that from react and make it an empty string now in the on mutate we can make a work of that ref by saying backup message dot current is going to be equal to message so now we're saving it inside of this ref in case anything goes wrong we can revert it later and we're also going to set the message State we are keeping track of in here to an empty string okay step number one for optimistic updates we want to cancel any outgoing refetches so that they don't overwrite or optimistic update the way we do that is by awaiting the utils.get file messages and we're gonna cancel all outbound calls right here and to be able to use a weight we're going to mark this as asynchronous then step two and let's mark this as step one and let's mark this as step two right now just for all reference we are going to snapshot the previous value we had so the cons previous messages is what we're going to call this is going to be utils dot get filemessages dot get infinite data this is like a API call we're just making to get the data so we can later revert it if we needed to and then step three right here for the optimistic updates for the best possible user experience is to optimistically insert the new value or new message right away as we send it the way we do that is by calling the utils.getfile messages because that is the API we optimistically want to change the data for and now we can use a utility called set infinite data instead of the get infinite data from above in here we can destructure the file ID and also we can destructure the limit and we're going to set this as the infinite query infinite query limit constant that we have already created awesome below this we can also receive the old data in the Callback and work with it in the function that ensues right here and this needs to be comma separated there we go now this first thing is going to contain the old data that we can now add the current message to right so first off if there is no old data which is in Edge case we do need to handle in that case we need to return an object from here and that object will always need to contain a Pages property and it also always needs to contain a page params property if you're wondering why that is that's because react query handles this infinite query always with a pages and a page param so we have to comply with that and return an object that matches that shape that react query expects then step number one for the optimistic update um kind of a meta step one in here I guess is cloning the old pages and we do this let's call this const or let new pages is going to be equal to and let's spread in the Old Dot pages and you're gonna see why we do it in this you know kind of weird way in a second then Second Step let's say let latest page this latest page is simply going to contain the latest messages and not the ones above it in the interval that we set so for example for us if the infinite query limit is 10 then the LA the latest page is going to contain the latest 10 messages in our chat and this is going to be equal to the new pages at the index of zero and we can just insert the typescript that this will definitely exist using an exclamation point and then lastly let's say latest page dot messages and now we are going to directly mutate this first off we want to insert the message that we are sending right now so if I type in anything inside of this chat input this should immediately be put into the chat messages up here so we need to insert it into the array with a created add value of a new date let's transform this to an ISO string just like this with an ID of crypto dot random usual ID crypto is always there it's a global utility we don't even need to import it the text is going to be the message and lastly as the is user message that is going to be true okay after this we want to spread in using dot dot all the other messages that were there the latest page dot messages so essentially we are just taking all the messages that were previously in the chat and moving them up one and now posting or message inside of here that's what this code is doing okay below that we're going to say new pages at the index of zero right here is going to be equal to the latest page so we're changing the data up here and now inserting it as the first or you know last page depending on which order you see it as and basically as the last message we are injecting it into the new pages right here and lastly we need to return the old pages right here we can just spread all the properties in from the old and the only thing that we are going to overwrite are the pages and these are going to be equal to the new pages that we have just created and just like that let's save and see what happens let's reload the page go into or chat window and let's say hello how are you again and click the sending button and what we should be able to see is our message pop up in the messages right here which didn't happen interesting okay but I think that happens because we're not quite done with the on mutate code for now there's like uh just a very tiny bit missing so let's try that and see if it works then first off we want to set the is loading to true and the reason we are only doing this now is because we want to add the loading state after adding the user message so the loading set imagine it's uh you know loading right here we want to add the user message to the chat window just like this and then display the loading message instead of first displaying the loading and then the message that's not the way it should be so we're only setting the loading to True after we have done all the insertion logic up here and lastly we are going to return from here the previous messages and these are going to be the previous messages dot Pages just like this dot flat map I explained what the flat map does for us earlier so we don't get like an array array which wouldn't be very beneficial and for each page that we get access to the Callback we're going to return the page dot messages and if this is um you know not any value if this is undefined or null which it could be because it's optional right here in that case we're just going to return an empty array beautiful let's save that and now let's see if our logic is working let's go all the way down into our chat window and say Hello World hit the sending button and it still doesn't put or message in there right away weird okay I'm gonna do some debugging and see why this happens Aha and I'm pretty sure I found why this happens it's a really simple fix and the problem is that for example the message that we're backing up here is the global state that we're keeping track of and it's not in fact the message that is also sent into the on mutate right here which is the one we will be using right so let's save this and reload the page and then let's see if this is working because it definitely should right before we continue we want to make sure our code works so let's say debug 2 let's hit the send and it still doesn't work well all right so after some more debugging I came to the conclusion that the problem is that we are not handing the loading state in the messages yet and that's the problem as to why we're not seeing our message once we send it directly so let's go ahead and handle the loading State instead of inside of the messages.tsx and then we will actually see our message pop up so the loading state that we want in here comes from our context we're already keeping track of it there as state so we can simply destructure it inside of here the is loading will come from use context and pass in or chat context in here okay and because we get a naming conflict otherwise we're going to call this is AI thinking and then instead of having the loading State or the loading message always there with a true we are only going to show this if the AI is thinking we're later going to update this even better in the chat context and I propose we actually do that right there and then we will see that our messages do pop up as soon as we send them in optimistic update fashion okay so let's go below the on success inside of our chat context and quickly finish up the on Arrow and unsettled to properly handle those cases as well for the on error the only thing that we want to receive here is not the first argument not even the second only the third and let's call it context inside of this on error what we can now do is well something went wrong so we want to put in the text we already optimistically put in the chat window back into the text box right to do that let's set the message to the backup message dot current that is the single purpose we backed it up for and also let's call the utils.get filemessages dot set data and now we want to roll back to the previous messages so we can destructure the file ID as the first argument and then secondly we can pass an object containing the messages and these are going to be the context dot previous messages just like this or if that is null or undefined in empty array that's why we have these two um question marks right here if this is null or undefined then we're going to pass an empty array in here and that concludes our error handling already really really easy and lastly the unsettled if we are either successful or not then it's going to happen in asynchronous error function and in here we're gonna set the is loading defaults because no matter what happens we don't want to be loading then and we are going to say await utils dot get filemessages dot invalidate and we can pass it inside of an object right here the file ID and just by doing this when everything was successful or not we refresh the entire data right here so we always get the most accurate data um after a message has been sent right we send the message we get an answer and then we restore the Integrity of the entire page but of course this is not going to flash or anything there's not going to be a wide screen while this loads this happens all in the background perfect and let's try out what happens now let's go back in into our chat window scroll all the way down and say hello good sir any message and hit send and you can see if we scroll down the message was sent here as we typed it perfect now we can't see the answer in real time just yet but we do see our message and then once we reload the page let's see what happens we should also be able to see the AI response perfect okay now that is something that's still not ideal right we of course don't want to you know refresh the entire page so you get the answer and but instead we want this to happen um just like that without refreshing the page and where we do that and where we can also display the AI message that we send back as a stream in real time is right below this on muted let's go below where that closes right here above the on error and let's insert something called on success so when we do get a response back from our API that will contain the readable stream that we can simply put into the message as we get it to have this real-time feeling well not just feeling it is actually real time first off we're going to set the is loading to false because the loading message that we see from the AI should not be there anymore then we can receive this stream we get back from our API simply automatically passed in here as a readable stream now what could happen is if we hover over this this stream could be null so we want to handle that case if there is no stream in that case we are going to return a toast notification to the user saying with the title that there was a problem sending this message sending this message as the description the description of this tools notification we're gonna say please refresh this page and try it again and lastly to inform the user user that this is a bad thing that happened as the variant we are going to pass destructive just like this then right below the stream we can now assume that this exists and we need to read the contents of it let's say cons reader is going to be equal to the stream and there's a method we can call on here called get reader and we also need to decode this because it is encoded the const decoder is going to be a new text decoder class oops the coder we can simply instantiate right here no additional Imports needed whatsoever let's also keep track of if we are done by saying let done is going to be equal to false okay one last thing is we want the response and accumulate it as we get it so let's initialize um a accumulated response let's say a cumulated response as a comment right here so we know what this will mean and let's call it egg response so just we have to type a bit less later and we can elaborate what this means and where we Define it okay this is going to be an empty string by default now we are actually going to read the contents of the stream that we get back from our API in real time so while we are not done reading in that case this while loop will continue to run and what we want to do in here is first we want to read the stream we can do that by destructuring both the value and the done and let's call it done reading from the awaitreader.read and simply call that then we're going to set the done to done reading so if we're already done there's no need to execute any further code and now to get these string content of this stream of this chunk that we are you know kind of mapping over I guess we could call it let's call it cons chunk value is going to be equal to decoder dot decode the value and this is going to be the actual string that the AI gives us that we now want to add to the message and to make it show in real time and also let's add this to our accumulated response by saying act response plus equals that's a shorthand and the chunk value so we can simply append the chunk value to the accumulated response okay and now what we're going to do is append the chunk to the actual message so the message you can see in the chat so that's updated in real time and the way we do this is probably one of the most elaborate logic steps but it is very similar to what we did in the on muted so don't be afraid of it and we're going to do it step by step together and then you're gonna find well it does look syntactically a bit odd it really makes sense what we're doing so we do this by calling the utils.getfilemessages dot and then set infinite data because we want to add to the data that we can see in the chat window in here we can first destructure the file ID and also just like above the limit that we are going to default to the infinite underscore query underscore limit and as for the actual callback function again receive the old data and then if there is no alt data as the first guard Clause we're going to return an object that contains once again the pages as an empty array and also the page params as an empty array just like this perfect then let's create a new variable called is AI response created and this is going to be equal to the Old Dot Pages dot sum which is going to return us a Boolean don't worry I'm going to explain this here in a second and for each page that we have in the old data that we get we want to check if the page dot messages dot sum and for any message inside of this page let's call it mess oops message there we go let's give this much more space so you can see this easier you can already format this for each message that we receive in this sum callback we want to return If the message dot ID is triple equal to the AI Dash response because we are hard coding this AI response and we're checking in any page in any message is there already a message that has an ID of AI response which will be the last one right here at the very bottom of the page right and if there is an AI response then we are not going to add a new one that means in the chat window once we send that message the AI response will also be added to the screen and then in real time or in real time I guess we could say and we get the response from the AI and of course we do not want to add this message multiple times um to the screen that would be a really bad idea that's why we check for each chunk that we add to it is there a message already and if there is then we're not going to create a second one but instead we are going to add to it as we get the chunk value and if there isn't then we are going to create one for the very first chunk so there's always a message I hope that makes sense and let's continue in this call right here let's say let's updated pages is going to be equal to and that's map over the old data Pages first so all Dot pages.map and for each page that we get in here let's execute some code inside of this function so first off we're going to do a check if the page is equal to the Old Dot Pages at the index of zero so if this is the last page and it needs to be Pages just like this awesome then and trust me what's about to come right now is not gonna take long and it is going to be the most in quotes complicated thing of this entire build and so stick with me here let's go through it together afterwards and just code this out for now and then see what this does in a bit more abstract way so just code ahead with me right now and let's get this done so first off if this is true then that means we are on the first page which also contains the last message because the order here is reverse right the entire messages are put in reverse and that means the first page contains the last message that we care about and that is why this if statement is so important let's first create a variable called updated messages we're going to make use of later and then if the AI response is not yet created if not is AI response created well then we are going to create it once the way we do that is by creating a new reference this is really important for the react different to pick up the change later so we can actually see it on the screen the way we can do that is by saying the updated messages are going to be equal to an array and inside of this array just like above we can simply create a new message by first creating an object that's going to get a created ad property as a new date.2 is4 string then we want an ID and this is going to be the hard-coded AI response that we are checking for up here that's what we make the idea very important that it matches this right here that we're checking for the text is going to be the accumulated response so we always show the latest data in here and the is user message Boolean is going to be false in here and then we can simply append all the other method messages that were there before by spreading in the page oops page dot messages just like this okay and this needs to be messages by the way great so in that case we are creating a new AI response but what if there is already an AI response well then in that case we just want to add to the existing response this chunk right here the way we do that is by calling the updated messages and these are going to be equal to the page.messages.map and for each message that we are mapping over let's execute some code right here let's do a Clause right here an if statement and now if this message that we're currently mapping over is the AI response If the message dot ID is triple equal to AI Dash responds just as hard-coded up here and up here in that case we are going to return the message properties that were there before by spreading in the message but also change the text to be the accumulated response and if it's not the AI response then we can literally return the message unchanged because we don't want to change any other message than the AI response right everything else should stay the exact same and we just want to append the chunk that comes in in real time to this message right here perfect and that's almost it already by the way okay so let's exit out of this else statement right here and right below this return an object inside of this object let's spread in the page so we're keeping all other properties and we only overwrite the messages and of course these are going to be the updated messages from above right below this let's return the page just like this so the other pages are going to stay exactly as they are only the first page like the last 10 messages or whatever you set this config option to only that page is going to be updated and if you're rendering out 30 messages above this well none of them are going to be changed we are returning them as they are and lastly right below here let's return an object and spread in all the old values and simply overwrite the pages to be the updated pages and this was already it I know this was syntactically the most um demanding part because because we need to pay attention so this is all immutable what that means is we want to create basically new references for each entry so that when we stream in chunks this happens really fast and for the react diffing algorithm to see that we want to re-render for each chunk else it would look really weird basically we would get the first chunk then a long time nothing and then the last chunk so the message wouldn't update any like in between so as an example if the response from the AI was like no problem let's just assume that string was the answer well basically the N would be streamed in as the first chunk then long time would happen nothing and then the O problem right here would be inserted all at the same time all of a sudden right so the first and the last rank but what we want is to be streamed in in real time as the chunks come in right and for that we need to create a new reference and each time we update this infinite data with the chunk value for react to know that it should actually display this because if it was the same reference react wouldn't know any difference and it would look really weird in the final message perfect let's save this and actually go ahead and send a message and see how this works let's open this up in full screen side by side let's give it a Reload just so we have the most fresh version and let's say in one sentence who was Plato and hit send right here let's see what happens if we scroll down I'm not sure why we have to scroll down at the moment but we can see the AI responded and streamed in the response in real time that is really really cool congratulations if you're following along up until this point this is a really really nice achievement we have made together we optimistically update and stream and responses back in real time that's not easy awesome and that takes us to the infinite query part as we scroll up currently we don't see any messages beyond the last 10 what we set our limits all right and as we scroll up of course we want more messages to render in and this is for maximum performance if there were 5 000 messages we wouldn't want to render all of them just as we scroll we render as much as the user needs which also puts less load on our database and in order to achieve this we have actually already laid the foundation and that is in these messages right here remember we can simply pass a ref to keep track of which message right here is the last one and if that is on the screen then we can start loading more messages and the way we do that is to a really um convenient helper let's quickly stop our server clear the screen and install one little package that will help us achieve this pnpm install and this package is called at manthean slash hooks we need one certain thing from this Library only and that is going to be something called use intersection that's going to help us detect if a message is intersecting with the top of the viewport so if it is actually um you know being rendered on the screen and if it is then we want to load more let's start back up our Dev server and quickly import that helper hook in here import use intersection from the package we've just installed at 19 hooks there we go and I think it doesn't recognize that we just installed this let's reload the window and then hopefully the typescript error will be gone perfect now the first step is actually assigning a ref to the div we want to keep track of and that's really easy let's just create a new constant called last message ref inside of this component and this is going to be equal to a use ref we get from react and by default this is going to be null but because we're in typescript we later want to tell typescript that this is going to take up an HTML div element so we can pass it as a generic into the user ref simply take this last message rev and now pass it on to our message and because we already know this is the last message message on the screen we can pass it as a ref in here now interestingly enough this is going to give us an error because there is one more step we need to take inside of the messages component or instead of the message component that allows us to actually accept a ref in normal react you cannot pass a ref as a prop as we are trying right now but there is a really cool trick we can use to achieve just that let me show you how follow along with me here in the message component Mark everything beginning from the um you know the first parentheses after the equals of the const message so basically the Callback function of the arrow function or whatever that's called right so basically everything starting from here go ahead and Mark that and we're going to mark it all the way down to the end of the component up until this closing curly brace right here so once we cut that using Ctrl and X we only have const message equals that should be the goal and how we pass refs in react is by using something called a forward ref that takes a generic of the um ref element that we want to pass in our case an HTML div element that we're going to later assign this ref to and it also takes in the message props um just like we had before nothing is different here and this is simply a function we can paste in or existing component right just if you if you missed that wrap it in parentheses and then paste in what we had previously and that's going to get rid of all the errors and actually allow us to use a ref inside of this component now to get access to the ref in here we can simply get it also from the props and oops not like that we can remove the type we've already got that defined up here and we can simply get the ref just like this as the second argument into this function right here so this is a forwarded graph of an HTML div element and we can simply pass it right on to the top level div as a ref just like this so now we actually can pass revs as a prop into this component and what that allows us to do now is to check if this div is intersecting um with a screen if it's already visible on the screen and if it is then we load more messages and for that we are going to use the really cool helper package that we have just installed and let's see how this looks like in practice let let's destructure the ref and the entry that's what it's called from the use intersection Hook from maintain we have just installed as the root we are going to pass the last message ref dot current so we're keeping track of this executive that we're passing as a prop and then the threshold we're just going to pass one okay and now one small little use effect goes right below that inside of this use effect right here we are gonna check if the entry right here dot is intersecting and if it is then that means we need to load more messages above this so we can simply call the fetch next page right here and invoke that awesome as the dependency array into this use effect we are going to have two things that we're using in here that come from outside and that is both the entry right here and also the fetch next page so if the reference of one of these changes then the user fact is going to re-initialize perfect we can save these messages and the last thing we need to do is pass this ref from the use intersection into the first message and oh I just saw we already passed the other ref and here no that's not what we want to do this needs to be the ref from the use intersection and then we can save that let's see how this Works let's go into full screen right here let's read all the page and by default we should be able to see of course only if the dev server is running which it is okay great we should be able to see the um 10 last messages and as we scroll up more messages are going to be loaded into our chat and that's the beauty of an infinite query only as we scroll only as we need more messages are being loaded in to put less strain on the database and give us a better user experience because the initial page load will be much faster and as we don't need to load um any amount of messages above the 10 that we have specified in our config which in reality you would often put a bit higher and like you know 30 40 50 for example so it will take quite a bit to scroll up and the most recent messages are always there but just for demonstration I think 10 is a really good number to use for this and congratulations with this you have literally just implemented one of the coolest ways of progressively loading the data in your application and also you learned how to implement optimistic loading stats if we hit enter now that's going to be put immediately into our chat box oops I didn't mean to type that in if anything goes wrong the message will automatically be put back into here for the best possible user experience so they don't need to retype it and we can actually ask the AI questions now about the PDF because it has all the knowledge in the background because we indexed it into vectors remember so for example um we can say when was Socrates born hit enter and it should say right here from a 469 to 399 BCE and it does he was worn born in 469 BCE which it got from the PDF very very nice all the PDF functionality is like zooming and whatnot work we can rotate it we can go into full screen which looks beautiful with a scroll bar by the way and we're making really really good progress actually I think we are actually done with the core functionality of this app now what is left is one fixing up the navbar of course we're logged in and don't want to see it to sign in and get started that's one thing we will fix and lastly we want to also implement the SAS aspect right so we want people to be able to purchase something via credit card or stripe and then to have a Pro Plan getting better answers and being able to upload um larger files so for example if we go into our dashboard when we click upload PDF and they're on the Pro Plan we want to allow up to 16 megabytes instead of just four megabytes right here right and currently there is no way to achieve that and that's going to be the last and also one of the most exciting parts of the entire application because you know let's be real here accepting payments is always kind of cool and to get actual users to pay for your services and the service we are going to use to integrate payments is called stripe one of the most popular payment gateways in the world we are simply going to sign in I'm going to do that on my right side right here and if you don't have an account yet then just create one and then you're going to be signed into your dashboard as well okay once we are logged into stripe we are going to go to the search bar and create a new product that's going to be under product catalog and then create a new product and this you can call this whatever you want I'm going to call this um Pro Plan just like this as the name and there you can enter a description you can even give it an image to make it like a really pretty I'm going to stick with the most basic variant right now and we can just say how expensive this will be and I'm just going to input 14 US Dollars and this is going to be recurring so is a subscription model basically you could also have this as a one-time purchase if you wanted to okay that's the product we are going to create let's hit save product and we need to confirm our password that's totally fine let's hit continue right here and that's going to create the product for us now let's scroll down to where it says pricing and copy this price underscore whatever whatever M right here this is going to be really important for us let's copy that close out of stripe go back into our code editor and now let's create a new um file under our config folder let's name it stripe dot TS and this is going to be important this is where we're going to use that pricing ID we just created in the stripe dashboard let's export econ's plans from here this is going to be an array and this Con this is going to contain an object with a name of free this is going to be our free plan with a slug of in lowercase free right now the quota is going to be 10 I just made that up I think that makes sense for the free plan and then the pages per PDF on the free plan can for example be five that's what I came up with okay then let's create an object in here and this object is going to be the pricing or let's just call it um price and not pricing inside of this object the amount will be zero because this is you know free and the price IDs are going to be an empty object for the free plan or not empty but just empty string values for both the test property and then both the production property these are just going to be empty strings we're not going to make use of them this is just so typescript doesn't complain it does when we copy this down and now create the Pro Plan the more exciting part Pro the slug is also going to be Pro the quota can be something like you know 50 if we wanted to and the pages per PDF let's Jack that way up to 25. okay the amount is going to match the stripe amount that's going to be 14 and for the test right here is where we're going to paste the stripe ID we copied from earlier we're gonna paste that inside of the test and if you were to deploy this application and go out of the stripe test mode that you're in by default then you're gonna do the exact same thing just for the production one but this is totally fine for our use case just the test version and we are going to be good to go and now let's create our pricing page where people can see what our service even does for them and how they can upgrade to the Pro Plan to create the pricing page let's go under source and then let's create a new folder called pricing in here goes of course once again the page.tsx where we create a cons pricing or let's call it just cons page I think that's easier as an arrow function and Export that page as the default at the bottom okay inside of this page goes all the jsx content the visual markup of or um you know pricing page first off however let's determine if the user is logged in or Not by destructuring the get user from get kind server session and also saying cons user is going to be equal to um that's going to be get user right here and then from this we are going to return a fragment so we don't need a wrapping div and at the top level this will contain a Max with wrapper just like this this is not going to be self-closing but get a class name of margin bottom eight margin top 24 attacks Dash Center and a maximum width of 5 XL in here inside of D Max with wrapper another div with a class name of MX Auto a margin bottom of 10 and on small devices a maximum width of large inside of this div in H1 element that says pricing there we go and give this H1 a class name of text 6xl a font Dash bold and lastly on small devices a text of 7xl to make it really stand out with a paragraph tag right below it that gets a class name of margin top 5 a text Gray of 600 and on small devices a text often large and this paragraph will say whether you're and then again for the apple straw we're going to use the and apos colon whether you're just trying out or service service or need more we and then again airpods and the colon we've got you covered period just some you know marketing copy I made up and right below this closing div we're gonna create a new div that's going to contain the actual content of the page with a class name of padding top 12 grid grid Dash calls dash one a gap of 10 and the large devices a grid calls of two inside of this div right here we need a component that doesn't exist in our app and this is going to come from our UI Library so we're going to say npx chat cn-ui at latest add and this is called tooltip that's later going to allow us to show some really cool and like explainer tooltips for each of the pricing points so people know exactly what they're going to be getting for their money because in this project I want you to write real good code like a real world application you hello and that is what real world applications have so let me show you um how we're going to do this let's start back up the dev server and by the way we can already open up the pricing page here um on the right hand side let's go to pricing so we can actually see what we are doing give it a bit less space four or four this page does not exist why doesn't this exist this should definitely exist oh of course the pricing folder let's cut that and paste it inside of our app folder that's where it needs to go paste right here and now we should be able to see our pricing page beautiful okay inside of this div that we just opened up we want something called a tooltip provider that's what we later need to be able to show these um tooltips on the pricing page inside of this provider let's map over all the pricing options we have and just to make our life easier I have decided to include these in the copy paste list right here and this just contains um you know all the features that we have in our app so navigate over to the copy paste list and copy the pricing options and we're gonna paste them right below determining the user one thing we still need to do is to import the plans from or stripe config just like that and as you can see we always have a text sometimes a footnote as an explainer than a negative property and you're going to see what this means here in a second and why this is going to look so good as well by the way we can simply map over these pricing items now so pricing items.map and we can just destructure right away the plan we can destructure the tag a line also the quota and the features from it and what we are going to return from this is a function first because we want some logic that is finding out the price so let's say const price for each item that we're mapping over is going to be plans dot find and for each let's just call it P for plans let's return if the P dot slug is triple equal to the plan dot lower or it's to lower case there we go invoke that so we're basically searching through all the plans and if it matches the one we're currently mapping over with this pricing item in that case we want the dot price at the very end and then dot amount and if this is not even defined in that case we can always fall back to just zero there we go and we also need to return some jsx from here to get rid of that really ugly red squiggly line so let's just return a div right here and of course that div needs to go into that function to get rid of the error then inside of this div first off we need a key because we're mapping over something and oops this needs to be features there we go as for the key this is just going to be the plan and inside of this div let's also give it a class name and this class name is going to be dynamic with our CN helper by default we're going to pass a relative oops relative a rounded of 2XL that's already formatted so we have more space then we're gonna pass a background white and a shadow of large and then optionally as an object if the plan is a Pro Plan then we're gonna render out a border of two a border blue of 600 a shadow blue of 200 and that again if we are on the Pro Plan so if the plan is triple equal to Pro give it a bit more space there we go and else if we are not on the Pro Plan then we're going to render out a simple border and a border gray of 200 just to make it really clear that the Pro Plan sticks out you know and we're going to render this if the plan is not equal to Pro there we go perfect inside of this div right here let's do a check if we are on the or if the element that we're mapping over is the Pro Plan if the plan is triple equal to Pro in that case let's render out some jsx and that's going to be a div with a class name of absolute a minus top minus five a left of zero a right of zero and m x dash also a width of 32 in rounded Dash full and let's go into full screen for this longer class name then we want a background BG gradient to R to write a from Dash blue dash six oops blue dash 600. a two cyan of 600 a padding X of 3 a padding y of two a text of small text Dash SM a font of medium and lastly a text Dash white there we go longest class name I promised that was really long and inside of this div we're just gonna say upgrade now you're gonna see if this will look really good let's see how that looks like and you can see for each plan that we're mapping over the free and the pro we can see it looks really really nice but we only see that little thing up until now and not the actual features now to render those out and make it look good let's create a div right under here with a class name of padding five inside of this diff in H3 with a class name of my three text Dash Center font dash display a text of 3XL and a font Dash bold right here let's give it a bit more space there we go and inside of this H3 we're just gonna put the plan so whether it's free or Pro nice there we go right below that goes A P tag with the tag line that we destructured earlier and this P tag will get a class name of text Gray of 500 right below this P tag goes another P tag inside of here a dollar sign and then the price there we go and this P tag will get a class name of my five a font dash display as well a text of 6xl their font Dash semi bold awesome and one more P tag right below this P saying per month so the user knows the payment interval and this is going to get a class name of text Gray 500. they've got it's it's save on that and we can see beautiful the pricing shows up there on the right hand side below the closing div let's create a new div right here with a class name of flex height of 20 items Dash Center justify Dash Center a border Dash B border Dash t a border gray of 200 and a background gray of 50. let's open up this div and create one more div in here with a class name of flex items Dash Center day SpaceX of one inside of this diff one P tag and this is gonna say the in cardi braces for a dynamic value quota dot to Locale string so we are converting the price or the quota whatever number this is right um for us I think that's like 10 or what it was to the local formatting right with commas or um dots it depends on where you are in the US versus Europe and then PDFs per month included right there let's save that we can see 10 PDFs per month included 50 PDFs per month included beautiful right below this we're going to render out a tooltip in this tooltip we literally just installed so there's really no work involved in making this happen let's import the tooltip from our custom component and pass it a delay duration of 300. there we go this is not going to be self-closing inside of here we want a tooltip trigger this tooltip trigger will get a class name of cursor Dash default and also a margin left of 1.5 and inside of this tooltip trigger we're going to create a help Circle this help circle is an icon from Lucid react let's give it a class name of height 4 with 4 and text zinc of 500. so we can kind of hover over this you're going to see it appear right here and if we hover over this it will explainer will appear inside side of the tooltip content that we get access to right here this is going to get a class name not a console log a class name of with 80 and a padding of two inside of her we're going to say how oops how many PDFs you can upload per month for example explaining what this is even for and if we now hover over this we can see this little explainer appearing beautifully let's go down one closing div below the tooltip two closing div and with one closing diff to go open up an unordered list a UL with a class name of my10 space y or 5 and a padding X of 8. inside of this unordered list let's map over the features let's say features dot map and for each feature we can immediately restructure the text the footnote and also the negative and then let's render out some jsx directly by wrapping it simply in a parenthesis just like this and we're going to render out an l i element a list element with a key of text and a class name oops class name or flex and SpaceX of 5. inside of here a div element with a class name of flex shrink of zero and inside of this div we are going to do a conditional check if this is negative in that case with a ternary operator we're going to render out a minus icon from Lucid react that gets a class name of height 6 with 6 and text Gray of 300. let's quickly fix that Arrow right here we can copy this minus icon copy paste and else in that case we're going to render out the same thing let's paste it in here but change it to a check icon and the text will be blue of 500 instead of gray 300. beautiful all the rest like the height and the width is going to stay the same across these two icons and below this div let's see if we have a footnote and again in a ternary operator if we have a footnote then we are going to render out a div and else if we don't have a footnote in that case we are going to render out a little P tag let's give it a class name of flex items Dash Center and SpaceX of 1. inside of here create a P tag let's give this a dynamic class name using our CN helper function and we want to always apply a text Dash gray Dash 400 but however let's pass an object as the conditional style if this is a negative point that we don't support in this plan as you're going to see in a second in that case we're going to overwrite this and the order needs to be the other way around actually class name first then the condition is going to be a text Gray of 600 if this is negative that way around there we go okay and inside of this P tag we are simply going to render out the text along with a tooltip to explain this right so right below this P tag let's open up a tooltip just like this tool tip and this tooltip will get a delay delay duration of also 300 there we go inside of this tooltip let's create a tooltip trigger just like that that gets a class name and that's going to be actually we can just copy it from above let's save ourselves the hassle copy this tooltip from up here with how many PDFs you can upload per month and let's just paste it in here save some time there we go but instead of the hard-coded tooltip content we can simply insert the footnote the little annotation the explainer that we want to provide for this point in the pricing perfect and then inside of this P tag right here if we don't have a footnote in that case we simply are going to render out this P tag from above we can copy and paste just the P tag from up here and paste it right down here and so the siding is going to be exactly the same perfect let's already save this and see what this looks like let's give it a bit more space nice there are or features you love to see that then under this UL element right here let's create a self-closing div actually just as a visual separator with a class name of Border Dash T and also a border Dash gray-200 right below the separator let's open up a new div and this is going to get a class name of padding 5. inside of here let's do a conditional check if the plan is the free plan a triple equal to three in that case we are going to render out some jsx and else we're going to do another check else if there is a user that we checked at the very top of the file then in that case we're going to render out some different jsx and else again we are going to render out a link tag we get from nextges that we just need to import in here and this link tag we can already do this is going to get an href off slash sine Dash in it's going to get a class name of a dynamic value in curly braces or button variants right here invoke that and pass it a configuration as a class name we're gonna pass a string of with four just like this by the way let's finish up the jsx right here and so we can already format this the user if there is a user is going to get a button that allows them to upgrade let's call this upgrade button just like this this is a custom component that doesn't exist yet we're going to create it in a second and we're gonna create another link component if there is a free plan right here perfect that means we can already format this file because these syntax errors are gone inside of the link on the very bottom let's finish that up first if we have a user in that case let's say upgrade now inside of the button and else if there is no user let's say sign up awesome and below this let's include a little icon the arrow right icon we get from Lucid react and let's give it a class name of height 5 width 5 and the margin depth of 1.5 perfect okay by the way let's just copy this link and replace the one on top because this is going to be very very similar the only difference is going to be the href and the classm so for the href right here let's redo that this is going to be conditional if we have a user this is going to redirect to the slash dashboard and if we don't have a user this is going to redirect to sign in so they can sign in before they upgrade their account perfect and as the variant for this link we are going to pass in secondary you know just so it doesn't pop as much and now the last thing that's left to do is creating the upgrade button let's create that in our components call it upgrade button.tsx and this is going to be a very straightforward component let's say cons upgrade button is going to be equal to an arrow function and also of course export default upgrade button from the very bottom of the file right here and from here we're just simply going to return a normal UI button that we have we're just going to say upgrade now and we can also include like a little arrow right icon we get from the seed reactant here just to make it look good and give it a class name of height 5 a width 5 and a margin left of 1.5 perfect this button will get a class name of with full so in style it matches the rest and on click when we click this button what we want to happen is to create a checkout session so the user can right away buy their upgrades and the way we will do that is with trpc first off however let's import the upgrade button into our page to see what we've been doing and if this actually looks good well it does look good it looks really good let's be real here and we can see the buttons right here on the pricing page beautiful so now let's integrate the functionality into the upgrade button and actually make it start a session so the user can buy their upgrade we're gonna do that in trpc let's navigate to the index in trpc where we declare all or API routes and let's create a new one called create stripe session and this is going to be a private procedure there we go and we don't need any input we can straight away call the mutation right here which is nothing else than a callback function we need to comma separate from the rest of the API now in here the logic is going to be pretty straightforward first off let's destructure the context because we're going to need it in this API route and then we can destructure the user ID from that context right away then secondly we need an absolute URL because we're on the server side unfortunately we are not able to use relative URLs and that is why we need to declare a little helper function that helps us do that let's navigate into a utils file under the lib folder and create a new export function and let's call it absolute URL that we can even call all on the server side that's just going to make things work in here we're gonna receive a path as a string like a relative slash dashboard path for example and inside of this function we want to do a conditional check first off if the type of window is not equal to undefined that means we are on the client side and in that case we can actually return the relative path because the client is totally fine with relative paths else if we have a process dot EnV dot versus Cell underscore URL it means two things we're on the server and we have deployed on versel in that case we're going to return a template string of https then a colon slash slash and then the process.env.versel underscore URL and then also interpolate or path into here so this is going to be the URL the root URL where we deploy to like https uh yourwebsite.com and then slash dashboard as the path lastly in all other cases we're going to return a template string of HTTP oops HTTP and then slash slash like a colon slash slash localhost of you know the either the process.env.port or 3000 which is basically the you know what you always host your app under and then also add the path to that and just like that we can save this utils file and always have an absolute URL and make it just work no matter where we currently are and we can make use of that right here in our API rods so let's say const billing URL billing URL is going to be equal to the absolute URL we have just created and in here we want the slash dashboard slash billing page and because we're on the server and maybe we're deployed on resell maybe we're in development this is always going to return the correct URL okay first off if we do not have a user ID in that case we are gonna throw a new trpc error just like this and the code is going to be unauthorized so we can make sure we have that afterwards let's see what the database entries for this user look like so we need to fetch the user the const DB user from our database and we can do that by saying oh wait DB dot user dot find First and we want to find the first user where the ID matches the user ID and of course in order to be able to use the weight we have to turn this into an asynchronous callback function perfect if we have no database user we can basically copy and paste the arrow from above and just change it to DB user just like that the user shouldn't be able to proceed okay and next up we want to determine if the user is already subscribed or not because if they are and they click the button then we want to take them to a page where they can like cancel their subscription and you know manage their subscription and if they are not subscribed yet in that case you want to send them to a page where they can subscribe or they can directly pay and to make this happen I've provided another utility to you in the copy paste list because usually I just copy paste this as well we could type this out I don't think it makes too much sense so let's head over to the copy paste list and just paste these few lines of code right here the get user subscription plan and whatnot that is under the stripe retrieve current subscription plan I don't think typing that out makes too much sense for us um at this point so where do we need this get user subscription plan let's create it in or lib folder right here and create a new file called stripe dot TS there we go inside of this scribe.ts file let's copy the code right here the Imports might differ for you if you haven't been following exactly one-to-one with the video even for me that's throwing an arrow that's fine but essentially the gist is we are getting the current user so whoever is logged in if they are not logged in we're returning no for all values or false to indicate that this user is not subscribed afterwards we are going to our database to see if we have entries for this user for example to see if they are subscribed if we can't find the user again that means they aren't subscribed and we return false and if they are subscribed well it's determined by these two statements or these three rather right here converted to a Boolean that way we can see if they are on the Pro Plan or if they're not and that's going to be the most important thing in here by far we can get their plan we can get if the they have canceled their plan or not and just returned that information from this function that's why I didn't want to type this out with you it's pretty lengthy the logic is very straightforward and we can just save ourselves a bit of time by taking it from the copy paste list beautiful the one thing we still need to install is Stripe Right here as a package that makes it super easy to work with or payment Gateway that is stripe so let's install that dependency that that load and then start back up or develop a server once that's done pnpm Dev beautiful I'm on version 13.7.0 yours might differ depending on when you watch this video but stripe is so popular and so big it should always be back where it's compatible perfect let's save this stripe file right here and in or API Rod we can now see if the user is already subscribed or not and send them to a certain page based on it so to retrieve the current subscription status let's say const subscript oops subscription plan is going to be equal to a weight get user subscription plan that we've just created and let's do a conditional check if these subscription plan dot is subscribe what I just mentioned is the most important thing about it are they on the Pro Plan or not that's what it's about and we have the DB user dot stripe custom oops customer ID and that needs to be the DB user of course there we go in that case we want to send the user to a management page where they can cancel or manage their subscription because of course they are already a customer of ours let's create that session const stripe session is going to be equal to a weight oops a weight stripe we can simply import that from lib stripe dot billing portal there we go dot sessions dot create and inside of here we can pass a configuration object with the customer that's going to be our DB user dot stripe customer ID and then as the return URL we're gonna pass the billing URL perfect right under here under creating the stripe session let's return an object that has a URL property that we can send back to the front end to redirect the user to and this is going to be the stripe session dot URL like a hosted page that stripe provides to us under this URL right here perfect and if this if statement fails that means the user is not subscribed and they now want to buy our product so let's let them do that let's allow them to do that the cons stripe session we're going to create in this case is going to be again a weight stripe dot check out dot sessions dot create and in here goes another configuration object where the success URL is going to be the billing URL the cancel URL is also going to be the billing URL the oops the building URL the payment method types you can Define for yourself what you want users allowed to pay with I'm going to choose cart and I'm also going to choose pay not pay now by the PayPal there we go as the mode we are going to choose subscription because we're building a subscription based SAS as the billing address collection we're just going to say Auto there we go and as for the line items what the user is about to pay for this takes an array inside of this array let's create an object where the price oops the price that stripe expects is going to be the plans that we get from our stripe config dot find and for each plan that we want to determine if it's the right one or not we're going to determine that by saying plan dot name is triple equal to Pro with the p being uppercase there we go and if that exists optionally then the price dot pry oops price IDs DOT test because currently we're in test if you're in production then you would use the production version right here for now the test is totally fine and as the quantity we're gonna pass one in here and we can even pass some metadata right below this array as the metadata we're going to pass an object containing the user ID which is the user ID and this is later going to be sent over to our web hook so we can make sure that we update this data for the correct users so we enable their plan and everything works for them and then lastly right under here we're going to return back the URL and this is going to be the stripe oops stripe session dot URL once again so we always redirect to the appropriate stripe session whether they are not a customer yet or the whether they are a customer yet that's the entire purpose of this API rod and that is the logic done we can now make use of that in or upgrade button by saying it's destructure something const empty object is going to be equal to trpc and now use the API Rod we just created create stripe session dot use mutation inside of here we can call an on success callback which means we now have the stripe session we can destructure the URL directly from it because we always get a URL back and we're going to say window.location.href is going to be either the URL if that's defined or it's going to be slash dashboard slash billing and because we know we're on the client side right here and we can just leave this as a relative path but again because we're on the client we need to mark this as a use client component for trpc and to work in here and lastly we can destructure the mutate what we can call to invoke this function in the first place and call it create stripe session and when do we want to call this create stripe session well when we click on the button right that should automatically work so we can simply pass an on click right here and pass it the create stripe session and invoke that we can save this and let's see if this works let's go over to our page put this into a larger screen and do we still have the dev server running yes we do awesome so we should currently be logged into the app let's go down here and let's click upgrade now and see what happens ideally that will take us to a stripe checkout page that we just created in the API Rod however that doesn't work right now and I already know why and I know you will get an error okay we didn't get the error but one thing we didn't do yet is the most important thing for stripe to even work that is pass the stripe API key that is the last API key we're going to add to this EnV file I promise and we're going to get it from stripe.com let's go back login to our dashboard I'm already logged in here on my other screen and in the search bar let's search for API key and go to developers API keys right here now we are going to create a new API key or it might already be there so if you don't have one yet create one on this page or chances are it might already be there for you under standard keys and the only one we need is the secret key so let's move the is over and save this in our EnV file under stripe underscore secret underscore key and set it equal to what we just copied over let's try this again let's restart our development server and try out if we do get redirected to the um stripe page to check out or um Pro Plan let's give this a second let it reload there we go and let's click upgrade now and see what happens that should take us to a stripe checkout page where we can check out with PayPal and by card beautiful and by the way because we're in test mode up here we can simply enter the email we use for or application I'm going to input hello Joshua coding.com we can enter out a fake card which stripe actually provides to us there's a stripe test cards page we can use for this test payment methods and you're going to see that one of the cards we can always use is four two four two four two and so on just Spam four two four two then any month and year and any CVC and any name of course will work and this is again because we're in test mode then you can enter this hit subscribe and that's gonna subscribe us to the Pro Plan it's going to process the payment method this is always going to work in test mode and because we entered these details right here and once that's done processing stripe will know that we are now a customer of the Pro Plan 4 or application and let's let this run for a second we will be redirected to a page that is slash dashboard slash billing the last page we will create in our app that's going to be a really simple really easy page to let users manage their subscription to our application let's go ahead and create that and this goes under our source folder and then app and dashboard let's create a new folder in here called billing and inside of this billing page dot TSX that we're going to create right here inside of this folder now inside of this page first it's going to complain that this is not a react component because we haven't created the page yet let's do that the const uppercase page or a capitalized page is going to be an asynchronous this time Arrow function that we are going to display export as the default at the bottom of the file inside of this page we are going to determine the subscription plan the user is on the cons subscription plan is going to be equal to a weight get user subscription plan or very handy Helper and we're simply going to return a client component from here in which we can pass in this server-side determined plan as a prop so this is going to be called the billing form very simple component we can use to you know let users manage and purchase a plan and as the prop we can already pass it in even the component doesn't exist yet let's pass in the subscription plan right here can close all of some files by the way there we go and let's create that billing form one of the last components of our entire application let's create a new file called billing form dot TSX and inside of this billing form first let's create it as a regular component const building form is going to be an arrow function that we are going to return or export rather as the default at the bottom of the page bidding form there we go this needs to be a client component so let's declare that at the very top of the file right here and also we want to accept the subscription plan as a property in this component and this is going to be of type billing form props that we need to declare right above this component the interface billing form props oops billing form props there we go um of course takes the subscription plan that we need to determine and let me show you a really cool typescript trick of what this type will be it's going to be the awaited utility type that takes a generic off return type that again takes a generic as the type of get user subscription plan and that's it what this means even though it might look a bit cryptic if you're not familiar with typescript essentially it goes ahead and sees what does this get user subscription plan output what is the return that's what this type is and then because that's a promise right here because that is asynchronous we are simply awaiting that and getting the result of the awaited operation which is nothing else than the actual output of that function you can simply use as a prop in here inside of this first let's destructure the toast from the use toast we're going to use that later use toast hook we have and secondly let's create a stripe session in here this page is like the alternative page to the pricing page that's going to allow us to do pretty much the same thing so this is also going to come from trpc dot create stripe session the same functionality dot use mutation again taking an on success callback inside of here we can destructure the URL once again right away and as for the logic in here if we have a URL if URL in that case we can set the window.location.href to that URL and if we don't have a URL for some reason we can simply return a total notification in here to let the user know you know something went wrong after clicking that button with a title of there was a problem dot dot let's pass a description and this is going to be please try again in a moment let's format this and lastly as the variant because you know this is something bad that happened and let's pass the destructive variant okay let's already destructure the mutation we can call to invoke this the mutate from trpc and this is let's call this the create stripe session once again and also let's get the is loading set for later because we're going to need it in the jsx speaking of jsx let's do that let's return something from here and at the very top that is going to be our trusted Max with wrapper you can use to make the width on all pages the same and let's give it a class name because we want this page specifically to be a bit less wide than the rest you're going to see why in a bit let's give it a maximum width of 5 XL in here let's create a form element inside of here and it's going to get no action but instead a class name of margin top 12 and also an on submit this on submit always receives an event and we can use this event that is passed to the form to say e dot prevent default to prevent the default submission of the form and instead call or create stripe session function in here perfect inside of this form we could write out the entire jsx for a beautiful looking component right now or we could just install a really nice looking what is this okay this is the arrow from before we fixed that or we could just install one last UI Library component and that is npx chat cn-ui at latest add card cr8 c a r d card um and that just looks really good by default that's what we're going to use to create this billing form um you're gonna see why we're why we're using this it just looks really good in almost no lines of code awesome after we installed that I think it's the last dependency of the app I'm not 100 sure but we are definitely coming closer to the end um and we're going to make use of this card right now inside of the form let's use that card as the card inside of here goes the card header and inside of the card header goes the card title awesome instead of it we're gonna say subscription plan subscription plan there we go and SD card description right below this card description we're gonna say you are currently on B and then inside of a strong tag strong HTML tag let's give this a bit more space and we can simply get the subscription plan from the props the subscription plan dot you know name right here in a dynamic value so we can interpolate it put it inside of this strong tag and then say plan period let's save that let's also import the billing form into our billing page.tsx and let's see what that looks like in the browser if we reload the page we should be able to see our new page right here with the billing form and this is why we're using the card it looks really really good out of the box however one thing that's missing is it's literally saying nothing subscription plan.name evaluates to you are currently on the plan that's fine for now because we haven't implemented the stripe webhooks yet so stripe knows that we already paid for this however or app doesn't because the webhook we haven't done it we're going to get to that in a second here so let's leave it at this for now let's get out of the card header and right below it create the card footer in here okay now let's open up the card footer in here let's create a button element that comes from UI button and this is going to get a dynamic value however before let's give this card footer a little class name to make it look a bit better for our use case and this is going to be Flex Flex Dash call let's give this more space to work with and items Dash start a space y of 2 on medium a flex Dash row on medium a justify Dash between and on medium a space X of 0. okay inside of this button that is going to be of type submit by the way we can pass that in as a prop we want to do a conditional if we are loading if is loading in that case we are going to render out a loader 2 we get from Lucid react there we go that's gonna get a class name of margin right 4 a height of 4 a width of four and then animate Dash spin and else if we are not loading we're gonna render out null okay below this one more conditional and that is if the subscription plan dot is subscribed if we are subscribed this button is gonna say manage subscription and if we are not subscribed it should allow us to buy the plan and this should not be a comma there should be a colon it should allow us to buy the plan so let's say upgrade to Pro there we go beautiful let's save that it's going to say upgrade to Pro again because we haven't done the web hook yet um or app doesn't know that we already paid in stripe but this is going to work in a second when we deploy this application and we're literally going to do that in a bunch of minutes okay one more thing in here and that is we want to display when this plan is going to be canceled or renewed if there is a plan so let's first track if the user is even subscribed because if they're not we're not going to display that message so let's say subscription plan dot is subscribed and if they are subscribed then we're gonna render out a P tag and if they're not subscribed again we don't want to show anything in this case inside of the speed tag let's give it a class name of rounded Dash full a text of Xs and a font of medium there we go let's open this up and do a conditional if the subscription plan dot is canceled in that case we're going to say your plan will be canceled on and then a space bar or else we're gonna say your plan renews on because we know that the user is currently subscribed and has an active plan and now to display the actual date we're going to use the format from datefns once again and in your past the subscription plan dot stripe current period end as the date we can tell typescript hey um we're sure that this exists using the exclamation point right here and last pass the format in which we want it which is going to be dd.mm.y and then we can also insert like a little period at the end save that and it's not gonna show anything because we're not on the Pro Plan but that is or billing form we can now actually buy the Pro Plan and for it to be reflected in our application we need to create one last thing and that is a web hook so this is how this works when we purchase process this is the purchase process when we as the user buy something that will be done over tripe let's copy this over it's going to be stripe and of course or app will sit in between because that is what generates the link to stripe that's what we just did in the API Rod um so or app for example and that's then gonna redirect the user and the stripe to complete the checkout process so we don't have to deal with um payments directly and once that is successful once stripe has received the payment stripe is gonna tell or app about it using a webhook and that's nothing more than it will send an API request to a route that we can determine and we want a route to be called under or API let's open up Source app API let's create a new folder called Web Hooks and in here let's create a new folder called stripe and by a nextges enforcement inside of this needs to go a route.ts file in which we can determine the logic that should happen when someone pays for or subscription and this is boilerplate I always just copy and paste this over from old projects so it doesn't make sense for us to type all of this anyways we will type the core logic but we are going to save us all the boilerplate that's just annoying to write so let's go into the copy paste list and copy all the boilerplate that we don't want to write under the stripe boilerplate section right here paste it in and then only the interesting logic we're gonna write together but let's quickly go through what's happening first off we're checking for the stripe signature and getting that and then we are validating that this event actually comes from stripe because no user should be able to invoke this because it essentially will give this API Rod the user the Premium plan we're going to give them that in this API rod and if anyone could call it anyone could give themselves the Pro Plan of course that's not we want and therefore we have a stripe webhook secret that stripe is going to send along with every request that we can then check against in if that is in the request and if it is we know okay it's actually stripe and the user has actually um paid for the service for example if the checkout session is completed if the user buys for the first time in that case we can add that to the user in the database so the way we do that is by saying await DB import or database dot user dot update now what do we want to update the user with or first off where do we want to update the user well we want to update where the ID of the user matches the session dot metadata dot user ID and do you remember where this comes from well if we look at our index trpc it comes from this metadata right here that we included the user ID that we now want to add and the data for in the database as for the data that we do want to add that's going to be the stripe subscription ID as the subscription dot ID that's going to be the stripe customer ID as the subscription Dot customer and we can just tell typescript that this is a string type the stripe price ID right here is going to be the subscription there we go subscription dot items dot data at the index of zero which is optional dot price dot ID and then lastly the stripe current period end is going to be a new date and in here we're gonna pass the subscription dot current underscore period underscore end times 1000. there we go that's already all the data we want to include in the database when somebody has bought for the first time and when their monthly pre plan renews then this function is going to be called the invoice dot payment succeeded and a large chunk of the logic is going to be the same so let's copy this from up here the database call paste it right here below the subscription and the only thing that's different is we now just want to update the user instead of also setting basic things on the first purchase like the subscription ID we can remove that also remove the customer ID and the only thing that's going to stay is the price ID and also the stripe current period end and also where the ID doesn't match the session.metadata.userid but instead we're going to do a different conditional check where the stripe subscription ID right here matches the subscription dot ID so this object be construct right up here that's not going to be in the wear check instead of the metadata because we've already created the user and that's already all we need to do in this API rod for it to work properly we're going to return a new response of status 200 so stripe knows that everything went well and now the last thing is the final step of this entire project now there are some minor things like for example updating the nav bar when we're logged in and but the biggest step right now is just deploying or application so that stripe can actually call this route on the internet right on the web first off before we deploy let's quickly run pmpm run lint to see if there's any building errors we need to fix before we can deploy this up to our cell and there is for example in the message.tsx where we forgot one thing let's head into there into the message dot TSX and because we declare this as a forward ref right here we also need to go to the very bottom of the page and give it a display name so the message dot display name is going to be equal to message there we go again just because this is a forward ref it's Randy linter again just to be sure but that was the only issue we saw perfect that means we are all ready to deploy this project up to versel and the internet and have people use it as an actual SAS step one in making this happen is creating a new GitHub repository under your GitHub account I'm gonna Mark mine as private for now but of course you have the repo to follow along with but it's not going to be this one as the repository name let's choose quill just like that we could enter a description doesn't really matter let's just click create repository and this is where we're going to deploy all the code from later the important thing is we copy this git remote at origin line all the other stuff doesn't really matter let's go back into our CMD and say git add dot let's say git commit minus M and then give it any comment name that you want I'm gonna say initial commit hit enter that's going to stay age all of her files and lastly we can add the remote origin that we want to push into and hit enter last thing we now want to do in the CMD is to say git push minus U for upstream and then we can enter an origin and this is going to be either main or Master let's choose master and hit enter git push minus U origin master and that's gonna push things up to our Master Branch instead of the repository so if we now reload the page we're going to see all the files inside of our GitHub repo right here and these are also going to be the basis of deploying our project on yourself so let's head over to versa.com let's login using GitHub I've already done that and then let's click on add new and add a new project that's going to list a bunch of the latest repositories you have I'm gonna choose quill for this the one we just created hit import and then let's hit onto the environment variables section one really cool thing we can do is simply head into our EnV values for um or local project copy all of these of course we can change these later which are not going to be localhost anymore but we can copy all of them go into this example name input field and hit Ctrl V and that's going to create all the environment variables at once for us now we're going to change the URLs here later from localhost to something else let's first just hit deploy and this is the moment where you just hope that everything goes correctly during the deployment and then we're also going to get assigned a URL while this happens let's already navigate over to kind.com because there's one thing we need to do and change when we have this deployed and that is add the deployed version URL as well I'm gonna log in using my GitHub account and then let's head over to our project here in kind once that logging in is done and let's go under switch business then the app we have created together the quill app and there will be a callback URL section right here it's on other settings then we're going to navigate to Applications we're going to click on our next JS app we have created together and then under allowed callback URLs right here and this is where we're going to enter or deploy URL and I just saw we got an arrow and I think I already know what this error is going to be let's scroll down can't resolve at Prisma slash client interesting that's weird but let's just go ahead and install the Prisma client package then let's go into our package.js and see if it's not installed for any reason okay weird for some reason it doesn't troll the at Prisma slash client but it should and it doesn't even have the regular Prisma that's weird anyways okay let's just install them I guess let's say pnpm install Prisma and add Prisma slash client and that's gonna you know install the packages we used through the entire project literally no idea why it didn't install them and one thing we want to do is because we're trying to deploy Prisma on versel one very tiny step we need to add right here in our package.json is going to be a post install step and then here we're going to say Prisma generate to make this work with versaille deployments once again we can add everything we can commit it I'm just going to use the same commit message because I'm lazy and then we're gonna push this to the master Branch again that is automatically going to trigger a rebuild on versel we can head back right here click on our plan and go into quill let's go into deployments and that will have triggered a redeployment of our app let's go into this and hope that this time everything will work correctly and then it's going to give us an actual um domain so this is the time where we hope that everything just works there's a lot of build logs in here the dependencies are in here it generated the Prisma client good that's very good now it's creating an optimized production build sometimes I just like to follow along with my builds like this and hope that they work but we can be pretty sure that they work and because we did the linting first so this linting we did up here pnpm runland this usually catches all the build errors that would happen and even though there could be some other errors in some edge cases like we just had for example in most cases you're gonna be fine to build and deploy your app if the linting is successful as well it's going to collect page data and I think that's a goal from versel I think everything went correctly and it looks like it did deploying outputs perfect okay so that will give us a designated Versa domain here in a second that we can simply use to update Our Kind right here there it is quill minusjet.versel.app perfect let's enter it right here in the allowed callback URLs and this needs to be https colon slash slash and then quill minestrat.4 cell dot app slash API slash auth slash kind underscore callback the rest is going to be the same and allowed logout redirect URLs is going to be the same thing right here without a trading slash perfect now we can also update our environment variables let's quickly go to this domain see if the deployment actually worked it does perfect let's copy this URL right here had over into oversell dashboard for this project under settings environment variables right here we can update the kind site URL for example this was something with localhost let's change that in production let's hit save the kind post log out to redirect URL it's also changed that without the trading slash hit save on that let's update the kind post login redirect URL as well for the slash dashboard for the deployed version let's hit save on that and I think we should be good to go let's check the rest as any of these contain localhost it doesn't seem like it only the deployed version perfect because we change the environment variables we need to trigger a redeploy for them to actually show up however one thing we are gonna do and before we do that is a really simple thing but incredibly important and that is secure or entire dashboard route so that it cannot be visited by anyone that is not logged in to do that we're going to go into our source folder and in this folder create a new file called middleware dot TS this middleware incredibly important incredibly simple as well let's simply export a cons config from here config there we go this is going to be an object with a mature property this is enforced by nextges by the way it needs to be named that way and this will be an array an array containing strings and this is going to be slash dashboard slash and then colon path star so any path under the dashboard we want to protect and also the slash auth minus call oops callback call back there we go these are the pages we want to protect so only logged in users can visit those and from here we're going to say export default of middleware that we can simply import from kind at the very top right here and that's going to do all the heavy lifting for us how convenient is that we can simply save this middleware and every row that goes into this matcher is now M secured so only logged in users can visit it perfect while we're at it we can already go ahead and start fixing the navbar I mean we're almost done with the entire build we can just do that as well where there's a different nav bar for logged in users that lets you navigate um to the dashboard for example if you're logged in so the links we currently have inside of this div right here that is hidden item Center space X4 and small Flex we can quickly change these up to render conditionally and we're going to start at the fragment that we have right here we're going to render that conditionally if we have no user we're going to show what we had previously however if we have a user under the register link and then the fragment that's closing right here we're gonna render another fragment and in here we're going to render um two things and that is a link we can simply copy it from above paste it inside of this conditional render right here and one more thing we want to render in here is the user account nav component right here this user account nav is going to be a really helpful pretty little drop down um that can show some of the user options if a user is logged in however before that we still have an arrow right here and that is um we don't even have a user and we also have a to-do of adding a mobile navbar okay first off inside of the nav bar we need to determine the logged end user you definitely know how this works by now let's say call scat user is equal to get kind server session and then the user is going to be equal to get user just like this and invoke it that means we can now conditionally render based on whether you're logged in or not and let's create the user account nav component however first of course when a user is logged in we don't want to display the pricing but we want to offer them the option to go to the slash dashboard so for the href and for the text let's insert the dashboard the rest is going to stay the same and then let's create the user account nav component by the way we can already open this up side by side with or page right here just so we have something nice to look at on the right hand side and which is the navbar that we're currently editing okay and by the way I opened up the actual app on the left hand side so you can see what we are doing let's create this user account nav let's go under or Source components create a new file called useraccountenav.tsx and inside of this file we're gonna make use of a drop down menu first off let's say cons user account nav is an arrow function and let's export default user account nav from this component perfect as for the props that we want to receive in here we only want three things and that is one the email of the user second the image URL if they have one and lastly the name of the user and this is going to be of type user account nav props is what we are going to name it of course we need to define those props as an interface right above here user account nav props and for each element that we're destructuring tell typescript what it is the email will be of type string and the name will be of type string and actually the email could even be undefined which is not a big issue but we need to tell typescript that could happen and lastly the image URL will also be of type string right here okay perfect first thing we want to do in here is determine the subscription plan that the user has because there's going to be one button that's going to be displayed conditionally whether the user is already a paying user or not so the subscription plan is going to be equal to a weight get user subscription plan and in order to be able to use the await syntax of course this needs to be asynchronous from here we're going to return some jsx and as I teased earlier this is going to be the drop down menu from our custom component that we have in here as the drop down menu trigger we're going to use a button that we provide ourselves that's why we're going to add the as child key right here and as the class name we're going to say overflow Dash visible for this component awesome inside of this drop down trigger let's create a button right here inside of this button however is not going to be text as you're going to see first off let's not get ahead of ourselves and pass a class name to this button this is going to be rounded Dash full it's going to be a height of 8 a width of 8 an aspect of square and a background slate of 400 slate is just another color we can use by the way inside of this button we want a little Avatar component and again we could code that ourselves but I think this time this is actually the last element we're going to use our UI library for so let's quickly go ahead and say MPX Shad cn-ui at latest add and this is called Avatar just makes it easy accessible look out of the box and we don't need to worry about the component whatsoever because I think by this point quite frankly the video is getting quite long and we're just fixing up the latest stuff and we've already deployed the project anyways and so we're almost done with the entire build perfect let's finish it up in the user account nav and inside of this button we can now make use of that Avatar we have just installed this Avatar is going to get a class name of relative with eight height eight and inside of here we're gonna do a conditional check if we have an image URL for example if we use Google to sign in in that case we want to display that image in a div with a class name of relative aspect oops Dash square a height of full and a width of full inside of this div we're going to use a next JS image to render out the image and this is going to get a fill prop a source that's going to be the image URL an ALT tag of profile picture and lastly a referral policy of no dash refer so Google Images load correctly sometimes they don't when you don't have this tag right here and else if we do not have an image for this user we are going to render out the Avatar fall back so this UI Library really handles every Edge case we can simply fall back to something we provide and this is going to be a span element and inside of the span we're going to say as a class name Sr Dash only so screen reader only and we're gonna put the name in here which means this is going to be totally invisible for anyone with uh you know PC but for screen readers they're going to see the name and then in here goes the icons that we can simply import from our custom component dot user this can be self-closing that's totally cool and as the class name we're going to insert height 4 with 4 and text zinc of 900. perfect let's save this and already import the user account nav in or navbar of course we need to pass it some props that is expecting for example the name let's start with that the name in here as the prop is gonna be well let's do a conditional check if we have no user dot given name or you have no user dot family name in that case we're just going to say your account because we literally have nothing to work with however if we have those then we're gonna say as a template string and to mix these values together first the user dot given name then a space bar and then the user dot family name to display them in the correct order let's format this that's going to be the name the email is much easier this is simply going to be the user user there we go dot email or an empty string if that doesn't exist and lastly the image URL is gonna be the user dot picture or also an empty string if that does not exist perfect let's save that and already see what it looks like let's move this into a side by side with a bit more space and we can't see it right now because we are probably okay no the dev server is running okay let's refresh this and because we're logged in we should be able to see the user account nav right here beautiful as a little icon when we click it right now nothing happens but the Avatar is there so we know the fallback works correctly beautiful we can close all of some files by the way we don't need we've already deployed our app and we know it's working in the browser so let's go back into the user account nav right here and actually give the user some options to click on when they are logged in right so we're gonna do that through a drop down menu we've already created that at the very top right here now it's time to go below the drop down menu trigger and create the drop down menu content in here in this drop down menu content we're going to pass a class name prop and this is simply going to be BG white and we're going to say a line as a separate prop is going to be end inside of here let's create a div element with a class name of flex item stash Center a justify Dash start a gap of 2 and a padding of two as well inside of here one more div element with a class name of flex Flex Dash call a space y of 0.5 and a leading Dash none inside of the stiff if we have a name we're going to do a conditional check if we have a name we're going to render out a P tag containing that name just like so and this P tag is going to get a class name of font Dash medium a text of small and a text of black perfect okay one more check and that is if we have an email in that case we're going to render out a P tag as well let's start it in a new line because it's going to be a bit of a longer class name and that is going to be with off a custom value of 200 PX right here so it never becomes too long even if the email is absolutely huge a truncate if it is too long then it will be cut off by dot dot a text of extra small and a text zinc of 700 at last and then here of course we still need to render out the actual email right perfect after that let's let's just save it and see what happens right here let's drag it over and we can see once we click it we can see in a beautiful animation or name and email that we entered with kind pop-up perfect now we just need two buttons and that is one to take the user to the dashboard and second to let them configure their subscription oh and third what I forgot is to let the user log out right currently there is no option for the user to log out and that's what we're gonna do right now so after this again this UI Library already thought of everything we can use a drop down menu separator just like this as a self-closing tag and this is automatically going to look good inside of here or rather after the separator let's insert a drop down menu item right here and give it a as child property inside of here we're going to create a link from next link and this is going to href to the dashboard so slash dashboard there we go and inside of here we're going to say dashboard perfect we can copy this menu item down by pressing shift alt and arrow down so we have it again but this time we want to change it up is for the content we're going to do a conditional check if the subscription plan dot is subscribed right if we have a pro user and by the way this should be optional in that case if they are subscribed then we're going to render out a certain link component let's just copy the one from above but at this time this is going to lead to slash dashboard slash billing or billing page right that we created that's going to say manage subscription and else if the user is not subscribed if they're on the free plan then we're gonna say in parentheses again another link this is going to go to slash pricing this time however and in here we are going to say upgrade and we can even give it a little icon so it seems even more pretty a gem icon we get from Lucid react and this is going to get a class name of text Dash blue dash 600 a height of 4 a width of 4 and a margin left of 1.5 perfect let's save that see what happens and we got an error menu item must be used within menu content oh of course because we put it outside on the same level as the drop down menu content all of these separators and items need to go inside of the content right here perfect okay after that let's one more time copy down the drop down menu separator just like this below this item and as the last item and here's something very very basic let's create one more drop down menu item and this is going to get a class name of cursor Dash pointer there we go as the child of this drop down menu item we're simply gonna pass a log out oops log out link we get from kind simply import that and then here we're going to say log out and hit save perfect that's gonna reload it should work now and we can see the beautiful drop down menu that is in here with dashboard upgrade and logout perfect that is exactly how I wanted this to be now what happens if we hit the upgrade page currently because our app things were not on the Pro Plan it's going to take us to the pricing page but now let's actually push these changes up to our deployed version and actually try out the Pro Plan because now stripe has a web hook to call and in order for stripe to call that webhook we quickly need to configure that not in these stripe docs but I still have it open here on the other monitor let's say web hook inside of the search bar go to developers and web Hooks and in here we are going to determine a new endpoint let's say add an endpoint and we need to copy in the URL of our deployed version so let's go to our quilljet.versel.app the URL that we got from Versa while just deploying this app let's enter it here and we know that this URL will be under slash API slash webhooks slash stripe and now we need to subscribe to certain events these are going to be let's scroll down a bit this is going to be under well we don't even have to search through all of this we can literally just go to our stripe a web hook you don't have to do this and let me just do it it's the checkout session completed so we can go to checkout and then let's search for checkout.session.completed and we also want to subscribe to invoice um dot payment underscore succeeded let's search for that invoice is that anywhere here invoice and that is going to be payment underscore succeeded that we want right here let's add these two events right here to a stripe webhook that means this is going to be fired every time one of these two events either or um fire that's it add endpoint and this is going to give us a signing secret that we are going to reveal this is what's really important to ensure that this webho can only be called by stripe so let's put this quickly into our DOT EnV file and technically we only need this in production anyways but we can simply add it to this EnV file as well if you want to and this is going to be the stripe underscore webhook underscore secret it's going to be equal to the secret we just got back from stripe let's save that and also very very importantly insert that into our deployed version and that's the way more important part because that's what's actually going to handle the web hooks so in Versa under environment variables let's add a new one that is literally what we just called it the stripe webhook Secret scribe underscore web hook underscore secret and the value is going to be what we just got from stripe and hit save and we're doing this before deploying our changes because when we change a environment variable we have to redeploy anyways so now is the perfect time to go ahead and say git add dot get commit minus M and I'm just going to say stripe stuff because I'm lazy and I don't like writing big commit messages and then I'm going to say get push that's going to push or changes up on uh to GitHub which is in turn gonna trigger aversel redeployment automatically and we can see that if we go under deployments it's going to say building right here perfect that means it's not only going to incorporate all the changes we did in the code with the user account nav but also it's gonna update all the environment variables that we just added okay perfect let's maximize this and then we should actually be able to subscribe to a proper Pro Plan let's give this a few seconds to load and deploy and then we're going to try out on the deployed web version the actual SAS version if the Pro Plan subscriptions work alright awesome it seems Purcell is finally done deploying and let's go to our app that is in the deployed State let's go ahead and sign in because this is the deployed version we do need to sign back in and it seems like the redirection URL with kind hasn't worked maybe because we haven't saved yet let's try again we did enter the deployed version earlier um so let's just try this again let's hit sign in and it says the Callback URL provided is invalid okay because we have a double slash right here and that means I messed up somewhere here okay and I think I found the issue let's go into oversell dashboard and what I think we did wrong is right here under settings and then environment variables what I didn't notice and I just closely re-watched what we just did is that for the kind side URL right here we have a trading slash and before that it wasn't there so this needs to be without a trading slash let's save the environment variables and what this also means because we saved an EnV variable we also need to redeploy our application so let's navigate to deployment and click the three little arrows on the right hand side site and select redeploy that's going to completely deploy our application from scratch again and this time the environment variables that we just changed will also be included in this deployment and then let's see if it actually works so let's give it a second and see back when it's done okay the versaille deployment is done just making sure we still have these in here they go unchanged and let's see if this actually works let's hard reload this page and hopefully if we go to sign in the kind callback will work and it does awesome and we can now actually log into our app I'm going to use email that's going to send the call to my email perfect that worked and we land on the dashboard but currently there is an error it says get HTTP localhost 3000 API trpc get user files connection reviews and the reason is that if we go into or code right here and go ahead to the providers and we created at the very early point of this video we can see that we hard coded the localhost ready here and of course there is no localhost when we deploy this to production so a very simple fix let's go into our providers and for the HTTP batchlink URL right here let's enter the app so loot URL and this is going to be slash API slash drpc perfect and I say we are almost done with the entire build that's going to make the dashboard work and while we're at it we can fix one of the few things we still have left for example that's going to be a mobile nav menu in order to do this we already know our app is deployed that's great however let's quickly start up the dev server and get the latest stuff done so we can really know that our app in the web is working as we expected and also what we haven't done yet is you know change the upload limits whether you're a paying user or not right now literally there is no point in paying for our application um as everything is the same for pro users and normal users that's one thing we want to adjust as well but first let's go and create the mobile nav so when we're not logged in and this gets really small we can see the nav bar literally just disappears and that's not ideal so what we want to do instead is let's go into our application right here and inside of the nav bar we have a to do we created very early on in this video and that is add a mobile nav bar right here so let's do just that let's create a new component under or components folder and let's call this mobile nav dot TSX this is again an error function called mobile nav is an error function and we want to return the or export this as default at the very bottom mobile nav there we go perfect which means we can already go ahead and replace the to do in or nav bar close out of all of these files and replace this with the mobile nav component as it is perfect now inside of the mobile nav the logic is going to be pretty straightforward first off let's return some jsx from here and we can also open up the application side by side just so we have something nice to look at while we do it let's give it a bit less space though that's a bit too much there we go okay inside of this mobile menu let's return a div and this ZIP has a class name of Small hidden what that means is this mobile menu is only ever going to show up when the screen is very small and else we don't show it at all inside of here goes a menu icon we get from Lucid react and let's give this a on click and we're going to define the on click in a second but of course when we click that little hamburger that's going to show up then we want the menu to open that's what the on click will be for and we also want to pass this a class name this class name is going to be a relative a z index of 50 a height of 5 a width of 5 and a text zinc of 700. perfect let's create the on click now it currently gives us an error and that's not what we want and in order to be able to use these states we're going to use for that we need to mark this as a use client component at the very top of the mobile menu okay let's have a state in which we keep track of whether this is open or not let's call the state is open and also set open and this is going to be equal to use State we get from react and by default of course we want this to be false and we can also pass in the Boolean generic if we want perfect and to make our life just a bit easier we can even declare a little helper utility function called toggle open whenever we call it this is going to toggle the state from false to true or from True to false depending on what it currently is and this is nothing more than an error function where we set open to whatever it was previously that we receive as the first argument right here and return the opposite so exclamation point previous just like that now this toggle open we can already pass into the hamburger menu and hit save on both the navbar and the mobile nav by pressing Ctrl shift and S that's going to save all of our files and we can see the little hamburger pop up here in the top right if we are on mobile or small devices in general if we give it a bit more space the real nav bar comes up and if we're on small devices the mobile nav bar comes up beautiful that's exactly what we want now inside of this menu let's display the actual you know nav bar on the mobile if the is open state is true so if is open in that case we're going to return some jsx and if it's not open in that case we are going to return no so nothing is shown on the screen whatsoever as the top level element inside of this is open conditional we're gonna put a div with a class name of fixed and animate Dash in so we get a beautiful little animation to slide in and to make it slide in we're going to use the slide Dash in dash from Dash top Dash five pretty long class name here but it does exactly what it says on the can let's give it a fade Dash in-20 and inset of 0 is at index of zero and let's give this all a tad more space just like this and lastly a width of full perfect let's open up this div element and inside of here goes a UL an unordered list element with a class name of absolute we want to give this a BG white a border Dash b a border zinc of 200 a Shadow Dash XL there we go a grid a width of full and let's now we can't even oh we can give this more space perfect a gap of three a padding X of 10 a padding top of 20 and a padding bottom of eight just like this pretty long class name the longest in this component I Pinky Promise let's open up the unordered list and inside of here we want to do a little conditional check and that is whether the user is logged in or not because just like we do that conditional check with the regular nav bar right we also want to reflect that in the mobile navbar now we could get the session from the client side or we already have it in the parent component that is the never so we can literally just pass it down without needing to load any more in the mobile nav bar on the client right we can literally pass it in and what we want to call this in the mobile nav we're going to receive this as a prop right here it's going to be is auth so is the user authenticated or not and we can just type this inline it's going to be very simple type is auth is going to be of type string just like this directly and it's the only prop that we need so it's totally fine to do this what that also means is now we get an error in the navbar because we need to pass the is off and the logic for this is going to be really easy we can simply pass a um something in these curly braces right here and that's going to be converted to a Boolean the user so do you know the exclamation point reverts the Boolean right if this is true by default then the exclamation point is going to turn it false and what the double exclamation point is doing for us is essentially convert this value what is really an object into a value that is either true or false like to a Boolean what that means is now we can import that in the mobile nav and why did I say this was a string of course this needs to be a Boolean I don't know how I messed that up let's change the iso3 Boolean in the mobile nav and then all the arrows are going to be gone and we can actually do a conditional check right here in the unordered list let's open that up and let's do a conditional check right here if we are not is off in that case we're going to render out some jsx and we also need to handle the other case where we are authenticated we're going to render out some different jsx now if you're not authenticated in the first um you know scenario let's render out a react fragment so we don't have to have another top level div that literally does nothing and let's create an Li element inside of this Li element let's create a link we can import from next slash link inside of here we're going to say get started there we go and we can also give it a little icon that's going to be the arrow right we get from Blue seed react as a self-closing jsx element and with a class name of margin left 2 a height of 5 and a width of 5. perfect this link of course needs an href and this is going to lead to slash sine Dash up perfect okay and to be able to format this that's already pass a fragment into the other state just so we can use prettier and already give this some you know proper formatting we can't get rid of the automatically inserted space bar here we don't really need that and let's give this link a class name and this is going to be Flex item stash center with a full a font Dash semi bold text Dash green Dash 600 now one really really cool thing that we want to implement on this link is if we save this and we are already on the sign up page or any other page like the pricing or whatever in that case when we open up this mobile menu and click that well we are already on the page right so what we expect to happen is for the mobile menu to just close currently it won't do that it's going to try to navigate us to the page but it's going to stay open and to make this mobile menu close when we click it and are already on the page which is exactly what any user would be expecting we can use a really handy hook that next year provides to us and that is called use path name and let's of course not say cons is equal to use path name but let's say const path name is equal to use path name and we get that from next slash navigation right up here and now inside of a use effect which we get from react which takes a callback function in here we can check if the mobile menu is open in that case we're going to say toggle open and invoke that right here and as for the dependency array we're going to do that every time the path name changes or because we're using the is open as an external variable instead of this use effect this also needs to be included in the dependency array so when one of these values changes no matter which one this is going to be run and therefore the model is going to be closed and what we can also do is Define a function const close on current is what we are going to call this and this is a arrow function that receives an H ref and this href will be of type string and because this is going to be an error function that's you know Mark it in the correct syntax just like this and inside of here we're going to check if the path name is triple equal to the href that we pass into this function in that case we're going to say toggle open as well so that is the functionality of when we click it and are already on the page on the current then it's going to close that page and not navigate us around for example how we can use this is on the link right here let's say on click on this link and this is going to get a function just like this and we're going to say close on current so that means if we are already on the page that is going to be slash sine Dash up then the link is just going to close and we're not going to get navigated around perfect we can now create one self-closing Li element right below here and this is just going to act as a visual separator let's give this m my of three a height of PX for one pixel height a width of 4 and a background gray of 300 just like this and we can copy and paste the LI element from above and just paste it right in here now there's certain things we want to change up for this link for example this is not going to say get started but instead sign in the href is of course not going to go to slash sign up then but instead of sine Dash in the class name is going to stay largely the same except we want the text green 600 gone from it and we want to close it if we're already on the sign in page and not the sign up page perfect let's copy down the visual separator once again one last time for this case and also the LI element down one last time and this time this is going to be the link to the pricing page so let's say pricing right in here this is going to lead to the href of Slash pricing that we've created earlier the class name is going to stay the same and also be close on current is going to be the slash pricing let's see what happens this only happens when we are not authenticated let's I think we are not authenticated so let's open up the mobile menu first let's reload the page and currently when you click the mobile menu nothing seems to happen very weird oh and I think that might be because we are actually logged in that means this case won't even happen we won't be able to see these but instead we can just see an empty fragment and that's what's happening so the mobile menu does open but there's literally just nothing to show inside of it so let's quickly handle that case let's copy down the two Li elements the last two from the case above because we're only gonna need two inside of here when we're logged in inside of the mobile menu and paste them right in here so starting with an Li then a visual separator and then the second Li element the first one is going to lead to the dashboard so the href and the um close on current both need to be the slash dashboard and I did this select both with control and D that's how I just did that and then the second element is going to be the sign out so let's say sign out in here the href is going to be slash sine dash out and also the close on current well we don't even need it in here for the sign out route because it's just a page we get navigated to very quick and then we're already gone and sent back to the home page or the sign in it really depends on where you want the user to be perfect let's save this reload page and now see if this actually works and interesting it still doesn't so when I click this mobile menu right now nothing really happens which is weird let me quickly debug this okay and the fix is actually surprisingly easy so this still sometimes happened to me that I messed this up we of course don't want the is open instead of the use effect because what will happen is when we click the menu that is going to set open to true we can remove the debugging log right here that's going to set is open to true however once that is changed true it's a right away gonna toggle itself back off which of course we don't want so let's remove the is open from the use effect dependency array read all the page and then we can see that the mobile menu actually works so for example oh one thing by the way we want to remove these arrows from the other elements we just want that arrow on the get started so let's navigate down let's remove the arrow right from the sign in and also from the pricing and also from the dashboard and lastly from the sign out we only want it on the get started to make it pop out visually okay for example let's navigate to the pricing page right here the mobile menu is automatically going to close beautiful and once we're already on the page and click it the mobile menu is also just going to close itself beautiful now you might be wondering these sign up routes or the sign in that we have down here Josh where do they exist because they certainly don't exist in or app if we go onto the app folder there is nothing like sign up or sign in and you are 100 right they don't exist in or app yet and in order to make them work we're gonna make use of a little trick and that's in the next dot config.js well it's not really a trick it's more like a redirect because currently these sign in and sign up routes do exist but under app API AI auth kind of auth that's where they exist so if we want these to work in a more readable fashion that the user when they hover over it they see they will get redirected to this instead of Slash API slash auth login whatever this looks much better and to be able to make this work let's head over into our next JS config right here and at the very top let's define the async redirects just like this with a parenthesis right here and then let's open up this function and we can already comma separated from the webpack stuff down here inside of here we are going to return an array and this array will contain two objects the first object will take a source this source is going to be slash sign in so where we want to redirect um you know when we type this in or app the slash sign in it will know okay this should be redirected to the destination which is going to be slash API slash auth slash login and we can also pass this a permanent of true because we want this to be a permanent redirect we can simply copy and paste down this object one more time and this time we're going to do the same thing for the sign up and this is going to lead to slash API slash auth slash register and this will also be permanent perfect so if we now stop our development server start it back up because we did a change in the next JS config we can simply reload our page and we will see that the sign up and sign in just works now in the mobile menu because of these redirects right here so let's head in here click sign in and that's going to take us to the sign in route perfect one big detail that we haven't paid attention to yet is at the very top of the website not on the website but in the tab that is open right now as the file icon at as the text it literally just says create next app and of course that's not what we want if we want users to use this app this should of course be custom with a custom fav icon the little icon in the tab that is called a fav icon and the text and the description and of course when we share this link to others we want there to be a really nice image that shows up this is something many tutorials just skip I don't know why I think it's really really important that show you how to properly set this up so it looks amazing to your users so in order to make this work we are going to go into a utils file right here under the lib folder go in here and let's export a function oops X portal export a function called construct meta data and this function takes an object first off with a title property that we are going to default to quill the SAS for students or whatever you want as kind of the tagline this is what's going to show up in the tab as the actual name later then the description we are going to default this to quill is an open source software to make chatting to your PDF files easy period perfect let's give this just a tad more space there we go as the image we are going to default this to slash thumbnail.png this will be the image that pops up when we share our application as the icons we're gonna say is equal to so we're going to default it to slash fairvicon dot Ico for Icon and lastly we know index we're going to default that to false to allow search engines to actually crawl and index or websites so they show up on Google as the typescript type we need to Define for this the title is going to be optional and a string same thing for the description optional and a string same thing for the image optional and a string same thing for the icons optional and a string and lastly for the no Index this is also going to be optional but this time it's going to be a Boolean of either true or false okay and let's say this is going to be equal to an empty object for now and of type meta data that next.js provides to us we can just import that typescript type awesome and then for the actual logic of this function right here we can return an object and inside of this object and by the way a function whose declared type is neither undefined voting or any must return a value okay so it's complaining that we're not returning something and that matches the metadata type yet we're gonna do that right now we're going to return the title also the description and we can just return this because we have given them default values up here at the top that's why we can do this the open graph we're gonna give this is gonna be an object and as the title we're going to say title as the description we're gonna pass in the description and as the images this takes an array and inside of this array we can open up an object that has a URL property that is going to be the image that we pass in here that is defaulted to slash thumbnail.png right below this open graph stuff let's um specify something for Twitter because Twitter gets something extra of course and this is going to take an object with a card property and this is going to be the summary large image separated by underscores then we're gonna say title we're also going to give it the description we're going to give it the images and this takes an array but we only want our single image in there so we're just gonna use that and lastly as the Creator we can pass our Twitter handle and for me that's going to be at Josh try it coding that's my name on Twitter better but you can literally pass your Twitter handle or leave this out altogether and if you don't even want something specific for Twitter when people share your application there because to make it look good we need to pass a separate um configuration object for it we're gonna give this the icons we're going to give this The Meta data base and this is going to be a new URL class we can instantiate and then here we are going to pass our production URL so for us that's going to be https colon slash slash and then I believe it was like quill Dash jet Something Something There we go yep it was let's copy our production URL paste it in here and remove the trading slash we won't even need that okay as the theme color we're gonna pass it just a simple hashtag FFF and then as the last thing we want to pass into this function it's going to be spreading in and then in parentheses no not apps in parentheses the no index and and an object so if that is the case if we have the no index property then we want to pass the robots object and we want to say index is going to be false and also the follow is going to be false to prevent um this page from being indexed to the search engines if for any reason we don't want that if we're just working on a private preview or whatever the reason might be okay and that's all we need to do to make this website look good when it's shared Almost Do you remember that for the image we are passing the slash thumbnail.png it's over um let's head over into the public folder right here and we can see there is no slash thumbnail.png currently now don't worry I've already created this for you let's head into the public folder in the GitHub repository and oh it's not here either well of course it's not there because that's the GitHub repository for this literal project but in the GitHub repo I'm gonna provide to you of course it's going to be there and it's going to be the thumbnail.png that's going to show up when some of these shares or application so just go ahead grab that from the GitHub repository that's going to be in there and paste it into your public folder so in the utils.ts it will know what this image even is when we reference it through slash thumbnail.png and lastly the fav icon we can see that is not in our app either and this fav icon what's going to show up instead of the little Versa icon that's at the very top right here this needs to go into our app folder so under Source app we can see there is a fav icon but that's not the one we want to show up this is just the Versa logo so let's go into the GitHub repository and the Fab icon you will be able to find under Source app and then the fav icon dot Ico just go ahead copy this file and paste it into your app folder I'm gonna just drag it over drag it in here and set replace you can see this icon is the one that will show up and now we simply need to invoke this construct metadata function anywhere where we want to change or metadata which we will do at the very root of the entire application instead of this right we don't want it to say create next app generated by create next step and that just looks really really unprofessional instead we can simply say export const metadata is going to be equal to construct metadata from or lib utils and invoke that once I save this and the development server is still running let's head over to our local host and we can see right now it says with the correct fav icon quill this says for students and if somebody shares a link to your application this thumbnail will pop up right below it and looks super professional and your app is just going to look really well put together because you know what you're doing after watching this video perfect and the last thing by the way we can get rid of the unused Imports in the layout by pressing shift alt and O that's going to get rid of this metadata right here that we don't need anymore and save that and the last thing we want to do is actually fulfill our promises we set on the pricing page where we say five pages per PDF for the free plan and a four megabyte size limit for the free plan and then we increase both of them for the Pro Plan right now it literally makes no sense paying for app because these limits are not actually enforced and the way we can enforce these is actually pretty straightforward so let's start with the pages per PDF start enforcing that for free and Pro Plan users and of course where do we do that well where we upload the file in our core.ts that handles the file uploading um pages right here the pages AMT which are nothing else than the page level docs.length this is where we can now enforce that free users cannot upload more than five pages per PDF of course on the server side really important this logic should always happen on the server side and there's still some debugging logs from earlier we don't even need them in here okay so how are we gonna do this let's start with an if statement and now first off we need the subscription plan of the user we need to know are they subscribed or are they not subscribed now how do we find this out because this is being called from a server so we cannot check the subscription plan from the incoming request because this will never actually be called from any client however the middleware right here will so we need to find this out in the middleware and can pass it on in the metadata to the on upload complete the const subscription plan in the metadata is going to be equal to await get user subscription plan just like this and we can simply pass it on subscription plan right here in what we return from the metadata and thereby make it accessible in or on upload complete in this metadata object right here which is awesome so now we can use that in this if statement to see if the user is paying or not now before we make the this if statement we can make life a bit easier for us and directly destructure the subscription plan from the metadata oops meta data and we can also directly restructure the cons is subscribed from the subscription plan just like this and that's going to make the if statement a lot shorter now let's declare a constant called is pro exceeded to find out if the user is trying to upload more pages than they can for the Pro Plan and the way we find this out is let's do a check if the pages AMT the pages amount is larger than the plans dot find which we need to import from or stripe config by the way and for each plan we're going to return is the plan dot name triple equal to Pro in quotes and we can tell typescript yes this definitely exists we know there is a Pro Plan and we want the pages per PDF from that and if this evaluates the true then the pro limit is exceeded and the same thing we can copy and paste down and let's say is free exceeded and this will be again the pages per PDF but this time not for the Pro Plan but instead for the free plan and that's drastically going to simplify our if statement the first check we're gonna do if the person is subscribed so if they are a pro member and they are exceeding the pro limit so is Pro X seeded or and by the way let's wrap this in parentheses so we're going to have two different statements right here let's open up the second pair of parentheses if not is subscribed there we go and the free limit is exceeded so is free exceeded so are they on the Pro Plan and exceed the pro limit or are they on the free plan and exceed the free limit well in this case we can simply update the file to an error status to let the user know you can say oh wait the B dot file dot update and inside of here we need to pass the data that we want to update with so as the data let's pass an object where the upload status will become failed to inform the user that something went wrong and where do we want to update this file this also takes an object where the ID matches the created file dot ID perfect just like that we can now make sure that free users cannot upload more than the pages per PDF which are five and for the pro pen these are 25 and we can hit save right here and one other promise we had if you remember that let's move this into a side by side was the file size limit upload 4 megabytes for the free plan and then also 16 megabytes for the Pro Plan how do we enforce that and that's actually pretty straightforward as well let's give this a bit more space and what we essentially want to do is modify this file size right here and the easiest way to do this is to actually create two different uploaders so instead of the PDF uploader let's for example call this the free the free plan uploader there we go and of course we also need a Pro Plan uploader and the easiest way to make this happen is to abstract away the middleware and the on upload complete into separate functions because the logic for each is going to be exactly the same whether we are in the free plan uploader or the Pro Plan the only thing that's going to differ is the maximum file size so let's grab all the content of our middleware function right here and cut that out using control X and create a new constant called middleware right above or file router this will be an asynchronous arrow function let's declare that and simply paste in the logic we had previously and now as for the middleware we can simply just paste in the middleware and replace all the other stuff right here and that's just going to work now we're going to do the exact same thing for the on upload complete let's create a new function called the on upload complete right above our file router and this is also going to be an asynchronous arrow function now the types for the on upload complete are going to be a tad more involved do you remember that we get the metadata and the file in here well we get the same values in this function then of course the metadata and also the file however we need to declare the types or self now because these are not automatically inferred by upload thing but that is not rocket science either for the metadata inside of here and this will not go in the function body but of course in the typescript Declaration body right after the destruction right here the meta data is going to be of type and we're going to use a typescript utility type for this the awaited which you've already seen previously in here we're going to pass the return type so whatever the function returns and the function we want the return value from is going to be the type of Middle where so we can simply get whatever this function right here returns as the type for this metadata and then as for the file let's just say this is going to be an object with a key value or a key property of type string a name property of type string and lastly a URL property of type string as well because this is what upload thing does provide to us and we could see that right here on this type okay and as for the function body this is literally going to be the exact same thing from or is upload complete so let's copy the entire thing from the on upload complete the entire logic and paste it in here in or custom on upload complete function go ahead paste it in here and now at the very top of the on upload complete there's one very small check we want to do because during the development of this app I notice sometimes this actually gets called twice resulting in the created file twice and we can really easily counter that because first let's check if the file exists and only if it doesn't exist yet then we are going to go through with the logic because obviously we don't want to create a new file index a new file and so on if it is literally already in our database so let's quickly say cons is file exist and this is going to be a weight DB dot file dot find First and we want to find the first file where the key of the file matches the file.key that is passed into this function just like so and if this file exists if is file exists in that case we are simply going to return we don't want any of the and swing logic to run in that case and that's just a really simple logical check that we can do to completely avoid any bugs in our application later on now as the final step let's pass in the on upload complete we can even give this a bit more space now and paste it right down here in the on upload complete replace all that with the on upload complete and all this Jazz really served one purpose now we can very easily declare a new plan uploader to do this all we need to do is copy and paste down the free plan uploader and let's call this the Pro Plan uploader that we can now use from the front end and let's give it for example 16 megabytes now you could also just remove this value and give it any other value you see fit I think 16 megabytes is a pretty solid value to use perfect now where can we make use of these uploaders and the answers in or upload button because that is where we have all the uploading logic remember and there is one error right here because the PDF uploader in fact doesn't exist anymore so based on whether the user is subscribed or not we now want to change between the Pro Plan or the free plan uploader to handle the starting of the upload and the way we know in this component whether we are subscribed or not is through a prop we can simply pass it into the upload drop the Drop Zone there we go as the is subscribed prop as so and we can simply decline it in line in typescript so the is subscribe is subscribed is simply going to be a Boolean value just like so perfect and now we can do a conditional check in the use upload thing let's go into the next line and if is subscribe is true then we know it's a pro user and we want to pass the Pro Plan uploader and else we can simply pass the free plan uploader only allowing up to 16 megabytes now if we scroll all the way down we can see there is still a small problem in this component and that is the upload Drop Zone expects this is subscribed prop but we don't even get it in the upload button so let's accept it as a prop here as well and first pass it already into the upload drop zone as is subscribed and of course so is subscribed there we go and of course in order to have this value in the upload button we need to accept it here as a prop as well and this is again going to be of type Boolean perfect this is going to give us an error in another component and that is wherever we render out this upload button because from there we need to pass it as a property and we see this is the case in the dashboard if we click on the error message right here we can see this upload button expects this is subscribed prop in this component we don't have access to this value either but we will also accept it as a prop here now what we're doing here is called prop drilling which means we're taking one prop and drilling it down really far into the component tree from the dashboard into the upload button right here and then into the upload drop zone right here however in this case prop drilling is nothing bad we could even use context to avoid it all together and just access the subscription property where we need it but in this case prop dreading really simplifies our code so it's totally fine to use in here and let's retrieve the subscription plan in the dashboard as well where we can get the property from so let's destructure the subscription plan inside of the dashboard and we can Define this type as the um page props we're not going to do it inline this time of course the page props doesn't exist so let's create an interface page props for it and inside of here we will receive the subscription plan and I'm really bad at typing this out so I'm just going to copy and pass it from down there and this is once again going to be the awaited a typescript utility type we don't even need to import the return type which again takes a generic in these brackets right here and inside of here the type of get user subscription plan so we're just inferring the output the awaited outputs are not the promised output and from this operation because it is asynchronous and simply assign it to the subscription plan and of course that's not gonna be the interface page page what did I do there but instead the page props and now we receive the subscription plan in the dashboard and can pass it right along to the upload button and of course this needs to be the subscription plan dot is subscribed to be able to access that property beautiful now the subscription plan is missing and type whatever whatever in the dashboard so let's navigate there and simply retrieve the subscription plan in here because this is server side without a use client at the top we are very safe to do so so the cons subscription plan is going to be equal to a weight get user subscription platform or stripe Library preparation station and for the dashboard which now expects the subscription plan prop we can simply pass in that subscription plan and everything will be fine perfect now the thing is when we try uploading a file right now it's still going to save four megabytes whether we are on the free plan or not so we just need to change the visual style the actual functionality does work now because we use different file uploaders 4 and 16 megabytes respectively but it's going to say four megabytes anyways so let's quickly go to the place where we are saying that in this upload button right here and adjust it so instead of hard coding these four megabytes we're gonna say up to and then dynamically insert if we are subscribed if is subscribed then we're gonna say 16 or else we are going to say 4 and then after the dynamic value m b so now it will actually display properly whether we are a paying user or not and the uploading functionality will also work with the same logic perfect so that are all the changes we wanted to tweak before for making this go live let's push these up to production because now we improved our app drastically it looks better visually and it has all the paying user functionality let's say git add dot after stopping or development server stage all the files we want to commit them and I'm going to say final tweaks as the commit message because I'm lazy hit enter that's going to commit all the files and let's say git push and hit enter that is what's actually going to push the changes up into our GitHub repository there we are that has been done and if we refresh our GitHub page now we will be able to see that things popped up like the fav icon we just put into our app or if we go into the public folder then you're going to be able to see that the thumbnail that we just inserted there is also going to be right in here beautiful let's wait for Versa to accept that deployment let's go into our quill app right here under deployments just pushing this into GitHub will have triggered an automatic deployment which is currently queued and once that is done let's take a look at how our actual production app looks over self-deployment has went through successfully and let's navigate over to Jet minus or what was it yeah vegetables quill minus jet dot for sale.app and let's first check the landing page if everything works correctly and this looks so nice that is a really really clean landing page if I've ever seen one okay let's navigate over to the dashboard and see what's up first off we notice our files do get loaded correctly which is awesome and let's see if we can upload a PDF okay it allows us up to four megabytes and by the way we still see the old fav icon that is because we have loaded the page previously but if we do a hard refresh of the page click here and this doesn't work okay anyways there's a hotkey for this it's a Ctrl shift and R that's going to do a hard reload without any previous cache and that's gonna refresh the correct five icon and that all your users are going to see okay so by default we can upload Pages up to four megabytes and only up to five pages let's try out if that's actually true true I'm gonna drag in the lecture notes we're also going to check if the file upload works correctly and which it definitely should we can see the progress bar redirecting and I think this PDF only has like three pages so it should be indexed successfully and it is we're all ready to start asking our questions for this PDF file beautiful let's now try uploading a PDF file that is larger than these four megabytes that's gonna be the marketing lecture let's try uploading that and we should see an arrow on the next page that this PDF file is too large for our plan and we do perfect this is just as we were expecting this is like 23 pages of PDF we can easily navigate to the last page using our super slick input right here I really love this and then we can see the too many pages in PDF your free plan supports up to five pages per PDF beautiful now let's try if we can actually pay for our app in order to make it work I chose these 23 because it is under or M25 page limit so let's try if this works let's hit upgrade now because we're logged in that's going to take us to this stripe page and if we input or email and that's pay by card with the test card we've already used to try this out previously any details in here because this is in test mode and then let's just enter a random name let's hit subscribe and this time we should actually see that our Pro Plan works correctly because we now deployed or application so let's Let It process again because we're in test mode this payment will always work and now the only thing you would need to do to actually deploy this is change from test mode to deployed version in stripe that's literally all you need to do perfect and we know this works because now instead of upgrade and the little pretty gem that was there we can now manage our subscription that takes us to the subscription page and it says your plan renews on oh and we forget a little space bar here no big deal on the 26th of October 2023 so in one month from now amazing and it says you are currently on the Pro Plan if we hit manage subscription right here there is a beautiful loading State and we give users the option to cancel their plan of course really really important that users are able to do this we can cancel our plan and it will even show that we canceled our plan back on our page right here so if we navigate to the manage subscription your plan will be canceled on and then in exactly one month and until then they will of course still be on the Pro Plan that they paid for that means we should now be able to upload files that are larger than 25 or larger than 5 pages that is the marketing lecture and right away we can see up to 16 megabytes we can now upload larger files because we are now a paying customer how cool is that let's try the marketing lecture again let's drag it right in here and I swear if this is not the cleanest file upload you've ever seen I really don't know what is it's gonna redirect Us in this time because we're a pro user it is actually going to process this PDF and index it and now we are all set to ask some questions about this PDF let's ask for example what is the objective of this course from the syllabus and hit enter let's ask that and the loading stage shows right here we're gonna get or answer back in real time how cool is that because not all PDFs are in this format right here we can of course rotate it if it's a different format and if it's too small we can always go into the full screen view of our PDF so if we scroll down all the pages are listed here one after another beautiful that is really really cool we can zoom in we can zoom out as far as we want and have our little you know controls up here that we have made a check for so we can only enter legit numbers in here introduction to marketing interesting structure and we can ask all the questions we want let's try this out for example the modern concept of marketing summarize the oops summarize the modern concept of marketing here on the left hand side let's try this out let's hit enter and we're going to get or answer in real time and one thing I also want to show you let's first check if this answer is actually correct the modern concept of marketing focuses on identifying and satisfying the needs and wants of consumers perfect so it's summarized exactly what it says here on the left hand side it knows precisely what we're talking about and always finds the correct page because we indexed it well and of course this AI can answer in beautiful markdown as well do the same summary of the modern concept of marketing in bullet points and I think I did a little typo there beautiful that's it enter the optimistic updates immediately send the message and if anything goes wrong we still have a plan for that oh and it seems the bullet points are not working correctly for the markdown and the fix for this could not be easier the only thing we need to do is install a plugin right here that works well with Tailwind that is required and then add Tailwind CSS typography we've already installed it we just forgot to require it in the plugins let's save that I'm going to redeploy and then this is going to work beautifully so with that deployment done as we scroll down you can see right here old message new message with beautiful bullet points everything working correctly in markdown amazing all the features are working as expected we can go back to the dashboard to see all the files we have let's delete some of them and then the last thing we want to check is the manage subscription right here does everything work and I mean we already did just second ago we canceled our plan and that reflects beautifully here we can still resubscribe by clicking the manage subscription and then our payment provider will offer us to renew the plan if we wanted to but of course we're going to stay on the Pro Plan as long as you know we paid for right the landing page looks amazing the pricing page looks amazing let's log out right here let's head on over to the pricing page and if this doesn't look good I don't know what else we got the call to action buttons right here sign in sign up perfect really really good job that is our application with a secure authentication with instant feedback the best possible user experience everything looks really good and I'm honestly really really honored that you followed along with this video and literally built an entire SAS application with me in this video my goal for this video was to teach you the most modern stuff to build proper production web applications I hope you agree that I fulfilled my promise I made at the very beginning of the video so everything is completely for free in this single video we're gonna go through it step by step from the most simple Concepts to the more advanced concepts that you can reuse across all your applications because they are not related to AI at all they are transferable knowledge that you can take and use in the future as much as you want congratulations on building your entire SAS app with me together message me on Discord if you followed along I would be more than happy to hear from you and I wish you all the best for your future projects that's gonna be it for this video I'm gonna see in the next one have a good one and I really hope again you enjoyed it have a good one and bye bye
Info
Channel: Josh tried coding
Views: 339,331
Rating: undefined out of 5
Keywords: react, nextjs, next.js, nextjs 13, next.js 13, full stack, project, project ideas, saas, ai, full stack project, 2023, next.js tutorial, tutorial, beginner, react beginner, nextjs beginner, next.js beginner, tailwind, trpc, app router, trpc app router, openai, saas platform, josh tried coding, joshtriedcoding
Id: ucX2zXAZ1I0
Channel Id: undefined
Length: 674min 50sec (40490 seconds)
Published: Thu Sep 28 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.