Full Stack Ecommerce Store With Admin Dashboard From Scratch - Next.js, Prisma, Stripe, Tailwind

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video I'm going to show you how to build a fully functional eCommerce site that has purchase workflows emails admin Pages literally everything that you need in an e-commerce site this includes it and best of all this is the smallest amount of code that you could possibly write to actually get this functionality you can see we have things like authentication for our admin Pages we have authentications for our users you can see we have a dashboard here that it contains all the different information we need as well as ways to like edit deactivate download products file uploads image uploads this is literally every single thing you would need to get started with an e-commerce project and like I said it's the smallest amount of code possible the reason this is really important is because you actually want to minimize the amount of code that you write so you can spend more of your time on the actual products and the implementation and design of your site so this is the quickest way for you to get up and running with an e-commerce site if you've ever worked on a software development team before you know that slack is one of the best tools out there for collaborating and communicating with other Developers but slack is actually raising the bar even higher by integrating in slack apps these slack apps can be built through a drag and drop workflow Builder or you can actually write them entirely from scratch using Code which is my preferred method and the really great thing about this is they allow you to integrate things into slack that you would normally need to create custom tooling for things like the ability to directly translate messages inside a slack or have a system for requesting time off or even managing your standup meetings or anything else you can think of on even a larger scale it's really endless when what you can do with this and the best part is is most companies when they build this they really focus on non-technical users but slack knows that technical users are their main customer so they're really focusing on things like adding in a CLI which let you manage all of your different apps and they even have their own developer program that you can join this developer program is really great because it has tons of great documentation and videos and events all about slack apps and everything around it and on top of that they have a full developer sandbox where you can spin up your own version of slack with all of the most premium features to be able to test out all of the different apps that you're building if this sounds interesting you're definitely going to want to click on the link in the description to sign up for the developer program and start building your own slack apps thank you very much to slack for sponsoring this video Welcome Back to web dev simplified my name is Kyle and my job is to simplify the web for you so you can start building your dream project sooner now for this e-commerce site we're going to be using the latest nextjs with server actions and server components we're also going to be using Prisma for our database and we're going to be using Tailwind for our styling now you don't have to use these tools but I find them to be the easiest and quick tools to get up and running with so to create a next app we can type in npx create next app at latest and we're going to say we want to use typescript eslint Tailwind Source directory and the app router and then just leave everything essentially as the default once that's done it'll install everything over here for us and as you can see we have all of our different files and we can run npm run Dev if we wanted to start this up now the very first thing I want to do is get our database set up inside of our application because it's much easier to do at the beginning rather than trying to add it in later and we're going to be using Prisma so I can go through the getting started documentation for Prisma and it's relatively straightforward first we need to make sure we have all these different libraries installed so if we look inside of our package J on you can see we already have the node and typescript libraries installed so the only thing we actually need is this TS node Library so we can just say npmi D- save Dev TS node and that's going to make sure we have that package installed as well the next thing that we need to do if we scroll down a little ways here is how do we actually set up Prisma so we can just copy this command which is going to install Prisma for us as a Dev dependency because we only need it in development it will actually compile the production version for us so that's why we're only installing this for development then what we can do is we can make sure that we initialize npx with Prisma and this is just going to be setting us up with a SQL light database now it doesn't really matter what database you use we're just using a SQL database and the reason I'm using SQL light is because it doesn't require you to download anything additional since it's all packaged in with Prisma now in the process of doing that you'll notice that we'll get a couple files over here we'll get ourv which contains our database URL this is just whatever the URL to your database is and as you can see that points to inside of our Prisma that's where thisd DB is going to be so inside of here we have our schema and the schema is where we Define all of our different models like you can see here so I want to set up all the different models we're going to be using for our e-commerce site if we look over here you can kind of see a general idea of the models we need we need a model for our different customers we need a model for all of our different sales as well as for our different products and then we also finally need a model for downloading these products because the way that this eCommerce site works is this going to be a digital download site so you're going to buy something and then you're going to be getting sent a link to actually download that so we need a table in our database to store those different download verification links so only the correct people can download these products and we can make sure that the links actually expire after a certain period of time just to make sure that no one's getting anything for free or sharing links around so that's just going to make this as secure as we can so let's go ahead and actually create our models we just say model and whatever the name of that model is we'll get started with the product model CU that's going to be probably the most straightforward model we have first of all we want to have an ID I'm going to make this a uyu ID so we're going to say it's going to be a string at ID and the default for this is going to be uu ID now instead of making you actually watch me type all this out I'm going to paste in what all of our different fields are going to be and then I'm going to explain to you exactly why we're using each one of these so we have our ID pretty self-explanatory the name of our product is obviously just the name we're going to be storing the price in sense just because that's how stripe is going to use the price so it's going to be easy to integrate with stripe which we're going to use for payments and also because that way you don't have to worry about any rounding errors or anything like that it's just always stored in pennies then we're going to have a file path this is the path to the thing that is going to be downloaded so whenever we create a new product we're going to upload a file with that that the thing is going to be downloaded so like a zip file for example or a video file and we're also going to upload an image to go along with it to show on the website so this is just pointing to both of those different locations then we have a description describing what it is we have this Boolean flag determining if the product is available for purchase this is a really important flag because let's say that someone buys a product we'll call that product a and then later down the road we decide we no longer want to sell product a well we could just delete delete that product entirely but by doing that the person that already purchased product a now no longer is able to download the product that they bought because we deleted it so by just making it so that something is no longer available for purchase we're able to keep that product available to download for people that already purchased it but it won't show up on our site for sale anymore this is also a great way to have like in progress products that aren't quite ready yet you can create all the information keep it unavailable until it's actually ready to go then finally we have created at and updated at Fields just to keep track of any changes inside of our model now the next model we want is going to be our user model and this is for our different customers we're just going to call it user but you could call it whatever you want so this is also going to have the exact same ID property which is going to be a uu ID by default then we're going to have a email here so we're going to say string and this is going to be unique so we're going to make sure we flag it as at unique and then we're going to have our same created at and updated at Fields inside of here now our user is going to be a very simple thing inside of our project just because we don't actually have to worry about username passwords or anything like that because everything is going to be taken care of through email so we don't have to worry about any password based authentication it's going to be as simple as possible now if you want to in the future add more advanced authentication that's super easy to add onto this project I tried to make this project as modular as possible so being able to add things onto it is incredibly easy but it's not going to be full of a bunch of stuff you don't need it's going to be the Bare Bones minimum which is really important now that's all we need to do for our user so let's go on to our order model and this is just to keep track of which products which users bought so essentially it's just linking the two together as well as a little bit of additional information so here I'm going to copy over everything we need for our order as you can see we have our ID pretty self-explanatory we have created at and updated at but the one thing that's a little bit interesting is we also have the price paid here as well that's because let's say our product sells for $10 and our user buys it for $10 and then a year from L now we actually increase the price to $20 we want to make sure we keep track of what price the user paid at the time they purchased it for all of their different receipt purposes and for our own tracking purposes as well that's why we have the price that they paid inside of the order so we know how much this individual product costs them then we're also linking to the user that purchased that as well as the product that they purchased now in order to make sure we link up all of our foreign keys and everything properly Prisma has a really easy way to do that we can just say we're going to have a user property which is going to be of type user which is the same name as this model right here and we can say this is going to be a relation and now we can specify what the fields are for this relation so this essentially is saying the ID is our user ID which is what that Fields property is for and we're saying it references the ID field in our user table so up here in our user table this ID is mapping to this user ID right here and then we can determine what we want to do whenever we delete this so we can say on delete if we delete one of our users well we want to delete all the orders associated with them so we're going to pass in this Cascade flag all that is saying is if we delete a user delete all of the orders associated with that user now the reason this is showing an error is because we also need to add our order up into here so each user is going to have multiple orders so we're going to say it's going to be an array of different orders just like that and it's actually just called order there we go now we can do the exact same thing with our product so this will be called Product it's going to be of type product and the field here is our product ID which references our ID now in this case if we have an order for a product we do not want to be able to delete that product because like I said the user will no longer be able to download the product they already purchased so instead of deleting the order if we delete the project or the product we're going to actually restrict this and essentially that's just saying that if we try to delete a product that has orders it will not allow us to do that which is really important for making sure we keep the Integrity of our database and to make sure users can still download their product now up here inside of our product let's make sure we add a field for our orders which is just going to be a type of order array just like that now we have everything linked up together now this is pretty much all the models we need but I'm going to add one additional model to keep track of all of our Different download links so I'm going to come in here we're going to call this download verification make sure I spell model correctly there we go and inside this download verification it's going to be pretty similar to what we have up here I'm going to copy all of this so we're going to have our ID we're going to have our created at and we're going to have our updated at but here instead I'm actually going to put an expires at and we'll make this a date time just like that and I can actually remove this updated at flag because we should never need to actually update this particular model so now you can see here we have our ID which is just a simple string we have a date that we created this download verification and we have a date for when this should actually expire so we can make sure that they only last for 10 minutes or an hour or a day or however long we want these to last for now all we need to do is link to what product we're actually allowing you to download with this particular link so we're going to have a product ID which is a string and then we're going to have a product so I'm going to copy this straight from here and in this case if we delete a product I don't care if we delete the actual verification links for this so I'm just going to set this as Cascade essentially saying if we delete a product delete all the different download links associated with it as well then up inside of our product we can just say our download verifications is going to be download verification array and that should hopefully clean up all of our different errors so if I just make this a little bit easier to see by collapsing down all of this you can see we have a table for all our individual products we have a table for each of our customers we have a table for every order that individual customers make so linking up our products and our users and then we have a table for creating links that allow us to download individual products and we're just saying this link lasts for x amount of time and it's for one particular product so anyone that has this particular ID is able to download a product for a given period of time based on what is inside this table now the next step is going to be actually populating our database with these new tables so if we look at the quick stock documentation and I scroll down here you can see that this is line right here which is just for actually creating a migration for all of our data so npx Prisma migrate Dev give it whatever name you want we're just calling it in nit and as you can see it's going through the process and it should run everything that we need to actually get up and started with our database it's going to be creating our database creating our tables creating our migration and it's going to be creating some production code for us that has all of the different functions we need to interact with our database so now if we actually look at our files you'll notice that we have this dev. DB which contains our actual database as well as a migrations folder which contains the migration to add all of those different tables to to our database so everything we need for our database is now set up which is great now the next step for us is to actually start working on pages which we're going to use and consume our database and to get started I want to work on the admin Pages first that way we have a way to actually add new products and so on to be able to populate our database before we actually go to our customer facing Pages next Now to create the UI for our application I'm using Tailwind like I said but I'm also going to be using Shad CN which is essentially a library built on top of Tailwind that has a bunch of really handy components for us to use as you can see over here we have a bunch of different components being listed and they're all really useful and really well tested so what we can do to actually get started with this is just run this Command right here npx Shad cn- UI and latest and we want to initialize all this is going to do is get us set up with a few different files that are going to be used inside of Shad CN it's going to ask us quite a few questions I'm just going to leave everything pretty much default so we'll say default our color will be slate we'll use CSS variables and then it's going to go through and initialize everything for us so if we look at our code you can see it's made of few different changes to our Tailwind config it's added this components. Json which is just used by Shad CN and inside of our source you can see it's added this utils file with just this single CN function which makes it easy to merge together different class names now the final thing we need to do to get Shad CN to work properly is just to make sure we hook up the fonts properly inside of this so we're just going to go through and use this exact code right here so if we go into our actual app folder here and we go down to our layout this is where you can see nextjs has a bunch of stuff set up for us for like the inter font and so on all we need to do is make sure that we add this section for variable so we can say our variable and we can call it-- font sand just like that and then if we scroll down here a little ways you can see that this class name is applying all that different information so what we can do is we can make sure we add all the different classes we want inside of here so for our class names what we want to do is we want to have a background which is going to be our background color so we're going to say BG background our minimum height is going to be screen just so we make sure it fills the full height our font is going to be that Sans font we're going to specify anti-alien and then we need to make sure we add in that font sand variable so to do that we're going to be using that CN function that was added by the Shad CN so make sure we get CN just like that and for the second parameter inside of here we're going to pass our font sand which in our case is called enter. variable just like that and make sure we close that off so now that's adding all the different classes we need to get our background color set up right as well as to get our font set up right the last thing we need to do is to actually change our theme to use that particular variable so what we can do is we can go inside of our Tailwind config and make sure that we find the section for our theme that we want to extend so we're inside of theme inside of extend here I'm going to change our font family and I'm going to say that our font family is just going to be equal to our Sans font just like that and we want this to be our variable for font sand which is the thing that we created with nextjs and then we want to add in all the other font family related stuff for Sans so what we can do is we can actually import that so we can just come to the very top of our file and say import font family and that's going to be coming from Tailwind CSS deault theme just like that make sure I spell that correctly and there we go now it's spelled correctly and we should see all of our errors go away and essentially it's just adding the inter font to beginning of our font list so that'll work properly so now to actually test if all of this is working let's come in here and we'll just say npm run Dev and hopefully our application will start up just fine and if we open it up you'll see that our application is loading on the right hand side of our screen which is great now we have a bunch of default Styles inside of here which we really don't care about so for now I'm just going to come in here I'm going to remove all of these default code and styles and everything just going to replace it with a single H1 that says High super straightforward super simple and now we can just get rid of all that and if you come in here and I zoom in a bunch you can see we have the text High showing up and the font and the background color all set up exactly like we want if we were to change our background to like red 500 we should see that it is now going to have a red background so at least we can see that everything is actually working now as I mentioned the very first thing I want to work on is our admin pages so inside of our app here we can just add a brand new folder called admin and that's where all of our admin pages are going to go also we can remove this favicon we could add our own if we really wanted and same thing ins inside this public folder we can get rid of these images we don't actually need those those would be things you would add in yourself same thing here with this metadata change it to whatever is based on your particular project now in this admin folder let's create a brand new file inside of here called layout. TSX I'm also going to create a page. TSX inside of here as well this is going to be like our root dashboard so inside of our layout we'll say export default function this is our admin layout just like that and this is going to be very similar to the layout that we have here as you can see it's going to be taking in some react children so I'll just copy over the props since they're exactly the same props just like that and inside of here if we wanted we could just return our children but of course we want to wrap some stuff around this so what I'm going to do is I'm going to add a navigation component at the top here which is going to be all of our nav stuff and then I'm going to add in a div here with a class name of container and margin y of six so it's going to make sure all of our content is centered and give it a little bit of space on the top and the bottom and then I'm going to put my children inside of that now our nav is going to be a special component that we're going to create so we're going to use this components folder right here I'm going to create a brand new file called nav do TSX and we'll just say export function nav just like that now this nav is going to be really super straightforward it's just going to be a simple nav component and it's going to have some class names applied to it and that's pretty much it so we're going to say background is going to be our primary color which in our case is a darkish black color our text is going to be our primary foreground color which is a white color we're going tojust or set this as a flex container and we're going to justify everything in the center also we're going to add a little bit of spacing around this so there we go once we have that done we can add in our children which is the only prop this is going to take in so we'll say children and this is just going to be children which is a react node just like that so that should clean everything up so now we have our nav and the next thing I want to do is create a another function which is going to be a component called nav link which just allows us to add links to our nav really easily that are automatically going to have Focus style applied to them based on what route we're on so we're going to have our nav link inside of here and this is going to work essentially identically to a normal link inside of nextjs so we're going to have a link is what we're going to return which is a nextjs component and we're going to make sure we just close off that and we're going to take in our props up here and these props are going to be coming just from our component props for our link so essentially it's going to get all of the props for our link component so type of Link just like that but we want to make sure we omit the class name prop because we don't want to be able to add class names to this so we're going to say that we are going to omit the class name prop from this make sure I spell that correctly there we go just like that so now I have these props and I can pass them in just like that and then I can pass in my own custom class name and we're going to again use that CN Helper because I'm actually going to be getting the specific path that I'm on to add special classes if we're on the correct path so I can say here const path name is equal to the use path name hook and since I'm using a hook I need to make sure at the top of this file I mark this as a client component by saying use client so now I have access to whatever my specific path is and now I can apply all of my default Styles which you can see here if I zoom this out are going to be just essentially a no background color or anything but you can see that when I'm on the selected page it has this different background color to it and everything like that so by default my classes are going to be a padding of four just like that I'm going to have a hover style where my background is going to be set to the secondary color and a hover where my text is going to be set to that secondary as well so text secondary foreground there we go then I'm just going to copy both of these Styles because I want to do the exact same thing when my focus is visible so I'm going to do Focus visible and same thing here Focus visible just like that so that way whether we're tabbed onto it or hovering over it we're going to get these specific Styles and let's make sure we import CN up here to get rid of that error and then finally down here I'm just going to say if my path name is equal to my props dohre so if we're on the current path that we're pointing to well then I just want to change my background so I'll say that my background is going to be that background color and then my text is going to be this foreground color just like that so now if I give that a quick save what we can do inside of our admin section is add in the import for nav and we can also add in some nav links inside of here so we can say nav link just like that and let's say that this one is going to say dashboard that's going to be my first link then we're going to have one for products and I want to make sure I add an hre here for the home page this one is going to be SL admin products and this one up here should actually say slash admin there we go copy this down because this one is going to say users and this one is going to say orders and the link text will say customers and sales just like that so now if I give that a quick save and I make this page actually do something so we'll say export default function admin dashboard return an H1 that just says dashboard super simple super straightforward there we go so now if I give that a save and I actually start up my application and we'll just make sure we go to that page so we'll just go to Local Host 3000 admin we'll close out of this old older version now you can see that we have these links up here and when I navigate around obviously nothing works because they don't have links for these yet but you can see my application is rendering which is great now for this dashboard I obviously don't want to just render out the text dashboard and instead I want to render out a grid with specific cards you can make this as complex as you want with different charts and dashboards and all that but in our case we're going to make it super simple and super straightforward so here I'm going to create a div class name is going to be grid we're going to say that by default on the smallest screen size we have one column on medium screen sizes we're going to go up to two columns and on large screen sizes and above we're going to go up to three columns just so it's going to be a little bit more responsive and also inside of here I'm going to say that we're going to have a gap of four I need to make sure that I fixed my typo here there should be no space in between those there we go and now inside of here I can create my different cards to do that again I'm going to be using Shad CN so what I'm going to do is I'm going to go over to Shad CN I'm going to search for the card component and you can see we have the card component right here and to install this is super straightforward I just copy this one line of code and it's just going to copy this card component into my application so once that runs you'll notice in this components folder I have this UI folder and inside of here contains all the code for the card I could change this to make it my own custom card but for our use cases we want this to get up and running as quick as possible so we're going to pretty much use the default Styles I'm going to restart my application and now I can actually use the card inside of here so make sure I import that from the component slui card there we go and then inside this card I want to have a header so we're going to get our card header inside of here and this is going to be whatever the text you want to say so in our case we could say something like products or sales let's just say sales in our case cuz that's going to be the first thing in our dashboard then we're going to have the card description and this is going to be like a secondary section so we'll just say description like this we'll get the actual real value in here in just a little bit I just want to kind of show you what this is going to look like then after that we need the card content so we're going to get that card content and inside of here we're just going to put a paragraph that has whatever our text is we'll just say text for now so if I give this a quick save and I actually go over to our page and give it a refresh we should actually see over here that this card shows up as you can see we have sales we have description and we have our t text now it doesn't quite look correct and that's because inside my card header I should have a card title which contains the text sales so let's fix that and then inside the headers where the description should be as well now you can see that my formatting is properly good just like I want it to be now I'm going to have three of these cards so I could copy this down and paste it three times just like this or what I could do is I could create a custom component instead so let's create a component for this called dashboard card and this dashboard card essentially takes in a title it takes in a subtitle and it takes in our body so we're going to have a title a subtitle and our body and that is our dashboard card props and let's create that type dashboard card props is equal to we're going to have a title which is a string and we're going to have the exact same thing for our subtitle as well as our body these are just going to be three separate strings that we're passing in then we're just going to return this exact content down here here so we'll say return just like that this will say title inside of our description that'll be our subtitle and then finally inside of our paragraph tag we will have our body just like that and now up here I can render out as many of those dashboard cards as I want and pass in for example my title which is sales the subtitle which will just say test and the body which will say body for now there we go you can see that card looks great and now I can copy this down and make it much easier and more modular to work with now in my case I want to get specific data to put inside of these cards like how many sales have I made how much money has my products earned me so I'm going to create different functions to get that data so we'll create a function here that's going to be called get sales data just like that and this is going to be coming directly from our database now to be able to use our database inside a nextjs we need to do a little bit of finagling of our code to actually get this to work but luckily in the Prisma documentation if you search for nextjs so if I just come up to the very top here search documentation for nextjs you should see that there's going to be a page exactly on how to use this inside an xjs and there should be a troubleshooting section that tells you what you need to do this actually isn't the correct page so let me search for nextjs again to make sure I actually get the right page so if I click on this second link right here you can see that this gives me some code that I can use to actually get the client to work properly inside of my application so I'm just going to take this exact bit of code copy it and inside of my source folder I'll create a brand new folder called DB and inside of that I'll create a file called db. TS and I'll paste in this code all this code is doing is making sure that pris works well with nextjs because this is just making sure that they're hooked together now instead of calling this Prisma I'm actually going to call this DB just because I like that naming of DB a little bit better so we're going to make sure that we change all of these different instances to say DB instead of Prisma we'll do the same thing here it doesn't really matter DB and DB just like that there we go now we can actually use that by inside of here just saying DB importing that and now we have access to everything from our database so for example we can query our orders table if we wanted to find all of our different orders now you will notice I got this question mark syntax that's CU it's using the global DB variable so instead of calling this DB for the global variable I'll call the global variable Prisma so this will be Prisma and then our local variable will be called DB that way we make sure we always import the proper DB variable and now you can see it's no longer nullified so now we have our order and we can do whatever we want with this and in our case what I want to do is I want to get the total amount of sales I've made as well as the total amount of money from those sales so we use the aggregate function to essentially do a sum and a count so what we'll do in inside of here is we'll get a sum and this sum is just going to sum together all the different prices that have been paid in sense so I want to sum together the total price and then I want to do a count and we'll just say true and this is just going to count the number of rows in our database as well so it'll give us the total number of sales as well as how much we've made in sales so I'll just say const data is equal to that and we'll make sure we await that because this is going to be asynchronous and then I can return this data in a little bit more friendly format so I'll just say return my amount this is the amount that we've made in sales total so that's data doore suum I want to get the sum of my price paid in cents or we'll just set this to zero if we don't have any sales at all and I'm going to divide this by 100 to give me an actual dollar amount so we'll divide it by 100 just like that then finally we're going to get the number of sales which is just data doore count so this is just a little bit easier way to work with that data now down here we can say sales data is equal to get sales data just like that make sure we await this and since we're using nextjs with server components we can just do this directly inside of our server component and I can even just destructure this out to be for example our amount but we'll just leave it as sales data because that's a little bit easier to work with and now inside of our subtitle we can say that we want to get our sales data Dot and let's in our case actually get the number of sales that we've done and then for our body we're going to give us the sales data Dot and we're going to get the amount of sales that we've actually done so now if we give that a save and we come over here you can see it says 0 and zero obviously I want this to look a little bit better though and I want to do different formatting so we're going to write some formatters and some extra code to make this look a little better so inside of our actual lib folder here we're going to create a brand new file called formatters TTS and I'm actually just going to copy this code directly over because all this code does is create a formatter for formatting currencies and formatting numbers it's just going to add in different commas dollar signs and so on inside of our code it's very straightforward I've covered this inl object a bunch of times on my blog and such so I'll link to that if you're interested in checking it out but overall this code is relatively straightforward it's just creating different formatters for us so now what we can do if we go back to that particular page is I can actually call that format number for our case so we'll say format number make sure that I import this and this is just going to make sure it formats it with actual commas and everything inside of here and same thing down here for amount I can format this as a currency make sure I pass that in and import this function there we go so now you can see it at least says dollar sign 0 and it'll give us other information if it was a large number for example if we put in here just a really large number you can see it has commas and everything so it looks really nice out of the box and this is just all built into JavaScript now for our subtitle to give us a little bit more context I'm just going to wrap this in some string templating there we go and I want this just to say orders just so we know that there have been zero total orders and they have accumulated to zero total dollars obviously as we make orders we'll get more information information inside of here now the next thing I want to do after that is going to be doing all of our different customers so we'll call this customers there we go and what we want to do is we want to have the average value of our customers so we'll have here our average value which is going to be some information that we place inside of here and then inside of our body I want to just have the total number of customers so this will be format number of some particular number and this will be a format currency of some particular currency so let's go up and actually get our user data up here so we can say async function get user data the first thing I want is the total number of users so we'll say const user count is equal to db. user. count that will just give me the count of the number of users and we can just make sure that we await that and then we can come down here and we can get our order data which is just equal to awaiting db. orders and this is going to be another Aggregate and in this case we want to get the sum of the price paid in cents the same thing that we did before and that's because I want to be able to get the average value of each individual customer so if I have my total number of customers and the total amount that I've made if I do the division I can actually get the average of the amount that each customer has actually paid now instead of doing these awaits back toback this is not super performant I'm instead going to put them inside of a promise.all so inside this promise.all I'm going to first call this function and then I'm going to call this function directly afterwards so then I can say const I'm going to have my user count as my first value and my ordered data as my second value from awaiting that there we go make sure I put the D right there clear out that and now you can see that if I spell this properly DB there we go that we are now able to do both of these at one time instead of doing them back to back next up I just want to return my information so we'll say we're going to have our user count just like that and then we're going to have our average value per user and this average value per user is just going to be a really simple calculation where first of all if we have no users well then our average value per user is going to be zero this is just preventing any division by zero then what we're going to do is we're going to take our order data I'm going to get that value for the price paid in sense this is the total amount I've made I'm going to set this to Zero by default I'm going to divide this by our user count and then divide it by 100 to make sure we convert from pennies to Dollars that's going to give me all the different user data that I need and I can use that down here by saying get user data and this is our user data now I want to again make sure I do these in parallel so I'm going to use a promise.all and I'm going to wrap both of these functions in it so pass in that function first and then get user data second and we say con sales data and user data just like that and remove these make sure that we await this and now we have our information just like we want it to and here I can use my user data. average value and my user data. user count to actually get this information so now you can see we have our customers each one has a dollar average of zero and we have zero total customers again we'll see more information once we actually start adding more things now the last thing I want is going to be my products so we'll say products and this should say customers instead of customer and this products is actually going to say active products because we want to determine how many active products we have I'm going to format a number here and this formatted number is going to be some particular number and this is going to be my inactive products so we'll say inactive after this there we go and then down here we're going to format a number with something inside of it we don't know what that will be yet though so we have our function for getting our user data our sales data let's do one for getting our fun product data so get product data there we go make sure I spell all that correctly and inside of here we can just say db. product to get what we need so essentially I just want to get the count of active and inactive products so we can say db. count and I want to do it where my is available for purchase is actually set to true and then I can copy that down and do the exact same thing for where this is is actually false now of course to make sure we do these in parallel I'm going to put them inside of a promise.all just like that there we go and then I can say that const what we're going to have is my active count and my in active count is going to be equal to awaiting that just like that and then we can just return that so we can return our active count and our inactive count as a fun or as an object sorry so down here get product data just like that call that function that's going to give us our product data and now we can use that down here so the first thing I want to do is my inactive numbers and then we're going to put our active numbers down here so active count just like that now if I save it you can see we have all of our different active product information as well as our inactive product information now I'm just going to fix the typo here of removing these parentheses obviously we don't need those so the brackets sorry and then what I want to do next is actually going to be adding in a loading state for our dashboard and this loading state for our dashboard does not have to be complicated good-looking or fancy because this is just a dashboard that our actual admin users are using so when you're using admin stuff it doesn't have to be beautiful it just has to be functional so we're going to create one loading spinner that works for every single admin page we create so we never have to worry about creating custom loading animations so we'll say loading. TSX export default function admin loading just like that and inside of here all I'm going to do is return a single div this div is going to have a class name of flex and justify in the center just like that so that this can be a centered loading spinner and then we're going to use this Loader 2 icon this comes from Lucid react which actually comes with shad CN UI so if you're using Shad CN this is just imported already otherwise you can create this own import yourself and what we're going to do is add some class names to this so we'll say size of 24 and we're going to animate it with the spin animation and that's the only thing we need to do for this loading page like I said it's going to be incredibly straightforward and simple now if we wanted to see what this would look like all we could do is just maybe slow down this particular database query that is happening inside of here so I could just say like a wait for 2 seconds so we'll say wait 2 seconds just like that and I'll create a really simple weight function function weight duration which is a number and it's just going to return a new promise resolve set timeout or weall resolve after our particular duration so it's just going to wait 2 seconds so now if I refresh this page you can see we get that loading spinner for 2 seconds before our page shows up super straightforward and simple it's nothing fancy but for an admin page that is more than enough another thing that I'm going to do automatically for our admin page I'm going to go to my layout and here I'm going to export a constant variable called Dynamic and this is going to be set to force Dynamic just like that and what this is going to do is it's going to force nextjs to not cash any of our admin Pages now you could add cach into these pages to make them a little bit faster but for admin p Pages you're generally going to have relatively good internet speed wherever you're working on these admin pages and you don't want these to always be cached because you want whatever the most up toate and recent information is so we're just going to ignore all caching problems that we could ever run into by forcing every single admin page to always be dynamically generated now if we did this throughout our entire application this would be a problem because users are going to be using our application all the time but since the admin pages are not accessed very often this is really not a big deal at all now the next thing I want to work on is this products page because once we have that done done we can actually go ahead and start working on the frontend customer facing portion of our application so we'll create a new folder called products and inside of here I'll create a page. TSX just like that export default function products page we'll call this the admin product page there we go and inside of here we'll just return an H1 that says hi just so if we go to the projects page you can see it says high right there so at least we know that this is working now inside of this projects page the very first thing that I want to do inside of here is I want to add in a page header I'm going to actually create a custom component for this page header that'll say products just like that and this custom component is only going to be used in the admin portion of our app so what we could do is we could create this in our components folder but since this is only used in the admin portion of our app I'd rather create a custom folder called unstore components in here and then this is where I'll create my page header now the reason I prefix this with underscore is that's nextjs way of saying that this folder will never be used for routing so we can put anything inside of here it doesn't have to be routing related and this means that this component will live in this admin folder which makes it easier to understand where different parts of our application are so now we can create that page header. TSX make sure I capitalize that export function page header there we go and this page header is going to be very simple we're going to take in some react children so we'll just say children is a react node just like that and here I'm going to return simply an H1 that is going to have whatever our children is inside of it so our text pretty much and then we're going to add some class names to essentially make the text a little bit larger so we'll say 4XL and then we'll add some spacing on the bottom of it super straightforward so now in this page if I just import page header give that a save you can now see it's a much larger font size and there's some space underneath of it I'm also going to add a button for creating a new product as well so I'm going to wrap this in a div cuz I want the button to be right next to the page header so to line these up side by side I'm going to be using some Flex box so we'll say Flex justify is going to be between to space them out and we're going to put items in the center and then finally have a gap of four between these elements then I'm going to use a button component now this component comes from Shad CN so I'm going to search for button and we're going to just do the install process for this super straightforward npx add button essentially so we can just run that code right here that's going to add that button component into our actual Library so if we look in here you can now see we have that Buton button component you can customize it do whatever you want again in our case we're just going to be leaving everything as the default so now I can import that button component and inside of here I can put whatever my text is that I want now in my case I want this to actually be a link that goes to another page so I'm going to put a link component inside of here which an hre that goes to SL admin products SL new to go to that new product form and this will say add product and to make my button work I can just say as child and that's going to make sure it renders this out as a link instead of as a button component so now if we come over here and I give this page a quick refresh and I make sure my application is running we should see now we have that button over on the right hand side now the final thing I want to render out is a table so we can say that we're going to have a function called products table just like that and inside of here we're going to return some content inside of here but that's going to be a table straight from sagn they have a data table which is great for doing different searching and filtering and sorting and so on if you want to get a more complex admin page these would be great but a super simple table that just has no sorting or anything can also be used so we'll copy the code to import that and again like I said if you want to make it more complex go with the data table it's much better for a larger more fully-fledged application but when you're just getting started you don't need something like that a simple table is going to be more than enough so now we actually have that table so I can come in here and I can make sure that I import my table make sure I get it from that component UI folder like that and then inside of here I can also say that I want to have my table header so we'll say table header just like that and inside the table header I'm going to have a row so we'll say table row and inside of that I'm going to have some individual table head elements so we'll say table head just like that and these are going to be our actual headline sections for our table now we're going to have just a few of these so I'm going to have name copy this down a couple times we're going to have price and we're going to have orders to determine the actual number of orders that we've had and then we're going to have two unique ones these actually aren't really going to have any text inside of them and they're actually going to be super small we're going to set the width on these to be zero that's going to make them essentially as small as humanly possible it won't actually be a width of zero it'll just be as small as it can possibly be now what I want to do inside of here I'm going to put a span and that span is going to have a class name of Sr only this is going to be screen reader only so that way the screen reader knows what this section of the table is for and this will be labeled as available for purchase and that's because we're going to essentially have a check mark or an X in this column to determine if this is available for purchase or not and if you can visually see the page it's really easy to understand what's going on we don't need an actual headline for that but if you're unable to see the page having a headline is really useful same thing here for this section I'm going to essentially copy exactly what I had here and this one is going to be called actions instead and this is because I'm going to have a really small little icon on the right hand side that I can click on to open up a drop- down menu of all the different things I can do like edit delete deactivate and so on so if I give this a quick save and I make sure up here I actually render out my products table give that a quick save give that a refresh over here we should at least see that we have those headers showing up as you can see we have the header section showing up on our page but obviously we don't really have any content inside of here yet what we could do is we could add in our table body just like this and inside of here we would Loop through all of our different products and render out some different rows but for now we don't have any products so let's just go ahead and focus on how to create a new product before we worry about how to render it inside this table so to do that what I'm going to do is I'm going to come over here I'm going to create a brand new folder called new and inside of here I'll create our page. TSX export default function this is our new product page and this is going to be very simple we're going to return essentially two things the first one is going to be our page header and that page header is going to say add product just like that and then after that we're going to render out our form so we'll say product form just like that make sure that I close all this off properly there we go so now if I get that a quick save you can see we have those two things being rendered so let's actually create the component for our product form now I could create that in this components folder here but since this is only for our products I we'll just create a folder here called components and do it inside of here so we'll say product form. TSX export function product form just like that now this is going to be a client component so I'm going to come up here and say use client just so we know that this is a client component and we're going to be needing to use labels and inputs as well as text areas and Shad CN has all of those so you can see here if we search for label we have the ability to import a label so I'm going to come in here we're going to add a label I'm going to do the exact same thing for inputs so once this finishes installing I'll do the same thing for an input component and I'm going to do the exact same thing for a text area so we'll have text area just like that that way we have all the different components that we could possibly need and if we look inside of here you can see we have all those new components being added which will make working with our forms much easier now Shad CN has a full form component that you can use and it has like Zod and it has react hook form all hooked up for you but since our forms are going to be really really straightforward and we have almost no form forms in our application we don't really need that super complex form Integrations we can just do everything on our own so for our return here we're going to return essentially a form and just to make sure that everything's working on this page I'll import our product form just like that and we'll go to that add product page make sure that I have our application started back up npm run Dev there we go so now if we give this a quick refresh it should bring us to that page it should say add product and then nothing because our form is currently completely blank so inside of our form we want to create a div and this div is going to wrap our label so we're going to have a label make sure we get that from the correct import and we're also going to have an input and again make sure you get that from the correct import just like that so the very first thing that we need is going to be our name so the type for this is going to be text we're going to have an ID which is going to be name we're going to have a name on this which is going to be name it's going to be required just like that our label will say name and our HTML 4 is going to be name so the reason we have an ID of name is so we can hook that up to our HTML 4 and the reason we give it a name is because that is the actual key that we use inside of our action that we're going to set up later to actually get the value from our form now the final thing I'm going to do is add some spacing so we'll say our class name here space in the y direction is going to be two and here our space in the y direction is going to be eight actually so now you can see we have our name input where we can pass in our name information now I'm just essentially going to copy this down because we're going to do the exact same thing but this is going to be our price in cents just like that our ID here is price in cense going to do the exact same thing for our name and make sure our HTML 4 is set correctly this is going to be a number input just like that and for this one I'm actually going to go a step further and that's because I'm going to add in a value prop which is going to be our price in sense and an on change prop so I can actually get the value of this dynamically so we'll say on change e and we can say set price in sense to e. target. Val so we can just create some state for this const price in sense set price in sense is equal to use State there we go and this by default is going to be zero and actually instead of giving that a default value let just come in here specify this as a number input so by default it's going to be a completely blank input now down here since our value is technically a string we need to convert this to a number so we'll say number or undefined so essentially if this is not able to be converted to a number it'll just give us undefined instead so now whatever we type inside of here is our price in sense now the reason I stored this in state is because down here I'm going to have a div with a class name of text muted foreground there we go and this text muted foreground is going to show the actual dollar amount version so what I can do is I can say format currency currency there we go and inside this format currency I want to get my price and cents divide it by 100 and then I want to render that out to this screen so now you can see right now it's showing n an but if I just defaulted this to zero if for example it was not a number you can now see it's saying zero which is exactly what I want and if I come in here and say that it's 100 pennies you can now see it says $1 or it says $102 and so on so it's a really easy way to see what the dollar amount is I'm going to copy down this up here and I'm going to paste that down so we get another input going and this one is going to be for our description there we go description and the name here is going to be description just like that there we go description and this one is going to be a text area so make sure we import text area we don't need to specify a type on that now you can see we get the ID name and required property and you can see our description is showing up right here now I'm going to do another copy down up here paste this down and this one is going to be for our file and this is the actual file we download so we'll just call this file this one will say file and make sure the type is file the ID is file and the name is file and now you can see we have this file Chooser right here I'm going to copy this down one more time because we also want to be able to select our image so this one is going to be called image instead of file same thing with the ID and the name and this will say image just like that so now we have our image selector down here as well and just to show you that there's nothing behind my camera you can see I just move it it's essentially just these inputs so there's really nothing that I'm hiding behind my camera now the very last thing that we need is a submit button so I can come in here with a button just like that I can specify that this is going to say save and the type is going to be submit and let's just make sure that we import this button component there there we go so now I have that save button showing up down here now in order to actually make this form work we need to specify an action on our form and this is going to be using a server action and the easiest way to do this is just to create some actions for our products so we're going to do this directly in the products folder we're going to create an underscore actions and actually instead of doing that in the products folder I'm just going to do it in the admin folder so this is going to be for all of our different admin actions I'm going to create a folder or a file here called products. TS and this is going to be for our actions for our products so I can create a function called add product make sure I export this and now if I make sure that this is an async function since actions must be async I now have a function that I can call from my client onto my server and to make sure that this runs on the server I can just specify up here use server just like that so now this ad product function is going to run on the server and it's going to take in form data which is of the form data object and this is coming directly from the information in my form I can even just do a quick console. log of form data so we can see exactly what's being pushed up now to make this work we're going to go to our product form and we're going to add in that action of add product just like that you can see we have no errors and if we give this a quick save and I open up my console let's type in something for our name so this will say test price we'll just make it $1 so we'll come in here just like that description we'll say body and we can choose some files so I'm going to say that this file right here is going to be some just SRT file and then we're going to use an image right here now if I click on save you can see inside of my console it prints out all the information to us so if we just open this up you can see we have our form data our name is name with a value of test our name here price and sense value of 100 description is body and both of our files are being passed up as well so it's super easy to pass up all the information we need inside this form data object now to do validation though is a little bit more complex so that's why I'm going to be using Zod for all of our validation needs so to import Zod we can just close out of this npmi Zod just like that this is a really great validation Library if you want to learn more about I have a full video on it I'll link in the cards and description for you and you can check it out it goes Super in depth and everything you need to know about Zod but with Zod the main thing is you want to be able to create a schema based on an object so we'll say Z which is what Zod is being imported as and we'll say object and this object is going to represent what the data we're going to be passing up is so in our case we're going to have a name and this is going to be a string so we'll say z. string and the minimum length is one essentially we're making sure it is required we're going to do the exact same thing with our description so our description this one is a z. string just like that and the minimum length again is one make sure I spell that properly then we're going to come down here we're going to have our price in sense this one is going to be a number and we want to coer it into a number so we're going to say z. co. number and that's because our form data is using string values so if we look up here you can see that this value is a string of 100 so chorus attempts to convert it into a number first and if it can't it's going to throw an error we're also want to make sure that this is an integer and that the minimum value must be at least one because our products cannot be free for this particular way that I'm doing things now the final things we have are our file as well as our image and these ones are a little bit interesting because the way that they work is a little different because there's no really handy like dot file that we can use so we're going to create our own file schema so we'll say const file schema is equal to z. instance of file so we're saying that this object must be an instance of the file object inside of this and if we look up here you can see both of these of already the instance of file which is exactly what we want so we're saying it must be a file and if it's not we're going to pass along a message that just say required just like that so now we can say our file schema just like that and what we can also do is take this a step further because when we add a product we must make sure that this file has a size and that's because if you don't put a file into your file input and you submit your form it will actually submit a file to your server but it'll be a completely empty file with a size of zero so to test for this we can use refine this will take in our file object and we can say that the file do size must be greater than zero and if it's not we can say that it is going to be required I can do the exact same thing for my image as well down here by F my file size must be greater than zero and that it is required if we don't have that now I'm going to take this even step further though by creating an image schema and this is just going to refine my image even further so this is going to be based on a file schema but I'm going to refine this just one step further by saying that my file size file. size whoops it's going to first take a file and it's going to say if my file size is equal to zero or if my file. type do starts with and it's going to be image slash just like that and now I can use this image schema down here as well so this may look a little bit confusing so let me explain exactly what's happening this image schema the reason I'm saying file. size is equal to zero or this particular check is if my file size is zero that means I did not submit a file at all so I'm just going to essentially ignore this check completely so if I didn't submit a file just don't do this check but if I did submit a file check to make sure the type of that file is of some form of image whether it's PNG jpeg it doesn't matter the type will start with image slash if it is an image specific file now the reason I'm doing my size checks down here instead of doing them up here is because when I add a brand new form or a brand new product my image and my file are required but when I edit a product I don't need to specify a new file or a new image so when I do my editing these will actually be optional fields which is why the actual section that checks the size is only inside the schema for adding and we can call this our add schema just like that so now we can use that ad schema with our form data to verify that our data is in the correct format and it will convert everything to the correct typ strip types for us as well so what we can do is we can say object. from entries and we'll take our form data. entries what that will do is convert our form data to an actual object that we can use then I can take my ad schema and what I can do is I can safely parse this information and what this will do is return to me some data so we could say const result is equal to that and if I look at this result object you'll notice that I have a success property of true or false and if it's true I'll have data and if it's false I'll have an error so if my result. success is equal to false then what I can do is return the errors down to user so I'll say result. error. form errors and I'll get the errors for each individual field this is essentially going to give me a bunch of different properties for my name description price and sense file image and so on so it's just going to give me essentially error messages for all of these then what I can do is if it was SU uccessful I can come down here I can create a data property which is just result. dat and this is all the data I have up here if I look at the type you can see we have a name String description string price and sense number and both of our different files and we can use that information to create a brand new product for us for I saying DB this is coming from our database. product and we want to create a brand new product where we have some specific data that we're going to be passing in so inside of here you can see we have all of our different properties so our name is data. name we're going to have our description which is data. description and inside of here our price and sense is data. price and sense now our file path that we have here as well as our image path these are going to be a little bit more complicated and that's just because we need to first save our file to our file system before we can actually save the path to them inside of our database so to do that I'm going to be using the fs module inside of nodejs so I can say import FS from and this is going to be coming from fs/ promises I want to use the specific promise version because it's much easier to work with in modern JavaScript so I can say fs. MK dur which is going to make a directory and we want to create a directory called products that's going to be where we store all of our different product files that they can download and we're going to make sure that we call this recursive of true just to make sure if we have multiple files we want to create it'll create them recursively which is great so then we can come in here we can say we're going to await doing that then the next thing that we can do is determine what our file path is going to be so we'll say file path is equal to this is going to be inside that products folder that we just created and then I'm just going to essentially create a brand new random ID so we're going to say crypto random uu ID so just going to create a random ID and then I'm going to append onto the end of that my file name just so I have a random ID to make sure there's never any conflicting files and then I'm going to put in what the file is actually called then what we can do is we can actually add this file to our actual file path so right now we've created the path we want to save it to next we can actually save that file so I can say fs. write file just like that we can pass it in the path we want to create and then we need to pass in our file now to do this is a little bit complicated all we do is just say buffer. from whoops. from and then we need to get our file so we can say data. file. array buffer and if we await this what it's going to do is it's going to convert our file into a buffer and then we can pass that buffer along to WR file essentially we're just taking our file from whatever format it's currently in and converting it to a file that our Act node.js knows how to use to write a file so this is actually written the file we can download and then we can essentially just copy this code and do the exact same thing but for our image as well now for our image we want to store this inside the public folder so it's easily accessible inside of our application so it's going to be inside a public SL products that's the folder that we want to create now for our image path here we can just say image path just like that that is going to be inside of the products folder as well and the reason we don't put public in front of this is because we don't actually need to specify the public folder the public folder is just any file that is publicly available on our site so we don't need to put public in front of that because when we use this URL to render our image it'll already assume it's inside of that public folder then what we can do is make sure that this is using the image name right here and this is using the image that we want to create and this is using the image path right here so now we're saving the image to that particular path and again I want to make sure I prefix this image path with public so it's being stored in the right place so we'll say public and then we'll put in our image path and image path already starts with a slash which is why there's no slash in between these two now for our data we have our file path and our image path just like that so there should be all the data we need to actually create our project so or our product sorry so we can just await creating that product and now our product has been created and what we can do is we can redirect the user to the admin page so we can SL SL admin SL products just like that and we want to make sure that we import this redirect from nextjs so there we go it looks like it imported the wrong one so let me make sure I remove that and actually import the correct redirect function which is from next navigate there we go that's all that we need to do so if there's some type of error it'll give us back an error which we can render on our page and if not it'll redirect us to the admin's product page so let's create a test two we'll make this one a little bit more expensive and we'll say body 2 and we'll even change the files here to be this number two file and this one will be the number two image now when I click save you can see it's creating that project and it should hopefully redirect me to the products page and as you can see it has now there's lots of stuff we can add to this to make it better such as error States and loading so let's do that now before we focus on this table so if we go over to our product form the very first thing we can do is add in the loading State that's super easy to do what we want to do is we want to take our button and we're going to move it into its own component so we'll say function submit button just like that and we're essentially just going to copy this button down exactly as it is the reason we brought this into its own component though is because we can use the form status hook just like this and this is going to give us our pending State there we go and now we can use that pending state to disable our button so we can say disabled is going to be pending so now if our form is currently in the process of submitting it's going to be disabled we can even change our text here so if we're in the pending State it'll be saying saving otherwise it'll say save just like that and now we can render that submit button up here close out of all of this right there and now if we go to the add product and we were to try to submit our form it's actually going to submit our form and show that loading spinner down here for us so what we can do specify a name specify price and sense specify a description now when I click on Save make sure that we actually specify these properties for now what I'm going to do is I'm actually going to for now we'll just pass in negative here and then we can actually create our images and stuff that we want and now when I click on Save you're going to notice it doesn't actually save anything or do anything now it is technically making a request but it's so fast we're not actually seeing it happen so I'm going to bring over our actual development tools we're going to go to our Network and what I'm going to do is I'm actually going to throttle this to be fast 3G so it'll hopefully slow this down a little bit so now when I click on save you can see is making that request but it looks like it's still not actually setting the pending status for our button which is obviously not quite what we want so we want to make sure that we fix that so let's get rid of all this throttling we also don't have any error messages showing up so we want to make sure we fix that as well so let's go ahead and fix the error messages and doing so may actually just fix our Button as well so up here we're going to need to import a brand new hook this one's called use form State just like that use form State and this is actually taking in the action that we want so this is our add product action and it takes in whatever our default value is going to be which our case is going to be an empty object and it's going to return to us an error which in our case is whatever we return from our action so here in our action the only thing we ever return is an error which is why it's returning to us an error and then it's also going to return to us an action we can use so we're going to call this action so now we just set the action here and that's going to work for our action but the one thing is you'll notice we get an error and that's because whenever you use this hook you need to make sure your action actually has two properties the previous state as well as whatever your current state is now since we're not using in this current state or the previous state I'm just going to set this to unknown CU we really don't care what that is but now you'll notice if I save this we now have this error object that we can use so for each one of our inputs if we have an error I want to display it so if our name property has an error well then what I want to do is I want to display a div the class name is going to be text destructive that'll give us a red text and inside of here I'll just put whatever error I have for my name property and we'll just close off that div there we go I'm going to copy this down do the exact same thing here but instead of name this will say price in sense same thing here price in sense and now copy this down another time and put it into here but this one is going to be for our description I'm going to be copying this down again here but this is for our file and then finally right here this is going to be for our image there we go so now we have errors being shown up so now if I click save we are getting an error that's just cuz we need to make sure we refresh our page before we actually try this so here I'm going to say test three price and sense is going to be negative so that should hopefully give us an error here's a random description doesn't really matter what we submit for these particular sections here but now when I save you can see number must be greater than or equal to one so our errors are being passed down to us and you'll notice my save right here is actually flashing to that pending State and if we were to fix this by making this an actual price and I click save you notice it says saving it was gray and then we got redirected to products now if I slowed down my page you would have noticed that saving text would have shown for longer but at least we could see that it was working now one final thing we should probably do with new products is we should probably make it so that they is available for purchase is set to false by default just so we know that anytime we create a new product it's always going to be not available for purchase so now let's go ahead and actually render out the rows inside of our table cuz now we have like two or three different products inside of our table that we can render out so inside of our page for that let's just go to that page there we go now we have our page opened up inside of our table body I want to render out every single row that we need so we can see here that we have our available for purchase name price orders as well as all the different actions associated with that so first we need to actually get our products so we'll say products is equal to a waiting and we want to search our products in our database we'll say db. products we want to find many so that's going to be finding essentially all of our different products and we'll make sure that this is a sync now I also only want to select certain Fields so inside of here I'm going to specify all the fields I want for example I want to get my ID I want to get my name I want to get the price in sense I want to get is it available for purchase I need all of that I also want to get the file path so I can download that if I want too and actually I don't believe I need the file path based on the way that we did downloads and then finally I want to get the number of orders for this so I'm going to select my orders true so I'm just going to get the count of the number of orders that I have as well and then finally I'm going to do a simple order by on the name ascending so at least we can get them in name based order so now we have all of our products and we can do whatever we want first of all if our products. length is equal to zero we should probably just say something like no products found doesn't matter what it is you can make it more beautiful than that but for now that's going to be perfectly fine obviously we have products though so if we refresh our page you'll notice this does not show up now inside of our body here though we can Loop through each one of our products so for each one of our products product there we go what I want to do is I want to render out a table row and inside this table row I'm going to have a table cell for each of the different things we need so sell just like that and the first table sell is going to be whether or not the product is available for purchase or or not also I want to make sure that I have a key on here so we'll say product. ID just like that so now inside of this table cell what I can do is I can say product. isavailable for purchase if it is available for purchase I want to show a check mark so I can come in here I can get the checkmark icon which is check Circle and that's going to be number two that's the one we're going to be using that's going to show that icon for us and then what we can do make sure we close all this off because if it is not available for purchase then we want to show The X Circle icon so we'll make sure that we import that icon just like that and again I'm going to put this inside a fragments cuz I'm going to add a screen reader only version as well so now you can see that those check marks are showing up because these two products are available for purchase that's just because we didn't make the change to our action until after we created these products so now what I can do is I can add my screen reader only section so I can say span class name is going to be screen reader only and this is just going to say available just like that I'm going to paste this down down and this is just going to say unavailable there we go so now at least the screen reader will know what these check mark icons actually mean so there's our very first cell our next cell is going to be much more simple because it's just going to be our product. name just like that so now you can see our name is showing up we'll copy this down again and this one is going to be for our price so we'll say format currency this is going to be our product. price and sense divid 100 there we go now we'll copy this down again this is because we want to figure out how many orders we have so we're going to format a number and this is just our product. count. orders we'll clear that out give that a save and you can see both of them have zero orders and then for our final table cell what we want to do is we want to essentially have that triple do icon so what we can do is we can call this more vertical that's the name of the icon that we're going to use just like that give that a quick save you can see that that gives us the icon over here and we're also going to add a screen reader only span as well so span class name screen reader only that's going to say actions just so that screen readers know what this is going to mean and this is actually going to be all inside of a drop down menu which again comes directly from Shaden so we can search for drop- down menu and I can scroll down and you can see the install for this is incredibly simple so we'll just come over here paste that down and it's going to actually install this drop down menu for us and then we can rerun our application once this is finished so give that a quick rerun there we go and now you can see that we'll have this drop down menu comp component so to use this we can come in here say drop down menu make sure we import the correct one put all of our content inside there then we're going to have our drop-down menu trigger and again make sure we import the correct one and this is essentially the button that we click so this means that this is the button to open our drop down so if we come over here that is going to be our button so if we give this a quick refresh make sure everything reloads on our page because now see when I hover over this I essentially get the ability to click on this drop down now the next step is to determine what the cont content for our drop down menu is going to be so we can have our drop down menu content just like that again make sure we import the correct one and inside of here we have individual drop down menu items so we can get for each one of our items we can render them out now our items are mostly going to be links or buttons so in our case we're going to be using a link for this first one because this first one is going to be for downloading our actual file but this is going to be a different link than you're used to because instead of using a nextjs link like this we're actually just going to use a basic anchor tag and that's because when we download something we want to send it off to essentially an API route that's going to be just downloading a specific file it's much different than routing to a particular page that's why we're using a normal anchor tag and we're even going to say that this is going to be a download anchor tag and then for the href we're just going to add in here a link that goes to SL admin products SL whatever our product ID is so we'll say product. idownload just like that so we can actually download that specific ID there we go and this will say download give that a quick save now if I open that up you can see I have that download option right there now obviously we want more items and this one should say as child just so it's actually going to show up as that particular link just like that now I'm just going to copy this drop- down menu item because it's going to be very similar for this one but in this case we're going to be using a link instead and that's because this is going to be linking to our edit page so let's making sure that we get this from react or from nextjs there we go and we're going to close that off it's going to have an hre and this hre is going to go to the SL admin SL products slash and this is going to be our product. edit there we go and this will say edit so now we have a link going to that edit page open this up you can now see we have both of those different options and then we need to have options that are going to be for deactivating SL activating as well as for deleting our particular product as well now both of these buttons are actually going to need to have client interaction since this is a server component we cannot do that inside this file so I need to create a brand new F component for that so inside of here I'll create a product actions. TSX there we go and inside of here I can export my function for example the active toggle drop-down item there we go and I'm also going to have one for the export function delete dropdown item so these are my different dropdown items for active chling as well as deleting and these are going to be relatively straightforward I'm going to return a drop- down menu item there we go and for this drop down menu item I first need to get some props I'm going to have my ID as well as my is available for purchase and this is going to be an ID which is a string and is available for purchase is a Boolean just so we can determine what the actual properties of this are so the ID of my product and if it's currently available for purchase now what I want to do is set up an on click this is the important part so in our on click we're going to come in here this is going to be a function and inside of this function all we're going to do is call start transition and start transition is going to be coming from a hook called use transition so we can say const is pending and start transition are coming from the use transition hook just like that make sure I don't import start transition up here since we don't actually need it and now we can start an asynchronous function which is going to do the toggling of our actual active state so what I can do is I can say await I want to toggle my product availability just like that my ID and I'm going to essentially do the opposite of if it is available for purchase and this is going to be an action that we're going to create so inside of here I'm going to essentially export an async function with that exact name just like that and it's going to be taking in an ID which is a string and it's going to be saying is available for purchase which is a Boolean and all we want to do is just update our product so inside here we're just going to do a quick await db. product. update we're going to do a wear my ID is my ID and then for the data we're just going to be setting is available for purchase to be whatever my is available for purchase is so it's just taking this information and updating my product accordingly and that's all we need to do we can also return the product if we really want to but it really doesn't matter this is pretty much all that we need to do to actually make this work so now inside of here I can make sure that I import this particular function I just created so now I'll give that a quick save I'll come into here and I'll say that if it is available for purchase then what I want to do is call this deactivate otherwise I want to call it activate just like that there we go and then we can use this is pending state to disable this so we'll say disabled if we are currently in the pending State now I'm going to do almost the exact same thing for my delete drop down button so I'm going to copy all this code down into here this is going to take in some properties which are going to be ID as well as disabled just like that and that's going to be an ID which is a string and we're going to have disabled which is a Boolean there we go now like I said this is going to be working very similar our disabled is going to be disabled either if this prop is passed in is true or if we are currently in the pending State and this is going to be called delete product now the reason I'm passing in my my disabled state right here is because I want to make sure that if I already have orders for that product I cannot delete it so if I have orders this disabled is going to be true because I can never delete something if it has orders so now we can create this delete product function and that's going to be inside of our actions section so let's come over here go to our products actions export async function delete product which takes in the ID of the product we want to delete and we'll say db. product delete wear ID there we go we're just going to delete that product await that and that should be all we need to do the only thing I would like to do though is first get my product just like that and if we don't have a product so if my product is equal to null then I'll return not found just so we know that hey we could not find this particular product when we tried to delete it there we go so now if I go to my actions and I import this particular function that should be my actions completely done now if I give that a save and we go back over to my page here all the way down to the very bottom we can actually add those actions in so we can say that this is going to be my ADD I'm sorry not add it is going to be my toggle active toggle drop down item and we're going to want one for delete as well and for each one of these I just need to pass along the props so this is going to be my product. ID and my isavailable for purchase is my product. isavailable for purchase and for this we're we're going to have our ID which is product. ID and the disabled prop is going to be set to my product Dot and I want to just make sure that my count for my orders is greater than zero so if I have any orders at all this will be a disabled prop now we are getting an error and that's just because I need to make sure that I specify that this is a client page so we can say use client just like that that should fix those problems now you can see we have my deactivate and my delete button showing up now one thing that I would like to do is just make this look a little bit better so I'm going to add in here a drop down menu separator there we go and this is just going to add a nice little line between these two so it's a little bit easier to distinguish between them and I'm also going to make this delete one actually have a red text instead of this normal black text so what we can do is we can go into our component UI I want to search for the drop- down menu and here I'm searching for the drop-down menu item now I want to make this drop- down menu item very similar to how the button is as you can see they're using class variants Authority for this which is a really great Library I'm actually going to copy over the code for this so you don't have to watch me type it all out but essentially the only thing that I'm doing is adding these three classes right here I'm adding a brand new variant called destructive and you can see here I'm adding just this background color destructive essentially I'm just making it so that it has a reddish color in the case that I pass on this variant of destructive then I'm going to be making sure I add a new prop for that as well so I'm going to say my variant here is going to be either default or just destructive since that's what I called it and down here where I have my classes being created all I'm going to be doing is taking this drop down menu item variance which comes from that CVA function I'm going to be passing that all into here so I'm going to get rid of all these default classes that are there paste that just like that I'm going to be passing in my variant and I'm going to be passing in my class name as well and we can remove the class name from down here and if I make sure that I get my variant from up here now you can see that I have my variant passed in so now my product uh component that I created for my actions I can come down here and I can specify destructive I'm sorry variant is equal to destructive just like that so now if I open this up you can see it has this red text and this red background which makes it a little bit more clear that this is a really bad thing I shouldn't click on unless I really want to now let's go ahead and test to make sure that these actually work what I could do is I come in here and click on deactivate and you notice it looks like nothing works but if I refresh my page you notice it actually set as deactivated now there's a few ways we could fix this but by far the easiest for us is just going to be going to that exact page and making sure that whenever we click on that toggle that we actually refresh our router so inside here we can actually get our router we can say const router equals use router make sure I get that from the correct import which is next navigation and down here I could say router. refresh so that's just going to refresh my page after my product is deleted or up here in the case where I'm starting that transition after I finished the pending State change it's going to refresh my router so now I can come in here refresh my page to make sure everything's working and now if I click activate you can see it actually toggles between those two different states to make it a little bit more clear what the state is I'm going to change this to a red color instead so on this particular page we'll scroll up until we find the actual section here and we're going to come down to the one for unavailable add a class name and this is going to be stroke destructive and we want this to be just like that so now you can see it's giving me that red color when it's deactivated and it's going to give me that perfect check mark when it's active now also let's just see if our delete works click delete you can see it's been completely removed when I refresh it's no longer there and that's exactly what we want and now if I finally delete this very last one you'll see we get that no products found message now one thing that we probably should do that we're not is deleting the old products and the images for those products that we no longer need so inside of our actions when we go down to this delete if we are able to successfully delete a product then what we want to do is we want to take the product and also unlink those files so we can say fs. unlink and we're going to pass in our product. file path make sure that await this this is going to completely delete that particular file I'm going to do the exact same thing for our image path and we just need to make sure that we add in slash or product not product public to the front of that so we can say public just like that there we go and that'll also delete our image as well so now we'll give that a quick save I'll delete the current files that we have inside of here and same thing inside of here I'll delete these files and also I'll make sure that in my git ignore that I have those folders listed so we can say product prods just like that and we're going to have public SL products that way this products folders as well as this public products folder no longer get uploaded to my git repository because they're purely for testing purposes so now let's add in a brand new product this one is going to be first product price here let's just make it $100 this is a product let's choose a file we're just going to choose this SRT file and for the image we'll choose this number two image now if we save you can see we have our first project it's currently not active let's just activate it for now you can see that it is currently active and if I delete this we should notice on the left hand side the products completely disappear so click delete and you'll notice that both the files for that product were deleted now let's go ahead and recreate that first product so we have something to work with we'll again give it a price of 100 I don't really care what the description is it doesn't matter we'll come in here we'll create those files right there now give that a save and we'll activate our product so it's actually available on the homepage so now let's go ahead and actually finish everything related to the admin portion of our product cuz we really don't have much all we have to do is worry about editing and this download button and both of these are so much easier than anything else we've done so far we're honestly on the home stretch for all the admin related stuff so let's come over here we're going to go to our products we're going to create a brand new folder called ID this is going to be a dynamic ID page and then inside of here we're going to create a brand new file as well this is going to be inside a download slash and this one's going to be route. TS and that's because we're going to be using an API route for our downloading and I'm also going to create a new folder called edit where I'm going to put a page for editing page. TSX inside of here now my edit page pretty much looks identical to my new page so I'm just going to copy this over this is going to say edit product instead this will be my edit product page and make sure that I get my imports correct there we go and same thing up here get my imports in there correctly now my edit product page is actually going to also take in the product information and that's because we have this Dynamic parameter so I'm going to take in my prams which is an ID just like that and I can also type this prams which is an ID which is a string just like that so now that I have my ID I can actually get my product con product equals a weight db. db. product. find unique and I want to get where my ID is my ID there we go make this an async function and now we're actually getting our product and we can pass that down to our form just like that so now if we have a product we can pass that down to our form and we can use that inside here so we'll say that our product is going to be a type here of product which is going to be optional and that's going to be of the type product which comes directly from Prisma so at least we know that this is of that product type now if we make sure that we give everything a quick save over here you notice we're technically getting an error that's because product is either going to be product or null and right now we're not accounting for null inside of this so we just want to make sure that this could also be null there we go that should get rid of all of our errors if we come over here you can see our error has been removed and now we have access to our product so we can use that for all of our default values so here this can be our product Dot and we want to get that price in sense so we're going to default that value to our price in sense and here we're going to make sure that this can also be undefined as one of the particular values now for our inputs we just want to make sure that we have a default value so here the default value is going to be product do name or an empty string just like that and essentially I'm just going to copy this down to everywhere in our application so down here we don't have to worry about it on this one but for our text area I'm going to essentially do the exact same thing default value is going to be product Dot and this one is going to be for our description there we go copy this and that's actually the last place I need to use that because down here we can't actually have a default value for our input for files we can't have a default file just because you don't have access to the file system in the browser what we can do though is change the required property this should only be required if our product is null because when we edit something we do not want to force them to actually pass in a image or a file because it'll just use the image or file that already exist so now if we go to that edit page we should hopefully see our form and you can see it's been pre-populated with all of our information so what I want to do is I want to add essentially just a little bit of information if we have a product for our file and our image I just want to show you what that name is so here if our product is not equal to null well then all I can do is just render out a simple div which has a class name of text muted foreground and I just want to render out my product. file path just like that and we'll close off that div give that a save and now you can see down here if I just make sure my camera is not in the way that you can see that we have that product file path showing up right there I essentially want to do the exact same thing with an image as well so it we'll come down here to where our image is if our product is not equal to null then what I want to do is I want to render out an image tag that has that particular I image so we can get that image tag from nextjs our source is going to be product whoops product. image path just like that and then we can just specify like the height for this is going to be 400 or something like that so let's just close that off and we'll say withd is 400 it really doesn't matter what the actual sizing is going to be and then we'll say our alt is going to be product image just like that so now if I give that a refresh you can see this image is showing up down here and again if I move my camera it just shows us a placeholder for what that image would look like that's a really great way for us to see what we currently have uploaded now the last thing to do is actually make it so that this works when we upload something so we need to create an action for that so we'll go over to our products we can minimize these actions because we don't really need to worry about them right now all we focused on is this actual action for ad so I'm going to copy this ad action and instead we're going to call this update now this update product is going to be almost the same but we're going to have an ID that we pass in here which is going to be a string there we go just so we know what we're actually currently up uploading or updating sorry then we're also going to have a schema for this so we'll say const edit schema and this edit schema is just going to be the same as my ad schema but I want to extend it by changing my file to be just my default file schema and I want to make it optional and I want to do the exact same thing for my image so we'll say that my image is the image schema and it is going to be optional as well now inside of our code we can use our edit schema to parse the information that we're getting and again if we have a false case here then of course we're just going to return our errors then if we get our data I also want to get our product so we'll say const product is equal to db. product do and I want to find unique based on the ID that we have so that'll be where our ID is equal to our ID and we can just await that like that and then if our product is equal to null we'll just return not found since we were unable to find this particular product then what we can do is we can update our product based on all of our information we only want to update the file path and the image path if they actually changed so what I can do here is I can say if product. I'm sorry data. file path or data. file if we have a particular file so if that's not equal to null and if our data. file. size here is greater than zero that means that we uploaded a file so what I want to do is delete the old file and create a brand new file so what I can do here is I can just say that I want to First unlink the original file so wait f fs. product I'm sorry unlink product. file path just like that remove the existing file then create a path to a new file and save that file and I want to store this in a variable so we'll say let file path equal and by default it's going to be equal to our product file path otherwise if we create a brand new file I want to overwrite that file path so essentially what this code is doing is if we pass up a file delete the old file save the new file and save the file path to the new file otherwise just keep our old file path now I'm going to do essentially the exact same thing for our image as well so this will be image path and this is going to be my product image path and if our image size and image are not equal to null then we're going to come in here and we're going to unlink our image and so on so to unlink our image we need to get it from the public folder and then we can say product image path just like that that'll delete essentially our image and then we can say that our image path is going to be equal to this brand new path I'll just copy up this code because this should be exactly the same same so I'll paste that in just like that and I'll delete this down here so now what essentially is happening if I make sure I fix that is we're defaulting to our default image path that's currently there but if we pass up a new image we delete the old one and create a brand new one then all I need to do is change this to update pass along a wear clause for our ID just like that and then finally we're redirecting to that particular page so now this should work if I just add a two to the end of this let's make our price 2 cents more expensive this will say description and let's just say that we only change our file here to be a file one but we're not going to change this this will still be our O2 file so now if we give that a quick save of course this is saying it is required that's because I think I made those changes after so I need to refresh my page let's add a two to the end of all of these this will say description and then finally I'll change my image to the number one image click save and of course it's still saying required so we have a problem this I believe is coming from our schema so let's make sure that our schema is being used correctly so we have our edit schema right here our edit schema is coming from our ad schema and you can see it's overwriting our file and our image section that is all correct but I just realized I think I know what the problem is in our form we're not actually using that update function we're always using the add function so here if our product is equal to null then use the add function otherwise use the update function but this update product function takes an ID so we need to bind that ID so by saying bind passing in null and then passing in our product ID we're essentially changing the first property of this our ID and passing it in automatically so that's why this is able to work now if we give that a save everything should work if I click save we should no longer get this required error click save and you can see that it worked and it changed this to first product 2 1,02 cents and if I go to edit you'll notice the image down here has a one while our actual text here still says two so it has been properly changed just like we expected now we'll just go back to all of our products and let's make this available by activating it also one thing I want to make sure I do is inside of here my is available for purchase should not not be changed when I'm editing my product that's why it changed it to essentially deactivated now let's minimize all these down and you can see everything inside here is working just fine now the final thing we need to get to work is this download button right here and this is actually not as complicated as you may think if we go to this download route what we can do is we can export an async function called git this is going to be a git route and this is going to take in a request which is a next request just like that and what we want to do inside of here is essentially just get all of our information we're also going to have some parameters so we can say prams ID just like that and give this a type prams which is an ID which is a string there we go so now inside of here I can get my data which is just going to be my product so we'll say db. product. find unique and I want to get that based on the ID so where ID and I just want to select essentially my file path there we go file path true name true perfect we'll just call this product so it's a little bit more self-explanatory what this is then what I want to do is if my data or I'm sorry my product is equal to null then return not found because we were unable to find that product but if we were able to find the product then I want to get all the information about the file I need to get the size of the file which comes from awaiting FS dostat FS which I probably should just import up here from f fs/ promises there we go so we can get fs. stat this is going to give me the stats on my file there we go then what I want to do make sure I call this product then what I want to do below this is to actually get the information from this so I want to read my file so I can say read file just like that that's going to give me my file and then I want to get what the extension is for this file which is just going to be coming from my product. file path dosit on the period And I want to just get the very last element by calling pop this is just going to give me the file extension at the very end so if it's MP4 it'll give me MP4 for example now I can use this information to return a download link essentially so I can say return new next response and this response is going to take whatever my file is and then I just have some headers that explain what this file is so the first header is going to be the content disposition and this is essentially determining what the file name and so on is going to be this is going to be specified as attachment semicolon and then we want to say file name is equal to and inside of quotes we're going to put our productname and then we want to add the extension onto that so we'll say do extension just like that so that's going to be what the default name for this file is going to be then I can pass down the content length which tells the browser how long this file is so it can give you things like download estimates so we'll just say size. two string to convert that to a string now that should be all that we need to do to get this to work so what I come over here is I can click on this download link and it's going to redirect me out the page and you can see immediately it's trying to download that file it gave it the name of first product 2. SRT so it got the actual file extension and everything correct and I could click save and it would download that file for me now that takes care of everything on this products page the next thing that I want to work on is actually making this admin page secure because right now everybody can access it which is terrible so what we're going to do is inside of this Source folder I'm going to create a brand new file called middleware dots and this is how we create middleware that runs before every single function or every single page call so what we can do is we can export an async function call it middleware just like that this is going to take in a request which is a next request object and inside of here all that I want to do is I want to check if I'm on an admin page to specifically make sure that I'm logged in so first I can export down here a config so we'll say const config and this is going to be equal to the URL route matcher so I can say matcher just like this and this matcher is going to be any admin route so admin SL path Star by adding this colon path star this is saying get any individual page that is at my admin route or Beyond so it doesn't matter if it's SL adminadmin products if it has SL admin at the beginning it's going to be falling under this middleware so this is going to run anytime we try to access an admin page so what I can do inside of here is I can first check to see if we are authenticated by creating a function so we'll say function this is going to be an async function this is going to be is authenticated just like that it's going to take in our next request perfect and this is going to determine if we are authenticated or not so if we are not authenticated so if a weight is authenticated is equal to false well then we know that we are not authenticated so we have a problem so we're going to return essentially a new response we'll make this a next response there we go and this next response is going to have text of UN authorized and then we can specify the status is going to be 401 saying that we're not authorized and also we can specify that we want to use a specific type of authorization so in the headers what we can do is we can pass along the header www Das authenticate and we pass in the value of basic and this is going to use basic authentication and this is actually something that's built into the browser so we don't even need to write our own authentication at all which is really really cool so now in our is authenticated function let's just return false for now to see exactly what is happening and of course we want to make sure that this is a promise so we'll say promise. resolve just because it's an async function there we go and we'll make sure that we come in here and we pass in our request and now if we give that a save you'll notice immediately I get this signin box popping up that's going to ask me for some type of username and some type of password and if I try to sign in you know I'm always getting a failure here because this is just returning false so what I want to do is I want to get that username and password and compare it to whatever the username and password is is I care about now this type of authentication is not great for customer facing stuff but this is our admin page I don't care how ugly it is it's super convenient it works really well and requires almost no code to get set up which is ideal so here let's actually go through the code to determine how we are authenticated or not so first we need to get the header for this so we can say const off header is equal to this is going to be from our request. headers and it's going to be the authorization header just like that and we also want to make sure we check the authorization header with a C capital A as well it may come back one way or the other so just checking for both is really nice then if our off header is equal to null well that means that we didn't actually pass along anything so we should return false and tell the browser hey make sure that you try to authenticate the user if we do have something though I want to check to see if the username and password are correct so we're going to get the username and password from this we're going to take our offhe header and we're going to split this on a space just like that then what I want to do is I want to convert this to a buffer so we'll say buffer. front just like that and the reason for this is because this is actually encoded it's encrypted and we need to decrypt these values so by saying that we convert it to a buffer we're going to change it from base 64 so it's not actually like securely encrypted or anything like that we're just essentially saying hey convert this down to a buffer based on base 64 and what I'm going to do is I'm going to get essentially the second value in that string now the reason we're getting the second value from this is because the offhe header looks something like this it'll say basic and then it'll have whatever your base 64 encoded string is afterwards so we're essentially just getting this second portion by using this property right here now the next thing I want to do is now that I have a buffer that I know is base 64 encoded I want to convert it to a normal string so this is essentially going to decode this value for me and I'm going to split it on a colon and that's because what's going to happen is the decoded value will look something like this it'll be username password just like that so I essentially want to use a colon to split these apart so now I have my username and my password and if I just do a quick console log of username password password just like this and I look at my console and I come in here and I type in admin and I type in password and I click sign in you'll notice I get admin and password being printed out so now I can use that information to determine if this is true or false right now it logged me in because essentially I was returning true from here even though technically I didn't actually have anything being correct yet so instead what I want to do is I want to return a check and essentially that check is going to take my username and check to see if it's equal to my process. env. admin username so what I can do inside of my EnV variable I can create an admin username we can call it whatever we want I'm just going to call it admin obviously make it more secure than that but for our cases is was just for testing it's going to be perfectly fine so now if our username is correct this will return true but we also want to make sure that our password is correct as well and I'm going to create a function called is valid password just like that this is actually going to be an asynchronous function so we're going to make sure that we await calling this is valid password and what this is going to do is it's going to take in my password as well as my process. env. hashed admin password there we go and I want to convert and I want to make sure I say this is a string there we go now the reason I'm forcing this to be a string is because by default this could be string or undefined so I'm just forcing it to be a string it's not really super important so now let's create this is valid password I'm going to create this in my lib folder here so we'll say is valid password. TS export function is valid password and this takes in a password as well as a hashed password and these are both strings so let's just type these out just like that now the reason that this was an asynchronous function we can make this async just like that is because what it's going to do is it's going to return Calling a function called has password which we're going to create in just a second this is going to take in our password and it's going to check to see if it's equal to our hashed password just like that and we're going to await that function so down here function hash password make sure I spell it properly up here there we go takes in a password which is a string and all I want to do is take this and I want to encrypt it in a way that is impossible to decrypt which is what hashing is so what we can do is we can say we want to create an array buffer and this array buffer if I can spell that properly is just a wait and we're going to say crypto dot subtle. digest digest is essentially keyword for hash we're going to use the sha512 hashing algorithm which is essentially just a way of changing our password into something that's difficult to discern and impossible to de Crypt and then we're going to pass in here essentially a new text encoder and we're going to encode our text so we're going to say encode our password now I know this is kind of complicated what this is doing but just copy this code word for word and it should work fine essentially all I'm doing is I'm taking my password and I'm encrypting it into something that is very difficult and almost impossible to decrypt that way people cannot decrypt what our password is then I'm going to take our my buffer I'm going to say buffer from my array buffer and I'm going to essentially convert it to a base 64 string because this is going to be a very very long string so by encoding it to base 64 it just shrinks it down a little bit because this is a very long string so now with that out of the way I can actually show you what this does inside of our middleware so for now I'm just going to comment out this return statement I'm just going to say is valid password just like that I'm going to pass it in my password and we'll just pass it in some junk it doesn't really matter all I want to do is I want to actually show you what's being executed from here so here I just console log all of our information so I can say console.log # password with my password just like that so I can show you what this hashed version of the password looks like so now if I try to refresh this page you'll notice it's actually considering me currently logged in so what I can do in my middleware is just return false there we go so we can actually pass in what our information is and in here I'm going to pass in the password of password and I'm going to click sign in and you can see it's printing out the hashed version of that password so I'm actually just going to copy that and bring that into my environment variables and this is our admin hashed password I'm going to put that in here as the value so now this is our hashed password technically you don't have to go through all these steps of hash in it you can just type in password just like this and it'll work fine the reason I'm hashing this is because in case I accidentally Commit This EnV variable for example I've already forgotten to add it to my get ignore so it would technically have been committed if I forgot to do that it at least doesn't expose my admin password I would still want to change it but at least it gives me a little bit of extra security so now with all that out of the way in my middleware I can come in here I can bring this check back in make sure I remove this and this up here and now hopefully our code should be working I just want to make sure I fix any parentheses problems I have give that a save and it looks like it works so now if I just cancel this you'll notice it no longer is rendering this page for me and if I type in admin and I type in the wrong password click sign in it should hopefully not work but if I type in admin and password and click sign in and make sure I spell that properly and sign in you notice it's still not quite working I think it's because I need to do a Refresh on my page to actually make sure things work and in my in is valid password make sure all that is working fine so now let's try that again admin password and click sign in and it's still not working the reason it's not working is because I called this hashed admin password instead of admin hashed password so now admin password sign in and it looks like it didn't work again give it a refresh just to make sure admin password sign in and what I may need to do is just stop my server and restart it so now admin password sign in and hopefully that should work this time and as you can see it did actually work that time so I just had to restart my server because my environment variables changed so now you can see that I'm logged in and the really great thing about using this basic authentication is it'll work until you essentially close your browser and reopen it so you can see I don't have to reauthenticate on every page it just stays authenticated until I close my browser and reopen it then I'd have to type those cred back in now that takes care of most of our admin Pages we'll come back and do the customers and sales in a bit while we have data to actually work with them but for now I want to work on what our actual main customer facing pages are going to look like so to get started on those what I'm going to do is I'm actually going to create a separate folder in our app and this is going to be a route group called customer or actually customer facing there we go so we know that these are all the pages facing the customer and our admin folders for all of our admin Pages Now by doing a route group with these parentheses it won't change our URL but allows us to create like a custom layout for all these pages so I create a layout. TSX inside of here and this is actually going to be pretty similar to our admin one so I'm just going to copy our admin route I'm going to paste it into here because it's going to be very similar except for obviously our links are going to be different so we're going to have a home link just like that we're then going to have a link for our products and then we're going to have a link for the user to download their order history so we'll say my orders products and just like that now all we need to do to actually see this in Act action is to create a page. TSX there we go and in our layout I'll make sure that I call this something like layout instead of admin layout and now inside of here export a default function this is going to be called homepage and we can just return an H1 that says hi and as you can see we now have home products and my orders as our nav links while if we're on the admin Pages you can see we get a different nav bar at the top that's why I have essentially two different layouts and two different folders now for our homepage essentially I want to have two separate sections I want to have a section with the most popular products and a section with the newest products so I'm just going to create functions for getting both of those we'll say get newest products make sure I spell that all correctly there we go this is going to return DB dot make sure I import DB we want to get our products and we essentially want to get all of them so we'll say find many I want to specifically sort them so I'm going to say order by and in this case I want to order it by how many orders we have actually gotten so I can go to my orders and I can get the count so I want to essentially order by how many I've gotten and here I want to order in a descending order and also I want to only get the products that are available for purchase so where products or I'm sorry where available for purchase is true and make sure I add in my comma right there so now I'm getting only the available to purchase products and I'm ordering by the ones that have the most amount of orders and I want to just get six of them so I'm just going to show the first six on the page now I'm going to do essentially the exact same thing for most popular so this was actually get most popular popular most popular and now for get newest instead of ordering by the amount of orders we've gotten we're just going to order by the created at date and in this case we're going to do the descending order now for my actual homepage I'm essentially just going to have two different sections and they're going to be two different grids so here I'm going to have a div and actually I'm just going to make this my main content because that is technically what it is and I'm going to add in a class name here space Y2 to give them a ton of space in between them and then I'm going to have a product grid section just like that and I'm essentially going to have two of these product grid sections one for my newest and one for my most popular so let's create this product grid section just like that and I'm going to make sure that I actually do some suspense loading boundaries inside of here so instead of passing down my products by awaiting this I'm going to pass down the function to get them so I'm going to say products fetcher is equal to and we'll say this is the git most popular and I can copy this down to here and this is going to be the exact same thing but it's going to be get newest products instead so this is going to take in a products fetcher which is going to be if I do my type for my product grid section props that product fetcher is going to be a function that returns to me a promise and that promise is going to be essentially a product array just like that and this product comes directly from Prisma I'm also going to be passing in a title here so now I can do my product grid section props and the title is just going to be a string that we put so title string there we go so now we can actually write out what the code for this will look like so let's create a div here for some spacing we'll say space Y is four just to space out our title from our grid and then we'll come in here with another div this one is going to be a flex based grid with a gap of four and this is where our title is going to go as well as a button to view all of our products so let's put an H2 that has our title just like that and we'll add some classes so it looks relatively good text it's going to be 3XL and we'll make it some bold font there we go so now if we pass in a title to one of these for example newest actually this is most popular there we go and this one is going to have a title that says newest there we go give that a quick save and then down here I just want to make sure I return this div so it'll actually work and now you can see it says most popular and newest now we need to add in our button and this button just like this is going to be a as child button and that's just because we're going to be rendering a link inside of it so we're going to render out a link that says view all just like that and this is going to be an hre that goes to our products page and let's make sure that we import this link from next just like that now for our button I'm going to add some classes to it this one is or not classes sorry this is going to be a variant and it's going to be the outline variant so we're going to have an outline button and then I'm also going to change a little bit how our link actually works because our link is going to be a class name of space X2 and that's because instead of just having our text like this when I put this inside of a span I'm also going to have a little arrow icon just to kind of emphasize that it's going to the right so Arrow right just like that and we'll say class name size 4 there we go so now we just have that Arrow off to the right looks relatively good and that's the entire section here for our like title then we can move on to our grid so div class name and this grid is essentially going to work just like the grid that we used on our admin page I'm just going to copy that grid cuz like I said it's the same grid same exact code so I'm going to come all the way up to here where we have our grid which is actually on this page here so I'm going to open this up you can see all my code for my grid I'm just going to copy that because again it's the exact same grid style layout and it's nice that we can just kind of repeat this across the page so there's our grid close that off and now inside of here I essentially want to have a product card there we go for each one of my different products just like that now this product card is a component where probably going to use multiple places I know for a fact we'll use it on the home and products page and you could see yourself using it in many places in an application so we'll put it inside our components folder product card. TSX export function product card just like that and inside this product card we'll just come in here we'll return a card make sure we get that from the correct location close that off we're going to need to have our card header just like that inside of here we have our card title which is going to contain our actual name of our product so we'll make sure that we get our name as one of our props we're then going to have the description card description just like that and this is actually going to be the price so we'll get our price in cense and we'll make sure here we format our currency of our price in sense divided by 100 there we go next we're going to render out our card content just like that and for this I'm actually going to put a flex grow class on here so I'll say Flex grow just like that and that's because I want this to actually grow to fill the full size of the container and to make that work we'll make our class for this actually be Flex we'll change the Overflow to Hidden as well and we'll make sure it Flex in the column Direction now the reason we're adding overflow hidden is cuz I'm going to add an image and I want to make sure the rounded corners of our card cut off our image now inside of here let's add a P tag and this P tag is just going to have our description just like that and importantly I want to make sure that I line clamp this to be just four lines long so it doesn't become too long so up here what just add that description there we go now the final thing I want to do in my card footer is I want to add in a button just like that make sure I import this and this button is going to be a purchase button and that's just going to be a link to a new page so it just say purchase make sure I import my link just like that and the hre for this is essentially going to be slash products slash and this is going to be my ID of my product SL purchase just like that and our button should be as child the size of this is going to be large cuz I want it to be the most important thing on the page and I'm also going to make it fill the entire WID by adding a w full class to it now if we make sure we get our ID in here that is everything we need except for the image so let's go ahead and add our image we're actually going to put it inside of a div so that I can actually fill the full size we need and then we'll put our image inside of there so get our next image just like that make sure our source is going to be our image path which again comes from up here image path there we go and then we need toess a our width and height or we can just say fill to fill the entire container and the alt on this is going to be whatever our name is so to make it fill our container we need to give this a position of relative so we're going to say relative on here we're going to say it's going to be a full width and the height on this is going to be Auto and we're going to make the aspect ratio video so it's going to be a 16x9 aspect ratio essentially so now we can create our type for our props we'll say product card props come up here type product produ card props and our ID is a string name is a string price in sense is a number and our description is a string and then finally image path is a string as well there we go so that cleans up everything inside here you can see there are no errors at all which is great so now we can actually render out this product card we just need to make sure we pass it in a product so we're going to Loop through all of our products so we're going to have our product fetcher for now I'm just going to do this in line here we'll move it around in a little bit but we can just await fetching all of our different products and we can add in a simple then here make sure that this is a sync and this should not be a then this should be a map there we go so for each product what I want to do is render out that product card pass it a key which is my product. ID and then pass it my product there we go you can see there are no errors and now if we look over here you can see our products are showing up if I just make this a little bit wider you can see we only have one product currently showing up and it's just because we have one product if we had more than one product you would see multiple products showing up so if we go to our admin page and we go to the product section we can just add a brand new product we'll call it second this one is going to have a price of $200 random description doesn't matter and we'll just choose let's say this is going to be number one and number one again it doesn't really matter we can give that a quick save we can activate this product and now if we go back to our normal homepage you can see now now we have both those products they have the exact same image so let's go ahead and actually change the image so we'll edit this one right here to have a different image we'll use image number two for this one there we go give that a quick save and now if we go back to our main page you can see we have both of our products one with the image one one with the image two and they're currently being sorted in order I can show you by moving my camera out of the way that you can see that they are being sorted in order where the newest one is on the farthest left oldest one's on the farthest right and since they both have zero orders you can see they just have a random ordering up here for most popular let me bring my image back here now this works great but what I want to do is I want to add a skeleton loading animation to this page so what I'm going to do is go to my product card here and I'm going to add a second export here this one's going to be exporting a function called Product card skeleton there we go and this product card skeleton is essentially going to look just like this card here but it's going to be a skeletonized version I'm actually going to copy the code for this so you don't have to watch me type it all out and I'll explain what it looks like so as you can see here we have the exact same structure for our card the main difference is in all the places where we had a text or an image we replaced it with a div so here we have a div that is an aspect video withth full and a background color of 300 gray same thing here we just have a random div which is a height of six and a width of 3/4 here we have a width of half and a height of four so we have very different heights of images and text and so on so it's essentially emulating what our card will look like and we added this animate pulse class to make the entire thing pulse in and out so now if we use this product card skeleton you'll see exactly what that looks like I'll just replace our product card with the Skeleton version give it a save and make sure that I import this and you'll actually see what I'm talking about you can see that it has this kind of look for when we're loading and it looks really good it's like a skeleton loading animation of what our product would look like and the way that this works is just adding a bunch of rectangles that are gray and make them animate in and out that's how all of this styling works so what we want to do is render our cards when everything is loaded and when it's not loaded we render that skeleton fallback so to do that the best way is going to be with suspense so if you want to render something with suspense spense like that you need to make sure that the component you're rendering is the async component is the code that's doing the awaiting so essentially here I'm going to put a suspense boundary and inside of here I would normally put this code right here but the problem is is that I'm using my await inside this async function so the suspense isn't going to work properly so instead I need to create a brand new function we'll make this an async function and we'll just call this product suspense just like that there we go and what it's going to do is just take this code right here and it's going to return it there go return that exact code right there and it's going to take in that product fetcher just like that and the type of that product fetcher is just right here so I'll just copy that type there we go so it's taking in that product fetcher and it's calling it just like that and actually I called it products fetcher there we go so let just returning to us all of our product cards and the reason we need to do this is because we need our async weight to be in a separate component so now I can put that code inside this suspense and the suspense will work properly where it actually shows me my fallback so I called this product suspense and I need to pass in my product fetcher just like that and now inside my suspense I can have a fallback and this fallback is going to be very straightforward I'm essentially just going to render that product skeleton so product card skeleton just like that and I'm just going to render three of them let's say so now when I'm in the loading State it's going to show three of these skeleton loaders otherwise it's going to show my cards down here now to make sure this all works properly I should just be able to remove the async from this function right here make sure that this is my only asynchronous function and my suspense boundary should work just fine where when I refresh my page it should show me the actual skeleton loading version now this is a bit difficult to see so let's just create a really simple weight function to actually slow this down a little bit we'll say function weight going to say it's a duration which is a number and we'll return a new promise resolve which calls set timeout with that resolve and the duration there we go super straightforward there we go so now this is a weight function and I'm going to make the wait for a different amount of times so I'll say this one's going to wait for 2 seconds and the one above it is going to wait for 1 second so this is just a really easy way to test what happens if we slow down the browser make these both asynchronous there we go and now if I give this a quick refresh you can see that we're getting that skeleton loading and the first one actually Pops in first at the top here because it's a 1sec delay while the one at the bottom is a 2C delay so again you can see that skeleton loading which looks really good obviously I don't want to have any artificial delays in here so I'm just going to remove that remove this weight function that's just so we could see how this works and that's all we need to do for this products homepage and the really great thing is our main products page is actually going to be very simple because it's going to be essentially this product grid right here so what I'm going to do is I'm just going to take this product grid I'm just going to take the grid section I'm going to copy it and I'm going to create a brand new page for this so inside of here a new folder called products and inside of that I'm going to have page. TSX then we can export a function called products page and inside of here I essentially just want to return this code right here so we're going to get our suspense boundary we're going to make sure we get this skeleton as well as our normal product card so this here should be our product card just like that and actually we can leave this how it was before so we can actually go back to where we have the product suspense we'll call this products suspense I'll copy this down this is going to be a function called products suspense and inside of here I'm going to essentially Loop through all my products so I can say const products equals and I want to get all my products so we'll call get get products just like that let's create that function there we go get products this is going to return db. products. find many and we want to get only the ones that are available for purchase so where is available for purchase is true there we go so now we can come in here await that make sure that that is asynchronous so now we have our products so now our product suspense is going to use those products make sure I spelled all this properly there we go and now here I'm just going to Loop through my products so products. map make sure that's on my return statement there we go for each product I want to return my product card there we go the key is product. ID and then we just just spread out our product there we go make sure I get all my typos fixed up here that should be all we need to do this should say product. ID there we go now you can see that that's working there's no errors here let's make it so there's like six skeletons that show up just because this is a larger page this the only thing on the page now if we refresh we need to make sure of course that this is a default export and now you can see we have our products showing up and they're just going to be ordered however we want so in our case let's order them by name so we'll say here here order by name and let's do this in ascending order there we go so now we have all of our products show up and they're going to be sorted by name so this takes care of our products page and our homepage and the my orders page is going to be a really simple page which is just an email field where you specify your email and will'll email you your products and download links now before we move on to being all to purchase and email order links and so on the thing that I want to work on next is dealing with caching inside of nextjs cuz right now everything is super heavily cached by nextjs and I want to make sure that we're using that caching while still making sure whenever things change on the admin panel for example we're updating all of our different pages so to deal with caching and xjs I'm actually going to create a helper function that will do all the caching for us so we can say this is going to be called cache. TS this is going to export a function called cach just like that and this cache function is going to essentially take in three different parameters the first is going to be our call back the second is going to be something called our key parts and then finally we we have our options that we're going to be passing into this now this cache function is essentially emulating the next cache function that's built in so if we come in here we can export or sorry import the cache function which is called unstable cache and that's coming directly from next cache and we'll call this next cach and there's a second cach we need to worry about which is from react so we can import cach directly from react just like that and I'm going to rename this as the react cache so these are two different levels of caching that we need to worry about one is for request memorization and the other one is for dealing with the data cache and everything else built into nextjs now if you want to learn more about these caching mechanisms I have a full blog article covering them in super depth so I'll link that in the description for you but in the purposes of this video all we need to do is make sure we wrap whatever our call back is inside of both of these different caches and if we look at the next cache you'll notice that this takes in a callback Key Parts and options essentially the exact same things right here so I'm actually just copying these types directly from this next cache function so what we can do is first create a type called call back again this is directly from that next cache and this is just going to take in any number of arguments and we don't care what the parameters on those are just like that and this is always going to return to us a promise with the type of any there we go again we don't care about what any of these parameters are we just want a function that takes something and returns something and then here we're going to say t is going to extend that call back so we're going to have some type of generic T type that is extending that call back and that is the type of this call back right here our key Parts here is just an array of strings and then our options is a little bit more complicated here this is going to have two options revalidate this is going to be a number and it's going to be optional this is essentially how many seconds before we actually revalidate it could also be false and then we're also going to come in here with tags and these tags are just essentially a string array and this again is going to be optional and by default this is going to equal an empty object there we go so now we have all of this set up what we can do is we can call next cache and we can pass it inside of here our react cache and inside of that we can pass it in our callback our key parts and our options so essentially all we're doing is we're making sure we first cach this using react and then we're caching it using next and passing in all these different parameters the reason I'm creating a helper function for this is so I don't have to always import both caches into my function and wrap both of them that makes it a little bit more confusing and difficult to work with so now let's go ahead and actually look at what this looks like in implementation I'll go essentially to any of our product Pages we'll just minimize pretty much everything we'll go to our very first homepage and our homepage has two separate functions get most popular and get newest we want to Cache both of these so the easiest way to use this cache is to convert these from normal functions to a constant variable and set that equal to calling our custom cache function and passing in this function as an arrow function so that's the first step the next step is going to be passing it in essentially our array which is going to be our array for our key Parts this is an array that must be unique because anytime you have the same array for two different functions that you're caching it's going to put them in the exact same place so this is like a unique identifier the way I like to really easily uniquely identify this is based on the route to this particular page so this is on our homepage so we'll just use slash as the route and then we'll use the actual name of the function now obviously if you wanted to have two database calls that are exactly the same on different pages this wouldn't work super well but in our case every single database call on our pages is unique and if I ever had a shared database call I would put it inside my database folder here for ease of use so now you can see that has cleaned this up perfectly and for this G most popular products I only really want to revalidate this every single 24 hours or so because I don't really care about getting this up to date every second I just care you know what every day tell me what the most popular products are for the entirety of that day it can stay exactly the same so here I can change revalidate here to just be 60 * 60 * 24 and that's essentially going to be one day in seconds so every single day this is going to invalidate the cash and get a brand new value for it now I can do the exact same thing down here for my newest products that's going to be equal to calling cache passing it in this Arrow function making sure I spell const properly and then down here we of course need our key parts and we're going to do the exact same thing where I just use the name of the function that's going to work perfectly fine for this use case so now both of these are cached which is really great so it's going to be a little bit quicker to load we can do the exact same thing on our products page as well so here we can change this to a constant variable that's going to be equal to call in Cache make sure we get our cache turn this into an arrow function just like that and our key Parts this is going to be SL products and then we'll just use the name here get products there we go so now this is going to be cached as well the important thing about this caching though is that we need to make sure we invalidate the cache whenever we actually change what the underlying data is so in our admin actions for example if we were to add a new product for example here or we edit or we delete or we update a product we need to make sure that we revalidate these caches so what we need to do is just wherever we go at the very bottom whenever we're doing our redirect that's the perfect time to revalidate a path so in our case we're going to revalidate our homepage as well as our products page because those both deal with our products so whenever we add a new product we're essentially removing that cach and forcing it to get the data again same thing here with update whenever we update a product we're saying you know what invalidate that cach get all the data again same exact thing here for toggling if the product's available make sure we also do the revalidation and if we delete a product project obviously we need to revalidate as well so now with that done that takes care of all of our caching needs so now this page will always be cached it should load incredibly quickly but if we run into an instance where for example we add a new product delete a product and so on it'll make sure to revalidate these pages so now that we have all that done the next thing to work on is going to be making this purchase button actually link to the correct page and actually set up our purchase so as you can see it's at product/ id/ purchase so we can at least create the page for that so inside of our app in that products section we'll create a folder for ID this is a dynamic route and then inside of here we'll create that products or sorry purchase SL page. TSX there we go export default function purchase page and for now we can just return the text of High inside of an H1 just to see if this is working so if we save this should actually refresh now we can see we're on that correct page now obviously to get set up with this page we need to actually get the parameter for our ID so that comes from our pams ID and we'll give that a quick type here pams is an ID which is a string now that we have that we can actually get our product from here so we can say our product is going to be equal to awaiting and let's make this an async function so we can actually use these server functions inside of here we'll say db. product and we want to find a unique one based on our ID so where our ID is that ID there we go so now we have our product and if for some reason we don't have a product we can't find one then we're just going to return not found because obviously we can't purchase something if there is no product purchase there we go now the next step after we get all that done is going to be actually implementing stripe now unfortunately the stripe documentation is not updated with the newest NEX 13 app router as you can see they're still using the Pages directory for everything but I've gone through converted this all over to the server directory and the app router way of doing things so that it's going to be the most upto-date and modern way of doing things now if we scroll down you can see we essentially have two parts when it comes to stripe we have the server portion of stripe and then later on we're going to be dealing with the client portion of stripe the server is where you set up all your payments and make sure everything's authenticated properly and the client is just where you show the actual card information for people to enter their credit card email and so on so we're going to go through the server setup first and really all we need to do is install the stripe and @ stripe stripe.js libraries so let's go ahead and install both of those libraries into our application we can just come in here close that out and we can make sure we install both of these there we go these are going to be used purely on the server for us to do all of our server related interactions and if we look over on the page essentially the main thing that we need to do is to create a payment intent essentially we're saying hey I intend to purchase something for a specific price as you can see it has all the code over here for doing that now I'm not going to go through this documentation step by step just because like I said it's not updated for the newest version of nextjs but I will show you the code that actually works with the newest version of nextjs which is a little bit cleaner than their documentation now the first thing we need to do is actually get stripe so we're going to import Stripe from and that's coming from the stripe Library so we can just type in stripe like that and then we need to create a brand new stripe object so we can say stripe is equal to creating a new stripe object and here is we need to pass in our secret stripe API key that's why this must occur on the server and not on the client so we can say process. env. stripe secret key now in order to get what our secret key and our public key are going to be if we go to stripe and we click on this dashboard link right here this will bring us to our dashboard where we can log in and everything and then once I log in you can see that we'll be brought to the actual test dashboard just like this and here I don't actually want to get anything set up right now because this is for like activating your account for live purchases and this is purely for testing but what I can do is click on this API keys for developer link and this is going to bring me directly to where my public key and my secret key are going to be now once I actually confirm my password you'll notice here that I have my keys showing up these keys I'm going to be deleting after this video goes live so you won't be able to use these Keys yourself but you can create your own keys but essentially we just want to copy this and put this into our environment variables so we'll find our EnV here and we're going to have our secret key set it equal to that and our stripe public key and we'll set it equal to our public key up here and since we want to make sure that this public key is actually accessible on the client we need to make sure we prefix it with nextore public uncore if we do that prefix that'll make it so it's available both on the server and on the client while all these will only be available on the server and I like to just separate them in my EnV variable files so I know which ones are public and which ones are going to be only on the server so now with that out of the way the next thing we need to work on is creating that purchase intent and I'm just going to manually cast this to a string just so I get rid of all those typescript errors so purchase intent is just you telling stripe I have a customer that is intending to purchase something at a specific price so what we can do down here is we can say that we want to do stripe. payment intents and what we want to do is we want to create a brand new payment intent and here is where you specify all the information you want as you can see there's a ton of stuff first of all we need our amount and this is going to be in sense so we can say our product. price in sense that's going to give us exactly what we need for our amount we also need to specify the currency which in our case is us and then you can add in additional metadata and this metadata is really useful if you want to tie a purchase to something for our case we want to tie this purchase to this particular product so on our metadata we're actually going to pass along the product ID as our product. ID just like that so now we're creating this brand new payment intent we'll just come in here payment intent just like that and this payment intent is for a specific amount it's in US and it's going to have this metadata passed along with it that way after the charge has been processed and everything we'll be able to use this metadata to hook up our customer with the product that they're actually purchasing this metadata section is really important now with this payment intent the thing that we really care about from this there's a lot of information but the thing we care about is the client secret because this is what we use on the client to essentially say that this is the payment intent they're working on you can almost think about this as like an ID for this particular payment intent now this client secret technically can be null so we're just going to do a quick check to see if this is equal to null if it's equal to null then there was some type of problem going on so we're just going to throw an error that says stripe failed to create payment intent just something to essentially say that something failed like this should never happen but just in case it does this is what we're going to do and then I'm going to create a brand new form we're going to call this checkout form and the reason I'm creating this as it's brand new component is just because it's going to be a client component since it's going to be using a lot of hooks on the client side for stripe and so on and here we're going we're going to be passing down my product so we'll say product is our product and we're also going to be passing along that client secret which is just our payment intent. client secret just like that so now we can create this checkout form I'm just going to do it directly inside of here in a components folder we'll call this checkout form. TSX export function checkout form and it takes in our product and our client secret just like that check out form props so now we can come up here we can type our checkout form props and we can say our product is going to be whatever it's going to be we'll figure that out in a second and we'll get our client secret which is a string there we go and let's just make sure we close this off and return an H1 that says form there we go so now inside of here we can make sure we import that there we go and now everything should be working no more errors now if we just go ahead and actually look at what we need to do for stripe I'll just expand this a little bit we got all all this server related stuff done for creating our payment intent relatively easy the next step is going to be building out the checkout page and we're going to be using react obviously so we need to install this library for react so we can just copy this exact library right here that we need to install and what we're going to do is we're going to come into our application npmi and install that Library just like that once that's done we can restart our server so npm run Dev there we go now we have our server working let's open up our checkout form so we can actually start working on that and let's look at what the code is going to look like for this section so the very first thing that we need we need to do is we need to call this load stripe function and pass it in our public key and this essentially is going to load stripe on the client so we have a server version of stripe which allows us to do things like creating payment intents and then we have a client side version which allows us to process payments for our user based on those payment intents so if we scroll down a little ways we can essentially ignore this entire section up here and you'll notice that right here there's this thing called Elements which is what's going to be handling all of the different checkout related stuff for us then if we look inside the checkout form the main thing we care about here is this client secret right here so we need to make sure we use that client secret when we initialize our content so if we scroll all the way down here where we're actually rendering out all of our element related stuff we're going to be using the client secret down here so I'll show you exactly what all that looks like it's not important you understand how all this documentation looks like just know that that's kind of what we're doing to hook everything up together so I'm going to bring this off to the side and I'll start writing out the code inside of here so we need to use elements so we're going to say elements and this is coming directly from that stripe react Library we just installed and the key thing about elements is it's going to take one major property or actually two the first is going to be our options and the options requires our client secret which we have right here so that's perfectly fine the next is it's going to require an instance of stripe just like this now this stripe instance is coming from our actual loading stripe call so we can say load stripe just like that we can pass it along our process. env. next undoru uncore stripe public key that's what we called this key we'll just mark this as a string and now we can say that con stripe is equal to calling load stripe then the really important thing to understand is inside this element we actually need to create a second component because essentially this elements is like a context wrapper that gives us the context for stripe and elements and so on so let's create a brand new function we'll just call this form it really doesn't matter what we call this and inside of here we can use the hook use stripe which gives us essentially an instance of our stripe variable so we'll say stripe is equal to that and up here I'm actually going to call this stripe promise just because technically this returns to us a promise which resolves to our stripe instance so down here by calling UST stripe it actually gives us our stripe instance that we need to use same thing here we can use the use elements hook and this allows us to hook up stripe essentially this use elements hook has all the details for our payment information email and so on we can pass that along to stripe and it's all going to work behind the scenes for us we don't have to worry about managing all that or verifying it stripe does all of that for us now with that done we can essentially render what we want in our case we want to render out the payment element this again comes directly from stripe and you'll notice just by using the name it element on its own without even adding really any extra customization we can come in here we can render our form you'll notice it'll actually look rather quite good so we can come over to our application we're going to refresh this page and you'll see immediately we'll get a checkout form that shows up using stripe it has verification and everything built in all we need to do is just add some extra bells and whistles around it to make it look nice now obviously I forgot to mark this as a client component so use client that's just cuz the stripe Library uses a bunch of hooks behind the scenes you'll notice now we have this nice checkout form showing up with all the information we need need as well as multiple different ways that we can check out all built into stripe you can customize this as far as you want by removing things adding things making it look different with CSS all of that can be done with the options you can pass inside of here like there's an entire appearance option for modifying exactly how everything looks but in our case the default actually looks really good and blends in with the Styles we're using for our site what I want to do is I want to work on focusing on adding information about what the product is we're purchasing before we dive further into the stripe integration so up here where we have our elements I'm going to add an entire section above this that's specifically dealing with showing out the information for our product so we're going to put this inside of a div and essentially I want it to look very similar to our card but I'm going to have image on the left content on the right hand side in this first div I'm actually going to have wrap my entire container and that's because I want to limit the size of this so I'm going to come in here with a class name I'm going to say that the max width of this is going to be 5 XL just so it's not too large and I'm also going to say the width is going to be 100% And then I'm going to center it with margin of Auto and I'm going to space out the content inside of here using space Y8 now now just by doing that you'll notice nothing changes cuz we're rather you know small screened already but if our screen was much larger you would notice it would shrink down the size of this so it's not too big that's essentially all this is doing just making it so it's not too large now the next thing I want to do is to create a div that's going to contain all my product information so this is going to be use flex box to lay things out with a gap of four I'm going to put the items in the center so everything is centered nice and neat now the first thing we need is our image and that's going to go inside of a div so we're going to have our next image right here that's going to be the image of our product and then the next section that I want is going to be all my product information so let's start with our image first we're going to have our product. image path just like that and let's make sure we actually fill out the types for our product so our image path is going to be a string there we go and we're going to have a name which is a string and a price in sense which is a number and let's put our description in there and this description is just going to be a string as well so now that we have all that we can specify what we want for this we just want it to be a fill and the alt here is going to be our productname just like that so that's our image done now let's make sure it's the right size we can say that we want it to be an aspect ratio of video we want it to not build the shrink at all so we'll say Flex shrink of zero we'll give it a specific width of 1/3 of the container size and then we add relative on there to make sure our image shows up inside that section so if we give that a quick save we should see now we have our image at the very top showing up which is great and it's always going to be 1/3 of our size next we can work on making sure all the content for our actual text shows up so we're going to have a div here with a text of large this is going to be for the actual price that we're going to be paying this is the most important part which is why I made it large for Mt currency this is going to be our price in sense divided by 100 so now we should see we have the price showing up which is great the next step we need is going to be adding the actual name of my product I'm going to put that in H1 product. name and then we're going to add some Styles so we can say here the class name for this is going to be text 2 XL cuz this is going to be quite large and a font of bold so now you can see we have our product name showing up and finally we're going to add the description that can go inside of a div and here we want to line clamp this we'll just do three lines we don't want it to be super long for the description and then we can actually come in here and say the text whoops text is muted just like that and we'll use the foreground one so we can say product. description close that off and now you can see we have our description we have the name and we have the price and the description will never get too long which is exactly what we want one last thing we want to do as well with the image is to set a class name of object cover just to make sure that it doesn't overflow the container or do weird stretching so now with that done we can actually work on making our form look a little bit better at the bottom here cuz right now we're just rendering out the blank payment element which is super not ideal so what we're going to do is we're actually going to put this inside of a form because obviously we want it to be a form that we can submit just like that and then inside of this form we're going to essentially put this inside of a card so we're going to use the card components that we've already imported so we have our card let's do our card card header at the top here we're going to have our card title at the top of that and this will just say check out there we go and we're also going to have a card description so we'll come in here with a card description and this one I'm going to give a class name of text destructive and this is where we're going to put any type of error message that we get so right now we're not going to actually show this unless we have an error message but for now I'll just put the text error so we can see what it would look like and as soon as we have an error message we'll make sure we hide this or show it accordingly next up we're going to have the content of our card and this is essentially just going to be our checkout form so I'm going to move my payment element up into this and then we're going to put in a footer here so we're going to have a card footer and this is going to be for our checkout button so we're going to have our button this one's going to say purchase and I'm also going to put the price inside of here as well so we're going to say format let me make sure I do this correctly there we go format currency price in cense divided by 100 there we go and let's make sure we pass in that price in sense price in sense is a number there we go so now we at least have that information make sure I import my button just like that and we'll add a few more classes to our button for example we'll make it full width we'll make the size here be large and then we'll deal with like disabling it and so on based on certain properties so first of all I want to have this button be disabled if stripe equals null or if our elements equals null just like that and that just means if either of these things are null obviously that means the form is loading so we should not actually show the purchase button now as you can see we have our checkout form and if I move my camera out of the way you can actually see that the purchase price is currently incorrect that's cuz we need to make sure we pass in our price in sense which is just our product. price in sense now if we give that a save you can see that this price right here has actually been properly updated I'm going to move my camera back to where it was just because that's not really important for us to see at this moment and now we want to finish on working on our form and making sure it actually works properly so we can actually submit the form and that again is going to be straight from the stripe documentation I'm not going to walk you through the stripe documentation I'm just going to show you how to do it so really the main thing we need to focus on here is going to be an onsubmit Handler so we're going to have onsubmit and what this is going to do is just going to call a function we're just going to call it handle submit and this is going to do all of our stuff related to stripe so we're going to come in here handle submit we're going to say that is a function it's going to take in a form event and of course the very first thing we're going to do is we're going to prevent the default of that form event let's make sure we get this import properly there we go and then what I want to do is I want to make sure that all the elements we need are there so if stripe is equal to n or our elements is equal to null then we just want to return because obviously there's a problem we don't have stuff loaded up yet so obviously we don't want to submit our form then what we're going to do is we're going to set is loading to true because we need to manually manage our own loading state so here is loading set is loading we'll just default this use state oops State there we go that's going to be false by default and we can use that down here for example if we are in the loading State then we of course want to make sure that this button is disabled and we can change our text down here too if we are in the loading State then we're going to say purchasing dot dot dot otherwise we'll say purchase and we'll say the price just like we had before there we go let's close that off give that a quick save and now our button will actually be able to update whenever we're in that loading state if I click on this right now you can see it actually changes to that text of purchasing and it Grays itself out but obviously since we're not doing anything our form the loading state is never being fixed so let's just refresh that back to what we had before now the first thing that we should do is check to see if the order exists so check for existing order essentially I don't want the customer to be able to purchase the same product twice for now we're going to skip this step just so we can make sure we get the stripe portion working and then we'll come back and fix this so for stripe all we need to do is call confirm payment so we can do that we call confirm payment and we need to pass in all of the different elements that we have so this will pass in all of our credit card information expiration dates and so on and then we need to pass in these confirmed parameters now these confirmed parameters essentially just takes in a return URL so we're going to come in here return URL and this return URL tells us where to send the person after the purchase is complete this is essentially like your success page for your purchase so here we can just get the URL so we'll say process. env. nextt public server URL just like that and then we can just add on to the end of This Server URL the actual page that we want to render so here this is just going to give us our URL to our server so this would be like Local Host 3000 and then what we can do is we can say we want to send them to the stripe purchase success page doesn't really matter what you call this page this is just a page that we're going to create in our application so essentially inside of here in our customer facing section we would create a folder called stripe and inside of here we would have a purchase success and a page. TSX inside of there and I need to make sure that I actually create a page. TSX file instead of folder so let's make sure that we actually delete that folder and then we'll come in here and we'll create a brand new file called page. TSX export default function success page and we'll just return an H1 that says hi there we go now we can minimize that down this is just where we're going to get redirected after we make that purchase now if this purchase was successful stripe is automatically going to redirect us to that page and none of our other code will run but if it's unsuccessful it'll call this then method and it'll pass along an error just like this we can actually use that error to determine what the error is and render it out to the user so we'll come in here we'll say that we have that error and in the case that we have an error what we want to do is we want to check the error type if the error type is equal to either a card error or the error. type is equal to a validation error well this means that it's an error message that we can safely show to the user and it'll make sense so we'll say set error message and we'll send the error. message otherwise in all other cases this error message is more of like jargon for developers so we should just give them a generic error message so we'll say set error message and it just says an unknown error occurred just like that there we go so now let's create some state for that up here error message set error message and by default we'll just have this be empty and it's going to be a string then the very last thing I want to do no matter what if we get to this point I want to set my is loading to false there we go that way it'll actually let us resubmit the form so now if we have an error it'll actually show up for us inside of this section all we need to do is render out the error message so down here we'll say error message just like that if we have an error message I'm going to render out the error message just like that and I think I accidentally imported something up here there we go make sure I remove that now we give that a quick save make sure that everything is hooked up properly looks like we're missing something that's because this should be on this line right there there we go cleaned up everything and now if we try to purchase this you'll notice we get our error message at the top here as well as error messages on all these fields so that's exactly what we want now the next thing is we need to make sure we get the email of the user as well and luckily stripe has a way to do this pretty much automatically and that's with this link authentication element if we add in this link authentication element and do nothing else you'll notice at the very bottom we get an email field being added in now all I want to do is just add a little bit of spacing so I'm going to come in here with the div which has a margin on the top of four I'm going to put my element inside of that just to space it out from the top section here and again if I move my camera you can see that this looks really good where we have our email at the bottom the purchase button and if we have anything missing you can see it's going to force us to fill in all of that information I can just move my camera back since we've seen all we need to see and now what I want to do before we finish up the stripe integration is make sure that we do this check right here for existing users or existing orders and to do that we need to make sure we get their email address because that's how we actually link together a user so we'll say email set email is equal to use State this is going to be a string and by default it's going to start out empty and if I come down here on my link this actually has an onchange event listener we can listen to and we can come in here and we can say set email this is going to be e do and what we want to do is we want to get the value. email so that's going to give us the actual email so every single time we type inside of here it's going to put that email directly inside of this section so now we have access to that email so if up here our email is equal to null obviously we cannot submit our form but now if we have an email we can actually check to get a user's order so we're going to call an action which is just going to be user order exists we're going to pass it in the email and the ID of the product that we want to check so make sure we get that product ID up here just like that and we'll pass in the type for that as well which is going to be a string and we'll pass that into here is equal to product. ID there we go so now we're going to make this action because essentially this action will return to us a Boolean we'll say order exist equals we're just going to await for this make sure that this is an async function just like that so if our order exists well obviously that's a problem so we should set some type of error message so I'm going to say that you have already purchased this product try downloading it from whoops it from the my orders page there we go and we'll come in here we'll set is loading to false and then we'll just return because we don't actually want to submit our form we don't want to make a purchase if they've already purchased the product so now let's go ahead and actually create this action so we'll we copy the name of that we'll come over here inside of our app we'll create a brand new folder called actions and inside this actions folder we'll create one called orders.tss so first of all this must have used server at the top of it and then we'll export a function it's going to be an async function with that specific name and it takes in an email which is a string and it's going to take in our product ID which is a string and all we need to do is just check for this order so we can say DB make sure I import DB we want to get an order and we want to find the first one that essentially has these fields so where the user email is equal to our email and where the product ID is equal to our product ID so we can await this and this will actually give us an order just like that and all we want to do is make sure this is not equal to null so really we can just return checking to make sure this is not equal to null so we'll just wrap these in parentheses is not equal to null just like that and what I'm going to do is down here we're just going to say select whoops there we go select make sure I put that inside the correct parenthesis select we're just going to set that to true just like that and actually instead of setting it to true I'm going to say that our ID is going to be true so all we're going to do is select one field this just saves us from selecting more data than we actually need because all we're checking to see is if this thing actually exists so this really simple function is just going to check is there an order for that email and that product if so the customer has obviously already purchased this product so now we can just come in here import this function and that should be all we need to do for this page to work as you can see if we look through here we have one error right here where our product ID and that's just CU we need to specify the type of this ID at the top here so now that's everything that we need for this entire page to be working so let's go ahead and test to see if this actually makes a purchase to do this we can set a card number and in our case it's going to be 42 repeated a bunch of times then you can put any expiration CVC and zip code as long as it's in the future and we're just going to put a random email inside of here it doesn't matter and now we're going to click the purchase button you can see it's loading and it looks like it didn't actually redirect us so something went wrong as you can see an unknown error occurred now just to make sure this wasn't a fluke what I'm going to actually do is completely refresh my page just to make sure there's no weird caching stuff going on and I'm going to enter in my card number expiration all that information and just a random email address to see if that error still persists cuz it could have just been some weird caching stuff but it looks like we still have that error so if we look through our code I believe the problem is is that we don't have this next public server URL specified yet so our return URL is broken which is called cusing this error so let's go ahead and add that into our environment variables come in here next public server URL this is Local Host 3000 just like that make sure we add the HTTP there we go so that is going to be the correct URL for our site so now hopefully this should work I'm going to make sure I restart my server just because I changed my EnV variable just to make sure there's nothing weird going on we'll completely refresh this page and we'll test this out to make sure that this is actually working properly so we'll enter in all of this information again random email address doesn't matter what it is click on the purchase button and hopefully this time it should redirect us to that page and it looks like it did and you can see in the top we have payment intent and other information inside of our URL which we can use to make sure that this was actually a successful purchase because there could be some type of error still even with everything going through so on this page I essentially want to show the exact same section that I showed up here so I'm going to scroll all the way up to where we have all this stuff for our actual card essentially for our at the top product section I'm going to just copy that CU we're going to make it look very similar on this page go to that stripe purchase success I'm going to come into here and this is where I actually want to render that information so I'm just going to paste it down obviously we don't need our form though so we can remove that section here let's make sure we import our image that we have and we also need to import this format currency function as well there we go now all we need to do is get our product as well as get all the information from our search prams so this has a search prams just like this search pams and we can type out this search pams pams this is going to have a payment intent which is a string and this is essentially the ID for our payment intent and we can use that with stripe to actually get the thing that we want to check to see if it was successful or not so we need to load stripe at the top of this page just like we have done before so if we go over to the page where we were doing that which was our purchase page you'll notice we loaded stripe at the top here I'm just going to copy that code cuz we're doing essentially the exact same thing now let's go back to our purchase success page and we're going to load stripe at the very top and make sure that we import Stripe from stripe there we go so now we're loading our stripe and we can use that inside of here so I can just say await stripe Dot and I want to get my payment intent so we'll say payment intent. retrieve I'm going to retrieve it based on my search cs. payment intent id so now this is going to give me a payment intent and this payment intent is going to have information about success and not success and it's also going to have information about my actual product code itself that's inside of our metadata so we can come into here we can say if payment intent. metadata. product ID is equal to null will return not found because that means for some reason this didn't have an actual product idea associated with it so that's a problem we should return something with that and we're also going to come in here with our product whoops product and we're actually going to get that from our database so that's await db. product.in unique and we're going to get it where our ID is our payment intent. metadata. product ID just like that and we're going to do the exact same thing here if for some reason our product is null that means our ID was incorrect so if our product equals null we're again going to return not found now here's where we can determine success versus error so we can say const is success is equal to we can get our payment intent. status whoops status and if that is equal to succeeded well that means it was actually a successful payment intent and we can render out the success message and we can render out all the other information related to that otherwise we can throw an error essentially so here inside of this information we also want to add an H1 at the top of this to specify whether this is a success or not so if it is a success we can just say success put an exclamation point make it really happy there we go otherwise we can say error close that off add some classes so this looks a little bit better so we'll say class name we'll have 4 XL for our font size and we'll make it bold so it's very large so now you can see success and big letters cuz this actually did work the last thing I want to do is add a button that allows us to actually download this file because once they purchase it they probably want to download the file and they may not want to go to their email to do that so here let's add a class name margin top of four to give it some space size is going to be large on this button and we're going to make sure that this button is a link so we're going to use as child now here we only want to show this actual link to be for if you have a success you can download otherwise you need a link that just essentially says try again because you had failure so here we're going to have is success and then what we're going to do is if it is successful we're going to render out an anchor tag that goes to the download otherwise we're going to render a link that goes back to our page so link this is going to be an HF back to the purchase page so let's make sure we get this correct this will be slash products SL product. ID slash we want to go to the purchase link there we go close off that link tag and here it'll say try again there we go so now if we have a failure our button will render out this link so we can just come in here we can manually set this to false to show you what that'll look like so if this is set to false make sure I spell that properly you can see this says try again obviously I spelled again wrong so let's fix that and if I click on this link you can see it's bringing me back to the purchase page now obviously this was successful so let's get this back to a success make sure I spell this correctly again and now it's work on this link right here this link is going to be a little bit different because we need a way to download a file and when we on the admin page we could just download the file no matter what cuz we were signed in as an admin but in this case we want to send them a link that is valid for either like 10 minutes an hour 24 hours whatever it is we want to create a verification link that says hey you are able to download this for a set period of time that way you can't share the link and give it to other people for free so I'm essentially going to create a brand new route inside of our products folder we're going to create a brand new folder inside of here called download and this is going to have essentially a download link ID or I think we call it verification so verification ID just like that so this is going to have a route inside of here route. TS just like that and for now we'll just export function G and we'll return just doesn't matter we'll just say hi obviously this won't work but it's just going to be a placeholder for now so now what we want to do is we want to essentially create a new verification link so we can send them to that location so on our href here we want to send them to that/ products SL download and we want to get the ID inside of here we need to create some type of ID so to do that we can create a download verification just like that this is going to be for our specific product so let's create a function for that create download verification which takes in a product ID which is a string just like that so here we can return DB we want to get our download verifications we want to create a brand new one inside of here and we're going to pass in some data this data is going to be rather straightforward we need the product ID and we need to have an expires at field and this expires at is essentially going to be a date that is in the future so this is really easy to write we can say we want to create a new date and for this new date we want to take whatever the current date time is we want to add in 1,00 * 60 * 60 * 24 essentially this right here is the number of milliseconds in one day so right here we've just created a brand new download verification that expires 24 hours from now for this particular product ID now if we scroll up here we can just make sure that we await this finishing and this will return to us this download verification ID all we want in our case is the ID so here we can just add in and a wait and we can wrap this and we'll get just the ID from this make sure that this is an async function just like that so now every single time we go to this page it's going to create to us a brand new download link that will last for 24 hours that links to this particular location for this particular product ID and we can make this button just say download so now you can see we have this download button and when I click to go to this page I'm obviously going to get an error just because my code for my page doesn't do anything correctly but if I were to go to here and I return a new next response just like that and this next response will just say hi now hopefully that should work and as you can see I get the text High being printed out now obviously we want to download the file here only if it's actually within that window so that's going to be the next thing that we're going to work on before we actually verify the purchase using stripe so the first thing we need to do is get our parameters so we have our request which is just a next request and we're also going to have our pams inside of here so pams is going to have a downlo download verification ID and let's actually type this out prams which is download verification ID that is a string just like that looks like I didn't quite type this out properly move this out one level deeper there we go that's all of our typing done so now we can actually get that download verification so the very first thing I want to do is get our db. download verification. find unique based on where the ID is is our download verification ID there we go make sure we await that and we can also make this an async function now that'll just get us that download verification but we also only want to get the download verification where the expiration date is also after a certain time so we can come in here and we can say expires at needs to be greater than and we want to set this to a new date so essentially we're saying the expires at is after the current time that we are currently at that will make sure we only get a download verification if it's not expired the next thing we can do is select all the fields that we need because really all we care about is the product so inside of our product let's select the file path and the name because those are the only things that we need to actually download this particular file there we go now from there what we can do is we can say if our data is equal to null then we can just return a brand new response because essentially we either had an expired code or we have a code that is no longer in our database at all so in both cases we cannot download this so return a new next response and this is actually going to redirect this to a page so we'll say next response. redirect we're essentially going to redirect them to a page that says their code is expired so we'll create a brand new URL and this URL is going to go to SL products SL download SL expired there we go and then we can also just make sure that we pass in the URL that we want to use inside of this new URL so let me make sure this is right there there we go essentially that's just going to create a brand new URL using our current local host 3000 and it's going to redirect them to that exact location now if we don't have a problem that means that we can download the file so we're just going to copy that code essentially directly from our admin page because it's going to be pretty much identical so inside of our admin Pages let's open that up we have our products we have our ID download and I just want to copy this entire section down here for downloading our product so what we can do is we can come into here right after this section we can just paste all this down make sure that we import FS so I want to come up here import FS from fs/ promises just like that and then we obviously need to get our product from our data so we can say data. product. file path say same thing here same thing here and same thing there perfect so now hopefully if I go ahead and refresh this page it should actually download this file and as you can see it's actually giving me that file and it's giving me the download link for it and I can download it if I want but if for some reason my code was expired or I type in an incorrect code in my URL when I do that it's going to redirect me to this expired page so let's go ahead and just really quickly create that expired page so it's going to be expired page. TSX export default function expired and instead of making you watch me type this out I'll just paste in the code for this is relatively straightforward essentially all I'm doing once I import these Imports I'll explain it is essentially I have an H1 that just says hey your download link expired and a button that brings you back to getting a new link from that my orders page so if I refresh this page you can see download link expired get new link and it's going to redirect us to the orders page which we have not created but it's just this page right here so that's working perfectly fine just like we expected to now throughout the this entire process there may be one thing that you notice that we haven't actually done yet and that is creating a customer and creating an order for them and that's because we don't want to do that on our success page instead what we want to do is we want to wait for a web Hook from stripe because stripe will actually send you a web Hook when a payment is successful and that is when we want to make sure that we actually create all this information so to do that I'm going to create a brand new section inside of our app folder we're going to come in here I'm going to create a brand new folder this folder is going to be called Web hooks I'm going to create a folder specifically for any web hooks related to stripe and inside of here I'm going to have a route. TS so this is going to be called by stripe when we have a successful payment and the reason you want to use this web hook instead of your order success page is it's going to be more secure because it's only ever going to get called when there was a successful payment it's going to come directly from stripe and there's different verifications set up so you can authenticate that this is the correct page now in order to test these web hooks if we go over to the stripe documentation this handle post payment events talks all about how we can use a web hook to set this up and if we go to the dashboard web hook tool it'll actually give us a dashboard where we can set up and use our web hooks if I move my camera out of the way it's a little easier for you to see you can see there's a button for testing in local and what we can do is essentially download the stripe CLI I'll Zoom this in a little bit so it's easier to see download the stripe URL we can listen on whatever Local Host we want and we can also trigger any different payment successes and so on if we want to so to do this I've already set up stripe login I'm already logged in on my PC all I need to do is just type in this exact command so if I open up a brand new terminal and I paste in this command give it a quick second to load there we go and we want to make sure that we're forwarding into this Local Host 300 SL and ours is called Web hooks SL stripe now if we give that an enter right here what it's going to do is redirect every single web hook that we have from stripe to our local host and you can see it's given us this web hook secret which we want to save inside of our environment variables so we're going to come in here this is going to be our stripe web hook secret and the really nice thing about this with local testing is if I zoom out a little more you'll actually see it gives me a bunch of code over here on the right on what I can do and you'll also see I get this received event section this will actually show me every single event that is received so I can really look through the data that's happening and if I wanted to I could trigger a specific request if I really wanted but in our case I don't want to worry about that instead I'm just going to test these manually because I have metadata and that metadata will not show up in these manually triggered events I want to be able to test it inside of my actual application so now that we have that set up I'm going to zoom this back in so it's a little easier to see I'm going to actually make this a bit smaller and we're going to come over to our application because I want to really focus on the actual setup of this so here we need to create a post event so async function post request is going to be a next request just like that there we go and this is going to be what's called by stripe and it's going to give us essentially a bunch of information that's going to be coming from the body so what we can do is we can say stripe which we of course need to create so we'll import Stripe from stripe just like that and then what we can do after that is we can say con stripe is equal to creating a new stripe and we need to make sure we pass it in our secret key so stripe secret key as string there we go so now we can say stripe Dot and we can access a bunch of things we specifically want to access the web Hooks and we want to construct an event from our web hook this will essentially verify that all the stuff being passed to this is actually coming from stripe that's why we have that secret key because it's going to make sure everything is working fine so first what we want to do is we want to take take our request. text and we want to send that along to our event just like this the second thing that we want to do is we want to take our headers and we want to get the specific header of stripe Das signature signature there we go this is going to be used to compare against our actual web hook so here our process. env. webhook secret our stripe one this is what's going to be compared against this right here so I'm going to make sure that this is a string just like that by saying as string and I'm going to do the same thing here I'm just going to cast this to a string so that'll all work inside of our typing and now this will give us an event or it'll throw an error if for some reason that the stripe header does not match our secret for example if someone tried to maliciously Target this endpoint by creating their own events to try to get something for free this will throw an error so that will not work now I want to determine what type of event I'm listening to as you can see there are tons of different events you can listen to in our case we want to care about charge succeeded so anytime we had a successful charge on our account it's going to call this right here and what we can do is we get all the information from here for example our charge is just our event. dat. object and this contains a bunch of different fields inside of it such as our amount it's going to contain our customer billing information like their email as well as our metadata for our product ID so I can get my product ID by getting my charge. metadata. product ID I can then get the email which is my charge bliing details there we go email that's that's going to be coming from that link component that we just created and then we have our price paid in sense and that's just our charge. amount so this is all the information that we need to create an order for a customer and we already know that this is verified by stripe because of this code right here so what we can do is we can actually create that order but first we want to make sure that our product exists so we're going to get our product by waiting db. product make sure I import DB there we go and I want to find essentially the first one the unique one where my ID is my product ID there we go so this is going to get a product and if our product is equal to null or our email is equal to null then we have some type of problem so we're essentially going to return a status that says that this is bad so we'll return a new next response there we go and this next response is going to say bad request doesn't really matter what it says but we're going to make sure our status is going to be a 400 status to let them know this was not what we expected there we go and now we can actually go ahead and create the specific information that we want so essentially all I want to do is create a user or update the user by adding an order to them and this is actually something we could do really easily with Prisma so we can say db. user and what I want to do is an upsert and what this will do is it'll either update my user or it'll insert a brand new user so we need to make sure we pass it information for both what we want to do in the creation process as well as what we want to do in the update process and these are both going to be the exact same Fields so I'll write them out and then we'll just make sure we put them in a variable so it's easier to work with so in our case we want to specify the email for our user and we want to specify the orders and we want to create a brand new order and that order is going to have a product ID and a price paid in sense just like that so this is all I want to do when I create a user and I want to do the same thing when I update so I'm just going to copy this say const user Fields is equal to this right here and I'm going to pass that along to both the create and the update so now what's essentially happening is I'm creating a brand new user with this email and I'm creating a brand new order for them but if the user already exists in our database instead it'll set their email to this email which is fine because it's already their email and it'll add a brand new order for them so it'll both do the creating and the updating Allin one all we need to do is specify a wear field here in the case of update so we're going to say where our email is email so what's going to happen is if our email exists in our database it's going to add a brand new order for that user if the email does not exist it'll create a brand new user with that email and also add that order to them now we can also specify what we want to select from here in our case just want to get the order that we created so I'm going to get my orders I'm going to order them by the created at just like that and I'm going to get them in the descending order and of course I just want to take one single order from here so from here I can get my orders and I can get the very first order just like that and if I await this I essentially have gotten whatever order I created right here inside this order variable now if I just stopped right here this would actually create the connection between user and Order and product which is great but I also want to send them an email essentially a receipt saying that they made this purchase as well as a download link inside of that so I need to set up the information for that as well so what we can do is we can create a download verification this is going to be the download link they can use in their email and that's just awaiting db. download verification we want to create a brand new one where our data is essentially our product ID and the expires that is going to be that new date and we're going to say date. now and this will be again 24 hours from now so 1,00 * 60 * 60 * 24 and if you wanted to make these longer or shorter verification expirations we could easily do that and this will say product ID so now we have our download verification we have our order that's all the information we need to send them in email now to actually do this email sending I'm going to be using a service called resend you can use any service you want there's tons of them I mean there's mail Gun there's things like send in blue there's twilio it doesn't really matter but I find that resend works really well it's pretty simple to work with so this is what we're going to be using to send out all of our different emails as you can see I've already done some test emails throughout here so it's relatively simple to get set up just create an account they have a really generous free tier that's what I'm using for this entire project now the first thing we need to do is actually get our API key that we're going to be using inside of here so let's just make sure that we get our key to get that I can just come in here edit API key as you can see I can't actually copy this API key so I should probably just create a brand new one so we'll just create an API key this will be test video just like that it's going to be full access doesn't really matter and you can see we can copy this because we can only see this key one time so we're going to copy over that API key and we're going to put that inside of our EnV variable this is going to be private so we'll send resend API key there we go paste that down just like that and again you're going to need to get your own API key cuz I'm going to be deleting this at the end of the video now if you're doing this for real you should probably restrict the permissions to things like sending access only but in our case it doesn't matter this is just for testing purposes so now let's go back to where we just were this is where we're ready to send our email and we need to send essentially resend emails through here so if we look over at the documentation for getting started that'll tell us what to do or I can just show you what to do we're going to create a brand new resend object which is going to be coming from this resend library that we need to install so to do that we're going to come in here we're going to add a brand new tab we're going to say npmi resend and we're also going to install react email just like this this allows us to write emails using react code it's a really easy way to make sure our emails work across all different browsers all different email providers because writing HTML emails sucks and this react email Library makes it a little bit easier and it's actually made by the same team as resend so you know that it'll work really well with resend so now that we have this we can import resend from resend just like that and this import should actually be in Brackets just like that and now I can pass it along my EnV variable process. env. resend API key as a string there we go now we have access to resend we can come all the way down here and use that to send an email and the nice thing is it's really easy to send we can just say resend. emails do send and now we can specify what the email will look like for example we can specify the from field we can specify the two field which is just the email that we've got we can specify the subject which is just going to be order confirmation and we can specify what we want this to look like inside of react so I'm just going to put an H1 that just says hi H1 just like that and I'm going to make sure that this is a TSX that way I can actually render out my react jsx inside of here so now all we need to do is specify from I'm just going to say that this is a support email so the name is going to be support and the actual email is going to come from our EnV variable we'll say sender email email just like that make sure I put a comma at the end of here that's all we need to do to be able to send emails now for testing purposes resent has a really nice email we can use so we can say sender email is going to be onboarding at resend. deev and allow us to send our emails with this testing purposes so now we can close out of all that and we essentially have a test email that we can send along all we need to do is make sure that we await this and then we can come all the way down here and we can make sure that we return a new next response just like that and we'll put this at the very bottom so no matter what happens it'll at least to send a successful response as long as all of our code works fine now we're going to come through and make the email look really good in a little bit right now obviously it's super ugly because all it says is high but this is just going to be for testing purposes to make sure everything is hooked up together so now we can come over here and let's create a brand new purchase we're going to come in here we're going to purchase final product to I'll expand this a little bit and we'll come in here with our test card number we want our expiration CVC and so on but when it comes to email if you're using resend you need to type in the email that you actually created your account for if you're using this for testing purposes so I'm going to type in the email that I actually use to create my account otherwise it won't let you send emails anywhere else so now if I click purchase on this it should hopefully work everything fine as you can see the download link worked if we come over here you can see it's actually sending along my web Hooks and I got 200 responses if I look in my developer tools for my web hooks Zoom this out you'll notice in my received event you can see we get all the information for our events that we received inside of our web hooks which is working really well and we should actually see if we go to our admin page that we now have a user that was created so I'll say admin just like that I'm probably going to need to log in actually it's saved for me so now we can go over to our products you can see we have our products we go to our customers you can see of course we haven't created this page yet but we should see we have a customer in here and we have an order in here and if I open up my email you can see I just got sent an email one minute ago that says hi onboarding resend. deev and it was sent to my email so I know that this is also working which is really great so we can close out of that we have everything working all we need to do is set up these ad pages that way we can make sure that our pages are actually working for creating users and creating orders and then we'll go ahead and make these emails look a lot better as well as sending along an email order history as well so let's go ahead and make this a little bit wider easier to work with and what I want to do is go to my admin section and I want to create a new folder for my users and I want to create a new folder for my orders as well so we'll get started with users first page. TSX and I'm actually just going to copy this code in I'll go through exactly what it's doing line by line but it's pretty much identical to the actual product section as you can see see here we have a function for getting all of our different users and we're just selecting all the things that we need such as the price that they paid in sense then we're rendering out a header that says customers and we're rendering out our user table if we have no users we just said no customers were found then we're risting out the email the orders as well as what the value is for all these different orders as you can see we're getting all of our different orders for the individual user and what we can do down here is we list out the email we list out the number of orders and we're just adding together the total amount that they've paid and we're dividing that by 100 to get the actual amount that they have paid in total finally we have this drop- down menu section which has one action for deleting a user so we need to create this delete dropdown item so I can come into here create a brand new folder called underscore components and inside of here I'll create a file for those user actions so this will just say user actions. TSX again this should be very similar because this is exactly what we did for our products I'm just going to copy this over again and I'll explain exactly what's going on but it's again this should look familiar cuz it's exactly the same as you can see we have our transition we have our router whenever we click the button we call delete user and then we refresh our page all we need to do is create the action for deleting a user so we'll do that inside of our actions folder here users. TS and again I'm just going to copy this code over because it's exactly the same as the code that we had for our products section instead so now if I give these all a quick save you can see over on the right hand side that we have all this information and just to show you what this is we're just getting our user calling delete and if it's n be return not found otherwise we return the user so as you can see we have my information for my user their orders and the value for all their orders in total now we can go ahead and work on the sales section as well to show what the actual sales we going to look like so that's again going to be very similar to what we've already done create a new file called orders. TS because we're going to have our delete function inside of here and inside of our orders we're going to have a page. TSX and we're going to have the exact same underscore components and that's because this is going to look almost identical so let me make sure I create that as a folder and inside that folder I'll create our order actions. TSX and again it's going to be identical with entally to this page but for our orders instead so let's go to that brand new orders page that we just created I'll paste in the code again I'll go through it line by line as you can see we're selecting all of our different orders ordering them in the correct order we're then listing out the header of sales and as you can see here we're just listing the product the customer and the price paid we're listing out that information down here and then finally we have our delete action right here so let's copy over that delete action code we'll just paste that into here this again should look pretty much identical to code we've done before because as you can see it's exactly the same we just swapped out order for user and so one then finally up here inside of our actions we have our user section so I'm going to copy over the code for deleting our user or not our user our order sorry so let's copy over that order code real quick paste that in here as you can see again identical to what we did for the user pretty much line for line we just replace user with order so now if we go over to that sales page you can see we have our sales as well as what product they bought the customer that bought it and the price that they actually paid for that product and we have the option to delete both of these if we really wanted to but in our case we're just going to leave them as is which is perfectly fine now I'm also going to test to see if this works if we purchase multiple products with the same person so I'm going to come over here go over to my products I'm going to attempt to purchase this product as well so I'm going to use the exact same card number that's our testing card number get all the other information we need type in the email there we go and I'm going to click purchase and we should see that this purchase will go through as well as you can see that looked like it was successful and now I'm going to try to purchase a product that I already own I already own this product so I want to come in here just put a bunch of bogus information and I'll try to enter my email address and we should get a warning saying that I cannot purchase this product so Kyle weos simplified.svg we can move on to the main section of getting our email done cuz that's kind of the last final step before we have our product done so if we go back to our Local Host 3,000 we want to get an email that we sent them for a purchase as well as an email for this my orders page that we haven't created yet so I'm just going to minimize this down a little bit and we're going to start working on that section next so we're going to kind of minimize all of this section because the nice thing about using react email is we can actually create our emails using react normal jsx so inside the source here I'm going to create a brand new folder called email which is where I'm going to put all of my different emails for example we're going to have an email for purchase receipt there we go. TSX and I'm going to do the exact same thing for order history. TSX as well and I'm going to go ahead and create an underscore components folder just to store a component because we're going to actually use that component inside of here and this component we're going to just call order information. TSX because we're going to show the same order information in both of these sections and we'll just call this normal components because it's not in our app directory so we don't need to start this with an UND score now in order to test emails efficiently you don't really want to be sending yourself emails constantly it's super slow not super efficient so instead the nice thing about react email is they have a way you can easily test emails we're going to create a brand new essentially script called email that allow us to start up a server that allows us to view our emails as if they are working inside of like Gmail and so on so to get react email to actually work all we need to do is type in email Dev and we need to specify the directory where our emails are which in our case is Source slail that's where we're storing all of our emails and we need to specify a port which in our case is 31 if I run that that'll get up and started with my email server now one problem with this though is that it won't copy over our environment variables and inside of our environment variables we have things like our server URL which we need to be able to access to send our emails so to make sure that this copies over our environment variables for testing purposes we can type in cp. EnV that'll copy our EnV file and we want to copy that to node modules SL react email and then we can just put double Amper sand here and essentially what this code is doing is taking our environment variables and copying them over to the location where that this code actually runs and that means we'll have access to those environment variables inside of this testing environment I'll show you exactly what this looks like if we open up a new tab we can run npm run email and that's going to open up a new server Force at Port 3001 after we give it a second if we open that up you can see it's running over here and by copying over these EnV variables we just make sure that we have access to them only in development mode so as you can see we now have this working obviously we have nothing inside of our folder being rendered so we need to go ahead and work on that next let's do our purchase receipt we need to export a function but to make it work with development we need this to be a default function so we're going to call this purchase receipt just like that now I'm just going to go ahead and bring my camera back because it shouldn't get in the way of anything up to this point so now for this purchase receipt all we want to do is render this out as an email so we'll say purchase receipt email to make it a little bit more clear and here all I want to do is return all the stuff we need and this is all going to be coming from that react email Liber so we need to first get the HTML element which comes from react email so let's make sure we close that off up here we want to import HTML from react email just like that and actually we need to get this from a specific Library called react email components so I want to say npmi at react email/ components that'll give us all the different components we need so we change this to be/ components you'll now see that that is actually working we can close out of that and now inside of here we need to specify everything else we need so first we can specify what the preview text is going to look like and for that preview text we can just come in here and it'll say download whoops download and we want to put whatever the product. name is there we go and view receipt there we go and we're going to get our product from up here so we'll just specify our product just like that that's going to be our preview which we can import then since we want to use Tailwind we can just specify Tailwind just like this and it's going to make sure we can use Tailwind directly inside of our email and we can also specify the body element that we're going to be using so we'll say body just like that and we need to specify that we're going to have a head element and again this must come from react email so make sure we get our head element from react email components just like that so now we have everything we need to be able to style out what we want so for example on our body we set the font to Sans and the background color to white just like that we can also specify here that we want our receipt to be spelled correctly now one thing to note about Tailwind is you will need to pass in your own custom configuration it won't use your normal Tailwind config cuz not everything will work for our case I don't really care about passing in custom config because these emails don't have to be perfect they just have to be good enough now inside of our body let's specify the container that we want to use from react email and this kind of wraps your entire email body and for this we're going to specify a Max width so we're going to say Max W is going to be extra large then inside of here we can specify we're going to have a heading and this is again going to make sure it comes from react email and this will say purchase receipt just like that and then finally we want to have all of our order information so we'll say order information and that's that component we're going to share between all of our different things so make sure I get that order information component directly from here so I'm going to say export function order information just like that give that a quick save and now we'll make sure that we import that right here so now if I give that a save you'll see that right now it's not is giving errors just because it's returning void let's come into here we'll just return something for now it says hi there we go just so that is working you can see now our only error is based on our props so we can say that type purchase email or purchase receipt email props is equal to and we're going to have a product and that product is going to have a name which is a string obviously we're going to add much more later but that'll work for now so purchase receipt email props there we go so now if we give this page a refresh you should see that it should actually show our emails if we look over here you click purchase receipt to view that actual email but of course we're getting an error because there is no prop being passed in so to fix this what we can do is we can take our thing purchase receipt email Dot and we can specify the preview props and we can specify this as an object so for example we can say that we want to have a product where the name is going to be product name just like that and we can say that this will satisfy our purchase receipt email props so it make sure that it actually fits within this type so now if I open this up and give this a quick refresh it should actually show our purchase receipt and as you can see it says purchase receip High we can test it on different screen sizes we can see the code behind the scenes it just gives us all the information we need we can even directly send it to oursel if we really want now let's move on and actually get our order information to work properly because obviously we want to make this the most important section and here we want to have an order a product and a download verification ID so we can come in here and make should we specify our order information props so our type order information props we're going to have our order just like that doesn't matter what it is for now our product and our download verification ID which is a string so now let's write out what this email will look like and then we'll fill in the rest of our props in a second so inside of here we're going to have a section and this again is coming from react email essentially you want to use react email for everything in your email to make sure it renders properly because it uses things like tables and so on which work really well in emails now we want to specify a row so we'll come in here with a row again coming from react email and inside that row obviously we're going to have some columns coming directly from react email so this First Column is going to be for specifying our order ID purchased on price paid and so on so we're going to come in here with some text and this text is going to say order ID and then below that we're going to have some text text as well just like that again make sure we get that from the correct import and this one is going to be our order. ID so inside of our order up here we're going to have an ID which is a string there we go and let's add some classes to this so it looks relatively good we're going to put no margin on the bottom of this we're going to have the text Gray 500 so it's kind of a muted color white space no wrap so there's no wrapping that goes on we'll say text no wrap as well and then finally We'll add some margin on the right of four then what we're going to do inside of here for this next one is add some styling as well margin on the top of zero so they're scrunched together and margin on the right of four to space them out from one another I'm going to copy this column down two more times because on top of our order ID we're also going to have the date we purchased this so purchased on and we're also going to have the price paid so we're going to say price paid just like that so for our purchased on we need to get that from our order do created at and we want to format this using some date format so we'll say date formatter format and we'll pass along that information and up here we can create that date formatter so we'll say const date formatter equals new inl oops intl. time formatter we want this to be using English and we're going to say that the date style is going to be of medium length that's going to look really well for us inside of our email so now we have that formatted and here we can format our currency currency of our actual price paid which is is order. price paid in cense divided by 100 there we go so let's get up here add in those props so we're going to have created at that's a date and we're going to have price paid in cense which is a number give that a quick save there's all that information make sure I spell everything correctly this should say created at there we go that looks good and now we have all that information and if I give this a refresh we should see that at the top of our email but of course we need to make sure all of our props are being passed into everything correctly so what we can do is we can go back over to our purchase receipt because our purchase receipt also needs to get in our order and our order should essentially contain all that same information so what we can do is come over to here and we can copy over what this order looks like and we can paste that down directly into here this is going to be our order there we go and then we can make sure we pass an order into here as well so the ID is just going to give it a random ID we'll get a random uu ID just like that we have the created at this is going to be just the current time doesn't really matter and for the price paid in cense let's just say that it's going to be $100 there we go now we give that a quick save make sure I pass along our order into here which is our order and our product which is our product and we need to pass along our download verification ID which is download verification ID that's going to come in from our props as well so let's add that up to here as a string and we'll add that into here as a random uuid so crypto random uid there we go that should be all the information we need now if we give this a refresh we should see we have our order ID our purchased ta and our price page showing up which looks really good now the next thing I want to do inside of our order information is add a brand new section that's going to be for displaying our product information so we're going to have a section inside of here and this is going to be kind of like a card so I'm going to add some classes onto here for Border we're going to have a solid border so we'll say border solid and we're going to say border Gray 500 just like that and also I want it to be rounded a pretty large rounding We'll add some padding into there and on the medium sizes we'll add a little bit more padding and we'll also add some spacing on the top and the bottom of four so there we go that's going to essentially give us a card and inside this card we can render out an image and we need to make sure that we use the image component that comes from react email there we go so we have our image for our source we can't just put our product. image path because this is going to be an image that we can't actually View properly because it's going to be just a partial path we need the full path which is why we actually need to get the process. env. next public server URL just like that there we go and then we can actually get our product image so let's wrap that in there close off our string and we make sure that our product has an image path which is a string there we go that's going to render out the image for our particular product and let's come over to our other types I'll just close out of all these tabs for now make sure I save everything we'll open up just the things that we're working on for now so inside of here our product is going to have an image path and up here we're going to have an image path as well that's going to be a string and for this image path I'm essentially just going to copy an image that we already have so here I'm just going to copy this exact URL I want to paste that down into here and that's going to work just fine this is just a JPEG I believe let me make sure that's correct yep jpeg there we go so now that we have that image path that should work fine if we refresh we should see our image showing up but it looks like it's not quite showing up that's CU it's inside the products folder there we go that should actually fix it we just need to put a slash to the front now if I refresh we should see our image and we can see our image is there obviously we want to add some classes to shrink it down so let's go ahead and work on that next for our order information on our image we're going to add a bunch of class names to this namely actually we can just add a width of 100% just like that and we can also make sure that we get it an ALT which is our product. name now if we give this a refresh it should shrink our image down drastically as you can see it fills just the full container size also we need to add our name to our product so up here name is going to be a string and we're probably going to have our description so we might as well add that as well description is a string make sure I put the semicolon in there I'll copy this over to up here because we're going to need to have our description which is a string just like that and here description we'll just add some description there we go it doesn't really matter what that actually says so now back into our order information everything should be working and we can work on actually rendering out our content so we're going to have a row add some space onto the top of that so we'll say margin top is going to be eight just so it's spaced out from the actual top image section that we have and then what I want to do is I want to add a column inside of our Row in this column I'm going to put a class name here of a line bottom and for this column I'm going to have in seere some text we're going to make this a text that is large bold font size remove all the margin from it except for we will'll add margin on the right of four and this is just going to say the product. name there we go so I'll give that a quick refresh just so we can see what that'll look like you can see the product name is showing up right there and again I'll just remove my image from the screen right now so we can see exactly what everything is looking like now we can move on to the next column column just like that and inside of this column we essentially want to have our download button so we're going to have a button and this needs to come from react email to make sure everything works properly because buttons inside of emails are essentially just links so let's add a black background to this we'll add a white text to it we'll add some padding on both sides of it and we'll make it rounded and we'll make our text a little bit larger so it's going to be a relatively large button for us to work with it'll say download and we need to to specify what the link is for this button cuz like I said in emails buttons are really just links so our HF for this one is going to be our download link so we can get that process.env and we want to get that public server URL slash products SL download and we want to get that download verification ID there we go give that a quick save that's our HF completely done that's all of our classes completely done if we refresh we should see we have a download button showing up on our page over here now to make sure our button shows up on the right I can go into our column and I can specify the alignment on the right and that'll push our button all the way to the right if I give that a quick refresh as you can see and by using aign bottom we pushed our text here to the bottom of our button which looks really good now below that column we can add in a column for our description so we can come in here with a column and this one actually is going to be in its own row so we're going to say row and inside that row We'll add a column and that is going to have our text and inside that text we're going to have our product. description and we'll just add some really simple classes here text Gray 500 margin bottom zero and that should be all we need to do to get this order information working so if I get that a save you can see our sum description shows up right here and we have our download link right now if I click it it'll give me the expired link because obviously that link doesn't work it's just a random ID but this is what we'll actually send to the user which looks really good and really clean so let's go ahead and actually send that email so if we close out of all of this stuff that we no longer need and we go into our app and we go to the web hook section you'll notice here we're sending just a placeholder email we can replace this with our purchase receipt email just like that make sure that I import this properly there we go and now all we need to do is pass along our order we need to pass along our product and we need to pass along our download verification ID just like that and that's all the information that we need now it looks like this could possibly not work right cuz all we need is that ID so we can just change this to be ID there we go so now we're actually passing along everything we need to this purchase receipt email so if I test purchasing something again I should get sent an email that looks just like this with a download link so let's test that I'm going to come over here I'll expand this side of our screen we'll purchase this very first Project Blue a bunch of 42s inside of here make sure that we specify an email and actually to make this actually work we'll need to first go to our admin page so we'll go/ admin and we need to delete the sale that we already have so let's say I'm deleting this second one that way you can repurchase this on this particular user because otherwise it obviously wouldn't work so this product called second we're going to purchase this make sure that we get everything entered in here correctly enter our email there we go click on the purchase link it should redirect us to the page with the download link and then most importantly inside my email I should have this sent to me and if I open up my email you can see I get the purchase receipt with our order ID the purchase date as well as the price that was paid you'll notice our image doesn't show up correctly and that's because we're running things on Local Host and my email provider has no access at all to Local Host but this will work in production because obviously your emails or your images will be hosted in a publicly accessible place I'm just doing this through Local Host which is why the email image does not show up but that's why it does show up in our testing version which is why it's really important to have that testing version that you can use now we're finally on to the very last section which is going to be this my orders page which is just going to send an email to the user with download links as well as receipts for their different purchases because you need to have some type of order history for users to look at so inside of our customer facing section we need to create a new folder for orders and inside of here we'll just create a page. TSX just like that export default function orders my orders page there we go and we'll just return H1 says hi for now give that a refresh you can see that that is working so for this we're just going to have a really simple card that shows up on the screen inside of a form so we'll say card get that from the correct component section and for our form I want to make sure that this is not too large so we'll say Max width is going to be extra large and we'll center with the margin X on auto so now inside of our card we're going to have our header for our card which is going to have a title just like that this will say my orders and then we're going to have a card description which will describe what this form is enter your email and we will email you your order history and download links and the reason we're doing it like this is because we don't have any authentication for our users so this is going to be the way they authenticate themselves through email it's much easier than creating your own authentication and gets the exact same purpose across now if you wanted to scale your application and add more things and eventually add authentication it would be incredibly easy to add because instead of having this my orders page you would just have a login button that they would click on and then they would have a my orders page that would show them the information and since all of our emails are written in react converting our emails to actual pages is really easy so this is a really easy way to actually get started with this authentication without doing a lot of the hard authentication stuff now here's our card content and inside the card content we essentially want to create what our form is going to look like so we're going to have a div here that's going to have our form information we're going to space these out in the y direction of two and then we're going to have a label and we're going to have an input just like this our label it's going to say email make sure we get this imported correctly same thing with our input make sure we import that correctly import here type of email we're going to have this be required name is going to be email ID is going to be email and we don't even need to really worry about a placeholder so we'll just leave that off and our HTML 4 is obviously going to point to that email ID so now we can see we have our email field right here which is great let's finally add in a card footer and this will be for a message that we're going to put essentially as well as our submit button so we'll put in our submit button the reason I'm creating this in its own component is because I want to be able to actually use some hooks inside of here so we'll export a function actually not export we'll just create a function called submit button just like that and the submit button is going to be able to get that pending status from our form because we're going to be using essentially form statuses and so on so we'll say use form status there we go and then we can return our button now inside this button if we are in the pending State then it'll say sending otherwise it'll say send just like that and also we're going to add in here some class name there we go this is going to be a width of full size of large disabled whenever we are pending and of course a type of submit and then we're going to make sure we import our button there we go so now we have our button being rendered out right here which is exactly what we want we're going to need to make sure that this is a client component there we go so you can see that that is working just fine and then up here is where we can actually set up our form and so on so let's go ahead and actually create the action for this so inside of here we're going to create a brand new folder this one's going to be called actions orders. TS and we're actually going to make it a TSX just because we're going to have some jsx inside of here now this is going to be a server so use server export function email order history and we're going to make this an async function and this again is going to take in our previous state which is unknown that's because this is a server action and it's going to take in our form data of that type form data there we go make sure I get the correct spelling on that now I'm also going to manually specify the return type of this and that's because this return type is going to return to us a promise that is going to have a message which is optional and and a string or it's going to have an error that is optional and a string just like that make sure I close all this off there we go perfect and now what I want to do is actually go through and make sure all of our form stuff we submitted is correct like our email is actually correct before we send the email so first of all const result is equal to I want to get an email schema which is just z. string. email so we'll put that in its own variable email schema just like that make sure we import Zod there we go so I can take my email schema I can safely parse this and I can pass in my form data. email because that's the only thing my form is passing up so this is just going to make sure that my email is valid so if my result. success is equal to false well that means I had an incorrect email being entered so I can return an error that says invalid email address there we go so now it's returning that error down to us otherwise we're obviously going to return some type of message to the user if we have some type of success so I'll just leave that blank helps us get rid of those errors up there so now what I want to do is I want to get my user so I can say my user is equal to awaiting db. user. find First and actually we can do find unique since we know that emails are unique and we want to get them where our email is our email and this is coming from our result. dat there we go and what I want to do is I want to select only the fields that I'm going to need so I know that I'm going to need my email field so I'll set that to true I know that I'm going to need to get information for all of my orders and I want to select specific information from here so we need our price paid in sense we need our ID we need our created at and we also need to get the information about each individual product for that order as well so here I'm going to select my ID which is true my name there we go image path and then finally the description this is just all the information we're going to send along inside of our email you can see this is quite lengthy but this is just selecting the information we need for our particular user now the final thing I need to do is add an order verif or download verification to each one of these users so I'm going to say my orders is my user. orders just like that and of course we could have a no user if we entered in an incorrect email so if our user is equal to null what I want to do here is I just want to return to us a message I'm going to copy this message and this message is just going to say check your email to view your order history and download your products now you may be wondering why I'm sending this successful message if the user is not there and that's because I don't want to tell people whether or not that email actually exists because that could be a security vulnerability since you could tell which emails people signed up for so this way I'll never be able to know if this email is actually correct or not if I'm entering my information it'll just always say Hey you entered an email the email was valid we don't know if there's a user or not but just go check your email it's again a security precaution so now what we can do is get our orders from our user we want to map through them and for each order we essentially want to add a brand new download verification to this so I'm going to return the entirety of my order and I'm going to add one single thing which is a download verification ID just like that I want to create it so that's going to be from my DB so we'll say download verification is db. download verification. create I'm going to create a brand new one where the data here expires in 24 hours so we'll say new date date. now plus 24 * 1,00 * 60 * 60 and then we also need to add in the information for our product ID which is just our order. product. ID there we go so now here's where we can actually send out our orders from our email so what we can do is we can say resend which we of course need to create all the way at the top here const resent equals new resent process env. resend API key as a string there we go and now we can send out that email so down here resend. emails. create and actually we'll do send and this email is going to be very similar to our other email it's going to have a from field which is going to be from support and then it's going to use whatever email field we specified process env. sender email just like that close that off we're going to have a two field which is going to be our user. email just like that and then finally we're going to have our subject which is order history and then finally our react is just going to be that order history email so we'll say order history email and this is a component that we haven't quite created yet but we'll just render it out like that now this email is going to return to us some data and if that data is an error make sure we await this up here then we know that there is some type of problem sending that email so if data. error then we're going to return an error there was an error sending your email please try again there we go otherwise we'll send down a success message so I'm just going to copy this message from up here and paste it down there that's going to be our success message so now we just need to Define what this email is going to look like and it should send it to the user with all the information that we need so I'm going to close out of all these different tabs that we have open I'm going to go to the email section and I'm going to go down to my order history and it's going to look very similar to our purchase receipt so I'm just going to copy this over this is going to be our order history email and the order history email is going to take in some preview props and we have our props up here so order history email props and order history email props order history email props now for this order history we obviously need to change our preview to say order history and downloads there we go then inside of here we want a lot of this to be pretty much the same the max width is the same this will say order history instead and then obviously this order information is going to be looping through a different set of orders so our props are going to be slightly different we're going to get in an array of orders instead so up here we have an array of orders and each one of these orders is going to contain an ID which is going to be a string it's going to contain a price paid in sense which is a number there we go it's going to have a created at which is a date it's going to have a download verification ID I'll just copy that over because that's exactly the same and then finally it's going to have a product and it's going to be all this product information right here so we can just copy all that over close all this out make sure we get all of our parentheses closed off and as you can see we now have an orders which we make sure is an array of these different objects that contains all of the this information we'll come here and fix these preview props in a sec but down here we're going to have our orders we're going to map through each order and each order is going to return to us one of these order informations so we can come in here the key is going to be our order. ID we can pass along our order our product is just our order. product and this is our order. download verification ID so that will give us all the information for each one of our orders the only other thing I want to do is I want to get the index as well from here just like that I'm actually going to put this in a react. fragment so my key will go on here there we go and the reason I want to do that if I just remove the key from this make sure I import react is that I want to add an HR at the bottom here and this HR is specifically coming from react email and I want to add it for all the emails except for the last so if our index is less than orders. length minus one then I'm going to render out that HR element that'll just put a line between each one of our different elements except for the very last one will not have a line so now let's fix these preview props we'll say our orders it's going to be an array where we have all these different props so my ID is going to be a random uu ID so we'll just copy all of this up into here there we go we're going to copy all of our product information into there as well and then finally our download verification ID will go up into there as well let's remove all this and let's create a second one just so we can test what this will look like with two separate things so again we're going to do this same date doesn't really matter we'll change the price here to something different we'll say product name to some other description and the product path can go to the same exact image or if we want we can select a different image so let's actually copy out a different image go into our public folder and we'll copy this image instead so now we can paste that down here there we go so now hopefully this will show up for us so now if we go over and we look at this we can open up the section for our order history there we go and now you can see we have our order history tells us the ID purchased on price paid and we have the same thing down here for our second product as well and we have download links for both of them that should work so now let's test to make sure this works by hooking up all the last remaining pieces that we have so if we go over to our customer facing section we have our order page we need to add an action to our form and this is going to be an action that we get from that form State hook so use form State we're going to pass in our email order history action and our default prop here is just going to be an empty object so we'll say const our data and our action is equal to that so here's our action now let's make sure all of our data is being processed so if we have errors and so on so here underneath of our input I'm going to add a section to render out our error so if we have an error inside of a div with the destructive text so we'll say text destructive in the there we go and what I want to do is I want to render out my data. error close all that off just like that so now if we have an error we'll render it out right here so let's just type in a completely bogus email address and click Send and you can see is not working let's refresh our page make sure that this works so now we'll send a bogus email address still not quite working so we have something going on and I believe what's going on is we're not showing our message anywhere I want to show the message down here so if we have a data. message then I want to render out the message we'll just put it in a paragraph tag otherwise I want to render out my submit button so I only want to show the submit button essentially if I have not successfully submitted that's just going to prevent us from spamming the form a little bit too much so now you can see it's properly saying check your email to view your order history and that's because we put in a bogus email it's going to say that no matter what now if our email is like invalid like this and we submit our form you're going to notice this says EMA invalid email address which is great and now if we submit a real email address for example webd simplified.svg let's make sure we import that and we pass in all of our different orders that should fix that problem but it looks like our orders are not lining up properly with the type that we specified I believe the reason for that is our download verification ID we need to get just the ID so we're going to say we want to await this and we want to get just the ID from that that should hopefully clean up all that and we want to make sure that this isn't asynchronous function just like that and now all we need to do down here is we need to await promise.all of our different orders because now that they our asynchronous functions it's actually turning them into promises essentially that actually should hopefully fix this so if we give this a quick refresh here enter in the correct email click Send we should see that it'll say that is actually sent you can see it's saying to check my inbox and if I open up my inbox you can see I have the order history for both of these products and I can click download and it's going to redirect me to the download for the first product here as well as for this second product they are both being redirected properly so that is working great and that's how you create a full nextjs e-commerce site from scratch now if you enjoyed this and want to learn even more about nextjs and some of the more advanced complex topics I highly recommend checking out my full nextjs simplified course I'll have it linked down in the description below it covers everything you need to know about nextjs and makes creating projects like this incredibly easy
Info
Channel: Web Dev Simplified
Views: 141,481
Rating: undefined out of 5
Keywords: webdevsimplified, ecommerce, next.js, nextjs ecommerce, next.js ecommerce, next.js store, nextjs store, next js store, next js ecommerce, react store, reactjs store, react.js store, react js store, react ecommerce, react js ecommerce, react.js ecommerce, reactjs ecommerce
Id: iqrgggs0Qk0
Channel Id: undefined
Length: 222min 32sec (13352 seconds)
Published: Tue Mar 26 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.