Build a Complete E-Commerce Shop with Next.js 14, Tailwind, React | Full Course 2024

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
yo my name is Josh and I work as a software engineer at a USA based company called abash and in this single video you and me will build an entire nextjs shop from absolute beginning to complete end to deploying it getting it live on the internet we're going to do that from scratch together in this one video you can probably guess that's why it's so long along the way you're going to learn three things in this video the first one is how to build beautiful functional frontend UI we're going to think about animations about fonts about backgrounds how to make this look really really good so if you put this on your portfolio it's going to stick out I even went as far as hiring a professional illustrator to illustrate the artworks for this project all of which are completely free provided to you secondly this is complete beginning to end there is no course at the end that I'm trying to upsell you all in this video and that even involves the admin dashboard when a user orders from our store of course we need a way to manage that order so the admin dashboard is completely included in this video as well and now before we get to the demo the third thing that I want you to learn in this video and that is the cont ceps the thinking behind writing great software this video is not only coding along with me we go in depth into the concepts of why things work as they do how do you implement a secure payment system that was the reason my first ever potential freelance client rejected me cuz I didn't know how to and I want to teach you how to do it that includes modern data fetching patterns in next ja and it also includes code conventions kind of best practices that I learned along my Developer jobs and that I want to teach you in this video as well so that is my promise to you that's what you're going to learn in this video let's get to the demo and welcome to case Cobra your image on a custom phone case case Cobra what I named this application allows you to capture your favorite memories with oneof one phone cases you can see an example here on the right hand side a phone case with a custom pet image and the user will be able to choose that image as you'll see here in a second we have a testimonial section with beautifully styled green accents I styled all of this myself and of course the green comes from the Cobra color right so these accents are really important and you'll find them throughout the entire website and this image right here along with the image you saw on the very top are the ones that are actually illustrated by the illustrator that I hired for this project so not only are we going to build a completely functional shop from beginning to end but the entire landing page with social proof and a beautiful example product section fully animated are all included in this video and we're going to do them first and we're going to do that animation purely with tail one CSS which is going to be beautiful and lastly to make it very clear what our website does upload your own photo and get your own case now to make very clear to potential users that check out your shop what this is actually about right any image can go on the phone case with high quality silicone material scratch and fingerprint resistant coding and a bunch of other points of course that only serve as an example so let's do it let's create our own case and that's going to take us to the upload section right here where we can either click to upload or fully implemented a beautiful full drag and drop functionality so I'm going to open this image up right here this is my pet rooster and let's drag this in so fully drag and drop supported right here that's going to upload or image to a super scalable cloud storage which is awss 3 and then redirect us to the actual main page of this app where we can style our custom phone case as The Styling options let's make this image a bit smaller so you can see this we can first off choose the color out of three supported you can support any color that you want it's incredibly easy and we can just go ahead and choose those different colors my personal favorite is black let's select the iPhone 15 as the model that we want to choose for this phone case and now the Apple inspired radio selectors if you've ever bought any Apple product on the Apple Store you know they do it just like this with exactly these radio buttons where you can choose between the different quality of materials so for example for the material let's choose the premium soft polycarbonate material and for the Finish let's also choose the textured finish and pay attention to what happens to the price it updates bam in real time as we choose these different options in our case let's go with a fully premium speced out version of our phone case and of course the user can style the image however they want they can drag it around as they wish even without these boundaries right here scale it up scale it down position it however they think the phone case looks best and I think in our case it looks best like this that looks really nice let's continue like this by hitting the continue button and as we continue you can see right here on the top we all always keep track of the current step that we are in and the upcoming steps with the illustrations provided by the illustrator and there we are or iPhone 15ks is the model that we chose in stock and ready to ship with the preview exactly how your user configured the image to be even with a highlight section right wireless charging compatible shock absorption and so on why should people buy from you I think that's really cool and then the material that we use for our phone case and a summary for the prices with dynamically the premium op options that we chose of course listed each one separately so we get to the order total listed right here if a user tries to buy without being logged in of course that doesn't really work right therefore we're going to save their progress very important nothing is lost the entire configuration as we did it is fully saved at this point and we can now log in using the sponsor of today's video which is kind they make it incredibly easy to integrate fully working secure authentication into your app and without them a video of the size would never be POS possible you cannot imagine how much work this is so I'm really thankful that kind sponsors this video it integrates extremely easy in minutes to your application and the authentication just works we can enter our email that's going to send us a code to our inbox so let's grab that code and copy it over hit continue and that's going to log Us in and automatically redirect us so honestly big thank you to kind for sponsoring this and if you want to support me and this video there's going to be a link in the description that if you click it they're going to know that you came from me of course you don't have to use that link but but if you do thanks now what about our iPhone 15 in case as you can see we are on the last step right here the summary where we can review our final design and actually check out to purchase this case so let's do that that's going to forward us to a hosted checkout page using stripe where we can input or email and the complete shipping address and let's actually pay for this product so I'm going to input the payment details right here and then hit pay no we don't want to save the card and that's fully automatically going to process the payment for us securely in the background and when this payment goes through you're going to see this button turn green and two things are going to happen first off it's going to load or order in verify that or order was correct your case is on the way so this means we have actually received the payment we are sure this is verified by stripe and we can also see a live preview of a user holding the phone exactly how the user configured it with their image and how they sized and positioned the image and so on in real time right here on the thank you page that is awesome we have also the shipping address the billing address the payment status which is actually paid of course and the shipping method and the product price and also fully automatically the user receives an email from Cas Copa thanking them for their purchase and telling them when it's going to be delivered and you might have noticed there's a secret dashboard button up here and that's only visible this button is only visible for one person and that's you that's the admin of the page for nobody else this button is visible and also nobody else could visit the page even if they try to this is fully secure where two things happen first of your goals right how much revenue is your goal for this week we're tracking the progress on your financials and also for the month right so we have a $500 per week goal and a $2,500 per month goal with our shop and not only that you can see all the incoming orders that I just made this one right here and the ones I did before which are going to be of course the ones that your customers make with the option to track the status between awaiting shipment by default and once you forwarded this request to your supplier or printed the case yourself and it's ready to be shipped bam just Chang the status right here and you can keep track of everything and if something is fulfilled well great just change it here and it's done and you never lose focus of your financial goals with this sh after that's all done let's sign out that's going to redirect us back to the homepage where we started your image on a custom phone case and you're going to learn so much as I mentioned in the beginning of the video good design Dragon drop functionality implementing secure payments implementing an admin dashboard dude I could go on and on I put put so much effort into the design of the page right here with the phone case and I'm pointing at my monitor that everything looks good that everything works and I want to teach you how to do it all right now if you have any questions on the way my Discord is in the description we're going to have a separate Forum just for this project and your questions so start your code editor follow along that's by far how you learn the most I really encourage you to follow along at your own pace to learn this with me and then let's get started okay man let's finally get started let's get coding I hope you're ready I hope you prepared everything your code editor um that's all you need pretty much and I'm going to start right here on the desktop so let's open up our terminal and CD to the desktop because that's where I want this project to live but of course uh you decide where to put this project right and then we're going to say pnpm DLX and by the way pnpm DLX if you've never used PN pnpm before is the same thing as npx right it's the same thing just for different package manager it does the same thing pretty much the same as yarn right it doesn't really matter which one you use I'm going to use pnpm DLX because that's automatically going to install all the dependencies for next um using pnpm as a package manager which I really like so we're going to say pnpm DLX or npx create-- app at latest so it's going to use the latest next CH version and then we're going to hit enter first thing that's probably going to ask us yes is the project name what do we want to name this let's say case Cobra and you can hit enter right now because I've already got a project named exactly this I'm going to say dashd but same thing let's hit enter that's going to ask us a lot of questions would you like to use typescript oh hell yes we do very important would you like to use esland yes we do it just makes good code quality a bit easier let's use twiin CSS yes um would you like to use the source directory this is very much personal preference right I really like doing this I think the code is is more like you have a better overview of the code that way so I'm going to choose yes here um but you don't have to would you like to use app router yes we're going to use the app router and no we don't want to um customize the default import Alias perfect that's going to install all the packages using pnpm which is really fast because it uses Sim links so it doesn't download all of the packages you can see right here it downloaded only four and then it added the rest because it reused those packages and from other projects that I already have that use the same dependencies and that's why pnpm is so fast but in the end it doesn't really matter if you use npm or yarn or pnpm you can follow along using any package manager awesome that installed everything let's clear the screen and then let's CD into that directory we have just created case cpra ddev or in your case maybe just case cpra whatever you named it hit enter and in this directory let's say code Dot and all that's going to do code dot is say vs code open up the current directory that's what the dot is for and that's going to open this up here on my left hand side beautiful let's zoom in so you can see this a lot easier and first thing we're going to do is verify if everything went correctly so I'm going to say Yar Dev or npm runev same thing we're going to execute the dev script and ideally that's going to start or development server that we can now um use and verify that xjs has in fact installed correctly so we should see like the basic blank nextjs page right here once this loads and sometimes the first time this loads takes really long I think that's normal yes there we are default nextjs page beautiful everything went correctly and the reason because it takes so long for the first time is because it has to create the next folder where it draws all the stuff from like the chunks and uh everything that it uses to display the website the first time we start um or app well it doesn't have that folder you know has to create everything from scratch beautiful now we know that nexj is installed successfully beautiful and the first thing that we're going to do together is start at the landing page and make things look good and why doesn't this take up all of the screen space there we go perfect okay so first thing we're going to do is start on the landing page to see like results fast right um so we're going to stop um stop the development server for a second there we go and the first thing we're going to do right now is say npx Shad zn-i at latest in it in or command line now what does this mean if you're not familiar with what this does basically Shar CN UI is a component library right basically like a UI Library you could say that gives us very nice looking Styles out of the box it's accessible I use it a lot on my projects I really like it and just executing the init command the initialization command here in our CMD is going to do a bunch of stuff the first thing it's going to do is ask us a bunch of questions with which style would you like to use doesn't really matter what you go for here it's very much personal preference I like New York let's go for zinc as the base color it's going to ask us would you like to use CSS variables yes we do that's important um we're going to hit yes there and that's going to do some stuff so for example one thing it's going to do is create a lib folder with a U.S file we didn't do that right nextjs didn't do that that's what the initialization command just did right here and this is a really useful um function we're going to reuse a lot across the entire application that basically allows us to merge class names it's very handy you're going to see that later and out of the box we get it with the UI Library same thing as some changes to or global. css right it puts some colors here that look great out of the box that we can also change later if we want but all the default styles that come with it look pretty pretty good as you're going to see here in a second when we start with the landing page so this is going to make it much easier to get get a good looking landing page and then we're going to start back up yarn Dev npm runev or development server okay beautiful so let's navigate to the main page. TSX under the sourcea folder right that's where the main landing page lives that we can see when we navigate to Local Host 3000 we can get rid of everything that's in here let's just close that down and delete the entire um main tag that is in here by default we won't need that and then let's get started in actually building the landing page and making something truly cool something that you can put on your portfolio something that you can be proud of um it's just a really nice app we're building right now together okay so let's create a diff with a class name and that class name is going to be BG slate of 50 which is a Tailwind color and if we hover over this you can see what this actually does which color this applies like this one right here I'm assuming that is you have the Tailwind CSS intelligence um extension install this one right here tail when CSS intelligence if you've got that vs code extension installed then you can hover over this and see what this actually applies under the hood I really like this extension it's much easier to get a feeling of which CSS is actually applied that way okay and inside of this diff let's create a section element and inside of the section element we're going to create a custom react component and that react component is going to be called the max with wrapper and the purpose of this Max with wrapper is to be a reusable component we can well reuse across um our entire application so we always have a well the same like padding and width on the elements right so the page just looks very consistent we are going to create that um in our components folder right here that was made for us by the UI Library nice we can just use that and let's create a new file in here called Max with rapper. TSX typescript XML and in here let's declare a con Max with wrapper and that's nothing more than an arrow function right default react component nothing special and that's export that as the default at the bottom the max with wrapper there we go now in here we're going to return a div element and that div is going to get a class name and that class name is not going to be a string but it's going to be something Dynamic and now is the first time we're going to use our CN helper function from or utils that we can just import like so with a default import alias and now comes two things okay this function takes as much arguments as you want basically but we're going to pass it a string first and that's going to be the default class names that are always applied well by default this is going to be a height of full in mx- Auto a margin X of Auto a width of four a maximum width of screen XL a padding X of 2.5 and on medium devices and upwards because Tailwind is mobile first a padding X of 20 and then we're going to say class name as the second argument into that function now where does this class name come from right this doesn't exist in or current setup here well that's going to be passed in as a prop a property into this react component we're going to receive the class name and also the children in this component you're going to see why the children in a second here when we use it and to tell typescript the type of what these will be we can um type them out like so we can type them right here that's going to be the class last name optionally that's why we put a question mark we don't have to pass it and that's going to be a string if we do pass it and then the children which are of type react node that is a type we can simply import from react right up here format the file beautiful and now we can simply render out these children right here dynamically inside of our Max with rapper component between the div perfect let's save that and let's demonstrate why we even did this so in our main page TX let's switch back we can now import this component and the cool thing is we can now give it a custom class name based on this specific section which we don't want to apply to all the other um times where we reuse the same component for example let's give it a padding bottom of 24 a padding top of 10 on large devices we want this to take up a grid on large devices a grid Columns of three on small devices a padding bottom of 32 on large devices a gap X of of zero on extra large XL devices we're going to give it a gap X of eight then on large devices a padding top of 24 on extra large devices a padding top of 32 and lastly on large devices a padding bottom of 52 very long class name I know I think this is one of the longest class names in this entire um setup don't worry and in here we can now pass any children a div element for example and these children are automatically passed as a prop into our Max with rapper right as the children that we're accepting right here and then rendered out inside of the diff essentially just wrapping them with a reusable width approach which is really nice so we can reuse this across our entire app and if we ever need some specific last name we can apply it just like this that we don't want to reuse beautiful the div inside of here is going to get a class name of call span 2 a padding X of 6 on large devices a padding X of zero and on large devices a padding top of four and inside of here let's create one more div and this one is going to get a class name of relative of mx- Auto of text- center of large text- left of flex Flex Das Co so we can align stuff vertically so this is the flex direction that we're applying here with flex Co in items Das Center and lastly an items Das start on large devices and upwards beautiful let's open up this div and inside of here we're going to put one more div and this is going to contain the snake image on the homepage so this diff is going to get a class name of absolute this is going to be a width of 28 a left of zero a minus top minus 20 a hidden and on large devices a block so only on large devices will this div actually be shown right let's give this just a bit more space there we go let's open this div up and inside of here is going to live an image right here this can be self-closing and the source of this image is going to be slash snake minus one.png and what the slash does is basically it tells next year as hey we want to import an image from our local public folder right so/ snake one.png would be public snake 1.png it would look for that file inside of our public folder now of course that file doesn't exist yet by the way we can get rid of everything in the public folder we don't need that we're going to provide our own stuff here and the easiest way for you to follow along is to visit the GitHub repository that I made for this project and then go into the public folder and literally grab all the assets that are in here right just grab the images put them in your public folder right here it's going to be the easiest way to follow along um I've got this open here on my right hand side monitor so I'm going to drag in all the stuff from the public folder that's also in the GitHub repository here on the right hand side put it in the public folder so we have a bunch of images um inside of here great let's give this a bit less space again there we go and if we save that then we should be able to see that hopefully um there is going to be if we put this into a side by side the snake image well right now there's not but it's going to be in a second when we create the heading first off let's give this image a class name and that's going to be a with of full okay and now let's go below this diff right here and actually create the main heading in H1 for or landing page H1 there we go and in here we're going to say your image on a and then inside of a span element right because we're going to style this a bit differently custom and after the span element we're going to put phone case right we can already save that okay that's on the page great but it doesn't look very nice yet so let's give this H1 a class name and that class name is going to be relative it's going to be WID of fit a tracking Das tight which if you're wondering what this is is the letter spacing so it's going to be a bit closer together it looks a bit nicer it's going to be text- balance it's going to be margin top 16 font Das bolt and then exclamation point for important right because we need to overwrite a default setting here it's going to be leading Das tight a text Gray of 900 a text of 5xl and on medium devices and upwards a text of 6 XL and lastly on large devices it up a text of 7 XL okay let's format this let's save this let's see what happens on our page your image on a custom phone case that looks a lot better already right but the custom let's apply a custom class name to the span element that we created and that's going to be a BG green of 600 a padding X of Two and a text- white in head safe all right that does look already a lot better beautiful and below this H1 element let's put A P tag and this P tag is going to get a class name and this one is going to be margin top of eight to kind of space it out from the heading a text of large oops LG for large on large devices a padding right of 10 a maximum width of Pros which is um like 65 ch right it's a good kind of width that tail gives us out of the box for text how wide it should be a text- center on large devices a text- left text- back balance and lastly on medium devices a tx- WP and hit save now inside of this ptag we're going to say capture oops capture your favorite memories with your own comma and then inside of a span element because we're going to custom style this as well one of one and the way we're going to style this one of one text is going to be with a class name of font D semi Boldt just to make it stick out a bit more it looks a bit nicer and then after the span phone case case Cobra allows you to protect your memories not just your phone case and this is just some marketing stuff that I came up with right maybe you want to change it uh is it the best probably not but is it you know does it look good on the homepage I think it does um all right beautiful that looks very very nice and then after this pag let's create a UL an unordered list just like like so and this unordered list is going to get a class name of margin top 8 a space y of two a text- left a font D medium there we go a flex flex-all for Flex column an items D center items D Center there we go and on small devices and upwards in items Das start beautiful inside of this UL element let's open that up we're going to create a div with a class name and that's just going to be space Y2 to vertically separate these now inside of this div let's create an Li element a list element and this one is going to get a class name and that's going to be Flex a gap of 1.5 and items D Center and lastly a text- left let's open up the dev element and inside of here two things are going to happen first off is going to be the text that we're advertising kind of this one is going to be high quality durable material right that's it save let's see what that looks like all right right here great that looks very nice however before that we're going to add a check which is going to be an icon now there's a really nice icon library that I want to show you that we're going to use in this project and that's Lucid dodev I use it in almost all my projects it's great gives you beautiful looking icons out of the box and we can install this icon library in another terminal just like so by saying PN npm install npm install yarn add doesn't matter which package manager you use luced D react and hit enter that's going to download the package for me because I use pnpm bada bing bada boom it's just going to reuse what we already have or no maybe I was wrong maybe it did download it I don't know anyways uh it's going to add it to our project so we can now import it right here from lucd react beautiful and this check is going to get a class name and that's going to be a height of five a width of five a shrink of zero so it should never be smaller than these values we put right here the height and the width and lastly a text green of 600 and hit enter all right that is already starting to look like something now Pro tip if we mark this Li element and hold shift alt and arrow Down Bam we can copy this entire element as it is down and we're going to make use of that to just change the text to for example 5year print guarantee I think that's spel guarantee I think that's spelled correctly it's a difficult word let's save that bam there we go fiveyear print guarantee beautiful let's do that one more time Market shift Arrow shift alt and arrow down and then last one we're going to say is going to be modern iPhone models supported and hit save on that as well beautiful there we go that's our kind of checklist section on the homepage I think it really adds to the landing page it's very clear to users and what they're going to get if they order from or store very very nice after the UL element let's continue and we're going to continue with a div element which is going to get a class name and this one is going to be margin top of 12 a flex a flex-all on small devices a flex D row an items Das Center on small devices in items D start and a gap of five to kind of separate them vertically awesome inside of this div we're going to create one more div element and this one is going to get a class name of flex and minus space Min - x - 4 you're going to see why right now let's open up this div and inside of this div element let's create a self closing image tag right and this image tag is going to get two things first off is going to be the class name and this one is going to be inline Das block a height of 10 a width of 10 a rounded Das full a ring of two and lastly a ring slate of 100 and then the second thing the image is going to get you can probably imagine is the SRC the source and this one is going to be slash users SL user- one.png and if you want just for accessibility reasons we can also give this an ALT tag this is optional but it does help visually impaired people so it's generally a good practice to do and that's going to be user image for example let's hit save let's see what happens bam there's a user image but if you remember the demo at the very start there were a lot more user images so let's recreate that and you already know the trick by now right it's shift alt Arrow Down Bam copy it down this one is going to be user 2.png right that makes sense the next one bam let's copy it down is going to be user 3.png let's copy it down once again shift alt Arrow Down Bam and this one is going to be user four but not PNG this one is going to be jpeg um there we go and then one more time copy it down this one is going to be user five also JPEG and not. PNG man that looks really nice already and don't ask me why right it's because where I got these images somewhere PNG somewhere jpeg doesn't really matter um same thing and because the last image is a bit skewed we can also apply an object Das cover uh to fix that it's going to look the natural proportions very very nice that is a super important section and we're going to make it even better right now by going below the closing div and opening up one more div and this one is going to get a class name and that's going to be Flex flex-all I justify Das between items D Center and on small devices items-- start and inside of this div one more div element and this one is going to get a class name of flex and also a gap of 0.5 just a little bit you know and inside of here we're going to render out a star icon that we get from from luced react or trusted icon library that I really really like by the way and this one is going to get a class name so the star element is going to get a class name of height four a width four it's going to be pretty small a text green of 600 and a fill green of 600 there we go and to make five stars cuz that just looks much better on the homepage than one star cuz nobody's going to buy from you if you have one star let's copy this down shift alt arrow down once twice thce and fource I don't know so we have five stars you know that's the that's the idea here nice that already looks very good and now below the closing div after the Stars let's create a P tag right here there we go P tag and inside of the spe tag we're going to say first off we're going to put a span element and that's going to contain 1,250 or any amount of customers that you have right that's going to change later of course um we're going to leave it like that for now and this span is going to get a class name of font D semi and then the rest of the P tag not the span is going to say happy customers and hit save beautiful 1,250 happy customers well that looks a lot more trustworthy if you ask me beautiful and do you remember the cool little phone that was right there bam on the hero section in the demo well that's what we're going to create right now and we're going to do that right here above the closing of the max with wrapper so leave all the divs behind we don't need them anymore right above the max with rapper let's create a new div with a class name and that's going to be a call span full on large devices a call span one a width of full a flex a justify Dash Center a padding X of 8 on small devices a padding X of 16 on medium devices a padding X of zero a margin top of 32 on large devices a margin X of zero on large devices a margin top of 20 and lastly a height of fit let's open this div up and one more div inside of here with a class name and that's going to be relative and on medium devices that's going to be a maximum width of XL and inside of this D we're going to create a self-closing image as always right and the source for this image is going to be slash your dash image.png right there and the class name for this image is going to be absolute it's going to be a width of 40 on large devices a width of 52 a left of 56 a minus top minus 20 to kind of move it upwards a select Das none so users are not able to select it using their Mouse a hidden on small devices a block on large devices we're going to hide it again uh by applying a large Hidden and lastly on XL we're going to apply a block once again now this this image in and of itself won't look very good on the landing page we can save that see what happens probably not a lot is going to happen because this is hidden anyways and right now you'll see that this doesn't really appear by the way there is or snake very nice um this won't appear because right now it doesn't have the space that it needs to show um we're going to fix that right now below this we're going to create an image self-closing and this one is going to get a source of/ l.png and a class name and the class name is going to be absolute a width of 20 a minus left minus 6 a minus bottom minus 6 as well and a select Das none beautiful and Below these two images comes the actual phone that we want to show on the landing page right and that's actually going to live inside of a custom component that we're going to call phone to make it reusable in a lot of places and that phone is going to be self-closing and let's create that component because of course it doesn't exist yet we're going to create it in our source folder under components create a new file called phone. TSX and as always and by the way we can close the sidebar that's going to be an arrow function cons phone is going to be equal to an arrow function that we export default at the bottom export default phone beautiful and inside of this phone we're going to return at the top level a div element and this div gets a dynamic class name just like or Max with rapper remember what we did there well we're going to do the same thing here here by importing or CN helper function and by default we're going to apply a relative a pointer events none which makes the user unable to interact with it with with their Mouse because it would be distracting a z of 50 so the Z index and an overflow Das hidden so nothing shows beyond the boundaries of this component and then we're also going to apply the class name to merge with right and if you remember what we did in the max with wrapper well we're going to do the same thing here we're going to receive a class name as a prop and that is going to be typed well not inline right we could type this inline as we did with the max with rapper if you remember uh we just defined the type right there we're not going to do that in the phone case because we're actually going to receive multiple props so let's name this phone props and Define this as an interface right up here interface phone props and we're going to extend this interface extends by something called the HTML attributes which is a type that we can import from react that takes a generic in these um brackets right here that's going to be an HTML div element right and that's a built-in type we don't need to import that and then what we're going to receive as custom props are two things the image source imgsrc image source that's going to be a string and a dark mode which is optional which is why we're putting a question mark and this can be a Boolean if it is even passed now I'm going to explain what this does here in a second don't worry if you've never um used this kind of extension before it's going to be very clear first off let's already import the most important props in our phone so let's destructure the image source the class name let's set the dark by default to false and we can receive all the other props that this component takes um right here dot do dot props um just like so now what is this kind of syntax right here the extends HTML attributes right let's say for example we imported the phone right here on our landing page the component right um now you can see well it receives a lot of props and these are exactly the props that any HTML div element right that's what we specified right here can take so we can overwrite the Styles just like we were using a div which makes this component very nicely reusable right that's the entire point of doing this we could of course just leave this away but then um we won't have like let's say we left it away right just for demonstration you don't need to follow along right now well bam there are way less props we can now pass in we can't style it just like a div anymore you see that's the point of what we're doing right here I think it's very important that you understand why we're doing what we're doing and then let's spread in all the props as if we were styling the div just applying them to the top level div right here um into the div so they are actually applied right and inside of this div we're going to create a self close cling IMG tag oops and that's going to get a class name and the class name is going to be pointer events none a z index of 50 and a select dnone as well and the source for this image right here is going to be well it depends right if the dark mode is passed as a prop lets to a check in that case we're going to use the SL phone- template D dark- edges. PNG and in the other case when the light mode is act activate it we're going to say/ phone- template D white- edges. PNG to use as the source beautiful as the alt tag which is just again generally a pretty good idea we're going to say phone image and below the self-closing image let's create a div element this one is going to get a class name of absolute a minus Z minus 10 to move it into the background and an inset of zero and we're almost done with our phone component let let's open up the div and in here is going to be a self-closing image with a class name of object Das cover and lastly the source for this image is going to be the imgsrc the image source that we're passing into a prop into this phone component now the alt tag can be anything you want for example overlaying phone image right and again this is just for visually impaired people it's not that important or if your image doesn't load if the internet connection is bad then it's going to show the alt tag but generally um it doesn't matter as much let's save the phone we are done here and you're going to see what this looks like it's going to be very very cool so let's navigate back into our main landing page and now we need to pass this um two things first off is going to be a class name well we don't need to pass it but it will look much better and this is going to be a width of 64 and what we do need to pass is the image source right and that's going to be/ testimonials SL one. jpeg which is an image I have prepared for you to use save that and W bam there it is how cool is that and here's the here's the kicker right if we now go into full screen bam your image appears right there the snake appears up there how nice is that hero section I think this is a really clear really nice hero section with social proof with the advantages of or service with a demo of our service and that just looks really really nice okay very very nice so next up is going to be our Navar because it's really important for how well the site looks and also for user navigation of course right and we're going to create that Navar inside of our components as a new file let's create a nav Navar do TSX there we go okay we can close the sidebar and the nav bar is of course going to be an arrow function constant nfar is going to be a self-closing function and let's export default Navar at the very bottom great now as the top level element in the nav bar that's a convention you're going to use the nav HTML element um it's literally made for being a nfar right so it just makes sense to use this one and this one is going to get a class name of sticky let's give it a z index of 100 there we go we want it to you know be above all the rest of the elements a height of 14 an inset X of zero a top of zero a width of four a border dasb for bottom a border gray of 200 to kind of separate it from the rest of the page a background D white-m sl75 so the sl75 is the opacity right if we remove this and then take a look at all the options there are you can see there's a lot of options for opacity and that's what the sl75 means a backdrop Das blur dlg and that's going to look really nice when we scroll over other content because it's going to blur the stuff under it it's going to look very professional and lastly a transition Das all for the nav element now let's open this up and we're going to use a component that we've already done and that's going to be our Max with wrapper and we can probably Auto Import it yes there we go perfect and by using by reusing this one we can make sure that the width is going to be the same as our actual page content that's very very helpful and inside of here we're going to create a div element and this one is going to get a class name of flex a height of 14 in items Das Center ad justify Das between a border DB and lastly a border zinc of 200 that's already it let's open this up and in here create a link element that comes from next SL link and this one is going to get an h and that's going to be the slash so this going to lead to our homepage and it's also going to get a class name and this one is going to be Flex a z of 40 and a font D semi Boldt there we go and let's open this link element up that's basically just an anchor tag under the hood right it's just the kind of nextjs way of doing this and inside of here we're going to say case and then inside of a span element it's going to get a class name of text green 600 we're going to say cobra cobra perfect effect and that's going to be our kind of logo you know so let's already take a look at what this looks like if we put it on our live page and the question is where do we put that on our page right do we do it in our landing page do we put it at the very top right here the answer is no in next chest there's something called a layout under Source SLA or if we go to the kind of file system here um you can see that under Source app layout. TSX this is where we're going to go because the root layout this one right here is automatically generated by nextjs and it will um be visible for all pages in the entire application right so if we added for example the nav bar here in the body let's import the nav bar as a self-closing HTML thing from components Navar there we go this is going to be visible in literally the entire application now it says Navar cannot be used as a jsx component and that's very probably because yeah we forgot to return the jsx in here of course that's important let's save all of our files the layout and the Navar and then we should be able to see yes beautiful the nav bar popping up right here on our page very nice and that's what the backdrop is for as we scroll you notice the stuff behind it is still lightly visible right because we made it 75 opacity and it's also kind of blurred behind the nfbo that's just a really nice professional looking effect that I want to show you that looks good on almost all pages I use it a lot it's a very very nice effect awesome now below this link element um and before the closing diff so still on side of it let's open up one more div and this div is going to get a class name of height full a flex items Das Center and a space X of four and inside of the sff let's open this up well we're going to do a conditional check and that is going to be based on the log in status of the current user so we can show different stuff to users that are logged in in versus users that are not logged in right for now let's quickly mock that at the very top of the um nav bar let's say con user is equal to undefined so let's just say a user wasn't there we're going to implement this together in a second don't worry just for now so we can write the logic for this already um that's a really good way without implementing the actual functionality you can kind of mock it out kind of pretend that the user is not logged in and then implement the actual functionality here in a second so we're going to do the check that I told you about and that's going to be if the user is logged in in that case we're going to return some jsx let's just wrap that in parenthesis for now and in the other case we're also going to return some jsx now what are we going to return if the user is logged in at the top level well we're going to return a react fragment which is basically not a Dom element it just allows us to put multiple children at the same level and these are going to be link elements that we get from nextjs let's mark that and then import it wait Did we already import the link oh we did okay perfect so the HF that this link is going to get is going to be a string and it's going to lead to SL API SLO SL logout right because if the user is logged in then we want to offer them an option to log out again I think that makes sense and the class name that this link is going to get is going to be dynamic now there's a really cool thing I want to show you coming from our UI library that allows us to make this link look good without doing pretty much anything or self and in order to do that let's open up our terminal I'm going to clear this here and let's say npx Shad cn- UI so or UI library at latest so it uses the latest version of it and then add button right and hit enter that's going to install a button component inside of or application that looks good and is fully accessible out of the box and the cool thing is we don't have to use the button itself but we can also only use the button variance from ui/ button where just installed or component right which is a function we can invoke this takes an object and we can pass multiple things in here like the class the size and the variant we're going to pass the size and this is going to be SM for small you can see there's a couple of options here oops uh there's a couple of options normally this should be types saave where doesn't this work that is weird Okay anyways and the variant is going to be ghost oh and I think I know why this doesn't work because we have a syntax error right here let's put a fragment instead of the parenthesis and then uh this why doesn't this well that is weird should I okay sometimes if typescript doesn't work you can just um reload your window by pressing shift alt no shift uh control and P and you can hit developer reload window and some sometimes that fixes things but apparently it didn't very weird normally it gives you typescript intelligence because these are the sizes that are allowed and typescript should know about that right now it doesn't work I'm not sure why honestly but um as long as we pass the size of small and the variant of ghost we are totally fine to go anyways inside of this link let's say sign out this is going to log the user out later as soon as we implement the authentication and then we're going to copy this link element down using shift alt and arrow Down Bam just like we did before and this time we're going to wrap it inside of curly braces to make it Dynamic and we're going to do a check and we're going to check for a property called is admin and if the person is an admin in that case question mark we're going to render out the link element and else we're going to render out null this is how we achieve the secret admin dashboard button that I showed you in the very beginning and of course also we need to mock this out for now const is admin is going to be set to false at the very top of the nav bar and we're going to implement that together in a second very nice so that's I'm going to be the dashboard button let's say dashboard in here and if we want we can also use a Sparkles Emoji that's what I did in the demo at least uh no let's not do that and copy and paste the Sparkles Emoji here I just think it looks kind of nice but of course we don't have to do it um it's just a nice little detail that we can do and lastly we're going to call copy this link element one more time and paste it below the Turner check and this is going to be or um call to action right so this is going to be the button for create case and the H for this API link is going to be slash configure slash upload because that's going to be the first step in our configurator right and the three steps to make your own phone case this one is going to be the URL for the first step that the user is going to be led to and below the text create case we're going to create an arrow right icon that we get from luced react this is going to be self closing and with a class name of margin left 1.5 a height of five and a width of five beautiful and let's remove the variant of course we want this to be a primary button a big colorful button right and instead we're going to pass a class name and this class name for this button is going to be hidden small Flex items Das Center and gap-1 there we go oh and of course this also needs to be an object format so not an equal sign but instead a colon right here beautiful okay very nice and we can copy the entire fragment that is rendered if there is a user from the turn check to the end right that we just wrote copy the entire fragment and let's paste it in the other case as well and by doing that we just save ourself a bit of writing work right so we can just kind of change this up right now so if there is no user in the case we're handling now we want to offer offer them an option to sign in right but also an option to sign up first if they don't have an account we want to be able uh we want them to be able to make an account and that is going to happen at/ API orregister that's where they can make their account and we're going to get rid of the is admin check completely nothing is going to happen in that case the second link we're going to have in the case there is no user is going to lead to the/ API SLO SL log right and the class name of course is going to be the same as the sign up button we don't want it to look differently so we're going to replace this entire class name we had from above with the um first link element so it looks the exact same right here and in case of sign up for the first link the second one is going to say login right if we save that we should already be able to see what happens let's give this a bit more space there we go sign up and log in that looks very nice however we don't want the right arrow inside of the login button that just looks a bit weird instead let's create a visual separator right below the second link this is going to be a self closing div element with a class name of height 8 a width of PX which is just a single Pixel if we hover over it 1px very nice a background zinc of 200 a hidden and on small devices a block and then the last thing is we can simply copy and paste the create case link down from the first first kind of turn re check here and paste it below or visual separator and then hit save very nice let's see what happens oh yes this already looks very good sign up login then the visual separator and then we have the let me zoom in so you can see this easier um the create case button right here that looks beautiful and by the way little Pro tip right now this button is black in the demo I showed you it was green right it was the primary color of this website how do we do that do we have to manual go in and change all the colors no in our UI Library there's a tab called themes right under ui. shad cn.com and in here bam if we head over to themes we can simply choose any Theme by clicking customize and selecting any color that you want basically you don't have to go with the green that I'm going to choose right now you can go for any other color it doesn't really matter and then we can click copy code right here copy everything that's going to give us all the CSS values for that color you just chose and if you now go back into our project right here give this a bit more space into our global. CSS then we can replace all the CSS stuff the dark and the light up until the add layer base don't include that and just hit crl + V to paste that in if we now go back into our app and give this a bit more space then we can see the default color for all the stuff has changed for example for the button and that's just a really nice little feature of our UI library to make our app look that much nicer now how cool would it be if these buttons actually did something right if users could sign up and log in already um and the answer is actually it's really easy to get working authentication up and running in literally minutes right and that's where the sponsor of this video comes in which is kind an off for modern applications provider um and they're very kindly sponsoring this video and I'm really happy they do because these videos like real talk they're a ton of work to make like they take really really long like 100 200 even sometimes 300 hours like all the recording code preparation everything around it it takes so long and if you want to say thanks right for for this video um there's going to be a link in the description that's going to lead you to their website through my link so they know that you came from me um and who knows man maybe they want to work with me again in the future so I can uh keep making these videos uh if you do big thanks right if you visit the website through the link and big big thanks and of course you don't have to right yeah you can just type in kind.com if you want and to follow along right now but if you do click then they know that you came from me it's like a tracking link that includes a parameter that has Josh in it so they just know you came from me and if you do again a very big thank you if you click that and who knows man maybe they want to keep working with me okay so what we're going to do for the authentication it really just takes two minutes to set up I I really like this um is we're going to log in with Google with GitHub it doesn't really matter whatever is easiest for you for you or you can also just choose your email um and they're going to mail you like a link that you can use to log in I'm going to land on my admin page because I already have an account with them and I already use them um if you've never worked with kind before chances are you don't have that in that case let's create a new business together we're going to walk through it step by step and we're going to create a new business um right here so basically what that's going to allow us to do is enter a name and then once we did that it's loading right now it's going to give us all the credentials that we need to make authentication work in our app as the business name let's use case Cobra um and I'm going to use Dash Dev because I might have already used the case Cobra name um but in your case you can just leave it as case cobra that's going to give us the kind domain number of employees doesn't really matter and I'm going to choose Europe Ireland region because that's closest to me um and you can choose the one that's closest to you and then that it save oh and that can only contain letters or numbers let's say case Cobra one in my case and then hit save beautiful that created our business right up here and then let's click the case Cobra 1. k.com maybe I already claimed the name by naming it that maybe you have to name it case Cobra 2 or three or something um but it it doesn't really matter what you name it right it it really doesn't matter and let's click I want to start a project from scratch and then that's hit next as the tech we're going to use it's going to automatically forward us to the documentation we're going to choose nextjs let's hit next right here and then you can select which authentication kind of methods you want like Google Facebook and then you can already preview your signin form right here I'm just going to leave it as email it's the most straightforward if you want to offer Google just check it right here very very easy and then let's hit next beautiful okay very nice almost there finish setting up your project and we're going to say connect to your next JS code base and hit connect right here and that's going to basically walk us through step by step on how to do this and honestly it couldn't be easier this is by far my favorite part of kind it's very easy to set up and I'm not saying that because they sponsor me I've used this before and uh it just takes like uh 2 minutes I'm going to show you so we can copy the npm link the npm package name that we need to install and let's yeah let's quickly clear our screen there's a lot going on here let's close out of that and then let's say pnpm or npm install um at kind OSS kind of nextjs which they give us here in the dashboard we can just paste that name in that's going to install all of the stuff that is needed and then we can already grab the environment variables that's going to be super helpful let's click copy right here um and that's going to be something like the issue URL the site URL that we're running or app on which is Local Host 3000 and then once we deploy your app which we're also going to do in this video no worries and we're going to change this to the deployment URL later right for now let's go into our VSS code let's close out of all of them and let's go into our side bar right here and create a new file at the root of our project and that's going to be the EnV file by the way we can already um go ahead into our G ignore and type EnV in here as well just so when we deploy this later we're not accidentally going to commit ORV file as well then we're going to copy all the environment variables that K gives us simply paste them in this EnV file and that's it we're done right these are sensitive pieces of information like or um secret or password basically for the authentication that's why we put it into a EnV file and that's it we can close out of the EnV file we're done here next thing we need to do is go and create this file right here under Source then that's going to be app let's create a new folder in here called API inside of the API folder a new folder called off and inside of the off folder a new folder called in angled brackets which is a dynamic route kind of so if you're wondering what the angle brackets are that's a nexts naming convention for dynamic API routes and then let's create a new file in here called route. TS inside of the kind off folder we can simply grab what they give us paste it in here bam we have authentication very very nice and that should already work literally right we can start back up our development server yarn Dev let's close out of the sidebar close uh wait why oh did I start this in two okay I started my development server in two different terminals that's not good uh we already have the development server running never mind um and then let's restart the page and that's all oh what happened here let's restart the development server yarn Dev that should not happen that looks very weird for 404 not found let's restart this that should not happen and then ideally everything should look as before and the authentication should work which we're going to test together right now once this loaded boom there we are okay so let's see if this works let's click sign up and hopefully our authentication is up and running let's see yeah it looks good all right first name last name let's enter an email I'm going to choose hello josr coding.com and then and let's hit create your account that's going to email me a code to my email that we entered I'm going to open up my email here on my second Monitor and then let's see the code that we have received there it is email verification code we can simply copy over the code we have received an the email paste it in here and hit continue very nice and then we should be logged into our application and we should also see the user dashboard okay now we are being redirected to/ dashboard which is a page that does doesn't exist in our app and that's probably because in the EnV file it tries yeah the kind post log and redirect URL that's what it tries to redirect us to let's leave that at Local Host 3000 because there is no dashboard page in our app right so once we log in we're just going to be redirected to the dashboard very very nice and what this also now means is that we can get the current user session in or Navar where we are currently just mocking it out remember if you go to the very top right here you can see user is undefined well that's not really true anymore we can now actually get the logged in user session and we can do that by destructuring something const empty object is going to be equal to get kind server and we should already be able to import this yep get kind server session from the kind npm package that we've installed SLS server very nice and what we want to get from here is the get user function that we can now simply invoke cons user is going to be uh equal to await oops await get user because this is an asynchronous function and that's going to replace or previous mock that we did right and in order to be able to use a weight we can mark the Navar is asynchronous a react server component so this runs on the server and not on the client so we can securely get the um user right and then the is admin check is going to be very simple let's say const is admin is going to be equal to user which is optional by the way they might not be logged in so question mark do email is triple equal to process. env. admin email so the way the admin authentication works is we can go into ourv file and simply name the admin email so for example admin email is going to be equal hello at Josh trite coding.com or this is going to be your email right don't put my email here put your email here so you're going to be your site admin right this can be any email you want and as long as you're logged in with that email well you're going to be recognized as the admin as simple as that so just like that we have working authentication you can see the state has changed we can now sign out because we just logged in if we oops if we hit that let's see what happens well we should be signed out and we are bam we can now sign up again we can log in again or we can create or case dude that is really cool I'm so happy they're sponsoring this video this is just nice uh we got authentication working in like 5 minutes right and then if we go into our kind dashboard let's go to home of our business and then we should be able to see yes total users right here one right cuz we now have one user and the more people use your app the more you're going to see right here along with their email and going to see the monthly active users and so on very nice we can also see them in depth right here by the way if you want under users all users uh you could even delete them if you wanted to do some uh manual testing and so on that's where it happens in the kind dashboard very very nice let's close out of the Navar cuz we're done here and also out of the EnV file now we can continue on our landing page to make it really really good I want to show you how to make an absolute killer landing page that you know hopefully converts very well right so people actually buy from your store and in order for that to happen one thing is very important and that is social proof so why should people buy from you and that's exactly what we're going to create next so with one closing diff to go and below the closing section on our main page. TSX if you're wondering where we are right here uh uh uh um this one right here this page right here um under the closing section this is where we're going to continue right now with and let's just make a common for ourself here right just so it's easier for us to see the value proposition section so what value do we provide how do we prove the value that we provide and that's going to happen inside of a section element as well the section is going to get a class name and that's going to be BG slate of 100 and also a padding y of 24 let's open this up and once again use our Max with wrapper very very handy component bam just like that and we're also going to give it a custom class name and that's going to be Flex a flex D oops Flex Das call items Das Center a gap of 16 kind of space out the items and on small devices a gap of 32 so a lot more inside of this Max Max with wrapper hard word to say let's create a div elment and that's going to get a class name of Flex flex-all on large devices a flex D row items D Center a gap of four and lastly on small devices a gap of six so just a bit more and inside of here we're going to create an H2 element a heading to element there we go let's put that into a view and this is going to say um what or customers say and this h two is going to get a class name and that's going to be an order-1 this is going to be important later because we're going to change the order of some elements we're going to see what that does a margin top of two a tracking Dash tight a text- center a text- balance a um exclamation point for important leading Dash tight so what we did on the main H1 way up here as well same thing we're going to do here for the H2 then a font Das bolt a text of 5 XL on medium devices a text of 6xl and lastly a Tex gray of 900 to make this very dark let's it safe we can already kind of preview what that looks like let's scroll all the way down there we go what our customers say however the customers we can make this look a bit better and that's by wrapping the customers instead of a span element and the span is going to get a class name of relative and a padding X of two and let's put the customer just the customers right nothing else in here so afterwards it continues with say but this allows us to style this word specifically a bit different and we're going to do this with a custom icon and we're going to create this icon we're going to put into the customers which is going to be like an underline very nice looking underline for this inside of our components folder let's create a new file called icons. TSX and this component is going to be very very easy let's say export const icons and this is going to be an object this is not even going to be a normal rea component just an object there we go and in here there's going to be something called an underline this is going to be a function that receives some props and these are going to be of type Lucid props which is a type that we get from our icon library that allows us to style the SVG kind of custom and this is going to return directly a jsx element and basically what is going to return you don't need to follow follow along right now it's going to be an SVG but just so we don't have to type out this SVG there's no point in doing that I prepared something for you called a copy paste list that contains all the stuff in this video that we don't need to type ourself like this SVG right why would you why would we type that that's not realistic Nobody Does that you grab these from websites and then put them into your app right you never type them so we can simply copy and paste them to save ourselves some time and if you notice we're spreading in the props that we give in here and the Beautiful Thing is what these props allow us to do right is anywhere where we now use this icon like in our main landing page where we can import the icons Dot and then we can say underline which is a self closing JS X element let's format this we can now give this props right that's what it's for these Lucid props that we um allow for the underline so we can give this a class name of hidden on small devices we're going to say block a pointer events none so it doesn't interfere with the user Mouse an absolute an inset X of zero a minus Bott Min - 6 and a text green of 500 and hit save let's see what that looks like hopefully if we go into like a big screen there we are beautiful underline that is not distracting on small devices mainly because if there's text under the customers and there's still the underline it would look distracting whereas if it's on one line it looks really really nice with the underline perfect that's exactly what the underline is for very very nice and now below the H2 element um we're going to create a self-closing IMG image element and the source for this image is going to be/ snake minus 2.png the image the freelancer that I mentioned in the very beginning has prepared for us and the class name is going to be a width of 24 in order of zero and on large devices in order of two right so let's see what happens let's reload the page what does this look like Well w bam there is our snake what the freelancer has drawn for us to make this project and what the ordering does right the I need to scroll down a bit here the order zero or order two that is basically for making the snake on top versus on the bottom right um normally it would be below the text because in the HTML it is below the text but we want it to be above the text um and when it's actually rendered out because it looks so much nicer and that's what the order is for and if we go into big full screen then you're going to notice that the snake is actually on the same level and I just think this looks so nice awesome okay so let's give this a bit less space and let's continue below the image and below the closing div and above the max with rapper is where we're going to continue let's create a new div element right here and the class name is going to be MX Auto grid maximum width of 2 XL a grid calls one a padding X of four on large a margin X of zero on large devices a maximum width of none on large device a grid cuse of two and lastly a gap y of 16 for some vertical separation let's open this div up and inside of here let's create one more div this one gets a class name of flex Flex Das Auto a flex-all gap of four on large devices a padding right of eight and on extra large devices a padding R of 20 let's open this up and inside of here one more div element with a class name and that's going to be Flex gap of 0.5 and a margin bottom of two and inside of the div we're going to create a star icon that we get from or trusted UI Library we've already imported that into this page because we already used it in the hero section remember right here so we can just reuse that and the class name for the star icon is going to be height five with five a text green of 600 and fill green of 600 as well and then holding shift a and arrow down we can copy it down once twice Thrice and four ice whatever you know uh so we have five stars once again can already save that see what it looks like let's see right here okay there's five stars that looks good and now comes the first user testimonial right so below the closing divs of the source with two closing divs to go let's create one more div here and the class name is going to be text of large and leading das8 there we go let's open this up and inside of here goes a ptag a paragraph element and in here we're going to say in quotes the case feels durable and I even got a compliment on the design period had oops had the case for two and a half months now and now in a span element right we're going to say the image is super clear so we kind of highlight this point or this part of the user review then after the span element we're going to say comma on on the case I had before the image started Fading Into yellowish color after a couple weeks love it now is that a real user review hell no I made this up right but in a real world shop of course you're going to use a real user review here this this is just a kind of well I just wrote it to make it look good but of course you can uh probably imagine that this should be real in your actual shop now the span element in here is going to get a class name and that's going to be padding 0.5 a BG slate of 800 and a text of white let's H safe and already see what this looks like all right we can see it right here very very nice and below the closing ptag below the closing div with two closing divs to go we're going to create create one more div right here this will get a class name of flex a gap of four and a margin top of two and inside of this div we're going to create a self-closing image element the class name of this image is going to be rounded full a height of 12 a width of 12 and object D cover and the source for this image is going to be slash users SL user- one.png right and then of course you can put an ALT tag let's just just say uh user all right and then below this um self-closing image let's create a div with a class name and that's going to be flex and flex-all so we can sort this vertically with a P tag inside of here that's going to say Jonathan which is going to be the username that wrote this review and the P tag is going to get a class name of font Das semi Bol and below this ptag we are going to create a div element this one is going to get a class name of flex Gap 1.5 a items D Center and a text zinc of 600 now inside of this D we're almost done by the way comes a check which is a icon we get from Lucid react right we already used it way up here in our landing page as well that's the check we're going to reuse it down here so this is going to be self closing the check icon with a cassm of height of four withth of four is stroke and then in angle brackets for a custom value that's going to be 3 PX for three pixels and lastly a text green of 600 for the check mark and below this this is just going to be a ptag saying verified purchase and this ptag is going to get a class name of text small that's it right let's format this file let's save this file let's see what it looks like bam Jonathan verified purchase wrote this review that looks absolutely beautiful and because we want two user review use and not just one we can go ahead and grab this entire div element and I have a shortcut to mark this entire div element in vs code that is called emit balance outwards that's what the shortcut is called but we can also just do a little trick fold everything here grab the div with a flex Flex Auto Flex call Gap four and so on and just hit shift alt and arrow Down Bam copy and pasing it down and we can also make a little comment for ourself right here saying something like second user review right above the code we just um copied down so we know what this is and let's change this user review right we're we want a bit of different text so before the span we're going to say I usually keep my phone together with my keys in my pockets and that led to some pretty heavy scratch marks on all of my last phone cases period this one comma besides a barely noticeable scratch on the corner comma there we go and now inside of the span element so we want to highlight this part looks brand oops looks we need to spell that right looks brand new after about half a year then after the span element we're going to say period I dig it awesome and then uh just a perod there and a quote to end this and of course just one quote vs code automatically does too let's change the user image this is going to be user 4 do not PNG but jpack in this case and let's also change the name from Jonathan to Josh or well basically whatever you want I just put my name here you can put your name if you want just put your name right here um hit save and let's see what happens bam there is the second user revieww very very nice with the different image that looks amazing awesome and now below this Max with wrapper that we have for the review section is going to come probably the coolest section of the entire homepage and that is the animated reviews that I showed you in the very beginning remember those phones with the images that were beautifully animated on the homepage that's exactly what we're going to build together right now and it's going to happen inside of a div element below this Max with wrapper and this div is going to get a class of padding top of 16 and inside of here we're going to create a custom react component and that's going to be the reviews section now of course that's a component that's going to be self closing and that doesn't exist yet so let's open our sidebar into our source folder and create a new file in our components folder called reviews. TSX and if we take a look at this section conceptually right how will this looks like um basically we will have the entire section right imagine this is the part of the homepage where this is going to live and we're going to have a lot of phones in here in three different columns right so there's going to be column one column two column three with uh each one slightly being offset so it looks a bit nicer there we are and then they're going to move oops they're going to move like upwards um in the animation so the question is how do we kind of architect this conceptually um and I think we should go for a top down approach which means we make the large Parts first and then drill down into the each individual components the alternative would be we start with the smallest part in here like each phone and then go ahead and construct a column and then go ahead and construct the grid I think it's more intuitive if we start from the top down where we start with the entire section the entire review grid and then go down smaller smaller into the individual components um so let's do that sounds maybe a bit abstract but you're going to see it's actually very simple so first thing we're going to do let's open this back up so we can see what we do in real time is going to be export function a reviews right so the main component that we want to import into our page. TSX and this is going to return or well Max with wrapper that we oops that we already know let's import that in here and this is going to get a class name of relative and a maximum width of 5 XL let's open this up inside of here is going to be a image tag self closing once again of course the area- hidden is going to be true so we hide this for screen readers just in nice little detail for accessibility reasons the source is going to be slash what- people oops people- R buying. PNG something I have prepared for us and then the class name is going to be absolute select dnone hidden XL block minus left minus 32 and a top of 1/3 let's format this so it looks nicer and let's already save this and import this reviews component into our page. TSX to kind of see what it looks like right so if we give this a lot more space now you should be able to see that hopefully yeah well nothing really happens because we didn't code any component yet but if we did then we should be able to see uh the image also pop up right so the main component inside of the reviews is going to be the review grid and that's going to be a component we're also going to Define in the same file right here because it's very closely related to this um review main component it's not something reusable and it's not meant for that either it shouldn't be reusable right doesn't make sense so let's define a function review grid up here and that function first off is going to get a reference a react ref let's call this the cons container ref and that's going to be equal to use ref a hook that we get from react and now because in nextjs these components by default are server s side components in order to use react hooks like USF we need to mark this as a client side component by putting use client at the very top of the file first line um say use client and that makes us able to actually use react Hooks and deopt from the server s side rendering of this page and we can also pass this user f a generic to tell typescript what the type will be this will be an HTML oops HTML div element right that is a type we don't need to import or anything it's already baged in or this could also be null because we're going to initialize it as null and once react actually sets the ref then it's going to be the HTML div element and this user ref we can already attach to a Dom node so we're going to return some jsx from this uh review grid and that's at the top level going to be a div element and that's where the Rev comes into play The Rev is going to be the container ref because this div is going to act as the container for the following entire review grid right and the class name for this div is going to be relative a minus MX - 4 A margin top of 16 a grid a height off and then in angle brackets for a custom value 49 RM a maximum height of again angled brackets 150 VH for view height right so it doesn't exceed 150% of the current user view port a gd- calls D1 items D start a gap of eight an overflow Dash hidden a padding X of four on small devices a margin top of 20 on medium devices a grid calls of two and on large devices a grid calls of three that's actually going to Define these grid calls here how many columns we have how many uh Columns of phones there are going to be scrolling by now why do we have the container ref what does it do for us and the reason we have this is so when we scroll on the main page and let's put this somewhere down here this is just for this project um as we scroll as the user Scrolls we only start animating this section once it's in the viewport once the user sees it right we don't want to animate it before otherwise the user didn't even see the animation and in order to check if the animation is in the view of the user we can use this container ref along with a or probably one of the most popular react animation libraries which is framer motion so in our terminal let's say pnpm install framer Das motion and that package gives us a really useful hook to check if something is in the viewport called use in view so let's explore that together let's say const is in view is going to be equal to use in view which is the hook we get from framer motion let's import that and in here we can pass the container ref simple right we now know if something is in the view of the user or not however we can also pass some options as an object right here we're going to say once true because if we didn't pass this otherwise every time they user scrolled up and down it would animate but we only want it to animate the first time the user Scrolls and the amount right here is going to be um 0.4 this takes a number this could also be sum all and so on but I think 0.4 is a good um in between here it looks very very nice so it animates like when it's a bit past the user viewport so it's already kind of in view of the user and so it doesn't already animate when the user just sees 1% of it cuz otherwise in that case the user wouldn't see the animation either and now is the time to actually construct the three different columns what do we want to show in the columns and the answer is well we want to show images of phones right and to make this as easy as possible for ourself let's go to the very top of the file and declare a cons phones in all uppercase because that is a convention for declaring um constant things across your application you'll find them at the very top in all caps so you know anywhere in code when you see stuff in all caps that this is a constant it's not fetched by any API and so on that's something I learned at my first developer job and I think it's a really really good idea for clarity in your code base right so I want to show you how it works and then we're going to put some stuff in here and that's just going to be strings right so this going to be slash testimonials SL1 JPEG and this actually maps to a path in your um public folder right here under testimonials one. jpeg 2. jpack 3. JPEG and so on these are the images on the phones that we actually want to display right and we can just mark this first string hold shift alt arrow down once twice four five 6 right so 1 2 3 4 5 6 very simple naming convention here and all separate them via commas and that's that's all right these are just the images that we want to show on the phones um in this section so in our review git to actually construct The Columns we can just say cons columns and now basically the idea is that we want to split these phones into three different columns right how do we do that well we can just use a little helper that we're going to write ourself called split array which is a function that doesn't exist yet because we're going to write it and the ideal way in which we're going to call this is we want to first pass the array that we want to split and then secondly how many columns we want to split this into so in our case that's going to be three right and in order to use this little helper well we have to write it let's define a custom function called split array and that's literally all it does right it just takes an array and then splits the array in the number of parts that we pass it so first parameter we want to accept is going to be an array and this is going to be the typescript type of array and we can pass this as a generic T um I'm going to explain what this does in a second and then the number of parts we want to split this into and that's going to be just a number in our case for example three and then this function receives a generic which is also going to be T and all this does right is it checks this T generic right here if we pass for example a string array then this T is going to be a string this generic is going to be a string and that might sound a bit abstract if you've never worked with any kind of typescript generics before but essentially what this allows us to do is to enforce type safety in this function so we can say con result is going to be of type array and this takes a generic which is going to be another array and in here we can pass the T generic so basically we don't know what kind of array that we put in here but whatever kind in our case for example string or this could also be a number array right whatever kind that's going to be saved in the T and we can store that to make the result here type save and that's going to be an empty array to initialize it right so all we're saying is we're going to have an array of an array of in or case strings but the function doesn't know it doesn't care that's what the generic is for Strings this could be numbers then the T would be number doesn't matter because essentially we're going to have a two-dimensional array right of column right um these three columns right here and each column will then have certain rows right so it's going to be two-dimensional columns rows of strings of paths right which could also be number that's exactly what the generic is for so when you think about it it's not actually that complicated but I admit yes if this is the first time you're using typescript generics then it might seem a bit abstract but that's a really good way to enforce type safety and inside of here we're going to create a for Loop now vs code automatically provides us a nice snippet to do that and and the iterator we're going to name I this is going to go to the array. length array just being the parameter that we accept in the function and the first thing we're going to do is say const index is going to be equal to I and then the modulo operator so a what's left from a division that's the modu operator and we're going to do that with the num parts right so we're going to divide and then what is left from that and oh this needs to be named index and if we don't have a result at index we can use the bracket notation to do that if we don't have a result index in that case the result at index so in our case just the module what's left from the division is going to be an empty array and else in that case we're going to say result at index. push and what do we want to push well the array at current I and then after the for loop we're just going to say return result so basically the array that we've been pushing into right you know right here or that we have been clearing so basically all we're doing here is splitting an array into a certain number of parts I think that is pretty straightforward if you're familiar with any kind of uh computer science Basics right so we take an array of strings for example or number array doesn't really matter the generic does account for that and we split it into three parts so what we expect to get back is an array array a two-dimensional array of columns and rows and if we hover over the columns we can see that's exactly what we get back beautiful to actually get access to the columns directly very simple we can say cons column one is going to be column oops columns columns at the index of zero because arrays are zero based cons column 2 is going to be columns at the index of one and of course that needs to be equal to there we go and then the cons column three so the third and last one is going to be equal to split array we're going to call that again and then the column cols at the index of two and we're going to split that into two parts okay and I think this split array function was actually the most complicated part this I'm not I'm not saying it's very complicated but I think it was the most complicated part from here on out it's going to be easier um definitely so now it's time for the fun part to actually render out the reviews right and we can already do that by checking if this stuff is in the view of the user so if is in view in that case we're going to render out some stuff and else we're going to render out null because we don't want to animate anything if it's not viewed by the user right and if it is in that case we're going to render out a react fragment which is just allowing us to put some elements um at the same level without rendering another Dom node for that okay so what we're going to render out in here is going to be the review column a component that doesn't exist yet we're going to create it together in a second and this is going to be self closing now what is a review column basically it's going to be let's go here it's going to be one of these columns containing multiple phone elements right that's all it does and that's a component we can create for example right up here above the review grid let's create a function review column there we go and open that function up oops there we go open that function up now what is this going to receive as props well basically four things it's going to receive the reviews which are the individual um kind of I guess we could also call it phone images right each one of them is going to be a string I guess originally this component I kind of modified it from tailin CSS U was meant for customer reviews but I modified it so it shows phones um you could also just name those phones I guess the class name then the review class name which is going to be a function you're going to see that any a second here that's going to be very cool and then a milliseconds per pixel how fast should these things even move and by default we can say zero and to actually tell typescript what the hell is going on here because it has no idea right well basically the reviews are going to be a string array then the class name is going to be optional we don't have to pass it that's going to be a string the review class name is going to be a callback function but it's also going to be optional right so we're going to put a question mark here as well and it will basically take the review index because the review class name is going to be dependent on the how many if review this is right because if we're on small devices we don't want to show three columns next to each other we want to show them below one another so basically we only have one column right in order to do that we need to know what is the review index and that's going to be a number and this callback function is going to return a string and then lastly the MS per pixel you can probably guess um it's going to be number and it's also going to be optional so we're going to put a question mark here as well awesome and I again I think this reviews component is going to be the most complicated component uh in this entire build but it's also one that looks really nice so I want to show you how to do it you know um so inside of the review column let's say con column ref and you already know the drill by now how a ref works by using the used ref react to and we can also pass this the generic of what the type will be so for example an HTML div element or we're going to initialize it as null there we go so by default it's going to be null and then once react has assigned the ref it's going to be an HTML div element and we can already do that assigning by the way by returning a div at top level and the ref of this div is of course going to be the column ref there we go the class name of this div is going to be dynamic where we can basically use our CN helper function let's call that and by default we're going to apply an animate Das Marque that's what it's called called the animation style that we're going for a space y of eight and a padding y of four and now also pass in the class D right here so if we want to overwrite this wherever we call the review column component we can totally do so because our CN helper function allows us to do that very very nice and then as the style last thing this div will take style right here and we're going to pass in an object and this is going to be a string in which we're going to say D- maret Dash duration how long will this animation take and that's going to be something called the duration a value that we haven't calculated yet which we're going to do in just a second and then we can cast the type of this to make um typescript happy that's going to be as react. CSS properties there we go now the only problem is that it doesn't know what the duration is well cannot find name duration um it doesn't know how long the animation should take and the answer is well the duration kind of depends on how high the column is right because if the column is way shorter right like this uh let me can I move that down like this then of course the element should be twice as slow otherwise it's going to be like way too fast right here right it would look very weird if we had the same speed every time and not respected how high the column actually is right because if the column is half the height then the element will be through the column it will move from beginning to end twice as fast basically um so the question becomes how do we know how high the column is and well basically that's really easy we can initialize a state for that I think there is a yeah there's a UST State snippet I have for that but you might not have it so let's type it out from scratch that's going to be const and then an empty array let's name this column height and set column oops column height by convention is going to be equal to use State a hook that we can import from react and by default this is going to be initialized as zero once this component renders and because one thing that's important is the user can resize this window right on different devices this window is going to be different sizes and to account for that for that resizing we can do that inside of a use effect and kind of listen to that resize event and then set the column height accordingly right so let's create a use effect that only runs at the very beginning on the first render of this component by making the dependency array empty right it's only going to run once and we're going to listen to resize changes of the column right so if we don't have a column ra. current if react has not initialized the reference to this St node yet in that case well we don't care we're just going to return early no logic is going to happen right but if we have that we can use a really cool buil-in API and that is the resize Observer let's call this cons resize Observer and it lets us listen to changes of resizing actions right that's what it does we can say new window. resize OB oops OB and we should be able to see that right here resize Observer that's the API we're going to use and it takes a callback function so every time a resizing action occurs we can listen to that and execute some logic inside of this callback function right so when this changes what should we do well basically we're going to set the column height to to the column ref do current do offset height and we're going to say by nullish coalesence just question mark question mark if this is undefined or null in that case we're just going to set it to zero right because technically typescript thinks that this could be undefined well it's really not right we've already done the check but uh you know just to make typescript happy we're going to say else this is going to be zero right so kind of the fallback value if you will but we kind of know that this will be defined just typescript doesn't know it now in and of itself this would not yet work because while technically we created The Observer we're not using it right it's just there in memory unused so we actually still need to say resize observer. observe we can call that function on here and what do we want to observe the column ref. current which is basically a link to this div right here so basically we're saying with this resize Observer listen to changes in this div that we assigned the ref to and now important important to avoid memory leaks let's clean this up as well in the return function of our user effect inside of here we want to say resize Observer do disconnect and just call that function right here just as a generally good practice to clean up after ourself very nice and now because we always know how high the column is we can change the speed of how fast these phones should move through the column accordingly and that's that sounds more complicated than it is it's dead simple let's say con's duration is just going to be a template string and that's going to be the column height that we dynamically insert into here using this kind of Syntax for JavaScript template strings and then times right here the MS per pixel Ms because these are pixels right this is measured in pixels the zero and whatever we're setting it to when it resizes and we can multiply it by the milliseconds we want per pixel and that's the MS we get right so that's going to be the duration we can straight up pass into or Style and now this animate marquet is not actually a builtin animation ENT Tailwind but it's something really simple we can add ourself in the Tailwind doc config.txt of our directory right here the Tailwind doc config.rb the animation we're going to create it's going to be the Marque animation and this is going to be a string that's going to say Marque VAR D- mar- duration right here that we're setting dynamically remember what we're doing right here that's what we're setting this marquet duration very nice and this is going to be linear infinite perfect okay now let's comma separate them right here and also create that marquet animation that doesn't mean anything right now as a key frame so CSS actually knows how to handle this this is it's very simple let's create a marquet right here this is going to be an object and at 100% just like so we're going to apply a transform property transform and that's just going to be a Translate Y of minus 50% right so it kind of slides up from the bottom minus y of 50 to zero just kind of nicely slides up and Fades in very nice let's save that and now let's hover over the animate Marque and ideally let's re load or window maybe that will work ideally if we hover over this we should be able to see that this now actually applies some kind of class name under the hood and there we are tailwood now recognizes this animation as something that we have created and that is exactly what we want awesome and for each review column of course in each column we want to show the individual elements the individual reviews right and the way we do that is by saying reviews that we pass in here as an argument right do concat and we're going to concatenate it with the reviews again just so we have more stuff to show right if you don't have a lot then well we just duplicate them as they are and then we have more stuff to show in each column that's all we're doing here and then we're going to say dot map and for each image source and the review index that we get right here let's render out or actually let's render out some jsx directly so we're going to wrap this in parenthesis and the component that we're going to use here is going to be called review right that's going to be one individual component that we're going to put right here this is going to be a self-closing um component and it's also a really simple component that by the way does not exist yet so let's just create it right here under the review column let's create a function review let's open up this function and the properties that this review will receive we can destructure right away here in a second let's call these properties the review props which is an interface we can find right above there let's scroll down a bit interface review props and this once again extends the HTML attributes there we go we need to import that from react of an HTML div element which basically just allows us to pass any prop that a normal react div would also take right as I showed you before and the custom prop we're going to extend this with is going to be the image source which is just going to be a string so what that allows us to do is to destructure all the props that any div would normally take along with the image source the custom one that we just added right and we're also going to destructure the class name and the rest we're going to receive as the props which just going to spread them in as is into the top level div and let's do that by the way we can return a div at the top level spread in all the props and this by the way is always the last thing that we do inside of the div because this actually overwrites stuff so if we pass the dot do dot props but past any prop that that involves again then these are all overwriting because they involve the same thing right if it's defined here the area busy and here and of course we're overriding this so this is generally the last thing that you do right so we're going to define a class name before that and this class name is going to be dynamic it's going to use our CN helper function and by default we're going to apply animate Das fade-in a rounded Dash and then an angle brackets for a custom value 2.25 RAM and that's just a value that I found looks very good with the phones that we're going to put in here and by the way we can also open up our main page so we can see what we're doing um in more real time a BG of white a padding of six an opacity of zero a shadow of XL for extra large and a shadow of slate 900/5 so a very light kind of black shadow with an opacity of five that we're applying here and we're going to merge this together with the class name that we can pass into this review as a prop right up here okay very very nice and now we also want to give each review an animation DeLay So they kind of pop in at different times right when we scroll into this not all the columns pop up at the exact same time but first the middle and then kind of the others and it's kind of randomized on how they pop up so it looks really really nice at the end right and the way we do that is very simple we can first Define a couple of possible animation delays let's say const possible and then an all uppercase by the way animation delays because this is a constant array and in here we can put for example 0 S for 0 seconds 0.1 seconds 0.2 seconds let's also put 0.3 seconds 0.4 seconds and one last one let's do 0.5 seconds there we go right so this is all the possible delays there are and the actual cons animation delay that we're going to use is going to choose any random one of these possible delays so we can simply say possible animation oops possible animation delays at the index of math. floor and in here we can pass math. random invoke that times the Poss oops possible animation delays. length so basically all this part means the math. flow math. random is um choose any random number between these that are possible right so at the end what we're left with is the string animation delay which which is going to be any random one of the possible ones very nice and we can simply pass that in as a style property inside of or div right here so we can say style is going to be equal to an object and simply pass in the animation delay right here because that's a property that CSS actually recognizes very very nice and then inside of this St what do we actually want to animate well it's each individual phone with a background image right and we already have the component for that already wrote the phone component which is awesome because we can now simply use it right here for each review and then pass it just the image oops image source that it takes as a prop which is or IMG SRC or image source now we're going to get two errors here one is going to be for the review column right because the each review expects properties that it doesn't get right now and we need to pass it those properties first one is going to be a key because we're mapping over something in we always need to pass a unique identifier to the most top level element that we're mapping over right and this is going to be the review index the class name we're going to pass into our review is going to be dynamic and that's going to be the review class name which might not exist right it's an optional prop that we receive for each column we can see that by the question mark here it's optional that is why if it is present only then are we going to invoke it that's what this syntax is good for only if if it's passed then we're going to invoke it and we're going to invoke it with the review index modulo the reviews. length there we go and then the last prop we're going to pass in here is the image source that it absolutely expects we need to pass that and we already have access to that as a prop in this component as well which is the image source beautiful so we completely got rid of that error and then we get one more error and that happens down here in our actual review grid so basically each review column expects a couple of properties for example the reviews but we've already done all the work that is needed for this to work you remember the columns we split this into well that's all the foundational work that we need to do so as for the reviews The First Column we're going to spread in the column one we're going to spread in the column three and you might have noticed because we calculate the column 3 a bit differently this is not a normal string array like column 1 and column two this is actually a string array array so this is a two-dimensional array which is why we're also going to call the flat method on here to make it a single array like a one-dimensional array just like the others and then we're going to spread in dot dot dot uh column 2 there we go those are going to be the reviews that we render out inside of this column as for the review class name what is that going to be basically this is the Callback function remember where we get access to the review index that we need in order to to check what should be the class name that we apply to this and to determine which class name should be applied based on the review index we're going to make use of a CN helper function which takes an object and makes this very easy for example we're going to apply the class name of medium hidden in the condition that the review index is larger or equal to the column 1length plus the column 3 at the index of zero dot length right in that case we're going to apply a medium of hidden and we're going to apply a large of hidden in the condition that the review index is larger or equal than the column 1 do length there we go and now to achieve that really cool effect where the middle row actually moves faster than the side rows which looks really nice in the end we are going to apply a Ms per pixel what this accepts as a property of 10 to the First Column and once we copy and paste down using shift alt and arrow down this entire column we can already change the MS per pixel to 15 on the middle row right Al so this will actually be slower right but same effect that I mention it's going to um slide at different speeds the outer columns versus the inner column it's going to look super super nice as you saw in the demo of this right and the class Dam we're going to apply to this is going to be right it's not going to be the riew class name it's going to be the standard class name it's going to be hidden and on medium devices we're going to say block and the review class name is going to be very straightforward all we're going to do is return the result of review index is larger or equal than column 2. length and in that case we're going to return a string of large Hidden and else we're going to return an empty string and last review column we're going to put here let's copy and paste this down one more time using shift alt and arrow down oh and by the way before we care about the third column let's fix the reviews that we want to show in the second column this is not not even going to be that much as in the First Column this is just going to be do dot dot column 2 so we're spreading in the second column and Dot do. column 3 at the index oops at the index of one there we go and then the third review column by for the easiest right we're simply going to use the column 3 do flat again to turn this from a two-dimensional array into a single dimensional array and we don't even need any array brackets in here we can simply put it in as it is right that's it and as the milliseconds per pixel we're going to set 10 and we can completely get rid of the review class name so what is up with these reviews right why are these so different essentially if we only show one column right we don't want to hide all other reviews and just show that single column right we still want to show all the reviews there are but just as a single column and that's exactly what the review class name and the reviews is for right if we only have have one column we only show this one and in that case we want to show all the reviews but not if we're also showing the other columns right that's what we have these checks here for so if all columns are visible we only show column One content and if this is the only column that we're showcasing because we're on mobile device for example um just imagine it would omit like all the good reviews that are in the other columns that would be horrible right we still want to show them just in this column now and that's essentially the LA IC that we have right here and that's why the logic gets less for each review column that we show because for example if we display two columns then the second column only needs to carry also the reviews from the third column that is not visible anymore and if we show all columns then we don't even need to care about that very very nice let's save that and now let's see what happens let's start back up our development server let's hit yarn Dev reload our page to actually see what happens and let's let this load again the first time always takes pretty long to load but let's give it a second and then we should be able to see something pretty cool let's let's let it out and hopefully everything works correctly we're going to see once this loads so let's scroll all the way down and where's the animation do I need to zoom out a bit okay where's the animation what's happening why oh we get an error right here what's the problem let's see invalid Dom property fill rule ah and interestingly I know where the problem is why the reviews are not showing this error is not the reason I think this comes from the icons that we pasted in we have yeah so the fill Dash rule should be changed to fill rule in or icons. TSX that's just the reason of the error it's not a big problem it's just a tiny thing that react just does the problem why we're not seeing the reviews actually lies in the fact that the animation it's not one that is actually baked into Tailwind but it's one we need to Define ourself but it's really easy to Define it we're going to go into our Tailwind doc configs that's where this is going to happen by the way we can get rid of the accordion animations these are automatically done by our UI library but we don't need them and then the fade-in animation that we use right now is going to be a string of fade in 0.5 seconds it's going to be pretty quick linear forwards there we go and then the actual animation is going to be defined right here in our key frames where we again can get rid of the accordion stuff we won't need that and then let's define the Fate In animation right here so basically this takes a from the from is going to be an object with an opacity of zero and well you can probably guess what this going towards we're going to animate two and this is also going to be an object opacity and this is going to be one and these Need to Be Strings by the way not actual numbers there we go so we have the from we animate to and that's already it we can comma separate this from the marquet animation save this and reload our page to hopefully yes sir there we go to actually see the phones pop in and animate so nicely right they only start animating once they're in the viewport that's what the frame or motion Hook is for that we're using so they're not animating right now but only as we scroll down they start popping in to the screen that looks so nice man and to put the cherry on top we can add even a little Fade Out and fade in at the very bottom right here to make the section really smooth into the rest of our page so let's go into our review grid where is the review grid here it is let's go into the review grid component down to the very bottom and then below all the review columns and the dynamic check we're going to put a self- closing div right here and this one is going to get a class name of pointer events none to not make it interfere with the user Mouse absolute inser X of zero chop of Z Z height of 32 BG gradient to B for to bottom and a from Slate of 100 and we can simply copy and paste this component down using shift alt arrow down there we go change the top to a bottom and change the BG gradient to B to BG gradient to T So to top right and if we now take a look at our page check this out let's read out everything and we should be able to see wow they smoothly fade in and out there's a smooth gradient at the top and at the bottom now this looks so so nice man great job and we're also making really good progress on our landing page that's what I love to see all right next up let's actually just finish the landing page hey dude I I really like this effect so we're going to head over we can close out of the reviews and the tail one config we don't need that right now let's head over back to our main page. TSX right here and below the closing section that's section is done the reviews are done we're going to create the last section of our landing page and this section is going to contain once again our Max with wrapper you know the drill by now so we get consistent with across our entire app and this Max with wrapper will get a class of padding y 24 um to give this entire section some nice vertical spacing inside of here goes a div element and that div is going to get a class name of margin bottom 12 a padding X of six and also on large devices a pad X of8 let's open this div up one more div in here and this one is going to get a class name let's scroll down a bit so it's easier to see this one is going to get a class name of MX Auto a maximum width of 2 XL and on small devices a text- center there we go let's open this up and inside of here goes an H2 element and honestly we don't need to write the entire H2 again we can simply copy and paste the H2 from up here from the previous section so we know we get the exact same kind of styling so let's mark this H2 element from the above section and we can simply paste it right down here to be the headline of our new section now of course we're going to change the text instead of the what or we're going to say upload your photo and get then inside of the span we're going to say your own case and instead of the say at the very end we're going to say now there we go let's save that see what happens and we should see that reflected let's go into a side by side here on our landing page very very nice upload your photo and get your own case now the only thing that's going to be styled different is that we're going to get rid of the underline we don't need that in here and then we're simply going to add one class name to the span and that is going to be a BG green of 600 oh and actually two class names uh and text white that's also important let's hit save and we should be able to see your own case is now highlighted that that looks really nice okay now two divs down closing H2 closing div closing div above the max with wrapper is where we are going to continue right now and we're going to create a div here and this will get a class name of MX Auto a maximum width of six XL a padding X of six and then on large devices a padding X of eight let's open this div up one more div in here this one gets a class name of relative Flex flex-all items D Center on medium devices a grid a grid calls of Two and a gap of 40 so quite a bit all right now inside of the stff we're going to put an image element of course self closing as always right and this one is going to get a source something we need to pass in here and that's going to be slash arrow.png you're going to see what this does here in a second and the class name for this image is going to be absolute a top off and then as a custom value in these angled brackets we're going to say 25 RM then on medium devices we're going to say top -2 minus translate - y -2 so these two are actually going to Center this element vertically if you've ever worked with absolute styling you know that's what it does we're going to apply a zindex of 10 and by the way if you didn't work with it before then now you know that's what it does these two in combination Center an element where you have the 1/2 and then the translate minus translate minus 1/2 this could also go for X right um so we have translate X and we have medium left one2 and then it would be centered horizontally and to prove to you that it works like that let's actually do it let's say left2 and then also minus translate minus x -2 there we go we now centered it horizontally and vertically let's give it a rotate -90 and on medium devices and up a rotate d0 to kind of un rotate this image very very nice below this image let's create a div element and this diff element is going to get a class name of relative a height of 80 on medium devices it's going to get a height of four there we go a width of full on medium devices and up we're going to give it a justify self end let's give this a maximum width of SM for small rounded - XL a background gray 900/5 that's the opacity once again a ring- inset a ring gray 90010 again for the opacity and on large devices and upwards a rounded D2 XL and inside of the S we're going to put the image right that's going to be an image tag just like so as the source we're going to use slh horse. JPEG and as the class name for this image we're going to say rounded D MD object Das cover a background of white a shadow D2 XL so quite a bit a ring-1 and a ring gray 900 sl10 and also a height of full and width of full as well let's save that and see what happens that's going to pop up right here beautiful that looks very very nice with the arrow pointing down and if we give this more space you're going to notice something cool bam the arrow doesn't Point down anymore now it points to the right hand side so this is fully responsive no matter where the user is which device they're using this Arrow will always point in the right direction and that will be to the second thing that we're going to put in here right below the closing div after the image so that's going to be right here with two closing divs and then the max with wrapper to go this is where we're going to put the phone right that we've used so often by now the class name of this phone we're going to give it is going to be a width of 60 and the image source for this phone is going to be SL horor phone there we go. JPEG and you're going to see exactly what that looks like the phone is going to pop up W bam there it is right how cool is that we have the image pointing towards the phone so it's very clear to our users what or um shop does right what they can buy here it's a random image they can turn into a phone case it's very clear with the arrow I really really like the section and now you know how to build something similar that's also fully responsive now let's give the user some reasons to actually buy from us right why would they buy from our store and to do that we're going to create an unordered list with a class name of MX Auto a margin top of 12 a maximum width of Pros on small devices a text large a space y of Two and a width of fit now let's open up this UL this unordered list and create an Li a list element inside of it the only class name this Li is going to get is going to be a width of fit as well and inside of this Li let's create a check icon as we've done before right and this one is going to get a class name of height five oops height five a width of five and a text green 600 and an inline and lastly a margin rate of 1.5 there we go let's give this entire thing a bit more space so it's easier to see and then below the check icon but still inside of the LI element let's say high quality silicone material there we go and we can simply grab this Li element hold shift alt Arrow Down Bam Bam Bam so we have four different check marks the second one is going to say scratch and fingerprint resistant coting the third one is going to say wire oops wireless charging compatible and then the last one is going to say 5year print warranty and it's it's really important to give users a reason to buy from you right what makes you different than your competitors why would they care about what you have to offer well you have to tell them right and that's exactly what we're doing here I don't want you to I don't want to teach you how to just write good software with all the logic that's going to happen in this app with the configurator and the um logic with purchasing stuff and so on we're going to do all of that but I also want to teach you all the other skills that go into making a really good landing page right and part of that is making very clear to users what your benefits are what's the benefit in buying from you and not from someone else right and then after this Li element we are going to create a div element just like so and this div is going to get a class name of flex and justify Dash Center let's open up this div inside of here goes a link element this is not self- closing but this is going to say create your case now and then we're going to put an arrow right icon from or icon Library we seeed react this Arrow right will be self-closing and it will get a class name of height four with four and a margin left of 1.5 now nextjs is very angry at us it doesn't know the H for the link and it also doesn't know where the link is even coming from right so we still need to import that from next SL link hit import there and then it expects an hre property where does this link go and this will go to slash configure slash upload as we've done before that's the first step of our configuration process um it will be hosted on this domain right here and also to make this link look good right now if we save that you're going to notice well this just looks really boring let's give it a class name and this class name is going to be dynamic so not a string and it's going to be the button variance that we can simply import from our um button component that give this really nice class names out of the the box and as the object we're going to pass in here the size is going to be large and we still don't get intelligence here that is weird I don't know why anyways the size is going to be large and then the class name we're going to apply is going to be MX Auto and a margin top of eight to kind of separate it from the bullet points and then hit save and as you're going to see there is a button that looks super super nice and we are almost done with the landing page the only thing that's missing now is the footer but for now that is very very very nice and dude I think the footer is actually going to be the easiest part of this entire application it really adds to the app because it looks so official if it has a footer right that's why I think it's important um and any app has to have a footer right if you want this to be something serious you have like your privacy policy cookie policy in the footer and so on and so on it's very important to make the app look credible and uh you know official and so I want to show you how to build a good footer and it's going to be really simple we're going to create it in the components new file footer. TSX go ahead and create that we can close out of the sidebar there we go by the way if you're wondering how I'm toggling this without clicking the shortcut is contrl B that's going to open and close your sidebar uh yeah that's that's just good to know I guess let's say cons footer and that's going to be an arrow function there we go and Export default footer at the very bottom as always okay we're going to return a footer HTML element from here of course what else right in the footer and this is going to get a class name of BG white a height of 20 and relative uh what is that not POS oh it is position okay relative position there we go inside of here Max with wrapper you know the drill as always uh we're going to use it in here and inside of the max with wrapper we're going to create a div and this div is actually going to be self closing right we're not going to use this this can be self closing this will just act as a visual separate between the main content and the footer so this div is going to get a class name of Border DT so it just has a border at the top and a border oops a border gray of 200 and that's already it that's the visual separator below this div at the same level we're going to create one more div and this one is going to get a class name of height F Flex flex-all on medium a flex Das row so it's going to change direction on medium devices a justify between a justify Das Center by default and an items D Center as well now inside of this div one more div and this one gets a class name of text- Center on medium a text- left a padding bottom of two and on medium devices a padding bottom of zero let's open this up and this is going to contain a P tag there we go with a class name of text- smm for small and a text muted for ground which is a default color that our UI Library provides to us this is like a normal kind of gray it looks pretty good out of the box and this ptag is going to contain and copy colon which is the HTML way of just describing the copyright symbol um we can let's already see what this looks like right let's save the footer and we're going to use the footer inside of our main layout so let's switch into our main layout under Source app right if you wonder the file structure where that is it's right here layout in Source app this is where we are and we are going to put the footer at the very bottom still inside of the body right here footer and import that there we go by the way if you're wondering why the Navar is throwing an error that's because typescript is kind of complaining we're going to update typescript later this is just a typescript thing there's absolutely nothing wrong with your code but if you're using not the latest version of typescript sometimes it doesn't recognize react server components so again this is nothing wrong with your code this is just typescript complaining as you can see everything works very very well and this is a known issue that was fixed in the newer versions of typescript anyways since we included the footer we should be able to see it and there it is there is our Copyright symbol at the very bottom very nice let's make it even better we're going to include the new date. get full year right here so it's going to show the current year for the copyright and we're going to say all rights reserved very nice all right let's save that that already looks fine as hell and now with one closing diff to go below the ptag below one closing diff let's open up one more diff this one is going to get a class name of flex items D Center justify Das Center that's it inside of here one more div this one gets a class name of flex and Space X of8 there we go inside of this div um is going to be a link element from nextjs from nextlink and this is going to say terms right that's going to link to our terms of service the hre oops the hre for this is going to be just a hashtag I'm not going to implement the legal pages with you right um you know how to do it after following this video but I am I I can't give you the legal text right I'm not a lawyer that would be pretty bad idea for me and it also depends on the kind of business that you do so this is where you put it this is where you put the link to your legal page but I don't think it makes sense in this video to build one together very boring nothing new to learn there and we want to focus on the cool stuff on the actual functionalities right so everything works the payment the configurator that's what we want to do that's what I want to teach you not the boring stuff okay so this one is going to get a class name of text small a text muted for ground and on Hover a text Gray of 600 there we go and we can simply grab this link element and copy and paste it using shift alt arrow down once and twice so that we have three in total the second one is going to say privacy policy there we go and the last one is going to say cookie policy and hit save and that's already our footer just like that we have a functional really clean minimal footer in our web app that looks good both on mobile and also on desktop by changing the direction of Flex from column to row and I think that looks super nice and that is our landing page so quick summary what we did until now we completed the hero section it looks absolutely great why should people use us right social proof very important that people know that we have happy customers and with a demonstration of what our service actually does here on the right hand side that looks nice with a little case Cobra logo that I even prepared for this phone awesome what are customers say with some reviews from Jonathan Josh hell yeah and some highlight text in here that looks very nice and then probably the highlight of this um landing page the phones that people have already configurated that people are actually buying from our store which if you sell some stuff and people agree to publish it then of course you could put your user data here right but again of course if they agree that to publish it you can't just use any kind of user data here that doesn't fly but this is also meant just as an illustration for potential customers to know what we sell and it serves that purpose very well and just looks amazing and then they upload your photo and get your own case now section where it's very clear what or service does we convert an image to a phone case here on the right hand side indicated by the arrow and if anyone is still not convinced then we do it here we name some benefits of purchasing from us and not the competitors and make it very easy to actually get started with the case creation that's our landing page very very nice and creating the case that's exactly the first step um uploading your image is going to be exactly what we're going to work on now because right now if we click on the create case button well there's a 404 and it probably shouldn't be that way if you want to turn this into an actual proper goodlook and functional store and learn how to do that along the way okay little cut there because for me it is the next day but we just finished the homepage I very much remember that and now we are going to continue on the first step which is the configure SL upload let's give this a a lot more space and where is that going to happen well let's go into our source folder and let me zoom in so you can see this easier uh yeah let's stay zoomed in like that that's better and now in our app folder this is where we're going to create a new page um to match the URL that we link to when we click the create case button right that's SLC configure SL upload and how that works in nextjs is that we want to create a folder for that thing that's going to be in the URL so that's going to be conf figure right that's going to be a folder name and then we want slash upload right so that's also going to be part of the URL that upload so it also needs to be a folder and not a file so configure and then a folder inside of here called upload and that is because nexj has a file based routing system right and then the content that's going to be displayed when we navigate to this path in the URL that is going to be defin inside of a page. TSX right that's a naming convention that's enforced by nextjs so next or framework knows the content to display for this URL all right let's get rid of the sidebar we won't need that for now let's focus on this page and let me zoom in even more so you can probably see this even easier so let's say cons page and this page is also nothing more than an error function right this is nothing more than a kind of regular react component that we simply name page and we're going to export default page at the very bottom as well now at the very top of the file we're going to say use client and because Pages by default are server side components just like any other component you know we need to opt out of that behavior by using the use client directive at the very top and that allows us to use react hooks which we're going to need in this component for the interactivity right so first off what we're going to do is return a div at the top level from this page and this div is going to get a class name and this class name is going to be dynamic and not a static string so we're going to make use of of our very nice CN let's import that there we go helper function invoke that and as always the first thing this takes is the default styles that are always applied that's going to be a relative a height of full a flex -1 a margin y of 16 a width of full a rounded - XL for extra large a BG gray 900/5 so a very kind of light opacity that we're giving the background here a padding of two a ring of one a ring Dash inset a ring gray 900 sl10 so kind of the same thing as the background just a bit higher transparency on large devices we're going to give this a rounded - 2XL let's give this a flex a justifi d Center a flex-all and an items D Center wow that's a long class name but I promise that is the longest class name in this entire comp component and um then what's going to be the dynamic part right why did we make this Dynamic so let's open this up and we're going to apply a class name of ring- blue- 90025 for the opacity and also a BG blue 900 sl10 also very light opacity and now comes the condition when do we apply these two class STS right here and that's basically if the user drags their Mouse drags a file over the upload form that we're building right now so in order to keep track of that we're going to make use of react State and we're going to call the state um is drag over and set is drag over and this is going to be equal to use State something we still need to import from react and by default this is going to be false right and just for personal preference I like to do this you don't have to I like to pass the actual generic of the type for this use state in here by default if we get rid of this typescript can already infer this right typescript can know what it is from this value right here but I just kind of like giving the generic explicitly I think it's a good idea it just looks better in my opinion and then let's say is drag is drag over there we go and right here for the condition um that we want to apply these class names for we can already save this file and we should be able to navigate to this page now if we hit the create case button right here in our top right that's going to take us to the page beautiful okay we just see a little area here it doesn't look like a whole lot yet but it's just going to get better from here so let's open up this div and inside of this div let's create one more div and this one will get a class name of relative of flex of flex D1 flex-all items D Center justify Das Center and a width of full there we go now one thing that's going to make this entire upload page really cool is the ability for users to drag and drop their files right how do we Implement that how does that work and well basically react gives you some things like on drag and if I hit the autocomplete like on drag on drag over stuff like that on drag start react gives you all those um events to build on top of but we're not going to implement a solution from scratch here I don't think it makes sense when there's already really wellb built battle tested ones such as let me show you react Drop Zone this is a library specifically made for this exact purpose right so you can drag and drop files over an area it makes it super easy there's um a lot of installs and it has been around for a long time let's see yeah like 2.5 million downloads per week people like this package and that's for a good reason I'm using it in a lot of projects as well let's use this one um instead of implementing all of it ourself this one just works so let's say pnpm install react SL or Dash um Drop Zone there we go hit enter we can give this a bit less space so you can see the full name react Dash Drop Zone that's what we want to install that's not even going to download it right CU I'm using pnpm that's very cool cuz as I said I used this in other projects therefore it just needs to link my dependency on my PC which is already there that's where the the pnpm cache is so cool by the way anyways after that's installed let's go to our Imports and let's say import drop zone as a default import by the way not as a named one and then while we're already here we can also grab the file rejection type we're going to need that later not right now from react D not Zone but drop oops I changed my keyboard there we go react Dash drops on that's where we want to get this from and this drops on that we just imported is nothing more than a regular react component we can now use and you're going to see why this is so cool okay let's render out this Drop Zone inside of the div that we have just created this is not going to be self-closing by the way and then if we see what this takes this takes a lot of very cool stuff like on drop accepted on drop rejected right if the file like um is not an image that we want to accept but a video that the user is dropping in here of course we want to reject that and then with this function we're going to be able to show Arrow later on so let's kind of get this basic infrastructure right um for the drop zone one thing we want to pass is the on drop rejected as I just mentioned right there was no point in getting rid of that and let's already kind of scaold this out let's define a const um and let's call it on drop rejected so same naming and let's just mark this as a function that does nothing for now right and same thing for the on drop accepted const on drop accepted and just kind of scaffolding this out makes it much easier and for us later to actually just implement the functionalities because we're already going to be done in the Drop Zone part so we're going to pass the onrop rejected to the um corresponding event on drop rejected in the drop zone same thing you can probably guess for the on drop accepted all right we're just going to copy and paste this function in here bam there we go we've got those functions and one very cool thing we can also pass into the drop zone is the accept property and this takes an object and in inside of this object we can Define key value pairs like for example we want to accept image/png right and now as an array we can simply tell the Drop Zone which file ending that is and that's PNG right so essentially all we're saying is yes we want to accept image PNG as a valid file type let's do the same thing alt um shift and arrow down to copy this down same thing for image/jpeg right with an e and then that's going to be jpeg also with an e and then of course same thing for um JPEG but without the e cuz it's kind of the same thing right uh it's both images we want to support both and just like that we have all the file types that we allow the user to drop in and if something that user drops in is not one of these file types then the on drop rejected will actually handle all the logic awesome and now as for the is drag over that we defined right here right how do we know if the user is currently dragging over this um kind of field well the drops on also gives us handlers for that and these are not drops on specific these actually come from plane react and that's on drag enter and we can say for example let's invoke this function right away set is drag over to true and then of course the opposite on drag leave right you can probably imagine what this is going to do same thing Arrow function and we're going to say set is drag over to false okay great and now let's finally open up this drop zone and get started in building the actual upload form so one thing that the drops on gives us before we Define any jsx is that we can destructure some things from here right so this is going to be a function that returns some jsx inside of this drop zone and if we now try to destructure you're going to see something cool yes we can we get a lot of information from the drops on the accepted files get input props is drag active a lot of very cool stuff and what we care about is the get root props function that we can destructure and also the get input props function that we can destructure from here and we're going to use them not right now but in a second first off let's define a div inside of here and this div is going to get a class name of height full a width of full a flex One Flex flex-all items D Center justify Das Center as well and now before we open up the div first off after the class name still inside inside of the actual div with the brackets we're going to spread in the get root props so this is going to take whatever this function returns as properties right and pass it right into the S and that just comes the get props right here from the drop zone that we destructured remember so we don't need to worry about any of the props that this div actually needs to take in order for the drops on functionality to work it's handled by the library and then inside of this diff let's now open it up we're going to create an input element that's going to be self- closing and this one very similarly gets the dot dot dot get input props and we can simply invoke that no arguments we need to pass in here and just like that bam that's it right the drag and drop already works and if we save that and reload our page well there's going to be an error sometimes that happens let's reload our page again hopefully that error will be gone great it is and just to test this out let's put some stuff um into the input box right here that's a Reload the page and if we now drag any file inside of here we should be able to see let's try this out let's drag it in here yeah there we go okay so we can see that while we drag this over this is just an image template I have for this project um there's going to be like a blue background and if we actually logged out anything in the on files accepted right let's try that it's console.log accepted there we go hit save and let's kind of give this console a bit less space reload the page and now if you drag anything in here and drop it we can see accepted right here in our console and we can handle later on the files accordingly that's going to be the upload right that we can then proceed with the user stuff and upload their image and then um forward them to the phone case creation right beautiful that works now one thing that's kind of weird is that if the text wasn't there that be input then this area would actually not take up any space right it looks very weird it's very crammed together and that's not really what we want so there's a really cool fix we can apply to that and that is actually not going to happen inside of this file so before we continue here let's just make this area take up all the space and push the footer to the bottom it's going to look a lot nicer and we're going to do that inside of our app folder and then let's go into our configure and create a new file inside of this folder and call it layout. TSX and hit enter and again that's under configure that's not in the upload folder so this in the configure folder and this layout const layout is just going to be an error function as well nothing special right yeah the naming is a bit different but let's export default layout at the very bottom the naming is different but nothing else and this layout basically just takes children and these children we can inline the type we can Define it right here are going to be of type react node that we can import from react there we go and now what this layout is going to do is simply w wrap all the children in a Max with wrapper or reusable component that we can import inside of the layout and simply wrap the children right here inside of it and this Max with wrapper is going to get a class name and that's going to be a flex of one a flex and a flex Das call and the interesting thing is in how nextjs layouts work is that this layout will now apply to all subfolders in the configure right because this is later on where the um design will also live where we can like drag around the image and specify which phone specs we want to have right that's also going to live inside of the same folder so the same layout is going to apply for it and in order for these styles to actually do anything we need to make a small change to our root layout. TSX as well so that's literally going to affect all the pages inside of our entire application and that change we're going to make in our root layout by the way if you're wondering where we are that's Source app and then this root layout right here that's currently still giving us the typescript error that's totally fine it doesn't matter we're going to fix it later um let's WP or children inside of a main tag and let's paste the closing tag after the children and this main tag is going to get a class name of flex a flex-all a minimum height of and then in angled brackets we're going to calculate something so we're going to say Cel and then in parenthesis this is going to be 100 VA H view height minus 3.5 Ram which is the Navar height and then minus one PX don't ask me why this one PX right I just tried it out and it worked so nice otherwise there was one pixel on the page scrollable so that just fixes that I'm not too sure why to be honest uh but it just works and then inside of the div we're going to create inside of the main right here this one is going to get a class name of flex -1 flex flex-all and a height of full and then grab the closing tag and paste it after the children there we go and now the footer that lives below the main tag actually needs to go inside of the main tag so all we're doing here is say we want to align items vertically right and we're telling this diff by saying Flex one which is just going to apply the flex 1 1 0% that it needs to take up as much space as possible and actually push the footer down to the bottom and if we save that we should see what happens let's reload our page bam there we go that looks so nice the upload area now takes up all the space on the page that it can but without pushing the footer out of the boundaries of the page right it just looks so nice instead of actually giving the like upload area a fix tyght this will now always take up the correct amount of space and it looks very very good perfect so with that styling fixed we can actually proceed in our actual drops on right so what's the logic for the file uploads that's going to be be the first kind of interaction with our back end that's going to happen in this page. TSX and I'm all here for it so first thing we're going to do is do a conditional check below the input and we're going to check if is drag over is true and if it is true in that case we're going to render out the mouse pointer Square Dash icon let's find it right here the mous pointer Square Dash from Lucid react beautiful this one is going to get a class name of height six a width of six a text zinc of 500 so a bit of a lighter kind of gray and then lastly a margin bottom of two and now if the drag over is not true well in that case one of two things can happen either we are in a loading State the user is currently uploading their image it's currently being sent to the server right and we're going to mock this out with a false for now this is where we're going to implement a loading State together in a second and if this mocked loading state is true in that case we're going to render out a loader 2 icon from lucd react which is basically just a spinner and to actually make it spin we're going to give it a class name of animate Das spin as easy as that right a height of six a width of six and let's also give it a text zinc of 500 and lastly a margin bottom of two as well and in the last case right if we're not dragged over and if the loading state that we're mocking is not true in that case nothing is happening this should be the default State and we're going to render out an image icon from Lucid react this is going to be self closing and it's going to get a class name of height six a width of six a text zinc 500 there we go and a margin bottom of two let's format this there we go that looks so much nicer let's save that see what happens we should be able to see the icon pop up um right here there it is the image icon and if we click this that's actually going to open up our file system beautiful okay now we know that works we can now drag stuff over we've tried that as well that's going to Trigger or on accept function beautiful and you know what I'm not super happy with this false we just put here I think a better approach is going to be to name this is uploading there we go and then to mock this is uploading at the very top because that's what we're going to replace with the actual uploading state so let's say is uploading at the very top and let's just say that's equal to false for now and we're going to implement that together in a second but the beauty of mocking this for now is that we can already get the kind of jsx done and and get all the styling done all the logic and place and then implement this together and similarly there's going to be a state called is pending but we don't have to mock this is pending this is actually coming from something called a used transition so this is going to be from use transition this is a react hook we can simply import and we can actually destructure two things from this used transition very similar to a used state right so we are going to put this into array brackets this is same thing the first return item in the array is going to be the is pending State and the second one is going to be the start transition function what is this good for we're going to use this when we navigate the user to The Next Step so we can show a loading State while the page is loading that's going to be really really cool so we also have this is pending State and if we are pending or uploading in that case we're going to render out the loader too so let's add the is pending state right here and in that case we're going to display the loader um to the user and that way we don't have to mock that as well nice okay so after this conditional check let's continue with a div element this one gets a class name of flex a flex Das call a justify Das Center a margin bottom of two a text of small and a text zinc of 700 there we go let's open this div up and inside of here we're going to do a check if we are uploading if is uploading is true in that case we are going to render out a div element and in the other case we're going to worry about that in a second let's make this div closing for now there we go and in the other case we're going to do a check if is pending so are we currently redirecting the user to the next step and if we are in that case we're also going to render out a div element and if we are not currently pending then we're going to do a check if we are currently dragging the image over the uploads box right so is drag over and if we are currently dragging over in that case we're going to render out a span element and then in the last case this is a long Turner I know in the last case default kind of case in that case we're also going to render out a span element so that's a pretty long Turner but that's actually not very unusual if you look at a lot of react projects because actually this is not very unreadable right you can see if we are uploading if we are pending this is kind of like an if statement but in jsx and I honestly kind of like it I think it's much more readable if we do it this way versus actually having a function or something down here and then handling the logic on top and we can just handle all the rendering logic right here in the jsx where it's meant to be and I actually kind of like this so in the case that we are uploading this div is going to get a class name and that's going to be flex flex-all and items D Center let's open this div up and this is just going to contain a P tag saying upload up loading dot dot dot there we go and then right below the ptag is going to live a progress bar and the Beautiful Thing is or UI Library already has a beautiful progress bar built in let's see what it looks like and it looks like this right here it looks nice I mean it's just a progress bar what did you expect right it's simple it looks clean that's exactly what we want so let's install that progress bar by going into our terminal and let's say npx shed cn- UI at latest latest there we go add and then it's called progress and hit enter and we can already while that's installing kind of take a look at the code demo what is this going to look like oh it's going to look pretty simple right we have a value we can pass in here and we can also give it a class name of the width that we want and that's basically all it needs already to work you know it's it's very nice so in here right below the ptag let's say progress once that's done installing and we can import that from our custom UI component and not from radics right from our custom um folder that we put our components in and this one is going to get a class name there we go that's going to be a margin top of two a width of 40 a height of Two and a BG gray of 300 there we go and let's give this a lot more space okay now the value for this progress value is going to be the upload progress which is a constant that doesn't exist yet so let's go ahead and create it together and that's simply going to be a state in our application as well let's name this the const upload progress and the set upload progress and this is going to be equal to use State and this is going to be Zero by default so this is going to be a number between zero and 100 and that's also just because I like this for personal preference tell typescript explicitly that this will be a number but you don't have to right it can definitely infer that from the default value and you know what let's already see what the this looks like so if the is uploading state would be true right let's kind of mock this out the true that's the beautiful thing about mocking we can do whatever we want we can just change the Boolean without having to do any interaction to actually see the state and let's set the progress to like 45 right let's just see what it looks like oh all right that already looks good what's the error hydration failed uh uh uh server rendered agent that's fine um we can just reload the page and the error is going to be gone and if that should be an error later on before deployment then of course uh we're going to fix it but chances are some of these arrrow actually fix themselves in next Str it's kind of crazy but sometimes when you reload the page is just gone um there we go okay so the arrow is gone we get a beautiful uploading State and if we click that that's going to open the file system and we get the upload kind of progress as well very very nice okay let's change this back to a zero let's defa the is uploading back to a f and let's quickly finish up the JS X because we're almost done and then actually implement the uploading functionality so in the case that we are pending this div is going to get a class name of flex flex-all items D Center and inside of here we're going to say in a P tag redirecting not redirection redirecting please wait dot dot dot there we go that's all we're going to do inside of here then in the is drag overc case the span we're going to render out that one is going to get a class name of font - semi bolt and in here we're going to say um drop file and now this entire span we're going to wrap inside of a P tag um so we can say um below the span and still inside of the P tag to upload there we go okay and now this last span element basically is going to be the same I think I kind of messed up on the span elements this does need to be a P tag on the main level so we can just copy and paste from the is drag overc case let's paste that inside of the standard last case as well and then inside of the span that is semi Bool we're going to say click to upload or at the very bottom still inside of the ptag but not in the span we're going to say or drag and drop to make it very clear to the user how they can put their files right here that is very important beautiful okay and now after the conditional check and after one closing diff and before the second oops closing diff right here we're going to do one more conditional check and that is if is pending is true in that case we're not going to render out anything null but in the other case where is pending is not true we are not pending in that case we are going to render out a P tag and that one is going to get a class name of text XS for extra small and a text zinc of 500 and this will tell people the allowed file types so this is going to be PNG JPEG and also jpeg with an e at the end I know these are kind of same thing but I honestly don't know why that's the thing um there are two different jpeg formats and we support both right that's all we're telling the user um very very nice click to upload or drag and drop and there seems to be a space missing here so let's add a JavaScript space behind the span element uh to actually fix that otherwise this looks kind of weird there we go click to upload or drag and drop and now the spacing is actually correct beautiful and now the most most important part of this entire upload page and that is going to be the upload right where are we going to get the progress from where are we going to store the user files what is going to happen when a user um drops a file that is accepted well spoiler alert it's going to be uploaded to somewhere and that somewhere is going to be let's go to upload thing.com that's going to be to S3 and we're going to use a service in order to do that now I want to be very clear why we're using this service um if I was building this myself I would not be using this service I would either go directly to S3 right this is Amazon simple storage service um and create an S3 bucket and upload directly to that that's what I would do right or also go to Cloud flare R2 which is an even cheaper um object storage with um an S3 compatible API right that's that's pretty cool and um I would actually probably go with Cloud flare I've never worked with them I'm more familiar with S3 I've used S3 a lot in the past and but Cloud flare is cheaper and it also has zero egress right so when you pull data out then it's not expensive in S3 it actually is um but we're not going to use any of these two tools that I would use personally instead we're going to use a service first off because this service doesn't require a credit card and AWS S3 does and secondly because this service code upload thing makes it much easier iier um to get started and uploading right it literally just takes like 5 minutes to implement and um I think it makes a lot of sense for a video like this because we want to learn about the core logic of implementing all of this and not really how file uploads work if we were to do that from scratch in S3 not only again would you need a credit card but it would take like one or two hours that we don't get to build any kind of cool functionality so that's why we're using this tool I want to be very clear um as to why we're doing this because again I would not use this personally um it can be cool for kind of getting apps up and running fast um but anyways we're going to use it right now together because it makes the entire file uploading very easy and simple to get started with so after logging in and going to our dashboard you can log in Via GitHub we're going to say create a new app and the app name what do we want to name it let's say case Cobra Dash let's just say D um DHD because I already have the case Cobra project in here but you can name it just case Cobra the app URL we're not going to pass anything in here and now the region is going to be uh paid plan required to change from default okay never mind we're going to stick with the default and then let's hit create app there we go congratulations you've got a place to store files okay great and that's actually going to give us the API key that we need so let me zoom out just a bit and let's go over to API Keys here on the left and then going to give us two things the upload thing secret and the upload thing app ID and let's make them visible let's copy them over and we can simply grab those and paste them into the EnV file that we already have in our project and we can just paste them in as they are the EnV file remember it's at the root of our project that's where we're pasting this in and what this allows us to do is to now install the package we can um use to interact with the service that makes it very easy for the file uploads in or app and that is going to be pnpm install and let's say upload thing and that's also going to be at upload thing/ react these are two different packages one is there for the core functionality and one is there to provide us with helpers for the react kind of library or framework whatever you want to call it so react specific stuff that we can use that makes it easier to integrate into our project and let's go over to the documentation of this um upload thing because that actually gives us a bunch of code to copy and paste into our project which makes it very easy to get started with and let's give this a bit more space there we go let's navigate over by the way we can close out of that let's navigate over to the nextjs app right here oh and yeah we already got the packages installed great okay we added the environment variables that's perfect and basically what it tells us to do is to go to this file path right here so let's give this a bit less space again that's going to be under and let's close out of the page for a second that's going to be under Source app and then let's create a new folder um inside of our API folder and that's going to be called upload thing and hit enter and then in here goes a core.ts that's what it's telling us and we can simply hit the copy button up here and paste all the code um inside of our app okay let's clean up some of this we can get rid of the uh console logs we can get rid of all the comments I think it looks very unorganized let's get rid of that there we go and we can also get rid of the comments up here up here up here and let's leave the rest for now okay the middleware we're going to change it later let's leave it like that just get rid of the comments um to make it look better and then we're going to go into this file path right here so basically at the same level as the core. yes we're going to create a route dos and this is going to be responsible for actually handling the API requests that upload thing makes to upload the images for us and we can simply grab the code again paste it in here and then get rid of all the comments explaining the code and it'sit save great and that's almost all of the configuration work that we need to do for upload thing so we can head back over to our own page give it less space and I say almost because there's one small thing that we still want to do and that's going to happen inside of our lip folder just right here and let's create a new file in here called upload thing. TS and inside of this file we're going to build on top of the react helpers that they provide us so we're going to say export const and we're going to destructure something from the generate react helpers and if we try to Auto Import this doesn't work pro tip if ever the Auto Imports don't work you can reload your window by pressing a control shift and P and then hitting developer window and sometimes that makes the Auto Imports work again not this time okay perfect well let's just import um the generate react helpers function or self then whatever man from the at upload uh not up stash upload thing/ react there we go and we can simply invoke this function and this function also takes a generic to make it fully types safe if we want and that's going to be or file router and we can simply import that from at app AP upload thing core so that's going to be a type defined in a file that we already pasted in right remember that's in our core. yes that's what we just pasted um with the middleware where we removed all the comments that already has a type that we can now use in our front end to make this entire thing type save across the stack right and from here we want to destructure two things which is the use upload thing and also the upload files that we can now make available to our react components to use anywhere right and that's literally all the setup we need to upload files that's why I wanted to use the service because it's so easy to get up and running fast so now we're going to go back into our upload page and now inside of here we can actually Define what should happen when a user uploads an image so we're going to say const and D structure ler above these two functions and that's going to be use upload thing so the hook we just got from the react helpers and this will take or image uploader so that's going to be the endpoint that we want to upload to and that is also defined in our core file right if we take a look at this we have the image uploader that's what it's called so that's what we get on the front end as well this is fully types saave how cool is that and we can also pass this some options right as an object as the second argument we're passing in here that is going to be the well we get a bunch of stuff but what we care about is the on client upload complete right and this is is a callback function in which we can simply destructure from an array the data that we get back from upload thing if we hover over this this is going to be the uploaded by as a string where does this come from why do we get this why is this type save well because that's what we return right here in the on upload complete if I say name John right here then check out what happens name String this is fully types save across the stack this is our back end this happens on the server this happens on the client but it's still completely typ saave that is very very cool and in or case what we want is not a name of John right we don't care about that instead what we care about is something called a config ID and one config will always correspond to one specked out phone right what is the color what is the image on the phone that the user configured and dragged around and uh basically like do they have the um smooth texture or the um the other texture and so that's going to be a config all of that information and that's what we want to know in the server file as well and in order to do that we can add a DOT input to our image uploader which takes a z doob and now what is z well basically we don't have that in our app it doesn't exist but Z will come from a library that is called Zod right here you might have heard of it maybe not it's a schema validation Library it allows us to pass in at runtime any object and then parse it to make sure that the type is what we expect let's say pnpm installs Zod to install that package and that then allows us to actually import at the very top import Z oops Z from Z Now runtime validation is a really good idea because not only can we ensure that this object right let's let's just demonstrate this maybe that was a bit abstract let's practice this in here we can pass an actual object and now we can Define whatever we want for example that we expect a config ID to be passed into this and this is going to be a z. string then we can invoke do optional so you don't have to pass it but if you do then we can work with it later and that's going to be important for actually creating this config in our database once the image is uploaded because this will run once an image is uploaded from the user right and now we can simply destructure that input right here in our middleware and we can get rid of all the logic that is already in the Middle where we don't need it and we can simply say return input inside of object notation why do we do this basically we're taking in the input we're passing it through the middleware and finally we can now receive it in or on upload Complete because the metadata that we get in here now contains a config ID whatever we return from the middleware right so that's how we pass stuff around from the middleware to the actual Handler of what should happen once the image is completely uploaded and in here now in the on upload complete we can destructure the config ID from the metad data. input right and this can now either be a string or it can be undefined and for now we're also going to return the config ID from the on upload complete now this happens on the server right this all happens on the server and by the way we can get rid of the all function we don't need that there we go and we can get rid of the unused Imports by pressing shift alt and all by the way like a pro tip and this config IA we're going to do some additional logic here later but this happens on the server and whatever we return right here is now passed back to the client so we can use it on the client right how is that going to look like well we can get access to the config ID now cons config ID in the on upload complete and that's going to be equal to data do server data Dot and now this is fully type save how cool is that right so we now have the config ID that is um specific to the phone that the user has just um configured and uploaded the image for and we can now redirect the user to the Second Step because their image is now uploaded when this fires right everything was successful we got the image now the user is ready for the second step and we can push them there using or start transition right so we can display a beautiful loading State while the user is being redirected and the start transition takes a callback function in which we can push the user to the new URL path and in order to modify the URL we're going to need access to the const router which is the use router Hook from Nex navigation not next router by the way that's for the old nexts the next SL navigation is what we want for this and then we can simply call the router. push so we're going to push something into the URL and that's going to be as a template string to slash configure slash design question mark So as a query parameter and the ID is going to be equal to and then in Dynamic syntax right here with this dollar sign we're going to say the config ID right so basically we're saying once the user is done uploading their image we're going to push them to the next step and also pass the ID of the config for the image they just uploaded right so we can use the same image in The Next Step the user doesn't have to do anything again they don't have to upload the image again right that's why this is so cool and now another really useful helper that we get from use upload thing is the on upload progress there we go and what this gives us is basically the P it's a number and this is the upload progress right so we can say set upload progress just to this P that we get from upload thing thereby we set it as a state right up here and we can now use this value to display our loading state right and that we're passing into our progress bar right here that's where it comes from awesome and now we're all ready to go to actually handle the undrop accepted and undrop rejected case so what should happen if a drop is accepted well basically we first need to accept the files so the accepted files and these are going to be a file array and these accepted files are automatically passed into this function by react drop zone and what we want to do with them is to basically upload them right away right so we can destructure the start upload function from or use upload thing Handler and now use it to upload all the files that were accepted so how do we do that we can say start oops not set but start upload there we go and this takes the accepted files and it also takes the config ID that we need to pass in because that what we expect in the core.ts right here because we specified it as an input right we need anything here it can be optional so we are totally fine to pass undefined which we're also going to do because passing in a string right here for the config ID will only be relevant later not right now so for now we're going to leave this as undefined and then we're also going to set is drag over to false because the user is no longer dragging over their image was successfully accepted and so we can reset that state all right now what should happen on the undrop rejected what if the user dragged in a file that is not a PNG or jpeg in that case first off we want to destructure that file from the array of rejected files that are automatically passed in here by react drop zone and this is going to be of type file rejection array a type that we have already import at the very start when we started this file right up here file rejection there it is and we can destructure one file from the rejected files there we go so this is going to be the first file that was rejected in most cases that's just going to be a single file that was rejected right so that is the file that was rejected and we're also going to say is drag over false because in either way right if it was accepted or not doesn't really matter in any case the dragging um animation should be done right and lastly what we're going to do why we need this file is we want to show a toast notification an error notification to the user and in order to do that or UI Library actually has something built in as well that is the toast let me show you what this does if we hit this button you can see there's a little notification popping up right there and that's exactly what we want to show the user that this file type is not supported they need to upload something else and in order to do that we can simply go ahead into our terminal let's clear this and say npx Shad CN d-i at latest and then add toast and hit enter that's going to install all we need to actually show beautiful toast notifications inside of our app and the only thing this expects us to do is to somewhere in our app put something called the can I show this to you yeah right here the toaster component so for example we can do that in our root layout right here we can go below the main tag for example and put the toaster that we can now import from our custom UI right here um into our component and what that does is basically it allows us to render out the toast it handles the all the logic basically to show the toasts right and now the question is how do we actually show the toast after putting or toaster in the layout and that will happen um via if we take a look at our UI Library documentation via the used toast hook we can simply grab this used toast hook paste it at the very top of our page right here so cons toast that we destructure from the used toast hook and then we can head over to our where is it onrop rejected right here let's use the toast function that we just destructured from the hook and this is going to get three things so the first one is going to be the title you know that's what we can see right here um in the example that's scheduled catchup in our case that is going to be actually a template string and in here we're going to say the file. file. type type is not supported period this right here the file. file. type is going to be the exact type that the user tried to upload um for example a video or a gif or anything that is not supported is going to be included put it in the error message which is very user friendly as the description we can pass in here we're going to say please choose a PNG jpeg or JP kind of same thing you know um image instead period and lastly as the variant this will be destructive to make it like a red tone to let the user know that you know hey something is wrong here um that is not a supported file type so just for an example I'm going to try to drag in a PDF file that should give us beautiful application PDF type is not supported very very nice and we can simply slide this toast away beautifully okay and if I now try to upload a normal image of myself then we shouldn't get a toast but instead we should um get the start upload function invoked and wa okay that worked really fast we already got redirected that's good but um currently we're mocking the is uploading uh state right so if I drag in this image then the functionality actually already works but we're still kind of mocking out the um is uploading so we can actually get rid of our mock because now we can get the actual is uploading State simply from or use upload thing we get something called is uploading and that's going to handle all the logic that is attached um to the upload process so what we can now do is if I drag in an image and actually let's reload the page to get the freshest version first just to make sure that any um recent changes do apply let's drag in the image uploading there go we're going to see the progress bar fill up hopefully there it is it filled up so that means it's done uploading and that's going to redirect us to the um next step in the process super good very very good because that means we implemented all the uploading successfully it works and we should also fun fact now be able to see that file in or uploading dashboard so if we go ahead into or I already have a lot of apps here let's go into here into my case coobra Dev or your project for case coobra into files and then then we should be able to see right here beautiful that the files are here uploaded it says the date when we uploaded them the size of the file and the route in which this happen the status and we could also delete them if we wanted to but we're not going to do that beautiful very very good work on the uploading now what's missing is these steps on top right um in the demo if you remember there was like step one step two step three like upload your image and configure your phone and then review it right that's super important for user guidance users always need to know what the next step is and where they currently are and that's going to happen inside of a separate component that we are going to create and that component is going to live in our components folder of course right let's create a new file in here called steps. TSX and that is going to make our site look so much nicer and the cool thing is all of the artwork to make the steps looks extremely nice is already prepared for you by the freelancer that I hired for this project let's declare Eon steps and this is going to be of course once again an aror function that we export default at the very bottom there we go and we can simply return a div at the top level for now from these steps now where are we going to use these steps let's already import them where we need them and that's going to happen in our layout for the configuration route so this is not going to be the global layout but the one that is specific to the um folder that we're specifying all the steps in right this is where the steps are going to live and they're going to live right here in or Max with rapper right above the children let's import the steps component already there we go of course right now it won't do much right it's an empty div so we won't see it show up on the page but that's exactly what we're going to do right now so conceptually how are these steps going to work and why are we also defining them as a separate component the answer is because we're going to mark them as a used client component why are we going to mark them because we need access to one react hook or rather nextjs hook it's nextjs specific that is going to be let's say con path name is going to be equal to use path name and what this hook allows us to do is to get the current URL where we currently are and based on that URL we can then know which step we are currently on because the step is actually defined in the URL right so the upload would be the first step but our component doesn't just know that it doesn't know that the upload is the first step we need to tell it and we're going to do that inside of a con steps which is going to be an array and we're going to Define it above or actual component in all caps lock because that's a convention for defining constants that I used at my previous Dev job that use now um it's just a very widespread thing to see that stuff that is defined as constant is is in all uppercase so when you're working in the code and don't see that on the top you always know that this is a constant value it doesn't change it's not based on any API response or so it's just a really good practice to do all right in here we're going to create an object and each object is going to have a name property first one is going to be step one add image so this is going to be the actual text that the user is going to see the description is going to be choose an image for your case and then last thing is going to be the URL this is going to live under slash upload so that's how our component knows which step we are currently in all right let's mark this entire object hit shift alt Arrow Down Bam and let's do that twice so we have three steps in total step two is going to be customize design and the description for this is going to be make the case yours and the URL is going to be slash design and then lastly step three is going to be summary and the description for this is going to be review your final design and then the last thing we're going to specify here is the upload or the URL rather which is not going to be SL upload but it's going to be slash preview right so this going to live under the slash preview URL and that's all the work we need to do for this component to know which step we are currently on in combination with used path name hook so at the top level we're actually not going to return it if that was just so we can already import the component um in or layout we're actually going to return a ordered list an O because well this is ordered right it's step one two and three so this is the perfect use case for an ordered list and the class name for this is going to be rounded DMD a BG of white on large a flex there we go on large a rounded dnone on large devices a b-l on large devices a b-r for right and on large devices a border gray of 200 there we go let's open up this ordered list and inside of here we're going to map over every step so we're going to say steps. map and for each step and also the index that we get access to in the Callback function right here we're going to execute a function first so we're not directly going to return some jsx that is going to happen down here with an Li element at the top level but first we want to handle some logic and that logic will be um is the current step that we're on is it already completed is it the current step or is it an upcoming step and we can do that because we always know which step we are currently on so for example right are we on the current step const is current is going to be equal to path name. ends with the step step. URL there we go right so if the current URL ends with upload then we are currently on the upload step I think that in intuitively makes sense right and con is completed is the step already done basically we can say steps dot slice from I + one and then we going to do a check for any step so we're going to say dot sum which basically goes through the entire array and checks if a condition is true for any element in the array right so we're going to say step and we're going to return the path name. ends with step do URL just like this let's format this give it a bit more space so what are we doing here what's the logic so basically we have step one we have step two and we have step three right let's zoom in a lot here there we go and if we are currently mapping over step one right imagine that case then how do we know if the step is already completed or not so basically we're checking for the next steps and let's get rid of the background there we go so in our case the next steps right here would be two or three if we're taking the First Step as a reference point for this logic then we're checking for the step two and three are they the current step so if step two um if the URL ends with step two right which would be slash design in that case we know we are already done with step one because we're already at the design stage right so in that case we're going to return true um because we're checking for any array element if this condition applies and if it does then the current step is completed I hope that makes sense um I think it does it's pretty straightforward logic here and then last thing we want to do is Define the IMG path the image path and that's going to be the image for this current step this is going to be a template string and then here we're going to say slash snake minus and then dynamically the I +1. PNG and that should be i+ 1 not I plus 0 because um arrays in JavaScript are zero based and the images are 1 2 3 we're just going to add add one right here and that's going to be the path for the image that we want to use for the LI element and now the LI is going to get a key because we're mapping over something it needs that that's going to be the step. name it needs to be unique and also it's going to get a class name and this class name is going to be relative overflow D hidden and lastly on large a fx-1 let's open this up in here is going to live a div element at the top level but no class name and inside of here a span element this span will get a class name and that's going to be dynamic we're going to use our CN helper function in here and by default we're going to pass absolute a left of zero a top of zero a height of four a width of one background zinc 400 large bottom zero on large a top- Auto on large a height of one and on large a width of four there we go now what's the conditional part in here well that's going to be the second thing we're going to pass into the CN function first one is going to be we're going to apply a BG zinc of 700 if this is the current step if is current and then in the other case we're going to apply a BG primary if is completed if the step is already done and this div is actually going to be self-closing there we go and now as an accessibility best practice I want to show you because this div or this span rather is purely decorational we're also going to say area- hidden um is going to be true we're just going to hide it for people that are on screen readers right because if you don't see it then it's not very important if it's just visual just as a kind of accessibility and best practice that I want to show you below that let's put a span element and this is going to get a class name that is also going to be dynamic we're going to use our CN helper function and the first thing we're going to check in here is if the I the current index of the step is not equal to zero in that case we're going to apply a large padding left nine and then the other case we're not going to apply anything and then as the default class name we're going to apply Flex items D Center a padding X of 6 a padding y of four a text- smm and a font D medium at last awesome let's open up this span element and inside of here we're going to create oops one more span element there we go with a class name of flex shrink of zero and inside of the span is going to live a self-closing image tag this image is going to get a source which is going to be the IMG path that we determined at the very top already all the work is done and it's also going to get a class name and this is going to be dynamic as well so we're going to make use of our CN helper function we're going to say Flex a height of 20 width of 20 object Das contain items Das Center and justify Das Center as well and as for the dynamic part we're going to apply a border Das none if is completed and we're going to apply a border zinc of 700 if is current so if that is the current step that we are on right after the closing of the image and one closing span that's where we're going to open up so with one closing span to go after that the closing div this is where we're going to create the one more span element with a class name that's going to be margin left for a height of full margin top 0.5 a flex minimum width of zero flex-all and justify Das Center let's open up this div and inside of here lives one more span element let's open up the span that's what I mean and inside of here is going to live one more span element with a class name and this will be a dynamic class name as well so in or CN helper function as the default let's apply a text of small a font D semi bold and a text zinc of 700 and then as for the dynamic part we're going to apply a text- primary if is completed and a text zinc of 700 if is current if that is the step we are on and inside of the span we're simply going to display the step. name that we are currently on beautiful so after the name right here is going to be one more span element I think that's the last one in this entire component and this one is going to get a class name of text small and a text zinc of 500 and inside of the span we're simply going to display the step. description there we go beautiful we can already save that see what it looks like and hopefully we're going to see all the steps show up here on our right side if the development server is still running oh there we are hell yeah that looks awesome we have all the images the steps the description that looks nice one thing that's missing is um the separator right so with one closing span two closing spans three closing spans that's where we're going to open up so right before the closing diff and just as a note for us we can even say something like separator here just so it's easier for us to visually see what's happening in our jsx right and what we're going to do here is first off a check if the I is not equal to zero in that case we're going to render out some jsx and in the other case we're going to render out null and the actual jsx that we do want to render out is going to be a div element so what is this for essentially all this means is that this separator will only apply between step one and two and between step two and three but not before step one right there should be no separator before this step that doesn't make sense the separator should be here and should be here but not left of step one and that's all we're doing with this I is not equal to zero check so this div is going to get a class name that's going to be absolute inset zero a hidden a width of three and last a large block now if we open this div up that's where the separator element is going to live but once again since it doesn't make sense to write out svgs or self you're not going to learn anything from that I prepar that all in a copy paste list for you it's a very simple SVG we can simply grab that as the separator second item in the copy paste list grab it paste it in here format or file hit save let's move the copy paste list out of the screen again and then let's see what it looks like let's reload a page and we can see nice there is a beautiful separator now between each individual step so step one step two beautifully illustrated by the illustrator by the way I I really like the images they have prepared that looks beautiful step one step two step three with separators in between so the user always knows where they currently are they never get lost and uh I just think it really adds to a good user experience we got the upload functionality working so right now if we try to upload any kind of image like for example if I just try to upload this random image from my file system that's going to work we also handled the case where images or where non- images are rejected right where we show the toes notification the uploading works and now we are successfully redirected to the next step in the user journey of defining or case and that's going to be probably the most interesting part of this entire build the next step um but very very nice you got everything working I'm really happy with the progress we're making and you're doing a great job following along I really mean that you know it's not easy building an application of this size but that's what I'm here for I want to teach you every step of the way how to do it and um very very nice good job all right and with that done we're about to hop into the coolest part Pro I mean all of them are cool but one of the most elaborate parts of this whole build and that is step two because step two actually involves the database the server side rendering the server s side logic um and so on and let's see what that looks like because I want you to understand this architecturally so basically we have step one then we have step two let's uh quickly sketch that out here we have step one we have step two and there we go let's change that and then last ly we're going to have well you can probably imagine step three right how are these steps going to communicate with each other and you've already seen a very basic introduction to that so what happens when we upload an image here right we attach the image ID to the URL and that is then forwarded to the second step right and the second step is going to be the phone case configurator so there's going to be like if you imagine this is the screen that you're going to have on step two right then the phone case is going to be here on the left hand side you can um or I mean kind of in the middle left hand side right here where you can like drag over your Custom Image and stuff right and then on the right hand side we have options like the finish the material um and so on but how do we get access to that image in step two well basically we already have it in the URL right that's how we're basically passing it from step one to step two that's by putting it as the question mark ID is equals to the the um image ID that we just did when forwarding the user right so when we um forward the user when we push this into the router well the image ID the config ID is already going to be there but it is not linked to any database entry yet so when we try to retrieve that ID in step two well from where do we want to retrieve it where is the image stored well technically it's stored in our cloud storage right but that's not really the best way of going about things we need a database sooner or later um it's going to be super important for this entire thing to work smoothly right and when you land in step two for the image to already be there to not be needed to upload again and so on um basically database super important for that and that's how it works architecturally right we pass over the ID from step one to step two and then in step two we can make a request to our database with that or containing that ID right we make a request to or let's make it a circle DB right up here the database is going to fetch the image URL and give us back the image URL right here that we can now use in step two to overlay on the phone so honestly that's that's the secret right that's how this works architecturally it's it's not rocket science it really isn't and um the the first step we need to do in order to get here right is to actually create a database and use it in our app so where's the first place that we want to use our database basically it's going to be in our core.ts files so that's under upload thing core.ts what should happen once the upload is complete well basically we need to store this image and the URL right that is passed to us from upload thing from our cloud storage we need to store that as the configuration in the database and in order to be able to do that we are going to use a postgress database hosted by Neon dote now this is not sponsored um I'm just going to log in with GitHub here again this is not sponsored um I like neon they're pretty good the only downside is that um you can only have one database on the fre tier and I am on the free tier but hey at least no credit card needed um but since I already have a database I'm going to need to delete that and there we go now we can start a new database um right here in neon together so um by the way if you missed the URL that's neon do Tech and that is going to be the database we're going to use in this app now originally I would have honestly used planet instead that's what I always did planet scale.com um but they recently removed their fre tier uh so I can't use it right you have to have a credit card in there now and pay like $30 a month for their database and that's just not an option for a video like this um even for my personal projects that's often just way too expensive um so I think this is a good use case for um the free tier on neon so the project name that's name it case Cobra the postgress version 16 is good let's name the database case cobra Cobra as well there we go and then as the region I'm going to choose Europe Frankfurt and you can choose whatever is closest to you or if you plan to deploy this whatever is closest to the users where they will be what you expect okay and great that's already going to give us the connection string we can simply grab this or just hit the copy button here at the bottom right close out of this and then paste that connection string into or. EnV file right so this is going to be the database uncore URL is going to be equal to the string we just got now why database URL well because that's a format that Prisma the orm we're going to use expects so let's open up a new terminal and in here we're going to install the omm we're going to use to interact with our database so we're going to say pnpm install Prisma and also add Prisma client the Prisma client is going to generate typescript types for us which're is going to make working with Prisma here loow Al very very enjoyable because it's going to be fully types safe um which is really cool and uh once that's installed we can just initialize it great that's done and once that's installed we can say npx Prisma in it and hit enter that's going to create a Prisma database file for us right here it created a new folder that command we just executed by the way let's clear the terminal just so it looks better and that created a new schema. Prisma right here and by the way we can also already open or app up side by side just so we have something nice to look at while we do this let's drag this over there we go okay that looks a lot nicer um okay so inside of this Prisma file we can get rid of all the comments we don't need that and basically the idea is that we can Define our data models inside of this file and then use them across our app to kind of um write data into our database or get data from our database and the way we're going to do that is going to be inside of a new DB folder right here that we're going to create in our source folder and in here let's create a new index.ts because the the question is when we are inside of a file where we want to fetch some server side data right so for example um before we write anything in the index here I want to show you why we do this right so let's go into our configure folder and in here we're going to create a new folder and that's going to be the design folder so this is going to be the second step of or user flow and once again inside of the folder lives a page. TSX just a naming convention that we kind of need to follow now in here let's create a con page which is going to be an arrow function and let's export default page at the very bottom now what happens by default is that we can actually mark this component as an asynchronous function why can we do that because this entire thing by default is a server s side component it runs on the server and only the HTML is then sent back to the client which means if we make a make DB call if we make a database call here that's totally valid to do that doesn't happen on the client it happens on the server and only the result that we can then include in the jsx is then sent back as static HTML and here's the thing in order to make that call we need a database client right we need a way to access our database and that's exactly what's happening in the DB index.ts that's what we made it for so in here we're going to say declare Global so we're overwriting the global typescript scope and we're going to say VAR cached Prisma is going to be of type Prisma client now this type Prisma client we can import that import at the very top Prisma client from and then add Prisma slash and we should get a to complete here client there we go by the way I'm going to give this H we can just close out of this there we go okay and now we're going to say let Prisma this will be of type Prisma client and the goal of what we're doing right here is you can probably tell from the naming we gave the cach Prisma well we're caching the Prisma database client if we already initialize the client once there's no need to do it again right there's no point creating multiple connections to database if we already have one and that's going to be some very straightforward logic so for example if the process oops process. env. nodecore EnV is triple equal to and we get some type safety here because that is a builtin environment variable that any noj runtime will give us if we are in production in that case we're going to say Prisma is equal to a new Prisma oops Prisma client right we're just going to call that in the other case else we're going to say well we're going to do a check right if we don't have a cache Prisma we need to create one client but once we have that client and this file is executed again well we already have the client to send back so if there is no Global Dot and now we get some type safety here cached Prisma there we go then we're actually going to create a new Prisma client for once right that's fine the global. cached Prisma is going to be equal to a new Prisma client however in the other case we already oops Global there we go however in the other case where we already have created a Prisma client in that case we can just say pris prma is equal to global. cached Prisma there we go okay and now all we need to do is say export con DB is equal to Prisma at the very bottom once you write this file once no matter how many projects you do this one any other project you can always just copy and paste this file as easy as that you write it once and then you never worry about it again we're never going to touch this file again we do this logic once just to prevent uh making too many clients but that's that's kind of it right technically you don't even have to do this right you could always invoke a new Prisma client if you want to but really there's no need to and I want to show you that and how to kind of uh do it a bit better right that's just important for me that you know how it works well and if you want to break that rule and always just invoke a new Prisma client well you can right technically we we could leave all of it away yeah you can but I want to teach you how to do it the proper way right and that's it we're already done in or in index.ts bam we can close out of that and let's see what the first thing we need to put into our database will be so what are we actually passing here well technically it's an image right and what characterizes an image well what's very important that we need to keep track of when the user Imports the image first off probably the most important is the URL where is this image hosted it needs to be hosted somewhere in order for us to then display it in step two right what's the other thing the aspect ratio we can have the URL of the image but if we don't know the ratio of how to display it it's going to look super weird in step two right it's going to be completely off therefore we need the height of the image and we also need the width of the image and of course from that goes the aspect ratio right if we have the height and the width that's all good and one thing we can already do while we are here we're not going to need it immediately but for l later and it's also related to the image is we're going to need to keep track of the cropped image URL right why do we need to do that well basically in step two the user can drag the image so imagine I mean there's a lot of boxes now I'm going to make this green just imagine this is the image right we can drag this image however we want over the phone as you saw in the demo in the very beginning right and then this image will be cropped so the left and right side will actually be gone in this case or now the bottom there not overlaying the phones so we don't need them we only care about exactly this part overlaying the actual phone right the entire rest on the left and right and bottom here we don't care about them and we need to keep track of how the image is cropped to then in step three display the actual preview how exactly the user configured it it needs to be the exact same thing and that's how we keep track of the cropped image URL and yeah that's pretty much it we can already get started oops inside of our schema. Prisma and and that lives in the Prisma folder right here and get started in doing a very basic modeling of this so now we're going to get started with database modeling okay so we're going to say model configuration and that's going to be the most important model in the entire app because that's going to contain all the things we just talked about so each configuration will have an ID which is going to be a string and in Prisma we can say add ID and we can also give it a default ID which in our case will be a collision resistant unique identifier we could also use a uu ID but they are longer and they take up more space then we're going to keep track of the width as we talked about as an integer it's just a number the height same thing is going to be an integer and lastly the cropped image URL will be also a string but we're going to make it optional why because in step one we're already going to create a configuration but we don't have this yet because the user has not yet um completed step two right in step two we get the cropped image URL in step one we don't so what we're going to do is create the configuration in step one and then later update it just this um kind of thing we're going to update in step two right so we can already save this the schema. Prisma and now to push those changes into our database to sync them with our remote database we're going to say npx Prisma dbm push bam we don't need to generate any kind of SQL migrations here now if you're going into production eventually that might be a good idea but this is the fastest way to get your schema into your remote database so the remote database actually creates the tables um that we need right and if we refresh this then we should be able to see um if we go to like overview somewhere well I don't know where it is right now maybe here under tables yeah there we are it created the table in or remote database called configuration so exactly what we have right here in our schema um in our Prisma schema perfect that's very very nice and now the first step is to actually create the configuration in Step One along with our image URL right that we can then pass as the ID to step two to refetch the image there um which is perfect for user experience because they don't need to upload anything again right and that's going to happen inside of our core.ts on the on upload complete this is a side function that runs whenever the image upload was successful so the image is now stored in upload thing now what should happen right that's the question and what should happen is that we store these values right here we just talked about so what we're going to do to get the height and the width is first off we need to make a fetch request con res is equal to await Fetch and we're going to make a request to the file. URL right because essentially we need to grab the image and then see how high is it and how wide is it and we need to do that inside of a buffer cons buffer is going to be equal to a weight and now we're going to use a library in order to do that one of the most popular image processing libraries and that one is called let me show you is called sharp I think it has a ton of installs it's used basically everywhere it's a high performance wait let's read that a high performance nodejs image processing um kind of package the fastest module to resize but it does a lot more than just resize it got like 5 million installs yeah it's do pretty well that's awesome and we are going to install that right now to see how wide and how high the image is so inside of our terminal let's clear that let's say pnpm install oops install sharp and we're going to install specifically the version 0.326 why because there was a incompatibility introduced between sharp and NEX it doesn't comply very well and this version doesn't have the incompatibility it works just as we expect um so we're able to grab the width and the height okay and now inside here cons buffer is equal to a weight and we're basically going to call Sharp that we just um installed right and that is going to come at the very top of the file from import sharp from sharp as a default kind of import not a named one and now we can simply call Sharp and pass it the buffer and no I messed that up the buffer is not going to be a weight sharp the buffer is just going to be a weight res. array buffer which is a function we can call on here and that's the first thing we need to do let's do everything in order here so we fetch the image we convert it to a buffer and now we can actually grab the image metadata so let's say cons image metadata is going to be equal to and now we're going to say a wait sharp and pass in the buffer cuz it takes the buffer right here right we could provide it some options if we want to we don't need to there's simply a metadata function that we can now call that's going to give us the width and the height that we can now simply destructure const width height is equal to image metadata what else is there we can see let's see chroma subsampling background channels compression delay density a lot of stuff you get the idea right we don't care about all of that we only care about the width and the height so we can correct irly display this image in step two with the correct aspect ratio right that's all we need now if there is no config ID passed into this route which there will not be by the way because if you remember if we go to our um upload page right here what we basically saying is the config ID is undefined we are going to pass it in step two but not in Step One the upload step right therefore the config ID will be undefined which means that there is no config and we need to create one or database later we're going to update the existing one remember with the cropped image URL but not in Step One in step one we still need to create it doesn't exist yet so we're going to say cons conf oops configuration is going to be equal to await and now we're going to make use of our DP our actual database client that we can now use to interact with our database and this is fully types Save By the way because it's Prisma which is great we can say db. configuration. create create and inside of here this takes an object so this takes the data property whenever we want to create something and it will ask us for all the data that it needs which is the height and the width oh and I think we forgot one thing which is the image URL Yeah we actually forgot that of course when the image is uploaded we also want to store the URL for it right that's literally the first aspect that we forgot here so the image URL is going to be a string and it's not going to be option we always want this string all right once again npx Prisma DB push whenever we make changes in our schema. Prisma to let our remote database know that we changed something in this file because otherwise it just wouldn't know this a local file right we need to execute npx Prisma DB push to push that into our database that's going to refresh our typescript and now we know that we also need to pass the image URL right so the image URL in this case is just going to be the file. URL that we get back in this function the height is going to be either the height or let's default this to 500 because this height could be undefined if the metadata is not contained in the image right if sharp cannot get it however that doesn't really happen just for typescript to be happy um it needs to know that this is a number though and same thing for the width this is either going to be the width or we're going to say 500 to make typescript happy beautiful okay and we can now simply return this return and we're going to say config ID is going to be the configuration. ID right so basically we are returning if you remember back to the client right the actual database configuration ID so that's going to be the one that we actually get back um right here right from the data that's going to be sent to the client side hook that we can use and in the other case where there is already a config ID in that case if you remember we just want to update this we are already going to be in step two at this point because step one already created the config so let's update the existing config con updated configuration is going to be await db. configuration. update and in here we're going to pass in object where basically whenever we update something we need to tell Prisma what is the entry you want to update and that goes by anything that's unique in our case that is the ID of the config that's going to be our config ID that is passed into this function um automatically and then the data we want to update this with is going to be the cropped image URL right as I told you about earlier which is just going to be the file. URL beautiful which means when in step two the cropped image URL will be uploaded in that case that will be the file. URL so the file. URL will actually be two different things in two different scenarios in Step One the file. URL will be the uncropped image that the user literally just uploads um in step one right that's just the user image not crop not anything that's going to be the file. URL and if the user actually drags around the image right here in step two as I showed you earlier and then crops it by clicking the continue button we're automatically going to upload that and then that's going to be the file. URL we're going to save as the cropped image URL right you're going to see exactly what this looks like later so basically we're storing in the actual user image that they uploaded uncropped and we're also for step two storing the cropped image I hope that's clear all right and with the updated configuration done we can simply now return that back to the client we're going to say return config ID and that's going to be the updated configuration. ID there we go let's format this very nice and now we can get rid of the Redundant return statement at the very end because in every case we are already returning a config ID so we don't need that anymore and then hit save beautiful that's literally all we need to do for architecturally this right here to work in step one we're storing the image along with the ID in the database so that in step two we can now retrieve the exact image that user uploaded based on the ID parameter because now that will be stored in our database which is great right so let's go into our design page and make that happen to actually see if it works right so instead of the comment we have here make DB call well let's actually make the DB call and step one in order for that to happen is to actually get the ID that was passed from the URL right how do we get access to this ID parameter and that's actually pretty simple in nexts a page automatically receives some properties which are the search prams and let's type these out let's say these are of type page props which is an interface we're going to Define right up here interface page props and this takes the search prams and this is a type basically provided by nextjs that we can just kind of type out ourself but they will automatically be passed in right for example containing the ID property right here and they are going to be of type in angle brackets key string so the key in our case ID will always be a string and that will map to either a string or a string array or uh undefined right if a parameter is not passed it's going to be undefined if pass it's either going to be string or a string array and to get access to the ID because we know this is called ID we can simply destructure the ID from the search prems and let's already see if this works so we're just going to put the ID right here save that and then let's see what happens so let me open up my browser here and um upload any kind of image let's upload uh just one that I prepared for up stash uh like a while back let's upload that and then let's Let It upload and then we should be forwarded with the ID parameter and actually see it on our page right and if we do then we know uh that we received the parameter in a correct way and uh why is this taking so long by the way is the dev server oh wait there's an error what's the problem let's see let's investigate this together uh uh uh why is that is a long error cannot find module sharp okay yeah sometimes something is wrong with sharp uh I think that is a version inconsistency so the error is happening right here in our core.ts it's complaining that it can't find the module sharp um so let me go ahead and fix that because of course that works just fine in My Demo let me see what's the difference and why it can't find the sharp module all right and I think I fixed the issue uploading complete simulating successfully simulated call for file okay that looks promising and now it should redirect us and my PC is 7 years old by the way so it's going to take my PC very long but that looks good redirecting and perfect there is the ID so this is the next page this is the layout remember it will always be visible perfect okay that's exactly what we want so the issue um is probably just restarting your server right that's what I did but also what I did is ADD like a little carrot here in front of sharp which lets the package manager know that we want to install the newest version um so just put that little carrot in front of the number 0. uh 32.6 and then say pnpm or npm uh install again that's literally all I did so put that carrot say uh pnpm install install that's going to upgrad or or update all the dependencies and see if we are up to date um and then very important make sure to restart your server sometimes that just fixes issues I think that was actually the problem and not really the car here but in any case um very nice it works now so we know we are correctly fetching the IDE right the configuration identifier on our page which means now because the image URL is in the database we can see that actually by going into a terminal and saying npx Prisma there we go Studio let's hit enter here and we can actually take a look into our database and that's going to open that up in Chrome but I want to open it up right here there we go we can see our configuration and if we give this a bit more space we can see our actual database and the image URL that is put in here which is the actual user um image URL uncropped if we take that and paste it bam there it is the image I just uploaded very nice and we also have the width and the height of the image perfect because now that is the aspect ratio of the image how we want to display it in step two right perfect so that's working exactly as expected very very nice and we now have the ID to work work with on the next page right so we can close out of a lot of these files and because we now know that we have the correct ID we can now do a check if we don't have the ID for any reason if it's not passed to this page so if anyone directly navigates to this page for any reason it doesn't make sense but let's check for that um or if the type of ID is not equal oops is not equal to a string in that case there is nothing to display right we need an ID for this page to work um anything else doesn't make sense so in that case let's return not found this is a function we get from next SL navigation and basically what's going to happen is it's going to throw a 404 error if the design ID um is not there right so if I crop that out or cut it out whatever you want to call it and then paste that now this check will be true and it's going to throw a 404 because we don't know what to display in that case right that doesn't makees sense but if the user follows the right steps right as we intend then the ID will always be there of course and they're not going to get a 404 now the let's actually get access to the configuration right to show the image the cons con uh configuration is going to be equal to a wait DB let's import our database in here do configuration. find unique because there will only be one configuration with this ID right it's going to be unique so we're going to say where ID so basically we're telling Prisma find the config configuration with the ID of whatever we passed into the URL as a query parameter in or case this and give it back to us right and if we don't have the configuration if nothing was found with this specific ID in that case of course um we're going to return not found as well so in that case the ID parameter is there but it doesn't match any entry in our database lastly we know there is a valid configuration and the valid ID which means we can simply destructure something from the configuration and that's going to be the image URL we're going to destructure the width and also lastly the height and now the actual core Logic for this component involves a lot of client side logic right where the user is able to drag around the image um on the phone right that's going to be purely client s side that that doesn't happen server s side that doesn't make sense it needs react Hooks and all that so we can't really do it inside a server side component therefore what we need to do is to create a new component and that one is going to be client side and we're going to call it the design configurator uh configurator because well that's exactly what it is and this component we can simply go to our file tree and create it in the same folder the design folder it's not going to be a reusable component because we don't need it to be reusable so it's totally cool or I think it's even better to create it right where you need it instead of in our components folder so inside of it here we're going to create the design oops let's spell that right design configurator do TSX there we go let's create that and this as always is just going to be a basic Arrow function cons design configurator is going to be an errow function and I need to switch keyboards there we go and fix that syntax error okay and we can export default design and I I said configuration again this needs to be configurated I hate to spell this word configurate it's I don't know it's just a hard word to spell um let's just copy and paste it down to export it as the default and now because we know what we need to pass into this configurator to correctly display the user image in step two let's also receive that in the design configurator as properties right so we get access to it right away so let's destructure the config ID let's also destructure the image URL and the image dimensions and let's say this will be of type design configurator props there we go and that's a type we can Define at the very top right here interface and because I hate spelling this word so much I'm going to copy and paste it there we go and that allows us to now Define and tell typescript what this will receive which are of course these three right here right so first one is going to be the config ID which is of course going to be a string the image URL will also be a string and lastly the image dimensions are going to be an object object of course consisting of the width width as a number and also the height as a number so we can correctly display the image to the user and I think the most intuitive way that we can go about building this component is by starting with the jsx right by kind of starting with how this is going to look and then later implement the functionality and dude if I say that you're going to learn a lot in the functionality of this component that's that's an understatement okay this is going to be really really cool and very applicable to all future projects that you're building you're going to see I'm not going to overpromise right now I'm just going to show you later and then you'll be the judge okay so anyways let's start with the design I think it's the most intuitive way to start here let's return a div at the top level this diff will get a class name of relative of margin top 20 of grid a grid calls three a margin bottom of 20 and a padding bottom of 20 as well now let's open this div up and inside of here let's create one more div and this div will get a class name which is going to be relative a height and then in angled brackets because this is going to be a custom value we're going to say 73.5 Ram then an overflow Dash oops overflow Dash hidden and I missed the angle bracket right here no that needs to go after the rim there we go so we have the height and then an angle bracket 7 37.5 Rim that way then the Overflow hidden after this we're going to give it a call span of two a width of four a maximum width of 4 XL a flex items Das Center justify Das Center a rounded dlg for Notch a border -2 a border Dash dashed dashed there we go that's going to make it look very cool a border gray of 300 a padding of 12 a text Das Center a focus as a pseudo class right that's why we put the colon right here because this is a pseudo class for the focus State we're going to say outline Das none because it would look ugly on focus a ring-2 on focus a ring Das primary and lastly on focus a ring off set of two right so when you tab into it it's going to have the little ring effect it's going to look pretty nice and then inside of here we're going to create one if this one with a class name of relative a width of 60 a background opacity of 50 a pointer-events d none and lastly an aspect ratio and this is going to be also a custom value in angled brackets and this value will be 8896 so 896 divided by 1,831 now why is this because that's the aspect ratio of the image we're going to use as the phone in this case right that's literally just the uh width and height of that image now inside of this div let's open this up and this will be the actual phone but in order for the phone to always maintain its aspect ratio to not size along with the screen we can use a really neat component that is called the aspect ratio from or UI Library well basically what it does is in code we can pass it a certain ratio right here and it's always going to maintain that ratio for example this 16 to 9 image right here it will always stay in 16 to9 format no matter how small or large we drag it and that's exactly what we want because if if the phone switched aspect ratio that would look horrible right so let's say npx Shar cn- UI at latest add and then it's aspect-ratio and hit enter that's going to install the component and then we will be able to import it and actually use it as the phone case there we go and after that's done installing we can actually go ahead and use the aspect ratio right here in or jsx and that comes from the components UI aspect ratio as I just showed you in the example we can pass this something called a ratio and this is nothing more than a number right and in our case this is going to be 896 divided by 1,831 so basically the same thing as the div above and the class name this is going to get is going to be pointer events none a relative a zindex of 50 and an aspect Dash same thing right we can basically just copy and P this down why do we need to pass the aspect again because when I was building this um one or the other didn't work it just worked together so passing it here and here worked but passing it individually just the ratio as a number or just the class name didn't work and the now it just works which is kind of weird but it's also like an unimportant detail this this just works that's all we need to know and let's pass it a width of full beautiful and inside of here we are going to use the nextjs image let's call it next image though because um we're actually going to use something called an image as an icon later on and to avoid a naming conflict with that let's name next image instead of just image so we're going to say import next image from next SL image just so kind of give it a different name and we can do that because it's a default import right and this next image will get in all tag and that's going to be phone image the source is going to be/ phone- template.png which is an image I have prepared for us and the class name of this is going to be also pointer events none a z index of 50 and a select Das none there we go let's save that let's see what happens and one thing we probably forgot yeah is in our page at the same level right in the design folder right here we forgot to pass in the props we haven't done that yet um so our page doesn't know how to render properly so let's do that let's import this component from the/ design configurator perfect and then pass in the config ID which is going to be our configuration. ID then the image Dimensions which are going to be an object made up of the width and or height and last thing we're going to pass in here is the image URL and this is just going to be the image URL there we go let's it's safe let's reload our page and is the dev server still running by the way no it's not we can completely yeah let's clear that that's yeah let me end it there we go all right let's close out of this Powers Shell let's clear this one and now let's say yarn Dev damn that was messy okay let's have one console open clear it and then run the dev server in here I don't like when I have like three terminals open and it gets super confusing anyways so let's start back development server and what we expect to see right now is the phone case right but that doesn't happen because it says image with Source um phone template.png is missing required with property well we can also just pass it the fill option right here right this is a nextjs image specific thing we can pass it and once we do the image is actually going to show up right here wow okay that looks beautiful this is going to be the area where the person can drag their image around or over the phone right and that already looks really nice with a little Cobra logo up here that I added um very very cool okay that's going to be the foundation for everything here to work and now let's also add a tiny detail that's going to be super helpful later on and that is when you drag around the image right here right if we drag this around then in the areas that are not the phone it should be more gray than the area that's overlaying the phone right to make it clear which areas are on the bone and which are not right and in order to do that let's create a div right here below or aspect ratio right here with three closing divs to go and this div is going to be self closing and it's going to get a class name that is going to be absolute a it index of 40 an inset of zero a left of three pixels as a custom value a top Dash PX a right also of three pixels there we go a bottom de PX for one pixel around it and then as a custom value 32 pixels just like that and then lastly a shadow and this Shadow will also be custom basically um the shadow is going to be the overlay for all the rest right it's not really just a layer because that wouldn't work well with the Z indexes it needs to be a shadow that this div is casting so the shadow is going to look a bit weird a bit unconventional it's going to be 0 uncore 0 _ 0 underscore and now 99999 PX underscore rgba and now we can actually pass the color value for the Shadow and this one is going to be 229 comma 231 with no spaces by the way then a 235 as the third one and lastly the opacity which will be 0.6 and we can hit save on that and there it is it just showed up it's basically um including the entire area but the phone right not the background of the phone and that's so important so only the image that's over the phone is more highlighted and the rest is kind of grayed out right that's exactly what this div is doing right here and what we can also do is already set the foundation for one really important option later on that is the color of this case right that's going to happen right below the diff we just created as a separate diff this one can also be self-closing and this one is going to get a class name that is going to be dynamic DC so let's use our CN helper function right in here import that and the default class name will be absolute in inset of zero a left of three pixels so basically the same as before right a top of PX a right also of three pixels same thing and a bottom of PX also same thing and a rounded Das 32 pixels very specific now the dynamic thing we're going to apply is going to be a template string we're going to change this later on for now um let's hard code this to background blue let's say 950 for example later on this will actually depend on the option that the user chooses for the phone case color right so this could be like red or black or whatever you want basically right and if we now red out the page then beautiful the phone case just change color huzzah right magic it just works by doing it in this here that we can later always um change for example like to a red 950 and R out the page and read out again there we go now it's red or a Inc 950 which is going to be or basically black but it looks kind of nicer than black it's not pure black but almost black it looks very very nice okay now where is the user image how do we make the user image dragable around here and that's going to happen right here below the diff we just created the self closing one one more diff down with two closing divs to go that is where we're just going to put the user image for now so let's create a div right here with a class name that's going to be relative a width of full and a height of full and in here we are going to put a next image once again this can be self closing the source is going to be or image URL that we fetched in the server side component remember and now pass down into the design configurator um which is just the uncropped user image this is going to get a fill the alt tag is going to be your image and then the class name is going to be pointer events none and we can already go ahead and save that so let's see what happens let's read out our page and what it's going to do is it's going to go ahead fetch the ID from our database and then display the source now the problem is it gives us invalid Source prop and that is because nextjs for any external image that you want to pull into your app you basically have to wh list that domain in our case the oops the hosted image um domain utfs doio which comes from upload thing right that's how they provide their files to us and we need to change that in the nextjs config we can simply say images and in here this is an object it takes domains and we can simply paste that domain utfs doio in here and hit save that's going to take a long time probably to reload or next. config.js so usually and the reload takes a bit longer but once we give it some time and reload the page now we actually expect the user image to show up right here right and once we verify that's the case then we can actually see how we can make it draggable and there it is very nice it looks horrible the phone edges are not working because the image is pushing it out of the boundaries and of course that's going to work later because the image we don't want it to be right here right it shouldn't affect the phone the phone should always look good right cuz if if we didn't have the image and the phone case looks perfect in any size right if we make it like very small like that or make it very large the edges are always exactly as they need to be and uh with the image right there that's just not the case so what do we need to do with this image to make it draggable over the phone case we're going to use a really nice library to do that and that is react R&D that is a draggable and resizable react component it's maintained by the way it's super up to date and it also provides us a ton of customization options that make it look very very good about 200,000 downloads per week that's nice we can see like a live demo here on the website with a screenshot where there are just kind of dragging around this like tweet or whatever it is and it's exactly what we want right it's a perfect match for us that's what we're going to use so inside of our project let's open up a new terminal and let's say pnpm install npm install yarn ad doesn't matter react d r n d and hit enter and once that's installed and probably it won't take very long with pnpm because I already have it on my machine here let's close out of that again and we can simply import that at the very top of the file import R&D from oops from react D R&D and then use that way down here to wrap this div inside of the R&D component this is not going to be self closing by the way and we need to wrap or entire div inside of it and now this R&D gives us a ton of options as I mentioned earlier that we can choose from right and for example we can well pass anything that it gives us access to right here which is a lot in our case what we want to do is for now just pass some defaults right so the x value by default this can kind of be anything that you want I feel like 150 usually works pretty well here so this is just the position where the image will first B when the user lands on the page right the Y can be like a 205 that's what I said it to in the demo and that works pretty well and by the way of course this needs to be an object that we are passing into default right here the height and width are important because this also takes those two right the height is going to be the image dimensions and because the user image is probably going to be very large let's divide that by four and of course same thing for the width right this is going to be the image Di Dimensions dot width divided by four as well and oh we forgot the dot height here because what we're passing it is an object there we go and so we're dividing both by four the aspect ratio Remains the Same but essentially we are making the image way smaller so when it appears here on the screen it's not going to be absolutely gigantic but it's going to be nice and that the user can see where it is drag it around resize it however they want so let's let's try it out let's reload our page and let's see what happens that is probably going to take some time class extends value undefined is not a Constructor or null interesting did we not mark this yeah okay so the problem is probably that we did not mark this as a client side component let's do that that's the entire point because R&D of course relies on user drag events and so on right it's heavily client s side so we need to opt in to the client side component all right there's the image and we can drag around the phone the areas that are not on the phone or over the phone are kind of grayed out which is amazing and the areas that are over the phone are kind of more visible more um opacity perfect and we can already resize the image but the aspect ratio is not maintained that is horrible um so the user will completely mess up their image if they drag it around and also the edges just don't look very good here like yeah you can tell that you can resize it but for example apps like canva do it much better where there's like big controls here on each Corner that make it very clear that you can actually resize this image and those are actually pretty easy fixes with react R&D as well because it completely allows us to customize anything that we want such as right the resize handle component let me show you how this works this is going to be really really cool first off let's lock the aspect ratio by the way um just so we fix um that the aspect ratio changes as the user drags it around now that doesn't happen anymore it will always remain the same aspect ratio perfect that's much better and then let's pass in something called the resize handle oops handle component right here this takes an object and basically we can Define for each item like the bottom left bottom right Each corner of this draggable component um how it should look how the um corner should look and in our case we're going to create a custom component for that and that's going to be super super simple component let's create it in our components folder new file let's call it handle component. TSX and this is of course const handle component going to be an arrow function and let's export default handle component at the very bottom right here and now what is going to return very very simple just a self-closing div element and this div is going to get a class name a class name I said there we go of withd five a height of five a rounded Dash full a shadow border BG of white a border zinc of 20000 then a transition and lastly on Hover a BG Das primary and we can save that that's literally already it we can now pass this handle component into the resize handle component function that takes things like the bottom right this going to be or handle component there we go we need to import that self closing perfect same thing for the bottom left same component basically we can just copy and paste this down let's put a comma at the end here alt shift arrow down once and twice and this is not going to be the bottom left now it's going to be the top right and same thing for the top left right here as well and once we save that you're going to see what happens it's going to be really really cool there are now these little dots on each Edge that make it much easier to resize this entire thing right as long as we're somewhere on that area with our Mouse we can resize this entire thing that is awesome and one thing we can also do to improve this is give the R&D a class name and that class name is going to be absolute a z index of 20 a border of 3 PX as a custom value and a border Das primary and once we save that that's going to give the entire thing a green outline now because the image already has an outline this very specific image looks kind of weird for me but it's going to look much better for you if your image doesn't have an outline I mean you can see it right now if you're following along um on your image it's going to look much nicer it's very clear where the boundaries of the image are and it's just a much nicer experience to resize it and drag it around nice great and that's a really important part of this design configurator done essentially the whole left side right and with where the user will be able to control their image and how exactly it will be put on the phone so we're going to continue on the right hand side right here because what's missing is all the configuration options like which iPhone model this is for the color the material the Finish um and so on and that's going to live right here below the closing R&D below the closing diff and with one closing diff to go that's where we're going to create a new diff right now and that's going to get a class name and that's going to be and let's give this a lot more space there we go and that class name is going to be a height of and now as well 37.5 Ram so same height as the entire phone area right a flex a flex-all and a BG of white and to kind of be separate from the other from the rest of the page and inside of here we are going to install a component from our UI library and that's going to be NP oops let's get rid of that npx shed cn- UI at latest add scroll Dash area and hit enter and while that installs we can already take a look at what this does let's pull this up essentially I mean the the name is kind of exactly what it is right if we put a lot of stuff into the scroll area then it's not going to expand too much vertically but it's going to stick to a certain height and instead have like a beautiful scroll bar for us to scroll through and that's how we're going to create the effect on the right side um with the Finish material and so on this is is all going to be inside of a scroll area so let's say this red thing was the scroll area right if we have like the finish and the material in here and a bunch of other options right then we can like the iPhone model for example is also going to be in here the model that the user can choose from and you can basically kind of scroll through this um instead of it being like super long on the page that's not really what we want right and in order for that to happen we are going to use the scroll area and that should be installed perfect let's give this a lot more space and then wrap this entire thing inside of the scroll area that we can now import from our own UI components beautiful this is going to get a class name and that's going to be relative a flex of one and an overflow oops overflow Das Auto there we go let's open this scroll area up and inside of here first thing is going to be a self closing div element and this div element is basically going to act as a little gradient just to make the scroll area look ni nicer so we're going to give it an area hidden and that is going to be true because um if you're on a screen reader then you don't need to know about this if it's purely like visual it's just for decoration and the class name is going to be absolute Z10 inset X of zero a bottom of zero a height of 12 a BG gradient to T So to top a from Dash white that's how it's going to start the gradient at the bottom and then also a pointer fend none and because we're not passing it a to value for example to Red 500 that's not what we're doing um therefore it's going to fade into transparency right so it's just going to act as like a oneway gradient on our page so if we save that we might already be able to see something let's pull this up into a kind of side by side view give it a lot more space and then let's reload the page okay right now we we're not really able to see it right because the options are not there there is nothing to choose from yet and but as we Implement that right now you're going to see exactly what that gradient does so let's create a div down here right below the self closing div and this one will get a colossum of padding x 8 a padding bottom of 12 and a padding top of eight as well now inside of here let's create an H2 element and this will say customize your case beautiful and this H2 is going to get a class name and that's going to be a tracking Das tight a font D bolt a text of 3XL and that's it let's format this save the page and hopefully we will be able to see what that looks like and there we are customize your case okay great and now below this H2 let's create a self-closing div element and this is just going to act as a separator between this H1 and the content that will be below it so the configuration options right so we're going to give this a width of full a height of PX for one pixel and a background zinc of 200 and lastly a margin y of six to kind of space it out from both the heading and the stuff that's going to be living right below it then below the sff you can see the separator pop up here and by the way what we don't have yet but what we're going to have soon is that um and why did this go out of the side by side by the way hey go go back into the side by side there we go um is that this stuff will actually be below the main phone configurator when we are on very small devices uh so it's going to be fully responsive now below this separator let's create a div element this one is going to get a class name of relative margin top for height full flex flex-all and justify between okay now inside of this D let's open it up we are going to create our first kind of radio group and that will be if we kind of just write the text here right now let's see where that's going to pop up um so let's reload the page you don't have to type this out why is there an arrow hydration failed that's fine let's just reload the page sometimes that happens um of course not in the final production build and right here that's where the first radio option will be and in order to make that happen we're going to make use of a UI library and that is called that's open up or terminal pnpm install at headless ui/ react this is a UI library that is made by the creators of Tailwind CSS so if you go ahead and check this out head ui/ react uh let's see right here last published 12 days ago nice um also a lot of installs now installs are kind of subjective right kind of the stupidest npm packages have a lot of installs for example uh there's a package called is even I'm not sure if it's one word or if it's separated by hyphen but um last published 7 years ago this one got like 200,000 downloads per week and last publish was 7 years ago I mean what would you publish it's literally just it tells you if a number is even or not it's literally the most mundane package and it's got like 200,000 installs per week which is which is insane okay this one doesn't but you get the point right the package quality doesn't always correspond to how many downloads it has but the last publish is usually a pretty good indicator to tell how well-maintained a package actually is um and the question is why are we mixing two different UI Frameworks right why are we using headless UI for this section right here and using shad and UI for the rest and the reason is that the shad and radio options are not good I don't like them and the radio group we're going to use from or headless UI library is really good let me show you so let's go to the very top and let's say import radio group from ad headless UI and we can probably also aut to import it yeah and now way down where we just were right here let's open up this div and this is is where we're going to use this radio group element this is not going to be self-closing but a regular jsx tag and this expects two things first off it expects the value what is the currently chosen option in our radio group and we are going to do a very proper good implementation for this so we're not going to hardcode the value down there in our radio group but instead as the user chooses a different option of course it should change therefore we need to keep track of these options in state in react state right so we're going to call this options and by convention set options as the second array D structure element and this will be equal to use state where we are destructuring from this is a function that takes an object and the first thing we're going to create in here is the color now we could just say for example black as the default color right but that's not really what we want to do instead I want to show you a much better approach and for that let's close out of so many of these files and open up our sidebar we are going to create a new folder in our source and then we're going to create it right here let's create a new folder and let's call it validators and inside of this new folder let's create a new file called option- validator dots and now inside of this file later on we're actually going to write a runtime validator you're going to see what that looks like so we can make sure that we always get the data we expect on our backend so for security that is a really good idea for now we're simply going to Define all the options a user can choose from in the single file so if we make changes here those are going to be reflected everywhere where we use it across our whole app for example the design configurator so we're going to export a const and then in all caps loog colors because this is a constant value right this will not change this is a great Convention as I told you about earlier to always know that this is a constant value no matter where you see it in your app you right away know it and each object that we are going to put in here will get a label for example the first option will be black the value will be black and lastly the tww the Tailwind color we want to apply if this is selected is going to be zinc 900 great okay let's give this a bit more space let's create the second object in here the label for this one is going to be blue the value is going to be blue as well but this time with a small B just like with a black and lastly the tww the Tailwind is going to be blue 950 so a bit darker and last option we're going to let the user choose from is going to be the label rows the value is going to be rows with a lowercase R and the tww is going to be rows 950 now if you want to offer any different colors they go right here of course right um You can offer like orange yellow green whatever whatever you want you can basically put it here that totally works um we're going to leave it at these three just to demonstrate and then we're going to say as const at the end of the array which is going to help us later basically telling typescript that this is not an array of any objects like this but it's literally an array of the exact strings that are in here so if we say as const and hover over this again now it's an array with exactly three elements with exactly the strings we have and not just any strings and any array length right so we make very sure this is an array that never changes um at runtime at least right that's the point and we always know it's exactly three elements and exactly these elements that are on here that's what the ascon means and that helps us in typescript later on you're going to see exactly why now in the design configurator instead of hard coding the option that we had previously we can now actually say that we want by default the colors that we can now import at the index of zero to be the first selected color and to get full type safety as to what or colors be can be set to right that's exactly what I just mentioned that's where the as constant comes in clutch so we can pass a generic into the used state to tell typescript what the type will be or what the allowed types are right and for the color what are the allowed types well basically black blue or rose right any of these separate objects is an allowed value inside of our state so all we need to do is to say that color the allow types are type of colors and at a certain number so for example this entry this entry or this entry right and what that does is now everywhere where we set the color we get full type safety right we know what the allowed values are for example that's going to happen in our radio group right here right the value is nothing else than the options do color and of course there needs to be an equal sign here there we go and then the on shap what happens um if the value is changed in the radio group first off we get a callback function in which we handle all the logic right and we get access to the new value right if we hover over this the new value awesome we get full type safety we know that any selected value will be either color of or allowed colors like rose blue or black right that is beautiful how does it know that well because the value is one color of those three that's exactly why the is const we add it at the very end is so helpful so inside of this callback function we can now simply say set options and we receive all the previous values as the Callback function parameter that is right here the previous so we can simply return an object directly to set or new options and that object is going to contain all the previous options we're going to spread them in there and then the color is going to be our new value basically all this means is all the other options right now we don't have any other options but later that's going to be like the texture or the Finish they're going to remain the exact same as the previous ones just the color is going to be our new value that was just selected in the unchange right so we're only changing the color um right here beautiful let's save that let's see what it looks like cuz right now we won't really be able to see anything right if we read out the page nothing happened why is that well because our radio group is actually empty we didn't put anything inside here yet so let's do that the first thing we're going to put in here is a label and that label is going to come from our UI Library as well let's say npx Shad cn- UI at latest add label and hit enter that's going to install our label component and once that's done beautiful we can close out of this and simply use the label right here in our jsx coming from our custom component let's give this a bit less Space by the way this label is going to say color and it's also going to display the Curve current selected color which is nothing else than the options do color dot and then of course the label right cu the label is the human readable version like black or blue um or Rose of or current option okay so let's save that we should already see that pop up here on our page beautiful black is by default selected because that's what we put as the default right here if this was one for example and we reloaded the page then you would see that blue is by default selected but we want to leave it as dero as black now to actually let the user choose between the different options let's create a div element and give this a lot more space this div element and that's a bit too much let's leave it like that uh we're going to worry about the responsiveness later just so we have everything on the page right now this div element we just created is going to get a class name that's going to be margin top 3 Flex items D Center a space X of three and that's already it now inside of here we can map over all the available colors and since we have already defined all colors that are available well nothing easier than that we can just map over those we're going to say colors. map and for each color we are going to return some jsx right away and that's going to be a radio group oops group there we go do option that we can choose right here now because we're mapping first thing we're going to do is pass a key into here which is just going to be the color. label as a unique value for each option and this also takes a value which is important to um pass in here because it's needed for this to work and that's going to be our color that we're mapping over great now each radio option is also going to get a class name the class name is going to be dynamic so let's use our CN helper function and import that right here and by default the class name we're going to pass into here is going to be relative Min - M minus 0.5 just a small negative margin a flex a cursor Das pointer items Das Center a justify Das Center a rounded full a padding of 0.5 to offset the negative margin then after the padding 0.5 in active ring 0o so with the pseudo selector a ring zero on focus a ring zero as well we want to hide that on active an outline Das none on Focus an outline Das none as well a border to and lastly a border transparent there we go that's the class name and now the dynamic part we're going to apply here is going to be our Tailwind um well basically what we defined right here as the Tailwind option right so I'm going to skip back in case you missed anything this is the entire class name and then as for the dynamic part we are going to apply a dynamic key so an angled brackets a template string and this is going to be border Dash and now in Dynamic the color. tww so a border zinc 950 for example or the Border blue but we only want to apply this class name to kind of give it an outline if this is the currently selected color that we are mapping over right so only the selected colors kind of stick out with a nice outline how do we know if this is the currently selected item or not and that's really easy actually with the Headless UI react because this class name we can simply cut everything here so let's mark it and press contrl X and don't worry we're we're not getting rid of it we're just moving it because what I want to show you is this is actually a callback function the class name that is simply going to return everything we just had right nothing changes about that but the cool thing is we can destructure now both the active and also the checked state from the radio group option and that's how we know when it's active or when it is checked and we're simply going to apply this conditional class name of the border now if either the current state is active or the current state is checked right that's all the logic that's going to happen here if we save that then we should already be able to see something hopefully and we are able to see well something on our page it's like a little dot it looks super weird and to make it look way better let's open up the radio group dot option and inside of here goes a span element and this is actually going to be self closing and the class name of this span element is going to be dynamic as well with our CN helper function so what are we going to combine in here what is going to be dynamic well the template string first BG and then in Dynamic syntax the color. tww so the rose or the blue or the zinc or whatever color the user is currently chosen then the second element as a separate string that we're going to pass into this function is going to be the normal class name so a height of eight a width of eight a rounded full we're also going to play a border a border Dash black and then a border opacity of 10 right so pretty low opacity let's save that and see what happens because hopefully we will be able to see that one thing doesn't work yet and I want to show you why okay and we we kind of do well we get the colors right here we get these beautiful little dots that we can choose from but they don't actually have color why is that and I mean technically it would be logical if these have colors right BG and then we apply the color dotw which is nothing else than like zinc 900 blue 950 Rose 950 so the um class name that results from that is actually valid so if we try to inspect this element and see well what is the class name it has a class of BG zinc 900 that's like the Tailwind class name for a black kind of background right why isn't it applying and the the answer is that Tailwind doesn't actually support Dynamic class names like these out of the box and a really simple or the most simple um kind of hack to make that happen that I read about online is to actually simply include these colors as comments in your code and this works surprisingly well let me show you as a comment both the colors we're going to say BG blue 950 and also border blue 950 because that's what we're going to use later now using shift alt and arrow down we can copy this down once and twice and for the second comment we're going to change this to zinc by the way if you're wondering how I selected two at the same time well you select the first one and then you press contrl D that's going to select both of them second one is going to be zinc and then the third one is going to be Rose so for every color that we have we include it as a comment and as long as we include these as comments anywhere in our code it doesn't even matter that's the funny thing if we now reload the page back the class names actually work the dynamic class names the colors actually show up and the reason that zinc is not working is actually because this is 900 and not 950 so it actually needs to match like the exact dynamic class name that you want to import later there's other ways to do this right but I think this is the best way there you can do like lookup tables if you want and there we are the zinc color just popped up beautiful and now the active color is actually selected with an outline can close out of that and give this a lot more space and this is some very nested jsx by the way um the the active color is actually now selected with a beautiful outline and the color is updating in real time using a react State um as well up here in the label that is awesome now okay there's going to be other options that the user can choose from but do you remember when we initialized the phone case background color way up here in this div right here we already made this Dynamic but currently hardcoded it to zinc 950 for the phone case background now how cool would it be if we can simply change this to now um use the options docolor dotw and hit save so let's see what happens ideally as we change this W bam or phone case background changes dude that is awesome that gives us full flexibility as for the colors that you want to apply and now because we made this we structured this nice we can simply go into our validators and if you want to add a color right here bam just create a new color with like yellow 950 whatever includeed as a comment and there you are there's your new color as easy as that no needing to change the jsx the core logic Remains the Same and it's not an exact match of the model view controller pattern right because that involves fetching data from the server but it goes in the direction of that you're not really mixing application logic too hard with the actual display of that logic right we're kind of separating out the colors there are in a separate file in a separate area so adding one does not actually change anything about the way we render um anything out right and I think that is a really good idea to structure your apps that way because it's just more maintainable it's just more fun to work in later on and if you ever need to change something you don't need to kind of think yourself into how are we rendering this but you just need to add a new Option here as easy as that great okay that is our color customization done now what is the next thing the next thing is going to be the model that the user can choose from so like iPhone 11 iPhone 12 iPhone 13 and so on and that's going to live right below our radio group right here let's create a new div and this one is going to get a class name of relative Flex flex-all gap of three and a width of four and let's open up this and inside of here is going to live or label and this is going to say model model there we go not model and if we reload the page then we're going to see that pop up it pops up right here below our colors but one thing that is not currently happening is that these are actually SPAC out so below or div um and above or radio group the first one let's create one more div and this div will get a class name and that's going to be flex flex-all and a gap of six and this diff will just be responsible for kind of spacing out the individual items that the user can choose from right so we're going to cut out using control X the closing div and paste it after or radio group or I mean this div right here now and because this div is going to contain all the radio groups right and if we now save our page we pasted the div right here let's reload it again uh we can see now it's nicely spaced out below each other beautiful and now for the drop down of where you can choose your phone model that will also be handled by your UI library in a fully accessible beautiful outof the-box kind of drop- down that we're going to use to make this happen and to install it we're going to go into our terminal let's clear this and say npx Shad and- UI at latest add dropdown-menu and then hit enter that's going to install the drop down menu once again into our local project into components UI that's where it stalls um that's where it stores all the different um kind of stuff that it installs from our UI library and that's where it just puts the code with zero abstractions with all the class names for us to change um if we wanted to right but we're not going to do that right now because this works so nicely out of the box we can now simply go ahead and import the drop- down menu component from components UI drop down menu beautiful inside of here this takes a drop- Down menu trigger that we can also import from the same spot right or custom components and this trigger will get something called an as child property um right here and all that does is by default this trigger will actually be a button and we don't want that right we want to pass our own button right here that we can simply import from our UI library and because it looks much better than the default trigger and by using the S child proper you can simply say don't render this out as a button itself self because then we would have two buttons right that's not good but just use the child as the actual trigger and just pass your properties on to it automatically right and this button is going to get a variant of outline it's going to get a roll and that's going to be combo box combo box there we go the class name of this button will be a width of four and a justify Dash between all right now in inside of this button we're simply going to use the options dot well now what do we use here dot What DOT like color no not really this is the model this is very separate from the color and if we take a look at how we did this for the color what did we put in there well basically it was where is it the option. color. label right so what we expect to happen here similarly same API right would be the options. model. label but that doesn't exist yet don't have an options. model therefore let's create it in the same kind of way as we did it for the colors right we can simply export a const models from this file and if we want to add a model at any time we can do it right here in this file without having to worry at all about the rendering logic right and this is going to be an object that gets a name property and this is going to be models and then this is going to get the options which are an array of individual objects each one containing an iPhone model right so the first option will be an object as the label we're going to say iPhone x so this is what's going to be visible to the user so we nicely want to format it and then as the value we're going to say iPhone x as one word no spaces all lowercase how we are internally going to identify this model okay same thing let's mark this entire object let's hold shift alt and arrow down copy it down change the X to an iPhone 11 and then the value also 10 iPhone 11 you can probably imagine what we're going to do next same thing mam iPhone 12 and also oops and also change that to iPhone 12 same thing for the iPhone 13 there we go and let's just copy this down two more times now one two change the first one to 14 and oops 14 and then the last one to the 50 there we go great and by the way if the iPhone 16 should already be out whenever you're watching this I don't know when you're watching this then of course you can also add the iPhone 16 down here if you want let's also Mark this whole thing as as con same reason as before so we can now very easily use this in our jsx right that's the beauty of the as con so what we can now do is say options model. label so same logic as we had before with the colors and of course in order for this to work we still need to add the model to or state in order to make it changeable by the user in real time therefore first off let's define the typescript type of what the model will be so the model will be the type of models that we can now import from our validators DOT options because we only care about the options for the state add a certain number so all this means is this can be something like the iPhone x the iPhone 11 iPhone 12 each one of these objects will be valid as the model and of course that also means we got to pass a default model and this will be the models do options at the index of zero so this will be the iPhone x so the first um array element by default you could also make this the last one so the most modern iPhone if you want to um it doesn't really matter I'm just going to go with the iPhone x here let's save that and scroll back down right here because what should happen already um if we read out the page is that we should see the drop down menu trigger at least show up cuz that's what we've already created right here with a button right and it does there is the iPhone x beautiful however right now we can't really choose anything here right if we click this nothing really happens that's not ideal and before we worry about that let's finish up this button by adding a chevron's up down um icon right here that we get from Lucid react right below our label this is going to be self- closing and it will get a class name that's going to be margin left to a height of four a width of four a shrink Das zero so it never gets smaller than these Dimensions um of the height and width that we just passed it and an opacity of 50 so it's not too visible but it's uh indicating that hey you can choose different models that this is a drop down and then SD content what should show up when you click this trigger that's going to be defined in the drop down drop- down menu content that we can also import from our UI library now let's give this a bit more space to work with and inside of the drop down menu content is where we want to display well basically all the options that the user has to choose from all the iPhone models right so we're going to say models. options. map and for each model that we get access to in the Callback function we are going to return some jsx right away and what we are going to return is going to be a drop-down menu item something we also get from our UI library that we can simply import webam right here and of course because we're mapping the top level thing always needs to contain a key and in our case that's going to be simply the model. label right that's going to be the unique thing um we're going to use as the key and now the class name each drop down menu item gets is going to be dynamic so let's use our CN helper function in here and the default class name that we're always going to apply it's going to be Flex a text of SM for small a gap of one an it items D Center a padding of 1.5 a cursor of default and not pointer and on Hover we're going to apply a BG zinc of 100 to this drop down menu item and now for the dynamic part so still inside of the CN function let's pass an object and we are going to apply a background zinc of 100 under the condition that the model. label is triple equal to the options. model oops model. label and all that check means is if this is the current selected version that's also in the state then we are going to display Oops I did a typo here BG zc- 100 there we go so if this is the currently selected option in that case we are going to apply a little BG zinc of 100 little darker background right so if we reload our page then hopefully um we should be able to see well we won't be able to see much and but if we just go ahead and render out the model. label right here already in our drop down menu item and then save that and reload the page again now the drop down menu should actually already work so let's reload it and see there we are that works and the current active item um will have little background right we're not handling any state changes yet so we can't switch the phone model we're going to add that right now but as you can see the iPhone x has like a little dark background that's exactly what this condition right here does for us beautiful so we know that works that is awesome now let's Implement that when you click a different iPhone model that actually changes the state and that's going to happen inside of the onclick Handler for each drop down menu item this on click simply takes a callback function and this is just going to say set options and we're going to set the options to whatever they were previously so we're going to return an object saying dot dott dot prieve so we're not changing any of the color or the texture or the Finish nothing of that sort just the iPhone model that we're going to pass in separately so we're only changing the model to the one that was just selected that we're mapping over right that's all we're doing and now as a tiny little detail but it's really cool we're going to add a little check mark in front of the current iPhone that is selected that's going to happen above or label inside of a check icon we get from l react this can be self closing and this check will get a class name and this class name is going to be dynamic so we're going to use our CN helper function for this the default one we're going to apply is going to be a margin right of two a height of four and a width of four as well and the conditional one we're going to apply is if the model. label is triple equal to the options. model. label so the currently selected one right this is the one we're mapping over this is the current Curr selected one in state in that case we're going to say opacity 100 and in the other case we're going to say opacity zero so it's going to be hidden but it's still going to take up the space which is important so if we reload a page then you're going to see each check mark will have like a reserved space now in the drop- down um kind of menu if it didn't have that then that would look super weird when all the text would be left except the one with the check mark that would look really bad so we are reserving the space for each check mark and only applying it if that is the currently selected phone beautiful so now we can actually change between phone models and that is reflected in real time um in or state as well and that's how the user is able to configure which phone model the case should be for very very nice we've got the color already Rose blue and black and the iPhone model works perfectly as well beautiful and now let's do the material that they can choose from the beautiful radio buttons and the finishes that user can also choose from right between um I believe it was textured and smooth finish right that is also going to live of course in our option validator in a separate file so if we ever need to change it then we can do so right here without um again having to worry about the render logic that's why I like this so much so first thing we're going to do is Define the materials what materials are there going to be let's say export Con in all caps loock materials because this is a constant as an object the name is going to be material that's how we recognize this in state and then the options are going to be in Array just like before with the iPhone models right and each option in here is going to be an object with a label and the first label is going to be silicone the value how we internally identify this is going to be silicone but with a lowercase S at the start the description is going to be undefined this is just for typescript important later so each field will have a description value so typescript knows and but this one won't actually have any text here you're going to see why in a second and the price is going to be and well we could just put the price right here right we could just say zero for the silicone but we're not going to do that one much better approach is that we Define the product prices somewhere else as well and we're going to do that inside of a separate folder and our source folder and let's call that folder config and these are values just like constants but the difference to the validators is that these won't actually validate anything at runtime these are just static files like for example the products. TS file that we're going to create in our config folder and this is going to be all the configuration we need for our products for example let's say export cons productor prices and if we ever need to change the prices of our products we can simply do so inside of our config right these are static things that we use across our app um similar to the option validators with the only difference being and that we're going to Define later on a runtime validator here the products won't have that so they live inside of the config the material prices let's care about those first the silicone is going to be zero and then the polycarbonate let's say that is going to cost 5core 0 0 so what is the underscore that's just a fancy way of formatting JavaScript numbers it's the same thing as a 500 which is basically in sense right um that's going to be important for later when we handle the checkout that it's actually in sense and not in actual dollars and the underscore is just a nice way of formatting this so it's very clear this $5 and0 right that's just a little hack if you have like a really long number like 1 million you can simply format it like that and it will be calculated as a million just like before but this is just much nicer for you as a developer and to intuitively understand what a number is especially at like really long numbers that's much easier you know as to compare it if the underscores are not there so just a little life hack um maybe if you didn't know it that's really really useful and then the finish right here the Finish pric is the smooth one let's define that as zero as well no extra cost for that and the premium one the textur is going to be let's say um $3 extra or if you want of course you can put this to any custom price that you want if you want something else here and then let's define this as an ascon for the same reasons as before so it's easier to work with in typescript awesome so these product prices coming from our con fig are now going to be used right here in our materials we can say the Silicon material will have a price of productor prices we can simply import that now material Dot and now silicon right full type safety here beautiful and as for the soft polycarbonate we can simply copy down this material option using alt shift Arrow Down Bam let's change the label this is going to be soft polycarbonate so that is what's going to show up right here in our actual like menu then the internal identifier is just going to be polycar carbonate there we go we can leave away the soft and the description for the polycarbonate is going to be actually a string now this is going to be scratch resistant coating there we go and as for the price of course we're not going to use the Silicon one but the polycarbonate price that we defined which is I believe like five yeah five extra dollars um if you want this kind of Premium option configured for your phone case and of course also use the S consons at the very end and the cool thing is because this is a very reusable way of declaring certain options in your app in this case what the user can choose from we can simply um Mark all the materials and also copy and paste that out using shift alt Arrow down we bam we can copy paste it down and now change the material simply to the finishes in the structure they are going to be the exact same both are going to have a name this one is going to be finish and both are going to have pretty much the same options only with different labels and prices so the Finish is going to be smooth finish as the label as the value this is going to be smooth the description is going to stay undefined but the product price of course is going to change this is not going to be a material but instead it's going to be the finish that we defined Dot and then of course you can probably guess this smooth finished price which is $0 extra same thing for the second option the label is going to be textured finish the value is going to be textured the description is going to be soft grippy texture that's what's going to appear actually in the button that's going to be visible to the user and then the price is going to be the Finish dot you can probably guess the textured finish and as easy as that we added a completely different option to uh whatever the user can choose from and now we are all ready to actually display them on the page right here for the user to select whatever they want to go for very very easily right because all the work is done very usably here in this file so we can now simply go ahead into our jsx and below the drop down menu below the closing div after that with three closing divs to go this is where we're simply going to map over both the materials and also the finishes inside of an array we can simply say map and return some jsx straight away from here and now also of course import both the materials and the finishes and the Beautiful Thing is because the materials and the finishes are the exact same structure they both have a name they both have options with the same label value description and price same thing pretty much um we can destructure right away for example the name we can also destructure the options and in our case we are going to rename the options to selectable options and not leave them as the default to avoid naming conflicts later on right and the jsx we're going to return from here is going to be a radio group at the top level each one of course again because we're mapping needs to receive a key otherwise react will be pretty mad at us and in our case the key is just going to be the name right so this is going to be either the Finish or the material as the key right here and the value of each radio group will be the options at the index of name just like so now typescript is of course going to be pretty mad at us because this doesn't exist yet it's basically saying dude what are you doing what the hell are you trying to do here there is no options at the index of name because the name is material or finish and or state doesn't actually have a key that is material or finish of course right we because we didn't Define that so to make typescript very happy with us and we're going to define the material key right here and this is simply going to be the type of materials do options at a certain number and then same thing for the finish right each finish is going to be the type of finishes do options at a certain number and that's going to be fully types safe because we Define it as constant in that file and now of course to Define default values for this what should be the default material well probably the one that has no extra cost associated with it right so silicone probably you could also select the more expensive one by default but that's probably going to make your users a bit upset because that's not really the user expectation right by default always the cheapest one is probably should be selected the material by default is going to be the materials do options at the index of zero which is going to be the cheapest one we just talked about same thing for the Finish that's going to be the finishes dot options at the index of zero as well and all of a sudden bam typescript is super happy with us it knows that these exist in our state now because we told it about that and now we don't get any more errors right down here where we are mapping over both both the materials and the finishes and we're actually able to continue here and get this done so the value is going to be the options at the index of name and on change what should happen if a user selects a new material or finish well basically we want to update or state and we get access to the value that the user selected in the Callback function and can work with it later um in the Callback function to Now update or state right so we're going to set the options we're going to set the state to everything it was previously that we get access to in this callback function by simply returning an object directly spreading in all the previous values so we're not changing anything besides the current name um and we're going to set that to the value now if you're not familiar with a syntax why did we put this in angled brackets basically this is dynamic now the name can be both let's visualize this this name can be both material oops or it can also be finish right we don't know because we're mapping over both and it could be both of them we don't really know which one but that's no problem because both finish and material exist in our state right both of these are a key in our state material and finish so whichever one is actually selected doesn't really matter that's the one we're going to set to the new value which is like silicone polycarbonate etc etc um all the options that there are for material or finishes so either way we're totally find the correct value is going to be updated in our state and this is just a really clean way of doing so of updating or state like that okay beautiful now inside of this radio group let's open that up and first thing we're going to do here is create a label not a label a but a regular label there we go and inside of here we're going to say the name do slice from 0 to 1 dot to upper case so we're going to capitalize the first letter in the Cur name and going to attach the rest of name do slice starting at one so everything besides the first letter so all we're doing is basically taking the name like material or finish and then upper casing the first letter so it looks um correct when we render it out if we didn't do this then both of these would be lowercase and that would just look a bit weird I guess now after this label we're going to create a new div and this one is going to get a class name of margin top three and a space one of four to vertically space out the elements a bit and inside of this div let's map over the selectable options so we're going to say selectable options. map and for each option that we get access to in the Callback function we are directly going to return some jsx and each um thing that we want to render out at the top level here is going to be a radio group. option just like before right and again because we're mapping first thing as always is is going to be our key that's going to be our option. value in this case very very nice and as always each radio group option also takes a value and this is simply going to be the option that we are currently mapping over and lastly this takes of course the class name how do we want to style this this is going to be dynamic as well just like the um options we did before and once again this is a callback function that's why I like this radio group from headless UI so much that we can simply destructure both the active and also the checked state from um to then conditionally style or um element right to use it in or CN helper function so by default the class name we're going to apply here is going to be relative block a cursor Das pointer a rounded Das large a BG of white padding X of six a padding y of 4 Shadow DSM border 2 border zinc 200 Focus outline oops Focus outline Das none a ring of zero on Focus also a ring of zero then an outline Das none on small devices a flex and on small devices a justifi dash between as the last default class name and then as for the dynamic part we're going to pass an object and we are going to apply a class name of Border Das primary under the condition that either this is an active option or it is the checked option right to just kind of make it highlight very nicely and then inside of our radio group option let's open that up inside of here first thing we're going to render out a span element with a class name and that class name is going to be flex and items Center let's open this up and let's put one more span element in here with a class name and this class name is going to be Flex flex-all and text- smm for small let's open up the second span and in here goes a radiog group. label as a span element that's a prop we can pass in here and inside of this radio group label let's open that up we're going to pass the option. label there we go that should be the option. label rendered in here and if you remember what the label is if we hover over this we can see silicone soft polycarbonate smooth finish textured finish basically all the labels we have across the two different categories that we are mapping over here so typescript always knows exactly what's up and that is so nice you can use that in all your other projects by the way you learn it here and then you can reuse it on all the projects that you want now this radi group. Lael will also get a class name and that class name is not going to be dynamic I don't know why it assumed that that's going to be font D medium and it's also going to be a text Gray of 900 um in here let's give this a bit more space by the way there we go and then after the radio group. label we're going to do a conditional check if we have an option. description in that case we're going to render out some jsx and in the other case where there's no description where it's undefined we're going to render out null and the jsx that we are going to render out is going to be a radiog group. description and I can probably use Auto completion here there we go let's open up the description scroll down a bit and each description is going to contain a span element and inside of the span element we can say option. description and just render that one out now this radi group. description takes a as and that's going to be a span element and it also takes a class name and the class name for this is going to be a string and it's going to be text Gray of 500 so a bit lighter than the M text we have up here and the span element is going to get a class name as well and that class name is going to be block and on small devices and upwards that's going to be in line just like so beautiful let's save that and let's see what happens let's give our app here a lot more space let's reload the page and then see what's up oh all right this already looks really really good we have material silicone and soft poly carbonet and we have the finish that we can now choose from between textured a soft grippy texture or the smooth finish and dude this looks so nice this is Apple inspired by the way Apple does it the same way right that's where I got this idea from and it looks absolutely great and now the only thing that's missing is how expensive is each option right how much more does it add to the price the user probably has to know that very very important and apple does it the same way by the way with a price on the right hand side so let's do that let's display the price to the user and that's going to happen below the two closing span elements and above the closing radi group. option this is exactly where we are going to put a radiog group. description again and this one is going to contain a span element before we worry about the content of the span element let's also pass an as span to this radi group. description which just means that this is going to render as a span element right so essentially this will be the same thing as like a span but with some custom properties that are automatically applied by the UI library and then the class name we're going to give to the radio group. description is going to be a string of margin top two Flex a text- smm on small devices a margin left four on small devices a margin top of zero on small devices a flex call and on small devices a text right the span element is going to get a class name and that's going to be font D medium and it's going to be a text Gray of 900 and inside of the span is where we are going to display the price of this item and instead of actually putting like a dollar sign in then the option. price we could do that but that's not a great idea there is a much better way that I want to show you and that is going to happen inside a inside of our lib folder right here we're going to create a little helper function inside of a li/ UTS and no dependency nothing this is just going to be really cool for formatting prices in JavaScript CU that's built into JavaScript if you didn't know let's export a const and call it format price and this simply takes in a price as a number and then this is going to do some stuff now that's not very specific what is it going to do first off we can create a con formatter and this formatter is going to be equal to a new intl. number format hell yeah that's a built-in formatter as the format we're going to choose en-us and then we can pass this a configuration object this takes a bunch of stuff that has to do with currency formatting in our case the style is going to be currency and then the currency is going to be USD and if you ever want to change the currency in your entire shop or have like a toggle at the nav bar where users can toggle between like Canadian dollars and US Dollars and euros and all your other currency rupees whatever um that is totally possible to just change in one place here and then it's going to be changed throughout the entire app that's why this is so much nicer than just putting dollar sign price right and then we can simply return the formatter which now has a function we can call called format wow and we can format the price right so this will just return a string that's the formatted price in the format that we specified right here that's awesome so we can use that instead we can simply render out the format price of the option. price divided by 100 right here and import or format price why divided by 100 because if you remember we have this as cents right as we defined in our pricing as 500 300 to have cents in there as well and to show the actual price we need to divide it by 100 again so if they if we give this a lot more space let's reload our page and see what happens because we're going to see the price show up right here on the right hand side extra $5 extra $3 and that just looks so nice and the user can choose from it that looks exactly like apple does it and if Apple does it then we're going to have a good experience with it as well and or users especially right are going to have a great experience with it as well dude it awesome and now the last thing that's missing here is the bottom at the buttom the button at the very bottom right that's what I meant to say the button at the bottom and that lets us continue to the next step after the user is done kind of dragging around and configuring their image as they want and that's probably the easiest part right let's do that let's implement this button and that's going to be happen um right here below our scroll area this is where we are going to create and let's give this a bit more space this is going to get a div element and this div is going to get a class name of with f a padding X of 8 let give this just a bit more space there we go all right so a with a full a padding X of eight as we have it then a height of 16 and lastly a BG of white now inside of here is going to live a self closing div Elm and what the hell did I just do inside of here is going to live a self closing div element that one gets a class name of height PX for one pixel a width of full and a background zinc of 200 and this is going to act as a visual separator between the above content and this button section that the button is going to live in now in here is going to live another div element this one gets a class name of withd F and this one is going to get a class name of WID and this one is going to get a class name of with full height full Flex justify end items Center there we go now let's open this div up inside of this div one more div and this one will get a class name of with full Flex a gap of six and items Das Center let's open this one up and inside of this diff is going to live a P tag a paragraph element and this one is going to get a class name of font medium so it's going to be the same font we as the price in our options right here and lastly a wh space- no wrap which is just going to make the text that is inside of the speed tag never wrap no matter how small it gets because that would look go awful all right and instead of here we're going to render out our format price utility function and in here we're going to pass the basore price that we haven't defined yet but that we should definitely Define um which is nothing else then you can probably imagine what it is let's go into our config file that is going to be for our product so Source SLC config SL products this is where we are where we also defined all the prices for material and finish let's from here export a cons basore price of course also in caps and this is going to be 140 this is just an arbitrary number that I chose $14 as the base price I think that's like normal for a phone case you can put this to anything you want you can make this 30 if you wanted to have like a really expensive phone case or five if you wanted to have a very cheap phone case and this is really just um what I came up with like $14 as the base price and then if you choose the premium options then of course it's going to be like a bit more expensive by5 or $3 um respectfully and that means we can now import or base price right here in the design configurator this will always be the case and now we can build on top of that with the prices from or state right so depending on which option is chosen silicone or polycarbonate or smooth finish or textured um we can add to this space price by saying options do finish dot oops finish. price we can add that and also plus the options. material. price and now the entire prices of course they're still in thousands so let's wrap them in parentheses and divide them by 100 there we go let's save all of our files and see what that looks like we should be able to see our price um pop up once we reload the page let's see and it should be also visually separated with that little separator we did from the rest awesome that looks very very nice now what's missing the only thing that's missing is the button on the right hand side that lets the user continue right let's give this like uh let's say this much space that looks good now let's create that button and the button is going to live right below our P tag let's create a button element and in here we're going to say continue and also include a little arrow right icon from Lucid react that gets a class name oops not a console log what are you thinking um oh that's because we haven't imported the arrow R um icon from Lucid react the class name for this icon is going to be a height of four a width of four a margin left of 1.5 and also an inline so it's going to be in line with the text now the size for this button is going to be small and lastly the class name for this button is going to be a width of full so it takes up all the space horizontally and once we save said we're going to see that button pop up right here dude that looks so nice we can scroll around here this content will always stay um exactly where it is and we got a fully working phone customization screen right here with live updates we can drag around the image as we want scale it up and down in the exact right aspect ratio that's awesome and can choose all the premium um options with live updating price by the way as we choose between the cheaper and the more premium options dude that is exactly what I'm talking about that looks so nice that's fully functional Apple inspired design that's exactly how they do it on their website as well and if like a trillion doll company like them does it then um we should too probably you know cuz it's probably a good idea and that just works really really nice very very good work man all right little cut there excuse me just for me it's the next day sometimes I split these up between multiple days because I don't know how long this video is going to be yet but chances are it's going to be pretty long you know like 10 11 12 hours and there's no way you can record that in a single day so uh just a little cut next day for me just so you know what's happening here so I just reloaded the page and the next thing we're going to do is make what we just created responsive so if we just go back just upload any image here and then we are forwarded to the next step right the goal right now is to make that responsive because it wasn't and it just looked kind of bad on very small devices so let's let this image upload um let's verify if everything goes correctly here in our console and it does that looks very good because we should be able to see the like green success message here and then the redirecting and on Local Host this takes insanely long right 8.5 seconds earlier uh before I was recording it was like 27.4 seconds as you can see that's just crazy man that's just way too long but also my PC is very old um anyways what we want to do right now is check this out this doesn't look very good let's go into like a side-by side view here and this is not responsive this looks absolutely awful and the fix for that is actually pretty simple so what we're going to do is inside our design configurator file that lives right here under configure design design configurator and we're going to specify that this grid should be by default a one column grid so we're going to say grid calls oops gd- calls D1 and only on large devices end upwards is it going to be grid calls 3 at the very top level div here and then similarly what we are going to do is on the other div that had the height of 70 no 37.5 RAM on this one um where the scr area is the next child that's where we're at this one is going to get a bit of a different class name as well and that's going to be a with full we're going to add here and also a call span full and that's going to take up all the columns it can unless we are on large devices in that case on large we're going to say call span one and then hit save that's already all the changes that we need to do and let's see what happens here and now we should be able to see there we go now it's fully responsive also looks great on mobile and we can scroll up and down here up and down here on the side as well and the user can actually click the checkout button right right here on mobile as well beautiful okay very very nice we've got the steps they're responsive this is responsive the bottom part is responsive as well now the next thing we're going to do is make this button actually work what should happen when a user clicks this continue button and well what we want to do just conceptually right is let's go to excal draw.com just conceptually what's about to happen is when the user clicks this continue button continue that one then we're going to do basically two things so thing number one we're going to fire off on that event is we're going to save the cropped image and let's put that uh under each other there we go we're going to save the cropped image right the image how the user has configured it is very important we need the cropped version of that to save in our database that's what we have the cropped image URL for in or Prisma schema you remember this one that's exactly where this one come in is going to contain just the overlap of the image with the phone how the user configured it right just like this for example and then the second thing we're going to do is actually update our database as well with that cropped image URL right so we're first going to save the cropped image get the URL from that and then we're going to update our database accordingly with that new URL so we always have the freshest data in our database and that's basically the two events that should happen when you click this continue button and we're going to start with the saving of the image right how can we save just the cropped area that was actually one of the biggest challenges when I develop this um app first like how do you get just this um cut out part of the image and it turns out it's actually not that hard there's a really cool way of doing this that works very very good and that is actually by creating something called a canvas so in order to do that the first thing that we need to keep track of are the coordinates and the dimension of this image so we can crop it exactly how the user configured it um when we save the image so we're going to do that in state we're going to create a new state in our design configurator and we're going to call that state const and worry about the destructuring actually let's destructure right now we're going to call this the rendered Dimension and then by convention the set rendered Dimension and this is going to be equal to to use State as I just said which we oh which we don't need to import because we already have one up here great and this use state is a function and it will take an object now the width of this image we're going to keep track of that as well as the height and we need to pass these default values right by default the width we already know and that is going to be what's coming in here as the image Dimensions as props right we can already use those um to initialize the default Dimensions that are actually rendered for the image and those are going to be the same ones as we do it in the react R and D um further down here where we divide them by four right same thing that's exactly what we're going to create the state with as well so that's going to be for the width the image dimensions. width ided by 4 and then for the height we're going to do something very similar the image Dimensions dots height divided um by four as as well very very nice that's going to be how we render out this image dimensionally and now what about the position this does not tell us anything about where the image is it just tells us about how large the image is right and to actually get the position we're going to say same thing right for State we're going to destructure the con rendered position and also of course again set rendered position and this is where we're going to keep track of the Y and X values in this certain box that we have right here of where the image is located and once we have that then we know where the image is and we know how large the image is and then we can calculate what part is overlapping with the phone it sounds like magic but it works really well you're going to see that so let's create the state cons rer position is going to be equal to use State and by default this is going to be um an object that's created with an x value of 150 and a yv value of 205 now again these are not any specific values these are the same ones we used in the react R&D if you remember same ones as here they just kind of work I just used them while developing this app and uh you could totally change them if you want there's nothing special about these numbers right here great so now we have a place to store the dimensions and the position but we're never actually setting them if you notice right we're never actually updating these values and the question is where do we get them from where do we get the dimension and position from and the answer is from R&D as easy as that we can pass the R&D a function or an event handler rather um and that's going to be the on resize stop and this on resize stop it takes a callback function just like that in which we can execute any logic that we want so let's already open this up and if we maybe hover over this we can see yeah it doesn't give us that much information but we can get get not the first item that's automatically passed into the function um ah there we go we get the event the direction the element the ref as the third one the Delta and the position okay that's a lot well we basically don't care about the event nor do we care about the direction right so we can simply ignore them let's do underscore and underscore underscore and by the way underscores are a Convention of you're not going to use this variable we don't need it so we're just going to name it underscore we do care about the ref as the third um parameter received in this function we don't care about the fourth so we're going to do three underscores for the fourth and the last one is very important we can directly destructure both the X and the Y value that react R&D gives us whenever the user stopped resizing this element and that's exactly what we're going to set as the dimension and position right so for example the set rendered dimension is simply going to be let's call this the height that we're expecting for the dimension is going to be Pars in so we are converting a string to an integer and the string we're going to pass in here is going to be the ref. style. height do slice and then in the slice from 0 to minus 2 there we go because actually this is a string let me show you how this looks like normally this looks like this 50px as a string and we don't care about the PX part that's why we slice it away we only care about the 50 but this is still going to be a string and to have it as a number we can use the parse in to convert that 50 um into a number as easy as that right and then same thing for the width that's going to be parse int and basically the same logic the ref. style dowith do slice we slice away the PX part 0us 2 and that's already it right we can get rid of the example don't need that and then lastly for the position you can see that's also provided by react R&D and we can simply use that as they are we can set the rendered position oops position and simply pass in the X and the yvalue and that's already it we get all the information we need from react R&D whenever the user stops their um resize element now what about dragging right this is for resizing but if the user drags around stuff the X and Y right here of course also change but this event is not triggered we would not catch that event right now and to catch that we also get something called on drag stop so whenever the user stops dragging and in the Callback function this takes we don't care about the event we only care about the data let's name it the second um parameter that we accept in the Callback function and now we can simply destructure both the X and the Y from this data just like so great and then we can set the rendered position to that X and Y we can simply pass in as an object very very nice okay let's save that file and now whenever we either resize this element or when we drag and drop it around and that didn't handle the restart well there we go whenever we resize it or drag and drop it around these functions are going to be called respectively depending on the action and update or state with the values that we now need to save the overlapping image whenever the user clicks the um continue button right let's do that let's finally get implemented because I think that's going to be a really cool part of this um saving process so we're going to create a new function call it async function save configuration there we go and this function let's open it up first thing we're going to do is create a tri catch block because we're going to handle some asynchronous logic let's catch the error and then later on we can handle the error accordingly so if anything goes wrong in the tri block we get to the catch block and we can I think we're going to yeah we're going to render out a toast notification to the user telling them that hey something didn't work while trying to save you know now the question is how do we handle the logic of the save configuration and to kind of get an idea of how this works right we need to know two different things first off where on the page is this container right this kind of grade out container we need to know because by default the numbers we're going to get are for the left of the page right the most left side of the page not the actual container right and then we also need to know where is the phone case inside that container so I think it's easier if I just make a little screenshot and then draw this out for you so let's kind of copy this paste it over over here uh there we go so let's kind of get an idea um let's give this a bit more space okay so by default all numbers we're going to get are going to be right here let's make this red this part this very left of the page right and that's not really what we care about what we care about is not the very left of the page but instead the container where that one is so the left side of the container and also where the actual f Lo case is on the page right we need to know those two not the most left one and how we do that is by assigning refs to them because if we assign a simple ref to an element a react ref let's create one let's say const phone case oops phone case ref is going to be equal to use ref and use ref is simply a hook that we can import from react right it's nothing special and we can also pass this user ref a generic and that's going to be type of element that we're going to assign it to in our case an HTML div element so react knows hey later on or rather typescript knows that later on this will be passed to a div element and point to a Dom node that is a div right and by default let's create this ref as null and then assign it to our phone case right now where do we do that we're going to do that inside of the aspect ratio because this is the container right that's the actual container for the phone case that's the Dom node that we want to point to with or ref so we're going to say ref is equal to phone case ref and similarly so we get access to the container and always know the X and Y value of that container this outer one that is with a gray background right we also need to know that one in order to calculate the phone case um image overlap we're going to do the same thing it's just going to be called different so let's create a new ref and that's going to be the container ref and that's also going to be a use ref for an HTM L div element um that HTML div element that didn't work and we're also going to initialize it as null right here and then we can simply assign this ref to our container and the container in our case is going to be the most top level diff kind of containing everything right this entire area right here that I just marked that's where we want to assign the ref to and in or um Dom that's exactly this element right here that's the entire left area and not the customization right that's not what we care about just this gray container so the second div in our um return statement that's exactly where we are going to assign the container ref just like so and that is the foundation for us to calculate the phone case image um overlap that we actually want to save in our database so inside of our save configuration inside the tri block right here in case anything goes wrong let's destructure some stuff from or phone case ref so con empty object is equal to phone case ref do current right that's going to be the actual link to the Dom node and then there's a function on here we can call and that is the get bounding client wct which stands for rectangle by the way now what does this do basically it gives us the exact coordinates of um or phone case ref so the Dom node we assigned it to that are the X and Y Val values that we care about and they're named a bit differently here in this case they are called left and by the way we're going to get an error right now why is that we can't just destructure these values why do we get an error that is because react is not sure that these actually exist so what we can do for example is either say on the very top and you don't need to follow along right now I'm just showing this if not phone case ref. current we could just return for example right and then we know that these exist at run time and we can simply destructure them right here the ones we care about are the left the top um the width and the height right but I don't think that's ideal right because if we just silently return then the user won't actually know why their saving configuration didn't succeed right there's no success message that's why we're just going to tell typescript yes we are sure that the phone case ref. current exists by using this exclamation point and if anything goes wrong at one time right if for any reason this should be undefined um then we can handle that in the catch block and show the to notification which is a much better way to let the user know hey something didn't work out here right and same effect right we can still destructure all the values that we care about um in here the first value we care about is left and let's rename it to case left using this colon right here that's how we can rename stuff that we destructure and the reason we're going to going to rename this is because later on we're going to work with another left property that's going to be from our container ref so it's just very clear what this left um value belongs to and by the way if you're wondering what that is it's the left offset to the edge of the page so this edge of the page it's the left offset in pixels that we have in our left right here same thing for the top we're going to call it case top and then we also care about the width and the height and then very similar with the top what is that from the very top of the page that is the offset to the phone Cas ref in pixels in PX is the unit that we're working with this is why these are all numbers great and we can get the same information from the container ref so the outer part of this entire thing we basically need the same information to calculate the actual distance that is not from the very side of the page but from inside the container so we're going to destructure from the container ref. Cur and tell types scope yes we are sure this exists by using the exclamation point get bounding client wck and simply invoke that and from here we are going to destructure the left and call it very similar to the phone case the container left the um top we're going to destructure that and call it the container top there we go we we don't need the width and the height we simply need the left and the top the same values we have for the phone case to calculate the correct offset not from the main um left of the page but just from the container um that we care about and the way we do that is we can simply say the cons left offet is going to be equal to the case left minus the container left as easy as that right and then same logic the top of set for or image is going to be the case top minus the container top there we go and that's how we know exactly where the image is on the current screen no matter how the user has sized their screen whatever they're viewing it on these calculations right here will always be um correct because they take the actual values from the user browser how this is rendered out right so if this was a very small window then the phone is closer to the edges that's of course in these calculations already right so they're still accurate and now the const actual X let's call it that is going to be equal to or rendered position dox minus or left offset and then the con actual Y is going to be equal to the rendered position doy minus the top of set awesome so let's quickly review the logic as to what what each part means means here and oops I didn't mean to do that so let's kind of clear up the drawings here let's go into a bit more space on excal draw and then see what's happening here so first thing what is the width the width is the actual part and let's remove the image just for now so I can show you a bit cleaner as to what the hell is going on here let's kind of screenshot this out paste it over here um there we go and then let's review the logic in excal draw so what is the width let's make this a lot larger the width that we have right here comes from our phone case ref if you remember what the phone case ref is it's the phone so the width is going to be the actual width of our phone how exactly it is rendered out in the user browser so it's going to be exactly this part right here in pixels from here to the right edge of the phone that's the width the height very similar exactly the same thing just the height of the actual phone case how it's rendered on the user browser and why we need this is to know how the image is being displayed and where the boundaries of that image are because everything that's left top right or bottom of the phone of course should not be part of the cropped image and if we imagine this in kind of a canvas scenario then the width and the height are going to be just the width and the height of the actual canvas whereas the actual X and actual y are the position of the image on that canvas so for example I believe the z0 point is um up here or was it down here well it doesn't really matter imagine up here up left is0 0 right then if the image was for example right here if this is the user image then the coordinates are going to be maybe like 10 10 or something right it's going to be very close to that z0 point if the image was down here then that would be on the Y AIS a lot more but the xaxis would still be zero right so all we're doing is that these actual X and actual y values they factor in the offset from the very left side of the page and similarly how the container is rendered out so these actually represent the coordinates of the image just on the phone canvas they are relative to the phone these coordinates are not relative to the left side of the container or the page right that's important so this would be 0 0 for the actual coordinates just imagining that um top left is actually 0 0 I'm unsure if it's top left or top bottom I for or left bottom I forgot that and but it also doesn't really matter because the same logic still applies either way the actual X and actual y this would be 0 0 and this would be like um for example the maximum distance that would be possible from the zero point and using this we know where the image is with the X and Y and we also know how large the image is can I grab this can I scale this up there we go we know the position and we know how large it is and that giv gives us all the information and by the way how do we know how large it is well that is the we're keeping track of that and say it the render Dimension right we're keeping track of how large the image is and that gives us all the information to know where and how large the image is on the phone canvas and exactly know where we need to crop it off right so let's do that we have all the information um so let's actually create the canvas to take um to or to export this image right the way we're going to do that is let's say con and let's give this a lot more space there we go let's say cons canvas is going to be equal to document. create element and we're going to create a canvas element now the canvas do width as I just Tau you is going to be the exact dimensions of our phone right because the phone should be the underlying canvas with these exact dimensions um right here okay maybe that's not totally accurate but you get the idea right the phone dimensions are are going to be the canvas size so that we can crop the image so the canvas. width is going to be our width and the canvas. height is going to be our height that we directly get from our phone case reference beautiful and now we need something called a context let's say const CTX is going to be equal to canvas. getet context and in here we're going to say we need the 2D context and if you've ever worked with a canvas you know what this is if you've never worked with a canvas don't worry basically what the context allows you to do is to modify the canvas and put stuff on the canvas like draw an image on there um as we're going to do and to get the image that we want to draw on the canvas well it's going to be the user image right and step one now is to draw the user image on our canvas so in step two we can actually um crop it the way we get access to the user image it's let's say cons user image is going to be equal to a new New Image and simply invoke that class for now now we need to set two properties on this user image for this to actually work first of is user image. crossorigin is going to be equal to Anonymous oh and we don't get type safy here Anonymous there we go and that is so we don't get any course errors in The Next Step so the next step is going to be user image. source is going to be the image URL and this remember we pass that in as a prop which is the URL of where we host the user image and so we don't get a course issue while we draw this image on our canvas which by default sometimes happens if we don't have the permission to draw that image and we can totally avoid that by setting the cross origin to Anonymous so we can always render out the user image and now we're simply going to await a new promise and we get access to the resolve function in the promise and what do we want to wait for until what happened well until the user image. onload equals resolve and not not online onload there we go so what this means is basically we are waiting until the image that we created here and set the source here has been successfully loaded from that URL that we passed into it here which also means now that we waited for it to load it's fully loaded and it's ready to be painted on our canvas and the way we do that that's exactly why we created this context the way we draw on a canvas is by using this context question mark because technically I guess it could be null right this is just to make typescript happy and we're going to say do draw image as easy as that and this takes a lot of values this draw image function it's going to take first thing it takes is the user image right the image that we want to draw on here then where should the image be put on the canvas well on our actual X and our actual ual y that we already calculated and then lastly how large should the image be not just where is it right not just and let me Mark this he can I can I mark this there we go not just where is this image in which corner or the middle bottom center top Center we know where it is now but how large should it be well that is simply our rendered dimension. width and also a rendered Dimension do height so we are creating a canvas that is exactly as large as the rendered out phone from the user browser we're putting the user image on there on the actual X and actual Y coordinates like the user configured it on their browser so this complete image that we put on the canvas literally exactly matches how the user put their image on the phone in scale and in position and that's important because that's going to be the basis for now exporting this image how do we export that well first off we can't just export a canvas as is that's an HTML element we need to convert it first and in order to do that let's say con base 64 which is the format we're going to convert this canvas to is going to be equal to the canvas Dot and there's a really useful function that's exactly for exporting canvases as Bas 64 and that is the to data URL function we can simply call on this canvas that's going to give us back a really long string containing the image data of the canvas as um basically for format as a string right and we can say const base 64 data is going to be equal to base 64 do split and we're going to split this at comma and then add the index of one why are we doing this if you take a look at the format um we can log this out let let me just show you let's log out the basics before I really want you to understand why we're doing what we're doing let's log that out and just for now let's also um trigger this save configuration whenever we click the button the continue button just for now right we're going to change this later but for now we're going to call the save configuration function and when we now go back into our app let's open up the console and let's see what happens right let's go into the console clear everything here and let's click continue oh okay you can see what happens wow that's a long string right and as you can see right up here it says data image/png Bas 64 comma this is the comma we will split at because we only care about the actual image data part we don't need that first part right here right so that's just how B 64 works we have that thing at the start that specifies what this is and so the browser knows this is BAS 64 but to save that image we already know this is BAS 64 and we don't care about that first part only about the second part that's why we can ignore the part at the index of zero right I think that makes sense we can get rid of that cons log M again and as I said I really want you to understand why we're doing what we're doing right um so now in order to save this file we can't just save it as a string either what we need to do first is to convert it to an image the way we do that is let's say con blob and that blob blob is going to be equal to a base 64 to blob which is going to be a function that we are going to create together and it will take two things it's going to take our base 64 data and it's also going to take the format in which we want to convert this so in our case we can say image/png so the way in which we specify this is that we expect this function to take in a Bas 64 image and then output us an image/png file and converting basic4 to lob is actually pretty straightforward let's um do it together for example let's define a function right down here and let's say cons um no we're going to say function and let's call it base 64 to blob and now let's open this function up and of course we already know what we should accept in the function which is the base 64 let's call it base 64 as a string and then other than that the M type and that's also going to be a string so basically what we want to export this as right in our case a PNG now how do we convert convert a b 64 string to an actual PNG it's not that hard the first step is converting the B 64 string to the individual bytes and we can do that by saying const bite oops bite characters and this is going to be equal to a o aop which is a builtin function and we're going to pass in the base 64 right here and that function is going to be very mad that we use it right that's normal it says hey this is deprecated don't uh don't use this there are better ways to do it and that's true there might be different ways but I always do it this way also in all my new apps and it just works very very well it should it does exactly what it should and I've tried doing it some other ways and nothing worked as good as just using this function so yes it is deprecated there's probably um another way to do this but this is just the shortest most concise way and it just works really well so I don't care if it's deprecated if it works it works and so we're just going to use it and then let's say con bite numbers so we're going to convert this to numbers it's going to be a new array and we can give this array a certain length that's what it takes right here and that's going to be exactly the bite characters. length so it's going to be exactly as long as that string in bytes right so now what that allows us to do is to for each um array element in the bite numbers to push the Char code into that array sound more complicated than it really is we're going to do that inside of a for Loop and let's say for let I is equal to zero so the iterator will start at zero until the I is smaller than the byte characters do length and because that's zero base that's going to be exactly the right length and lastly we're going to say I i++ there we go as so we're going to go go in one steps now inside of this for loop it's going to be one line it's going to be very simple all we want to do is to push the Char codes of the bite characters into the corresponding array element in the bite numbers so in order to do that we can say the bite numbers at the index of the current iterator so that will be like zero 1 etc etc until we reach the bite characters. length is going to be equal to the bite oops characters do Char code at that's what I meant with the Char codes and this takes an index and if we take take a look at what this chart code at function even does it says right here if we hover over it um it Returns the uni code value of the character at the specified location so in our case all we need to pass in here is going to be the current iterator right and that's already it we now populated or bite numbers array which now means we can convert it to a u in8 array so let's say con bite array is going to be equal to a new u in 8 array and pass in the bite numbers right here now if we hover over this we can see the type is a new u in aay exactly what we have right here why are we doing this right that's very important to understand so that we can now convert this to a blob element and a blob for example takes the mime type that we want to convert it to so taking the original string and converting to a bite array gives us the full flexibility to now make the MIM type out of this array the way way in to do this is to let's say return a new blob and we can call this Constructor with the by oops bite array and now the oops I misspelled that bite array and now is the second thing we can pass in here the type it takes the endings and it also takes the type we only care about the type and that can simply be our mime type we can specify we want this bite array as an image/png and that's it right that's all the logic to convert to convert a base 64 string encoded image into an actual PNG that we can now use and store in our um upload things storage right so we have the blob element right here that's exactly what we need and now the last thing we need to do is to convert this to an actual file element right nothing easier than that we can say called file is equal to a new file we can call it just like we did here with the B array the new blob right where it takes an array and that's the bite array this also takes an array and that's simply going to be or file right and then we can also specify the file name let's just say for example fil name.png it doesn't really matter what we put in here and then the last thing are the options that this takes so what we are going to pass in here is going to be the type and that's going to be image/ PNG which just means that the file we created here um is going to be of Type image PNG oh and the of course this doesn't take the file but it takes the blob right so we are making a new image/png file called fil name.png which doesn't really matter because upload thing will automatically generate a file name for us and as the basis for this file the actual file content um we are using the blob that we just um got from or Bas 64 string that we converted into a bite array right awesome and that file is a normal PNG file that we just got from our canvas that is amazing because now we can simply upload this file right into our cloud storage right so we're all ready to go the only thing we need to upload is an actual function to upload and you know where this comes from we can simply destructure something we're going to worry about in a second from use upload thing and we can import that hook from our custom helpers and simply pass the image uploader in here and that's already all we need to do we can simply destructure the start upload function from here that we can now use to upload the file we just created which is going to contain the exact way in which the user dragged around their image and scaled it and simply call the start upload function this takes an array that's going to be our file technically if we wanted to only upload one file what you would expect is to just pass in the file that's fine this always takes an array in case you want to upload multiple files which we don't we just care about the one and then let's also call this with or config ID that we are required to pass and in this case we are actually going to pass the config ID that we passed in as a property into this component you remember when we scroll way up right here that's what we receive as a prop which is the configuration ID that comes from our database that we put in here why are we doing this why are we now passing the actual config ID string because and that ties in very nice with what I showed you earlier because we want to not create a new configuration if no ID is passed but now right the configuration it already exists in our database because the user image is already there so now in this case because in the upload we had the first step right where we create a new configuration now we simply want to update the existing configuration with that cropped image that's exactly why we made it optional right here because now only in the Second Step do we actually update that with the cropped image URL that we get from upload thing that we get from uploading the cropped image into our file system right so it all ties in I think that's amazing I really hope you can follow um it all ties in now that's how the back end comes together that's why we have this if statement with the configuration ID because if we now pass the configuration ID the second lse case is going to be um run this one right here and when the image upload is done server site is going to be executed and update or database it ties in so well really really nice all right and if anything goes wrong during this entire step oh and by the way that's also a wait the start upload this is an asynchronous action so we want to wait until that's done and if anything goes wrong during these stages up here or during the upload for example let's inform the user that something in fact went wrong and that's going to happen inside of a toast notific ation so the very top it's destructured toast function from or use toast Hook from or UI Library awesome that means we can now render out a toast error let's do that inside of our catch block we can now simply invoke that toast function and pass it the title and the title is going to be oops something went wrong then the description is going to be there was a problem saving your config please try again because we want the user to try again it would really suck if they lost their configuration for any reason and lastly the variant is going to be um we can choose between default and destructive this is going to be destructive because um something went wrong right awesome let's try that and by the way do we get anything back from the start upload um not really okay so let's take a look at everything works correctly we're going to save our configuration and what we expect to happen is to then have the cropped image in or upload things storage right so let's try out if that works let's clear the console let's reload the page and hopefully everything works correctly I'm going to open up my Explorer here so I can drag in an image and sometimes this takes a really long to load okay let's load or network tab so we can see when the uploading here the left um starts by the way get rid of that there we go okay and let's try this so I'm going to and just to verify before clicking the continue button let's go to upload thing.com into our actual storage right let's click uh dashboard let's open up where is it case Cora Dev there we go and then head to files okay right now we have 1 2 3 four five files in here now we should have six after clicking the continue button so let's do that we we should see a network request pop up which is the upload and that does happen beautiful and now we should be able to see six images if we reload or upload thing and we do beautiful there is file name.png and if we open this up that is the cropped image of how we configured it on our phone it starts at the etss right the s from the sets it's kind of cropped off because that's how we configured it and it also is right here on the cro image and then the last thing we see is these two lines of text at the very bottom and that's exactly as they are configured by the user as well and then we can see for the text here it might be a bit small for you to read checking it says here and then only the CH EC are still visible on the phone area and then same thing right here beautiful very very nice that's exactly how we configured the image and uh yeah that's perfect okay we now know that the uploading works correctly which means when we hit continue then the user image how they configured it is Pixel Perfect um saved in our database which now means we are all set to actually also do the second thing that should happen when we save this right the cropped image is now saved now what we want to do is to update our database with what you might wonder well what about these options the color the model um the material and the finish what about those well we don't currently save those and of course we should those should be stored in the database as the user configuration right and the first step in order to save this in our database is to even have the option to save it in our database because currently these um like the finish and the material the model these options don't actually even exist in our database and I want to show you the way to do it that and that is by creating something called enums now enum order status let's create that one first in our schema Prisma now what is an enum well basically it's just a list of valid entries for example let me show you what this means the order status which we're going to need for later is going to be either fulfilled this can also be shipped or lastly this can be awaiting shipment you see what this is again we're not going to need this right now but I want this U I want to use this to illustrate the point these are the three possible order statuses and that's that these don't get a type these are not like string these are not like integer no that's not what this is for this is just a list of valid entries that's all an enum is um in our database model same thing let's create an enum for the phone model you can probably imagine what this will be right if we take a look at our validator what are the phone models that we support well all of these in the options the iPhone x iPhone 11 iPhone 12 and so on this is a list of valid entries for the phone model which is exactly what we are doing with the enum so let's say iPhone x is going to be a valid phone model same thing for the iPhone 11 same thing for the I and I keep spelling them wrong iPhone 12 iPhone 13 iPhone 14 and lastly iPhone 15 and maybe who knows if you're watching this and the iPhone 16 is already up out uh go ahead and add that but when I'm recording this it's not out yet um so this is basically just a list of valid phone models that we can later use and to store which model the user selected the possible ones in our database right and similar thing for the case material that they can choose right here between silicone and polycarbonate we're going to create an enum case material and to store which one material and to store which one the user selected we are going to create both of these in our database as well that is going to be silicone and then the second one is going to be poly carbonate just like that lastly of course we also want to keep track of which finish the user selected in or database so you can probably guess by now enum case finish is going to be either let's see what do we have the smooth or the textured right so let's add the smooth and let's also add the textured enum right here beautiful and now where do we use these enums right now these are just independent well we store this information along with the model configuration right um because that is part of the configuration of the user and which options they chose obviously is a part of that so let's keep track of the model that the user selected the iPhone model and that's going to be the phone model and this will also be optional because we're not going to initialize the configuration with the phone model as this is only chosen in step two of our configuration and not in step one right so we're going to update the entry later with this but not we're not going to know it when we create the configuration right then the material is going to be the case material same logic case material and same logic here this needs to be optional because we're only adding it later on in step two the Finish is going to be the case finish same logic optional and lastly I think what we did not add yet are the model colors yeah right we didn't add those so what we need to do is to create one last enum that's going to be the available colors for a phone model let's call this the case color and the ones they can choose from are black blue and Rose and if you decided to add any color here yourself of course include that um as the possible case colors that user can choose from that we now can keep track of in our database so let's also add the um color in here and that's going to be the case color same logic also optional cuz we're only adding it later in step two beautiful okay now once again after making changes to or schema. Prisma we need to tell or remote database about these changes and in order to do that just like we did last time let's execute the npx Prisma DB push command that is going to push these changes into our remote database and it's also going to restart our typescript server so all of our local types are also up to date with our database config and why did this not work Prisma schema loaded from your database is now in sync operation not permitted interesting why did that happen Okay I don't know why it happened but it works just fine if we look into our remote database um it did actually update the table with the color finish material and model so that worked just fine so this error is not related to the pushing and I don't really know what it's related to maybe this because I have low dis space on my PC that could be an issue let's just reload our window and um we should be up to date with the types script types um anyways let's continue and if that should be an issue later on then we're going to fix it but I don't think this is a big issue and chances are you are not getting this error I think this might be related to my this space I'm not sure um again if this should be an issue later on that you are also facing of course we're going to fix it but I don't think this is a big deal um so let's just go ahead and continue so to keep track of these values in or database the model the material and so on our remote database now knows about these values and that they're optional how do we put them in our database and this is where the coolest data kind of fetching or modification approach of all time comes in I like this so much and I want to show you exactly how to do it so what we're going to do first things first is install an npm package and that is at 10stack react D query and that's going to allow us to do something absolutely magical I only learned about this like like two or three weeks ago that this is even possible and I really want to show you how this works and um as the basis we're going to use react query or now known as tanac react query same thing and it's gotten a lot of traction and I use this in literally all of my projects it's it's got to be my favorite library of all time it's absolutely nice and so if you've never used it now is the time to learn it together with me it's it's really good and I'm going to stop hyping it up I promise let's just do it so we're going to say pnpm install at T stack oops T stack SL react Das query and hit enter that's going to install the package and we can set it up um once it's installed and there we go it's installed beautiful and the setup for this package is really easy what we're going to do is let's go into our sidebar and create a new component in our component folder and this component is going to be named providers. TSX because what react query requires is a provider a context provider um so for react context right and that can only happen in client side components so at the very top we're going to say use client and now let's define the const providers is nothing more than a regular react component right that we're going to export default at the bottom providers now what is this going to return this is going to return the react query context provider basically it just allows us to use react query across our entire application if we do this um so in here we're going to return a query client provider and we should be able to automatically import that if we're not able to because it comes from a package which we have just installed typescript might not know about it yet so let's quickly reload or develop our window using shift control and P and then hitting developer reload window that's going to reinitialize typescript and now it should hopefully know where to import this from it still doesn't wow okay anyways let's just import it ourself then let's say import query client provider provider there we go from at 10stack SL react D query and then it should know about that right so now it should complain property client is missing that's fine at least now it knows where to grab this from which is T stack react query and this takes something called a client this client is very useful for caching T query automatically has built in caching and to take full advantage of that let's create a cons client that's going to be equal to a new query client and simply invoke that the query client also comes from tack react query the query client there we go and we can simply pass this client into the query client provider all of this we only need to do once to use tack react query across or entire app by the way so just conceptually what we're doing here we're basically wrapping or entire app inside of this query client provider let's call it uh qcp query client provider that's this component we grab from react query and now the beautiful thing is anywhere where we use um react query in our app for example let's say over here we fetch some data or over here we fetch some data if we fetch data from here for example from uh let's give this a bit less space there we go for example let's say we fetch data from SL Aiello and we also fetch the same data like 10 seconds later in another component because we use the query client provider and WRA or entire app with it that is actually going to Cache the result from this API hello so that when we call it from anywhere else in the application we can simply draw from the cache instead of making that same request with the same answer again right so it handles caching and a lot of stuff other than caching as well but I'm not going to bore you too much much with it um under the hood it's super useful and it just does all that under the hood for us and that is why we wrap or app inside of this query client provider because again I want you to understand why we're doing what we're doing and in order to wrap or app we need to receive some children in this component and these children we can inline their type children are going to be of type react node so any react component is totally fine or any HTML element totally fine as well and we can simply render them out right here inside of our query client provider essentially wrapping our app and now to actually use it we're going to do that inside of our root layout let's navigate over here and we can simply wrap the entire um app which are our children everywhere where we need access to react query let's cut those children out and wrap them in our provider's component that we just created then paste the children back in here so we're wrapping all of um what comes then in our app as the children with the query client Provider from react query and that's the basis for how we do it we can close out of this file we're never going to touch it again we don't need to we're completely done in the providers essentially all we just did is make us able to now use react query to save the um the phone color the phone model the material and so on and now comes my favorite part what I um kind of hyped up like 5 minutes ago I want to show you how this works so in our design folder we're going to create a file and that is going to be called actions. TS these are serers site logic functions that we can write in here assuming we say at the very top use server that's how we specify to nextjs this is nextjs specific that we only want to execute everything that is in this file on the server never on the client right and that's important because we're going to use our database in here first thing let's say export async function save config and this will be the function responsible for saving or like Color Finish material and model right that's going to happen inside of this function and this is something called an RPC a remote procedure call essentially just looking like we are calling a function which also means because we treat this as a function we can simply retrieve all the parameters that we um need right which is the color the finish the material the model and lastly the config ID need to save this to so this is not something we need to pass as URL parameters this is not something we need to pass inside of a post request body no this is as if you were calling I mean technically you are literally calling a function and that is what's called an RPC a remote procedure call something that's a extremely important pattern I learned this at my first ever developer job and rpcs are amazing that's how literally our entire software worked back at that place where it worked all right and in order to tell typescript what these are we can simply benefit from the validator that we have already created because all the allowed values are literally already inside of here similar they are already in our database right and the database that's the beautiful thing about Prisma if you look in here these are actually fully valid types that we can simply get from our database how does that look in practice well let's tell typescript the type of these um options so for example the color we can simply grab the color and we can import that from Prisma client right or it's called the case color I guess right then the Finish same thing that's going to be the let's see what we call it in Prisma the case finish right we can simply put that right here and import that type the Mater material there we go is going to be the case material simply import that from Prisma as well the model is going to be what did we call that one I forgot uh did we call it the phone yeah the phone model we can simply grab that paste it here import that from Prisma as well and how cool is that right we can literally just grab the types as they are and then we can also get the config ID and that's simply going to be a string nothing special but we need to know which config to update um with these values right and that's already it inside of this function we can now execute server side code because at the top we specified use server this will only run on the server just like an API route for example right that means we can now access our database we can say await DB and import our database to um interact with it do configuration at the very bottom right here do update of course right the configuration already exists and now we want to put like the color the model the material and the Finish um and update the configuration with that value first off we need to specify which configuration we want to update with the wear clause and that's simply going to be at the ID where that is the config ID that we pass into this function as an argument right and then the data we want to update this with Prisma also expects that is going to be the color the finish the material and the model we can simply pass in here now there's a bunch of other options of course but we don't care about those and we only care about these four four things that users specified right here in their selection screen right and that's it that's all we need to do right we can already save this actions. TS file let's close out of some of this and we are all ready to go and use this function with react query now in or design configurator and that's the beautiful pattern that I want to show you how does this work let's worry about the destructuring later let's say const empty object is going to be equal to use mutation which is a hook we can destructure from react query and this takes an object in here we can specify the mutation key and this is going to be in an array the save Dash config this is any string that you want you can put whatever you want in here this is important for caching if this is cached then if we have this mutation at any other place if we want to and we can benefit from the cache and this is also for invalidation right the data we get from this this is not something we're going to take advantage of we don't need that right now but if you wanted to say um that the cach should be invalidated that's in here and that's what we do with the mutation key or the query key if we had a used query which is used for get requests um that's what the mutation key is for in our case this is any arbitrary string we're not going to use that part of react query because we don't need to but react query allows you to do that and that is beautiful and as for the mutation oops mutation FN mutation function that we want to pass in here this is going to be an asynchronous function and this will let's open this up take some arguments let's name this arcs and these arcs are essentially exactly what will be pass in the um save config um function that we have right here right so one thing we can do is to literally grab the entire typescript annotation for a save config and then export a type and let's called this for example the save config arcs type and that is going to be equal to an object that contains these values and then we can simply use the save config arcs right down here and we can now also import the save config arcs into this file right to directly type out um what the arguments are that we want to put in or mutation function now what that allows us to do is wherever we call the mutate what we can destructure from the use mutation and let's rename it let's save save config and now wherever we try to invoke this function you don't need to follow along I'm just showing this to you um then we are expected to pass in these variables fully typ save um where we now need to pass in oops like the color config ID material and so on wherever we call this function which is awesome now inside of this mutation function we're going to do two things if you remember what we want to do here it's both update the database and also save the cropped image right and this can actually have been independent from each other they are not related so for performance reasons we are going to execute them at the same time simultaneously the way we do that is by using an await promise.all and now all array elements that we pass in here are executed at the same time and that's just faster and it's also just more efficient right so the both things we want to do is first save the cropped image we already have a function to do that if you remember it's right down here the save configuration ation which means we can simply go ahead and call that right here and the second thing we want to execute at the same time as the save configuration is our action the save config right and to avoid a naming conflict between our mutate and the save config and we're going to import that and rename it as underscore save config and then yeah let's just import that first let's go to the very top and let's also import the um save config but let's rename it as underscore save config from or do/ actions to avoid the naming conflict as I just mentioned and now we can simply call it and we are expected to pass all these arguments in here but we already have access to them as the arcs right here so we can simply pass in the arcs as they are typescript is Happy awesome very very good and now whenever we call this function this one function everything is going to be saved right these two functions are called and everything is saved for us which is really nice we don't have to worry about the logic then anymore we just need to call this function up here and everything else is handled for us beautiful now if anything goes wrong during either the save configuration or the updating of our database in that case on error that's what we can pass into react query this is one of the most useful stuff we can pass in here what should happen if anything goes wrong we can simply render out a toast notification let's call our toast as the title for this toast let's say something went wrong as the description for this toast let's say there was an error on or end period please try again period and lastly as the variant of course to make this like red and seem like an error we want to use the destructive variant for this TOS notification beautiful now what should happen if we are successful right we also need to handle that case what should happen in that case basically we want to redirect the user to the third step right step two is done everything is saved and now we want to go to step three um to let them review their final design with the image that we saved and in order to do that let's handle the on success M case as well this is just a callback function and now we want to push them to that third step and in order to do that just like from step one to step two we're going to use the next J as a router to do that const router is equal to use router which is a hook we can import from next SL navigation and not next SL router as I mentioned earlier because that's for the old kind of next J and now inside of this on success we can simply router. push where do we want to push the user to a template string and that's going to be slash configure slash and then preview that's going to be the name of the third step right here and lastly we want to append the configuration ID so question mark ID is going to be equal to and then as a dynamic um kind of insertion into this template string the config ID that is passed into this component as a prop right from our server from the main page. TSX which is a server side component that renders out this design configurator awesome okay we handled the error case we handled the success case let's try if it works so what we expect to happen right now and by the way we can also start at let's start at zero right let's go to the homepage let's verify that everything works up until this point that might take a bit because again my PC is very slow I already have the new pc by the way um it's a Mac and I'm really happy because it's much faster than my new pc but I didn't want to switch out my operating system while making this video like in half of it that just didn't make a lot of sense to me but it would load the pages much faster um so but I'm still using my Windows PC my seven-year-old one to make this video um so it takes a bit longer sometimes all right so let's click create case and I'm just going to upload uh this image of me right here you can upload any image that you want any PNG jpeg or JP EG that you want and oops no I didn't mean to do that I didn't mean to click all right we are redirected that works we can kind of put our image anywhere uh we want it let's put it so I'm kind of peeking into the phone right here let's select uh blue as the color or actually no I like black more let's go with the iPhone 15 select all the premium options and then once we hit continue we expect everything to work well and for us to be redirected um to The Next Step which of course will be a 404 page because it doesn't exist yet but let's try that out let's hit continue we should see the network request being made successfully that is good and then hopefully we will be redirect it let's give this a bit less space let's see what's going on in our console uh uh where are we running this okay upload things successfully simulated callback that is good why are we not being redirected interesting so what's happening we didn't get an eror message oh wow dude okay we never even okay well my bad we never even call the save config function of course nothing is going to happen man all right uh we we of course need to call the function right so all we're doing right now is saving the cropped image of which of course is good but uh we want to actually save everything so let's go down to our um button right here that says continue and actually call the correct function let's call the save config and in here this takes the um arguments the config ID that is passed into a prop as a prop into this component the color is going to be the options. color. value the finish that this takes is going to be the options. finish. value so basically all the values from the state that the user chose right we can simply pass in here same thing for the material that's going to be the options. material. value and lastly the phone case model is going to be the options. model. Val awesome we can now save that and let's try this again let's reload the page that's going to let us resize uh or image well once that's loaded which is a bit slower on my PC we can already go ahead and open up the network tab because that's where the successful Network requests will come in if everything works correctly all right let's read out the page all right let's make the image larger same thing I'm going to uh make it so I Peak into the phone right here and then let's choose just the polycarbonate is fine we don't need to finish and um let's hit continue and then okay something went wrong good that we tested this what is the problem we can take a look into our console probably and as what is happening 500 in 80 milliseconds so it seems like something is failing on our actions. TS do we get any error message right here invalid Prisma doc configuration. update interesting so it does seem to be a database issue maybe the issue from before wasn't so mundane as I thought um so first step is let's get rid of all these consoles by the way that's just way too much let's try pushing into our database again npx Prisma DB push so the issue probably is with our database I wonder what it could be interesting Now it worked there was no error it didn't change anything from before but that pushing just worked did we not push after creating the enums that's po oh that's possible I think we didn't push any changes after creating the enums that is why that didn't work okay so we need to push our changes of course to synchronize or remote database with our local Prisma file that's very important so our local and so the remote database knows about these enums right it has to and if we want to actually update that enum um or those multiple enums right here in our function right of course um we need to push into our database let's try this again let's reload the page and now hopefully we should see the network request being successful and we should also get redirected to the slash preview which is going to throw a 404 but as long as we get redirected we know that everything is working correctly all right let's reload the page there we go let's drag this image kind of larger same thing and let's hit continue and now these requests should be successful um if we did everything correctly the that one is successful that one is successful good the Callback was successful as well nice and we are being redirected to a 404 perfect that's exactly what we want nice very very good that means we set up everything correctly the cropped user image is stored ex exactly as the user config configured it um in this step right here we are forwarded the configuration is saved in our database via the action very very nice and this pattern um that I hyped up earlier of that we can simply use a server action inside of react query and handle the loading State and the success as as this like this that is so cool I'm such a big fan of this as I said earlier I only found out about like two weeks ago that this even works with server actions and we don't even need to write an AP for this that is so nice and now you know about it too I'm really happy with how this works very good everything works up until this point you did a really good job all the configuration is saved um like the color and the phone model and the user image which means that's the perfect basis to now summarize everything that the user chose all the premium options uh and the phone model and so on in The Next Step where they will then be able to actually check out to pay us for their phone case which is going to be one of the most fun steps because we're implementing um payments there with stripe that's going to be really cool very very nice I'm very happy that works perfect so when we now click this button we are redirected to the preview page so let's do that once again let's just click the button because that's right where we can continue on the preview page and just as the same logic you remember how we get the ID for the configuration inside of our configurator it's passed as a um query parameter right we have the ID is equal to and then dot dot dot I'm just going to say that that's the configuration ID right that we then receive in a server side component first that's going to be the page. TSX and then that's passed into the client side component as a why can I not copy that hello thank you that's then passed into the Cent site component as a right so we can access it um in the client set component and do whatever we want with it the same logic is going to apply right now for the preview step because as you can see we also passed the ID of the configuration to that URL so what we're going to do is also fetch the configuration for the ID on the server s so that doesn't happen on the client because of course we're going to use the database to do that and then we already know when the page is rendered and that this configuration exists and what the configuration is in the preview for the design right so same logic as we did before so let's give this a lot more space the let's take a look at the URL that's going to be SLC configure SL preview and in nextjs fashion how that works is we need to go into our app folder into configure and then create a folder with the name that's actually going to end up in the URL so in our case that's going to be preview and inside of here create a new page. TSX just like before with the design and the upload and with that kind of naming convention and as always the const page is going to be an arrow function that we can simply export default at the bottom there we go and now again by default this will be a server side component in which we can receive the ID as a search parameter that we can immediately destructure so let's get access to these search params right here in the page properties and let's say these are going to be of type page props and now to Define them we can create an interface right above our page interface page props and we can now simply Define the type of those search prams the search PRS we did that before are going to have a key and that key for the search prams is going to be like the um ID for example right this is the key so question mark key is equal to value and that might be a bit small for you let me quickly copy that over uh and make that a bit larger so if we paste that in then with the question mark we're basically saying we want to append a query parameter to the current URL that's what that means the ID is the uh can I make this yeah the ID is the key so that's what we have right here that's a string and then the value of that key will be this entire part no this entire part there we go that's the value right so we have the key that's going to be a string and that key is going to have a value and that's going to be of type string or string array or undefined if the query parameter is not passed in perfect so now typescript knows what the hell is even happening here and we can immediately destructure the ID from the search prams to actually oops search per Rams so get access to this value that is highlighted red here on the right hand side and if no ID is passed into the this page when calling this URL or the type of ID is not equal to string in that case well something is wrong right we can't process the ID that doesn't make sense and so in that case we're going to return the not found that is provided to us by nexts once again to throw a 404 error right if the ID is not even there or if it's invalid format if it's not a string then let's actually get the configuration from our database for this ID right let's say cons configuration is going to be equal to await DB let's import our database here and in order to be able to use the await keyword we need to mark this page as an asynchronous server s side component because this runs on the server we can totally do that then let's call the await db. configuration. find unique to grab the configuration of the M ID that was passed in as a qu parameter to see if this actually exists right so to tell Prisma where it needs to search which property to search for we're going to pass in the where ID and if that doesn't exist if there is no configuration with this ID that was passed in that's very possible right then we can say if not configuration in that case we're also going to return a not found that's going to throw the 404 error beautiful okay and now lastly we know this configuration exists we know the ID is valid it exists in our database so actually let's give the user what they are searching for which is the preview right like a summary of their order what are they ordering the color the model um and so on and that's going to happen inside of a client side component because we're going to make use of some client side utilities um like for example state so let's define a new component in here and return that component and that's going to be a design preview component that of course currently doesn't exist in or app so let's create it and we're going to create it right here as well in the preview folder because it's going to be very closely dependent on the logic of the page right it's not going to be a reusable component we don't want it to be so let's call it the design preview. TSX and once again const design preview nothing else than an arrow function then we can export default design preview at the very bottom and then we can already go ahead and import that in our page. DSX for the preview all right let's head over to our design preview and actually return some jsx let's just return a fragment so typescript is happy so it knows this component does something and then inside of this component the first thing we're going to do is create a div element and this div is going to get a class name of first off pointer events none because this is going to be a purely decorational div and you're going to see with what exactly that's a pretty cool effect I'm going to show you the select Dash none we don't want this to be selectable ABS absolute there we go an inser of zero an overflow Das hidden flex and lastly a justify Center and because this div is purely decorational um we're going to give it an area hidden of true as well so it's not going to show up if you're going to use any kind of accessibility um device like a screen reader for example so just as an accessibility best practice and inside of here we're going to make use of a react package called react Dom confetti yes that's right we're going to put some confetti here react Dom confetti let me show you what this looks like I think there's a demo page even for this confetti package yeah there's a demo here let's open it up let's play around well basically you can Define some stuff and then um play some confetti with that with the velocity inter duration and stagger and a lot of stuff you can custom here we we don't even need to do all of that um it's going to be just a really fun kind of confetti effect to let the user know hey you're done like nice you configured the phone case you can now order it if you want it's just a nice little detail and um in order to do that let's say pnpm install react Dom oops con there we go confetti and install that package now there's a lot of configuration options as you just saw angle spad velocity and so on we're not going to worry about any of that all we care about is that we import confetti as a default import from react Dom confetti at the very top of the file and we can now simply put that confetti as a regular react component self closing by the way inside of the div element and this will take two properties first off this needs to know when it is active when do we want it to be active well basically and we can already save this to get rid of the um error that we get right here um oh and is the is the dev server not started well let's start it up again after installing that package um so we can actually see what's happening um right here um now the idea is that this takes something called the active property and we want it to be active as soon as the page loads right so in order to do that let's create a state and yes this actually needs to be done through State you can't just pass it a true that just doesn't really work I tried that and so we're going to create a cons show confetti and a set show confetti State and that's going to be equal to use State and simply call that function by default this is going to have a value of false and then instead of a use effect right under that let's import use effect from react instead of a use effect this takes a callback function and we can simply set show confetti to true so by default it will be um not showing and then once the comp component actually mounts once it renders immediately the confetti will be active so we can say active is equal to show confetti and that's going to work now in order to use this client side react hook we still need to mark this component as a client side component by saying use client um at the very top by using that directive and now we can pass this a very minimal config into this confetti object this config is going to be an object and we're going to say element count 200 and we're going to say spread oops spread 90 and there's a bunch of other options like the random height uh and some stuff you just saw here in the demo but we're not going to worry about any of that this is all we're going to pass in here let's try this out let's see what happens once we load this page we should be able to see and why are we getting super ex oh there we go okay yes that's our confetti that looks awesome and we can also see that that the steps automatically work we are on step three we have completed step one and two and if we reload this page there's going to be some beautiful confetti flying from the top that just looks awesome all right very very nice that's just a cool detail but details like this really make your app that much better I just really enjoy little stuff like this little interactions you know that's what makes a web app really fun not only to build but also to use right um so right after or confetti we are going to continue with a div element and this div is going to get a class name of margin top 20 a grid grid calls of one a text small on small devices we're going to give it a grid calls of 12 on small devices we're going to give it a grid rows of one then on small devices a gap X of six on medium devices a gap X of eight and on large devices a gap X of 12 so just a bit more let's open this div up and put one more div in here and this one is going to get a class name of small call span 4 which means from the 12 columns we create this is going to take up four of those from small devices and up on medium devices it calls span three so it's going to take up three columns on medium devices there we go medium devices a row span two this time not column but row span and then lastly on medium devices a row and two and this defines in which row um do we want this to end so basically if if we had um imagine this 2 by two grid right um let's say let's say stroke is going to be white there we go and we have this 2x two grid then for example what a row span two means is that it will take up two rows right that's that's all it means very very simple all right now inside of this div element let's open that up goes or phone right the kind of phone preview that we created as a custom component and the image for this phone will actually be the cropped user image that we want to show so basically we want to show the user the phone as it looks like as they configured in step two um right so the same kind of version so they know hey what I did is actually what I'm getting what I'm ordering um right so now we need access to the cropped image that we're keeping track of in our database as the cropped image URL right we know um where that lives but we now need to get access to that to show it in our phone nothing easier than that because in the overarching service s side component we already have access to the configuration right so we can simply pass this configuration as a prop into or design preview um component you can simply say configuration is equals to configuration there we go and that's currently not a prop we are expecting in this child component but we should be expecting it so let's define the configuration as a accepted property and this we can inline the type is going to be of type configuration as an object and the type of this will be configuration because this exists in our database we can simply grab that type from um Prisma client right everything we Define in our Prisma schema like the configuration the case color the case finish and so on these are all actual typescript types provided to us that's why Prisma is a really nice orm to work with it's very developer friendly and just like that we get access to our configuration in this client side component and can now use this cropped um kind of preview URL as or image source for the phone so in here we can pass the configuration Dot and then we should be able to see the cropped image URL right here and because we know that in step three this will exist because step two is um Done Right the user has uploaded this already so we know it will exist but typescript doesn't because technically um this is optional typescript doesn't know that we updated or config so let's put an exclamation point at the very end to make typescript happy because we know better than typescript in this case okay little cut there because I noticed one little change that we need to make make very very small change by the way because as you'll notice right now the image is not actually displayed correctly this looks a bit weird the image that we have is correctly cropped right but the position on the phone is not right and so to fix that we can go into our phone component if you're wondering where in the file tree that lives it's right here under components SL phone that's where we are and simply to the image class name of this phone we can add a minimum width oops A Min width of FO and also a minimum height of full and if you're wondering what does this apply under the hood it's the Min width 100% And Min height 100% so we're basically telling the image um at minimum you should take up all the space you can and there we go now it's cropped correctly as the user did it right perfect so it takes up the space It should awesome and because we allow for a custom class name to be passed into this phone we can also give it the class name for the color right to make the color of the phone um whatever it should be and we can grab that from our configuration we can destructure from the configuration property that we pass in here let's grab the color and now to actually get the Tailwind color that corresponds to that color or we can simply take a look into our option validator right what are the colors that we support um and what are we searching for basically we're searching for the value right here um inside of the colors array so the color here can be like black or Rose or what was the third one we support blue right so we basically want to find the color where that's the value and then apply that um object's Tailwind property right maybe that sounds a bit abstract all I'm trying to say is let's say const tww is equal to and let's search inside of our colors that we still need to import from our option validator do find and for each let's name this the super supported color for each supported color that we know is all right to have um as the background for the phone we can simply return if the supported color dot value as I just explained is triple equal to the color that we get from our configuration and only what we care about is the to um tww of that if that exists right and if it does then this is either going to be zinc 900 blue 950 or Rose 950 that we can now pass in as a class name into our phone because we do support Dynamic class names here so let's instead of a string do this dynamically and use our CN helper function that we still need to import and as a template string we're going to apply the BG of M Tailwind right here so let's save that and let's see if that works let's reload our page and now our phone case should be black perfect and if we configured it as um Rose or blue then that would change accordingly and we could make use of this Dynamic Tailwind background for our phone beautiful okay let's format this file let's expand this let's see what's happening here um very very nice our phone is displayed right here and now very similar to how we found the correct color um for this phone case we now want to find the correct iPhone model that the user bought right so we can get access to the model we saved in our database by destructuring the model From Here and Now what we care about is the label for this model right what we grab from our database this model right here um is going to be the lower case it's going to be this one iPhone x iPhone 11 as like one word not properly formatted not what the user should see as the label right what we now want is the label for that value and we can apply the same logic we can simply search for it in our supported um models so what we're going to do is we're going to destructure or lay from the models um in all caps because this is going to be our constant that we're going to search through and we're going to search through the options for each model do find we can destructure the value for each entry here and simply return if the value is triple equal to the model and to tell typescript yes we know this exists this will be supported don't worry we can put a little exclamation point at the very end and because what comes from our database is of course going to to be a supported model right but typescript doesn't doesn't really know it sometimes it's a bit well I'm not going to say stupid because how should it know right that would be unfair but sometimes we have to tell it as some stuff so we can destructure the label from here and we're going to call this the model label just so we don't get confused as to what the label is for it's for the model that's why we name it that and that we can now render out in our jsx and actually display to the user so after one closing diff after the phone let's create a new right here with one closing diff to go this one is going to get a class name of margin top six on small devices a call span of nine on small devices a margin top of zero and on medium devices a row end of one let's open this up and inside of here create an H3 a heading three element that's going to say your and then dynamically the model label and then case right and we can even uppercase the C just to make it look a bit nicer and this H3 is going to get a class name as well that's going to be a text of 3XL a font dasb then a tracking Dash tight and lastly there we go and lastly a text Gray of 900 just like that let's format this and already see what this looks like let's give this a lot more space your iPhone x case and why is the text so small did I do a typo I did text 3XL there we go if we don't do a typo then the text should be a lot larger and it should look a lot nicer nice your iPhone x case beautiful let's give this a bit less space that's automatically going to be below our phone and below this H3 let's now put a div element with a class name that's going to be a margin top of three Flex items D Center a gap of 1.5 and a text of Bas and inside of the stiff let's put a check icon this check icon comes from Lucid react it's going to be self closing and this one gets a class name of height four with four text green 500 and right below this icon we can simply say in plain text in stock and ready to ship to let the user know hey if you order now and you're going to get it pretty fast hopefully right you're not going to have to wait long for your phone case perfect with two closing divs um later with one closing div to go this is where we're going to create one more div and this one gets a class name of small call span 12 on medium devices a call span 9 and a text off base inside of this div let's open it up and scroll down a bit we're going to create one more div and that's going to get a class name of grid a grid CA of one a gap y of 8 a border dasb for bottom a border gray of 200 a padding y of eight on small devices a g with calls two on small devices a gap X of six on small devices a padding y of six and on medium devices a padding y of 10 let's open this div up and inside of here lives one more div this one is not going to get any class name it's just as a wrapper for the grid inside of here we're going to create a P tag and that's going to say highlight or let's say highlights there we go and this ptag will get a class name of font DH medium and a text zinc of 950 pretty dark and then below the PCH we're going to create a o in ordered list right that's what it stands for with a class name of margin top three a text zinc of 700 a list dis which is basically going to put like little dots and before each item in this list and a list inside that's how we Define that they should count as padding for the list and inside of here we're going to create an Li element and first one is going to say wireless charging compatible there we go so basically we're listing a few highlights of our product the second Li element is going to say TPU shock absorption and this this is just some stuff that I made up okay like I have no idea what you're selling in the store or if you're just showing this off on your portfolio that's totally cool as well um it just sounds good for a phone case I have no idea what this actually means I just grabbed it from some other phone case shops I guess um so I hope that's actually a thing like TPU shock absorption I think it is though I think it is third one is going to say pack oops packaging made from recycled materials recycled materials and then the last Li element is going to say five year print warranty warranty there we go okay okay so basically just a bunch of I guess marketing stuff or you know basically listing the highlights of our product and why are the dots for the list not showing up did I yeah I made a typo there we go uh list dis there we go all right let's reload this and then we should yes see them show up right there perfect okay and now below the closing div after the O Let's create one more div this one is also not going to get any class name let's open this up and let's create a P tag in here that's going to say materials and this P tag is going to get a class name of font medium and a text zinc of 950 as well and just like with the ordered list above we're going to create an ordered list here as well that gets a class name of margin top 3 and a text zinc of 700 and just like before a list of disk and a list inside to list the bullet points inside of the tags right and in here lives An Li element that's going to say high quality durable material again something I just made up right because it sounds good and then the second Li element in here Li is going to say scratch and fingerprint resistant coating awesome let's save that and see what that looks like let's read our page cuz they should be listed right here perfect let's relo that again to get rid of the error so sometimes that takes really long they're going to be listed here side by side that looks amazing all right so very very nice we have the highlights we have the materials we have the phone exactly how the user configured it with the cropped image that looks awesome now we want to list the price so if the user bought the item right now with their configuration of the um texture and also the Finish how much will that cost the user we need to tell them and we're going to do that after the closing o after the closing div and after one more closing div so with two closing divs to go is where this is going to happen let's open up another div right here and to space it out from the um top one we're going to give it a class name of margin top 8 just to kind of space it out inside of here create one more div this one will get a class name of BG gray 50 a padding of six on small devices a rounded large and on small devices a padded of eight let's open this div up inside of here let's create one more div and this one just gets one single class name and that's going to be flow root and to be honest I don't fully know what this does it just kind of works when we use it um we could Google and uh I think we can flow root and that's display flow root so if we hover over this this actually applies the display flow root and um again I'm not totally sure what it does flow root it just kind of works so the element generates a block box that establishes a new block formatting context listen I'm not a CSS expert I'm not going to lie okay I know how to make web apps they're probably better CSS people than me like Kevin Powell for example that could explain it much better than I could so I'm not going to try to explain this I honestly I don't know what what it does uh it just kind of works so let's just use it oh and by the way also it's not only going to be a flow rout but also text of small um because the text we want to put in here the pricing shouldn't take up that much space on the screen now inside of here let's create a div element and this one gets a class name and that's going to be Flex items D Center a justify between a padding y of one and a margin top of two let's open this div up and inside of here lives A P tag and that one gets a class name of text Gray 600 and inside of here we're going to say base price so you can probably imagine what we want to put in here let's create another P tag right below that and this one will get a class name of font medium there we go and also a text Gray of 900 to kind of nicely contrasted with the lighter and thinner version um of the base price right here and inside of here we're going to say format price we're going to use our utility function that we have written once to format the base underscore price that we have to finded in our config that is the basis for all products with no premium options selected and remember because that is in thousands with the underscore so we can also use cents here we want to divide the base price by 100 to show this in dollars or whatever you set your format price to it doesn't have to be dollars right this could be Euros rupees um it doesn't really matter whatever you prefer and now along with the space price we also want to show how much the Finish costs right so we need to know which finish did the user choose and we can do that by destructuring simply the Finish from our configuration we also already have the information in our database we can just go ahead and grab that to now go back down here to our base price and if the user chose anything as the Finish then we're going to add to it so after the closing div let's create a conditional check right here if the Finish is textured and we get full type safety here by the way hell yeah in that case we're we're going to render out some jsx and in the other case we're going to render out null now what are we going to render out that's going to be a div at the top level and honestly we can just go ahead and grab the one from before because it will look the exact same so let's grab the div for the base price and let's just paste it in here that's going to work beautiful and instead of base price we're going to say textured finish and then of course we're not going to use the base price in here but the finishes let's import that from our option validator do options at the index of one do price why at the index of one well because that is or textured finish right this is index zero this is index one that's where it lives and it also contains the price right here and honestly you know what that's not even the best why did I do that in the no you know what I much prefer we use this one right here we can directly use our product prices yeah I think that's a much better option let's use our product prices instead just on the Fly here kind of improvising but I think this is a much cleaner um approach actually I really prefer this so instead of um going into our finishes array we can actually just grab the actual price right away much nicer beautiful all right and we want to do basically the same check for the polycarbonate right if the material is a polycarbonate material the premium material we also want to display that extra cost to the user so just like with the Finish we can simply go ahead and grab the the material that the user chose in step two from our configuration and then use that to check if the material and again we get full type safety here awesome is polycarbonate in that case as the soft polycarbonate material what we're going to write inside of the first P tag we now want to display the price for that and you can probably imagine what that will be the product prices do material do poly carbonet and because that's in thousands again divided by 100 beautiful let's save that and see what happens let's reload the page and then at the bottom we should let's give this a bit more space see that the base price pops up right here and if I zoom in like to the normal level there it is $14 that looks great and while I didn't choose any extras for my phone maybe you did then you're going to see that show up right here um maybe I should have done that actually you know what we can do we can go in our database and change it let's visit our database let's say npx Prisma studio and we can kind of emulate um that I chose the premium options right we can go into our configuration and what is the current ID it's uh it ends in 7n for me so it's going to be this one right here so let's just pretend that I went with the polycarbonate and the textured one so the premium options let's save that and then when we reload this page we should should actually be able yes to see that the premium options are listed as extra cost right here that is awesome all right we have all the premium options listed as separate um prices let's give this a bit less space well that doesn't let's yeah there we go that's a kind of half screen and we can see them all right here the texture finish the soft polycarbonate material um beautiful now let's list the order total and that's going to happen below this conditional check let's create a new div element right here this is going to be self closing this is going to act as a visual separator between all the stuff up here and the total order price right so this can be self closing with a class name of margin Y2 to kind of space it out vertically and a height of PX so one pixel and let's give it a background gray of 200 if we save that we can already see that popping up here on our page once my PC loads there it is there is the separator awesome and below the let's create a div element this one gets a class name of flex items Center justify Das between and lastly a padding y of Two And when we open this up first off let's create a P tag saying order total and this ptag is going to get a class name of font semi bolt and a text Gray of 900 and so it's a bit stronger in color than these above ones and then after this pag we're going to create one more more P tag containing the actual price it's going to get a class name of font semi bolt as well and similarly a text Gray 900 so it's going to be the same style as the div above and now we want to display the total order price and we could probably inline calculate this right here but we don't really want to I think a much cleaner approach is to go to the very top of the file and kind of calculate the price here it's just um much easier if you kind of divide the logic the jsx into two separate pieces logic jsx um and kind of maintain that split so in here let's say let total price is what we're going to name this and that's going to be default to the base underscore price so that was the like 1,400 cents that we have right and if the material is triple equal to the polycarbonate full type safety oh yeah um we're going to say price is plus equal the product underscore price is we had earlier material. polycarbonate there we go so we're adding that price and now if the um finish is triple equal to textur in that and why is this giving us an error by the way cannot F name price oh because we named it total uh price that's going to be like the total order price I think that naming is better than just price there we go um so if the Finish is textured in that case we're also going to add the price Plus plus equal we're going to add the product prices Dot and now finish dot textured right beautiful and once again I forgot that this is total price there we go and now we have the total order price for everything including the premium options if the user chose them and we can simply render that out inside of this P tag but first of course render it inside of our format price function so it's going to be nicely formatted divided by 100 because we still have the thousands in there right and if we save that then beautiful we're going to see the order total pop up right here and we are all good for the customer to start their order beautiful now the only button that's left here is the well the only thing that's left here is the checkout button right where should the user click if they want to check out well on the big ass checkout button we're going to create for them so after the closing ptag and three closing divs with three more closing divs to go this is where we're going to create one last for this entire component and that's going to get a class name of margin top 8 Flex a justify Das end and lastly a padding bottom of 12 and inside of this diff let's open this up we are going to create a button component there we go instead of here we're going to say check out and also include a little arrow right icon from Lucid react and because that's going to look a bit nicer this icon will get a class name of height four a width four a margin left of 1.5 and lastly an inline so it's going to be in the same line as the checkout without using any kind of flex and this button is going to get a class name of padding X4 on small devices a padding X of six and on large devices even more a padding X of eight awesome so let's save that and let's see what this looks like and the user should now once we reload our page it automatically does it have a big green button that's very obvious to see where they can check out and complete their purchase beautiful all right dude and um kind of as a bonus I guess uh you could call it um what we're going to do right now is before we actually implement the payments right here um I want to make this app look a lot better in about five minutes together with you cuz originally I plan to do this like in the very end of the app um but I think it does make sense to do now so what we're going to do to make this app look better is three things we're going to change the font of the app to look better we're going to change the background to have like a really nice grainy effect that's going to look very high quality and we're also going to implement loading states on the buttons and the reason I want to do this now um is just honestly I I just appreciate you watching the video and following along um I that's how it's possible to even do these videos right and to have people following along with them and so I I kind of want to do it right now so app looks good while we um do the rest of this app while we implement the payments and so on and uh I kind of forgot to do it at the very beginning but I also think it's just a nice bonus for the people that actually stick around and watch the whole video to learn how to do it and not just the people that only watch the intro and uh you know just want to grab everything and kind of run with it but for people like you who actually want to watch the video and learn from the video and uh kind of follow along how I intend these videos to be you know so um I think it's going to be cool if we just make our app look a lot better in and it's just going to take like five minutes right it's going to be pretty straightforward um so the first thing we're going to do is to change our um font and that's the easiest step we're going to go into our main layout and there's a really cool font that I um found and I want to show you this font and that is called uh recursive there we go and that also comes from next f/g Google by the way by the way we can close out of a lot of these files and if you're wondering where we are we are inside the root let's give this more space the root layout in Source app and damn that is a huge phone in the small screen and um the root app and layout that's where we are we can simply import the recursive from next font Google and then simply call it recursive and use the recursive do class name uh Cross or application and if we reload our app you're going to notice that changes the font to a font that I really really like and so let's give this a second to reload there we go I think that font looks so nice and I want to show you that font because I use it on my personal websites and I'm just a big fan and uh I really hope you will enjoy it as well now second step what's going to probably make the biggest difference is the grainy effect in the background I think that's going to be a really nice part of this kind of um bonus and we're and we are going to do that there we go inside of our global. CSS file that is where we're going to specify this granny effect um now let's go to the very bottom and create a new class in here and let's call it grainy dash light and we're going to address that class with the dot selector right here let's open this up and what we're going to change in here is the background Das image and we're going to set that to a certain URL now um I've got this URL already prepared right here and so I'm just going to uh paste this in you are of course going to find this base 64 encoded image in the copy paste list so you don't have to type anything here out yourself I'm going to put the gra oops grainy light effect right here in the copy paste list the URL um that you just need to grab and paste in here as the um background image and we're going to make use of this graany light in our root layout as well the file we already have open and that's going to be in our main tag right here we can simply add the granny Das light class just like so and hit save and let's see what that looks like we can reload our page and as you can see that applied like a really subtle nice gray um kind of um grainy effect to the background and I think it's a nice little detail that makes the app feel a lot more high quality versus like the white background that we separate the grainy from the steps and I think this looks so much nicer than having everything in white it's like a very kind of faint grayish color technically I did this with um Tailwinds gray 50 color so it's a light shade of gray and it looks really really good just those two changes right the font and the grainy background make the app looks so much nicer all of a sudden and that's really what I wanted to show you so thanks for following along and I really hope you're going to use this in your um future apps as well right the font and the background um they're my favorite at the moment this is probably my favorite style combo I used this in an app I just launched so kind of side fact right here with a grainy background and the same font and people really like this app and uh I think in part it was because of the design choices there um but anyways that's just my two cents now what we're going to implement a nice user interaction is the loading States for the button so when you click a button and you are for example when the checkout is loading right when we click this button there should of course be a loading State on that button and doing that will happen inside of our button component so we can simply pass it a prop that's going to be the is loading prop and again that is a huge phone wow and that's going to be the um let's go into side by side the is loading prop that is going to um create a loading state for this button um so let's navigate in here and down here for the button we are going to accept a new prop and that is going to be the is loading property right here that we can also now add as a custom prop for this button because by default it doesn't take that right we can accept the is loading prop which is optional so question mark right here and that's going to be a buan and while we're already here we can also specify the loading text also a custom property optional and that's going to be a string if we do decide to pass it and then we can also destructure the loading text right here um in our button great now we simply need to use it and in order to do that let's unself close the comp that is already here create a closing element for this comp that makes up our button and now we can do a conditional check basically inside of here right we're going to check first things first if is loading is true and we have a loading text so if both are passed in that case we are going to render out the loading text here else we're going to render out the children and why do we not have the children property accessible here do we still need to destructure that let's go into here let's destructure the children from the props there we go we can simply get them as properties and render them out right here and then in the other case where we are just loading where is loading in that case we're going to render out some jsx so let's just leave that at parenthesis for now and in the other case we're going to leave this as null right so what is the jsx that we want to render out basically it's just going to be a span element that's going to get a class name of margin left 1.5 Flex items St Center and a gap of one let's open this span up and inside of here will'll live three span elements the first one is going to get a class name of animate Das flashing and if we hover over this you can see that nothing really happens and that's because this is not actually a builtin um class so what we're doing with these three divs that we're going to create is a custom really nice looking loading State and in order to have this animation we can simply head into our Tailwind docon fig. yes that lives at the very root of our project and this is a very very simple animation we can add let's call it right here in our animation section of the Tailwind config the flashing animation and as a string this is going to be flashing 1.4 seconds infinite linear and we still need to define the key frames for that let's do that right here under or Mar animation the flashing is going to be very straightforward it's just going to be an object and the first key is going to be 0% comma 100% And that's going to be an object syntax in opacity of zero and that is a string 0.2 and then the 20% of this 20% that is going to be again as an object the opacity of one so basically and that one needs to be a string uh we're saying from 0 to 20% the animation should be very fast right from 0.2 to 1 and then the kind of out animation from 20% to 100 will be a bit slower right and that's how we achieve a really nice um kind of Staggering effect on this animation and defining this animation in or Tailwind config already makes us able to actually use it right here in our span as long as we save that of course the animated flashing is going to work let's give the span a width of one a height of one a BG of white a rounded Das full and inline block as the what is that position no display inline block and this span can actually totally be self-closing we're not going to put any text in here as this is already the loader that we want to have now we can copy and paste this down once and twice using shift alt and arrow down and now to the second one let's add a delay off and we can choose from any delay that we want we are going to say 100 for the first Span in here and then let's give the second one a delay of 200 and that's going to kind of stagger the animation um so the first one is going to animate first then the second one then the third one and all of this is only going to happen if we pass an is loading State into this button and just like that we have created a beautiful loading State let's try that out right so let's go back into our um preview page into our design preview component if you're wondering where we are in the file tree that's right here configure preview design preview and let's just for Simplicity sake pass this button is loading um of true that we have at the very bottom right here let's reload our page and as you can see beautiful the loading State now shows up and if we also Define a loading text let's do or let's just say loading as an example and hit save you're going to see it says loading dot dot dot and now we could also disa able the button disable whenever we are loading because of course and disabled there we go that's going to disable the button while it is loading so the user is unable to interact with it and just like that W bam we built like magic a beautiful very subtle um loading animation for button it's as easy as that and that's massively going to improve the user experience throughout our app when buttons actually have loading States it trust me it makes a very big difference these kind of tiny details like loading States for example right so how do we know when this button is loading when should we actually display a loading state to this button and the answer is that is actually going to come from react query because if you remember what the goal is for this button it's to create a payment session right we want to generate a URL that the user can use to pay for or phone case using Stripe Right so we're going to do that with react query we're going to say const empty object is equal to use mutation you've already learned what this is right this is to mutate data to change data somewhere in our case to generate a URL using stripe so the mutation key we're going to pass in here what I told you about is used for cashing and invalidation is going to be get check out session separated with hyphens the mutation function well we don't have one yet right what should happen when the user wants to create a payment session if you've never done this strap in we are going to create a payment session right now together using stripe and it's actually not nearly as complicated as you might think um it is to do this we are going to go into our preview folder where we already are with this design preview component and let's close out of some files here and we're going to create a new file called actions. TS just like before remember and in here we can say use server to only execute all functions all RPC functions we have in here M server site and never on the client and from here we're going to say export const create checkout session just a function we can call um that will create a payment session for this user and of course this needs to be equal to an async um error function in this syntax right here there we go okay so we made a function called create checkout session and this will receive only one param and that's going to be the config ID right so the identification of our current configuration the user configuration and we can inline type that the config ID is going to be of type string to make typescript know what the hell this is what we're working with inside of this function now first things first let's verify that this configuration exists by fetching it from the database let's say cons configuration is going to be equal to a wait DB let's import or database do configuration doind unique and this as always takes a wear argument where the ID of the configuration that we are searching for matches the config ID that is passed as an argument into this function then if we do not have a configuration if the configuration if whatever we passed in as a config ID doesn't exist in our database in that case we're going to throw a new error and this is going to say no such configuration found now a checkout session is always going to be a user specific we need to know which user purchased this item in order to be able to get their shipping address and so on their email and send it to them right we need to know the logged in user and the way we do that is by using our off provider we can simply destructure something we're going to worry about that later from get kind server and we might already be able to to import this get kind server session as easy as that we know who is logged in um into our app right we can simply destructure the get user function from here and now to get the actual user object we can say cons user is equal to await get user that's basically going to read the cookies that are attached to this API request and give us back the currently logged in user if there is one right if there is nobody logged in this is going to be null and in the case where they aren't logged in if there is no user well that doesn't work we're not going to create a payment session for you in that case you need to be logged in so we're going to throw a new error if there is no user saying you need to be logged in there we go after this guard Clause we always know that there will be a user 100% because otherwise we wouldn't get to this point of the code and we can also get access to the finish and the material of this configuration why do we need access to these so we can let the user know how expensive this will be we're going to create a payment session with exactly the amount of dollars that the user kind of chose for the base price and the texture finish and the polycarbonate or you know the other option they have instead of the polycarbonate right we're going to calculate how expensive this case will be server's side that's never something you want to receive from the client side because the client can manipulate that and you could just send like0 and the client wouldn't have to pay anything we need to calculate the server site and to do that let's say let price is equal to base underscore price and import or base price which is $14 for me um in my example and it might be different for you if you chose a different base price and if the Finish is triple equal to textured in that case you want to add to the price um so we're going to say price plus equals product underscore prices we're going to import that dot and now you can probably guess we're going to check for the Finish dot textured right and similarly if the oops if the material is triple equal to polycarbonate let's check for that so the premium option we're going to add to the price server s as well so we're going to add the productor prices do material do polycarbonate price whatever we specify there I believe it was like um $5 and then $3 for the texture finish and just like that we have calculated or price and if the user didn't choose any premium option then the base price would be um $14 right next up we are going to create the actual order now what is an order well basically when the user buys something we need to keep track of that in our database to know if they have paid to know what exactly they ordered what the amount was what the shipping address is and so on right so we need to get some data modeling done and we're going to create something called an order together in our Prisma schema so let's switch over let's create a new model and that's going to be an order and we're going to create one order each time someone buys something in our store an order will be created in our database now the ID of an order is going to be a string it's going to be at ID and at default this is going to be a CU ID a collision resistant unique identifier the configuration ID is going to be a string and we're actually going to link this to a and string needs to be uppercase and we're actually going to link this to a configuration because one order will always have one configuration attached to it you can't order something that is not a configuration you always order um the certain like image how you dragged it the color and so on all of that makes up a configuration so we're going to link that um right here in or order as well so the configuration for each order is going to be of type configuration and if we now format or file using prettier then the at relation is automatically going to be done for us by um Prisma or by prer I guess and but for Prisma how cool is that we don't even need to worry about it right and if you're wondering what my shortcut is for formatting it's shift alt and F and to use prettier to automatically do that beautiful okay now we need the um user who ordered from our store and that's going to be of type user now we don't have a type user and that's probably one of the most important models in any application the user model um so we still need to create that one let's create a model user right above her order and each user will get an ID that's going to be a string and basically same thing at ID at default CU ID Collision resistant unique identifier there we go the email we want to keep track of for each user that's going to be a string and lastly we're going to keep track of the created ad value when was this entry created in our database that's going to be a date time an internal or like a built-in rather um data type that we can use in Prisma and at default that's going to be right now and the updated at property we're also going to keep track of that is a date time and at updated ad beautiful okay so this just contains when this entry in or database was last updated and that can sometimes be really handy so we always want to keep track of that as well along with the created ad awesome so that means we can now link each order to the user that made it and that's very very important so we know the email for example um that the user has that ordered this product so we can send them like shipping information or if anything goes wrong we know their email to reach out to them and so on you always want to um associate an email with a purchase in any app basically we also want to keep track of the amount and that's going to be a float that's going to be how large is the purchase price for this order in our case in this case um $22 that's going to be the amount we're going to keep track of an is paid and that's going to be a Boolean and by default this is going to be false so that's just going to be is this order actually paid did stripe verify that the user has actually paid the money you're going to see exactly why this is so helpful later we're also going to keep track of the status and we've already created the enum and that's going to be called the order status you remember when we did that let's see let's go up to the order status that's either going to be fulfilled shipped or awaiting shipment those are going to be the possible statuses for or order next up we need to know the shipping address where should this case be sent and to do that we're going to do that inside of a separate model called shipping address there we go go let's open this up and this is going to get an ID that's going to be of type string and at ID at default um CU ID and all this does is that whenever we create a new entry Prisma does the ID generation for us and we don't need to worry about it basically it's just easier each shipping address will have a name and that's going to be a string a street and that's going to be a string a city string all of these are going to be string so I'm not even going to say string anymore justum that all of the following will be a string right postal code string then a country then a state also a string but this time this is going to be optional um because state is yes it's yeah sometimes you don't get that information um but as long as you have all the other ones you're good right you can still ship um so we're going to make this optional just in case the phone number same thing this is going to be a string but optional the user doesn't really have to pass it and lastly we're going to link this to the orders and this is just going to be an order um array so one shipping address can receive multiple orders right if you order five products from my store then um your one shipping address has five orders associated with it and similarly that's also automatically going to create the relation in or order for us however what we do want to change is let's make the shipping address lower case I just kind of prefer it Prisma by default makes it uppercase I'm not really a fan of that so let's change the shipping address to lowercase but it really doesn't matter it's just personal preference and let's copy down using shift alt arrow down the shipping address bam and rename this model to the billing address because what's very possible is that the billing address is different than the shipping address but essentially contains all the same data right so still ID name Street City and so on nothing changes about the data um except that we now when we format our file um also get a billing address relation in or order model right and I'm also just going to change the B to lowercase just personal preference again I think it's nicer and lastly we also want to keep track of when an order was created so we're going to add a created add property at the very bottom date time at default now just like before and then similarly the updated ad that we also keep track of as a date time and this is going to be at updated ad um so Prisma automatically keeps track of when this entry was updated and adds this row accordingly or Updates this row accordingly right beautiful so just like that we have modeled a complete order system for or database with an is paid property and that's going to be really important because we only ever want to ship something if we are 100% sure the user actually paid if we actually got the money so this won't come from the user but from stripe or payment provider so we are actually sure that we have received the money now to sync or remote database as always we're going to say npx Prisma DB push that's going to push our changes into remote and that should only take a second and we are up to date great awesome so what that now means is we can continue in our create checkout session action right so let's go into kind of s by site here there we go nice um so when we click this button this function will be executed creating the payment session for the user and um what we first want to check before creating a new order for this user is is there already an order for this configuration um because if there is then we don't need to create a new one right that just doesn't make sense so we're going to create a let order and this will be of type order that we get from Prisma and um if we reload the window then hopefully pris M will automatically let us import this type and if not then we need to manually import it and this type exists because we just created our model called order and that will automatically create the typescript types for us um which is really nice in Prisma great that worked we can Auto Import this or this could be undefined and we're going to default it to undefined right because we're going to do a conditional check right now this conditional check will be based on if we have an order or not so let's check let's say con existing order is going to be equal to wait db. order. find first which order do we want to find where well we don't know the ID of the order that we want to find right so we can't search by the ID but what we do know is the user ID right because we're linking a user to an order that is going to be the logged in user ID and also the configuration ID we know which is the configuration. ID from above right and based on these two we can actually uniquely identify an order based on configuration and user awesome so we now know if there is already in order for this exact configuration that the user chose with these exact finishes and the image and so on or not and if there is if we have an existing order in that case we're going to set the order to that existing order we don't need to create a new order that's the entire point of this check and else if there's no order for this yet well in that case can create a new one let's say in that case order is equal to a wait db. order. create and we're going to create it with a data object and that's going to be first of the amount which is going to be our price divided by 100 then the user ID is going to be our user. ID so the logged in user and lastly the configuration ID is going to be equal to our configuration. ID the we also already know now typescript is complaining here what's the problem type amount number user ID is not assignable to type without property status is missing in type okay so what we probably forgot to do is to not give this a default value if we go into our order and then these status right here yes that's exactly the problem and we did not give this a default value so right now it's asking us to hey please provide a value for the status you need to do that other ones are all optional but of course the default status should be the well let's let's choose let's look at it should it be fulfilled should it be shipped should it be a waiting shipment well of course it's not shipped yet it's not fulfilled yet it should be a waiting shipment by default right so we can simply give or status for the order a default and that's going to be awaiting and we can autocomplete this to a waiting shipment and then also don't forget to npx Prisma DB push it's going to push or changes into remote and it's also going to generate or typescript types locally at least it does it sometimes just to make sure I'm going to restart my window using shift control and P and then saying developer reload window and with that we definitely get the newest typescript types and we can continue in creating the payment session and as you'll notice now typescript is happy and Prisma is happy we don't need to pass anything else we just assign the result of this operation to or order that we are keeping track of great and now is the time to actually make the payment session to do that we're going to use a tool called stripe stri oops not stript stripe striped is a payment provider it's great the developer experience is awesome let me just show you how to do it in our project let's clear the terminal let's install a new package and that is the stripe SDK let's install pnpm install stripe it's probably if we take a look at stripe on npm while that installs going to have a lot of uh weekly installs it's very wellmaintained let's take a look at how many people use this um 1.7 million yeah that's that's pretty solid man and we can simply go ahead to stripe log in and we're going to need or stripe API key um in order to do this so if you don't have an account with stripe yet that's of course totally free there we go and once we are logged in and we can actually grab our API key from here and so we can simply enter API key in the search bar stripe is extremely good when it comes to developer experience and that's one of the things they do the best right their documentation is great and so is their search bar so we can simply go to developers API keys and this is where you can grab your secret standard key um if you don't have one yet simply create one you can click um create like a standard key I've already did this um so my key is going to be right here um or maybe yeah I think you don't even have to create one I think this is going to be here by default you can simply click reveal test key and um I'm just going to do that I'm just going to change it um after this video so that's grab or secret key we're actually going to need that and then we can use that in our app this is what we're going to use to safely interact with the stripe um payment service so with this secret key copy to our clipboard let's head into our EnV file and we can put it in here as the stripe secretor key and just paste the secret we just grabbed from stripe and now to make working with stripe just a bit easier we're going to create a little helper and that helper is going to live in Source then in lib and let's create a new file here called stripe. TS just a little stripe helper where we can import Stripe from stripe the package we just installed and then export a cons stripe which is going to be equal to a new stripe which is a class and in the Constructor we're going to pass the process. env. stripe secret key that we just pasted into or. EnV or we can do nullish call lessons with an empty string just so typescript is happy because by default this could be undefined right and typescript is like hey this can't be undefined this always needs to be a string so to make typescript happy if this is undefined or null then in that case we're just going to pass an empty string in here um and then typescript won't be mad at us and then as the configuration we're going to pass as an object in here the API version is going to be and we get full type safety here wow that is pretty good and then typescript let said that to true because hell yeah we are working in typescript and then we can close out of that stripe file and that's literally all we need to do as easy as that stripe integration done we can now use it first off to create this payment session we need to know which product is the user buying and to let stripe what the product is let's create one let's say cons product is going to be equal to a weight stripe dot um and we still need to import Stripe from the helper we just created do full type safety by the way awesome products. create so this is an API that's kind of similar to Prisma right it I mean look at this DB order create stripe products create that's pretty familiar nice the name is going to be custom iPhone k and this is actually going to show up in the purchase menu for the user the images are going to be just the configuration. image URL so this is the image that the user uploaded originally they can see it in the kind of check out page I just think it's a nice little detail and the default uncore prore data we're going to pass in here well we basically Define the currency and how much the product costs right so the currency is going to be USD D and the unit amount will actually control how expensive this product is and since we already calculated that way up here where was it um with the price we can simply use that right we know the price is correct we calculated its server site that's very important as I mentioned earlier this cannot come from the client and we can simply use that price and that's actually going to be the price that user now pays in Stripe Right and with that product we are all ready to create create the actual payment session and we do that by saying const stripe session is going to be equal to a weit stripe. checkout checkout. sessions uh let's just get some autocomplete here do create again very nice API this feels like writing Prisma to be honest and we're going to pass a bunch of configuration stuff in here for example the successor URL where should the user be redirected when they actually paid when we receive the money in that case we're going to send them to the is a template string dynamically insert the process oops process. env. nexu server URL separated by underscores right we can simply change that in our EnV config later and the user will be sent there so this is going to be either like local host or later or deployed version of this app right slash than- and then question mark order ID is equal to the dynamic order. ID right and that's how we can fetch that order on the thank you page and actually check has the user paid for this or not and the cancel uncore URL is going to be let's just copy and paste the success URL right down here the cancel URL is simply going to be a bit different slash configure slash preview question mark So as a query parameter ID is equal to the configuration. ID so if for any reason the user decides to cancel and click on the back button on the Strip payment session then they are just redirected to the configuration screen where they can drag around their image right so the progress is not lost for them we don't want to lose that customer as the payment method types we can pass in there we go payment method types whatever we want to allow right there's a lot to choose from you decide what you want to allow basically the most standard options that we want to go for are both card and also PayPal we want to allow and that's the payment methods that user can actually pay with as the mode we're going to say payment alternative would be something like subscription we don't care about that the shipping undor address uncore collection is very important we need to know the shipping details so for the allowed countries we can pass in here um this is basically a list of all the countries you um allow to ship to so people from these countries can order from you just as an example we're going to use de and we're also going to use us of course all other country codes are literally supported here and that stripe supports so you choose your country whatever you're from USA India Germany wherever you're from doesn't matter and put it here and all the other countries that are allowed check out from you as the metadata we are going to put in object and the user ID is going to be the user. ID and the order ID is going to be the order. ID this is very important and you know why because whenever we get a web hook we're going to get to that later don't worry about it for now whenever we get a web Hook from stripe telling us that a checkout was successful stripe verified that they have received the money and the user actually paid then we need to know which user was this and which order was this for which order do we now actually need to ship right that's very important that we pass the metadata and now to tell stripe what the actual products are that the user is even buying well we already created the product we just need to use it and we do that inside of the line underscore items this is what the user is actually buying this takes an array of objects where the price is going to be the product oops the product default price as a string we're going to cost that and to make typescript happy and then the quantity is going to be hardcoded to one because the user probably only wants to buy one case right if you wanted to you can Implement a functionality where they can buy multiple but who wants to buy multiple of the same cases right I just uh put this as one I think it makes more sense and then we're simply going to return from this function as the URL the stripe s . URL what is the URL basically it's a hosted checkout Page by stripe and if we now put the user to this URL if you push this into their browser they're going to be on the hosted stripe checkout page that is secure where all the payment is all handled for us right so we don't need to um have in mind any security stuff great very very nice this is the logic to create a payment checkout session and we can simply now use this function in or use mutation right here right as the mutation function and import that great now what should happen on success if the um URL creation was successful this doesn't mean that the user has paid yet this simply means that stripe has created a session um for the user in which they are able to pay right this doesn't mean that the user already paid um in any case we receive the URL we can destructure in the Callback function and why well well because we returned URL from our action right we returned something called the URL and because this is fully type save we know this URL will be on the front end and if we have this URL that's going to be the URL of the host checkout page well we simply want to push the user to that URL and in order to do that we need access to the router right to push something into the URL so at the very top we're going to say const router is equal to use router and that as always comes from next SL navigation we can simply import that and now if we have the URL we're going to say router do. push URL there we go and else right what should happen if the URL is not present because this could be null it's probably not going to be you know but just to make typescript happy and for any eventualities we're going to say else throw new error and this is going to say unable to retrieve payment URL period and now where do we catch this error this happens in the on error property this takes a callback function as well and this is or place to inform the user that hey something went wrong and please try again right and in here we're going to render out a toast notification which also means we need to destructure at the very top of the toast from our use toast hook right and simply invoke that that allows us to actually render out toast notifications on the screen right now and we're going to do that in or on error so we're going to call the toast function and the title of this aror toast is going to be something went wrong the description of the toast is going to be there was an error on or end please try again right so the user doesn't think they're stupid right the problem is on our side and um they should probably just try again and it might just work and then the variant is going to be destructive of course to let the user know hey something went wrong here right and now to call this to make this entire thing happen we can simply destructure the mutate what's actually going to invoke the entire flow we defined right here from or use mutation and let's rename it because mutate is not a great name for this let's say um create payment session because that's exactly what it does I think it's much more concise and we can actually use that now in or jsx so just for demonstration let's already try this let's create a payment session whenever we click the button at the bottom right here right so on click of this button we are just going to invoke the function of create payment session and then all the rest should hopefully be handled for us and of course this create payment session expects the config ID because that's what we receive as a parameter in our um create checkout session right so we need to pass it as an argument wherever we call the function and this is nothing else than the configuration. ID there we go right so what is the configuration well that's the prop we receive in the design preview from the server right from the server side component into the client side component that we are currently in beautiful let's try this out this should work now let's give or application a lot more space to work with here let's go into our Network or console refresh that or clear it rather and let's try this out let's hit the checkout button what we expect to happen is we are redirected and to stripe to actually pay $22 let try it wow that was fast all right something went wrong well what is the problem message you need to be logged in all right of course we are not logged in that's why we can't do this well let's log in for a second um and trust me we're going to do a bit of nicer logic here in a minute I just want to verify with you together that the payment actually works as we expected and then in a minute we can worry about the login looking good right and so I'm going to enter my hello jri coding.com email that's going to send us an email there we are I just received the email let's paste in the code and let's hit continue beautiful so now we are logged in let's click create case and of course um we're going to have some logic where if a configuration is already created you're going to be not be sent to the landing page right but to that configuration so you don't need to do everything again but since we don't have that logic in place yet let me just upload Lo um an image right here that's going to take us through the entire flow once again and I mean that's also just good to verify that everything is working right just to be really sure and we can also see the gradient or the the grainy effect rather in the background it looks really nice awesome let's just kind of put our image on the phone case you choose any image from your PC and I'm just going to go with soft polycarbonate I'm just going to go with one option that's going to be $19 we're going to verify that in payment session and let's also go for an iPhone 15 all right by the way I don't actually have an iPhone 15 but but but it doesn't really matter let's just pretend I do let's click continue oh and one thing we also need to do is add the loading state for this button right here that's going to be very nice detail as well um we we're not going to forget to do that we're going to do that um it's going to be very simple and then once that's done we should land on the um preview page aome your iPhone 15 case based on what we just chose awesome the image is right here this looks so nice man honestly wow very very nice page all the highlights materials um perfect now let's click create checkout and now let's see what happens what we expect to be sent to is the stripe page where we can actually buy the product let's it check out and why why did this not work what's the problem now message undefined reading find First interesting so it's complaining this seems to be an error from Prisma in or action file um that the find first cannot be executed um so it can't find the order did we not push or changes no that's weird let's quickly verify that um that this table actually exists why can it not do that that should totally work let's head into neon Tech and let's verify that we have the table there should be an order table right here query completed with no result yeah that's fine because there is no order yet all right this makes so much sense okay so here's the problem so the user we created in our database the ID was automatically created by Prisma right that's not what we want that's not going to be um realistic we get our user ID from kind from our off provider right um so all I did was go into the actions and actually log out these values to see what's happening if any of those are undefined and now I know what's the problem this user ID um so whatever kind generated for us so if you want just go ahead and log out this user ID click the button again that's going to um log out your user ID that kind gave you for your email that you are logged in with in the console and that is exactly what the user ID needs to be in our database how did I not notice that all right let's save that um let's change the ID to the kind user ID and then let's try that again because essentially what's happening is that the user ID doesn't exist this user ID property did not exist in our database as long as it doesn't match the actual logged in user right so let's try this again and if any if I mean this should work right let's check out again let's click this button and no you can't okay but that's a hey hey that's a different error we're making very good progress invalid URL undefined okay that's actually an expected error and that's a good error and I want to tell you why dude we are already at the point where this is a good error but as you'll notice in software development if you get different errors that's very good progress because this is an error I can tell you exactly um why it happens and it's the best error to get so all we're missing is inside of our action it's complaining that this um environment variable doesn't exist and of course it doesn't because we never defined it but nothing more simple than that we simply need to Define a variable with this name right so let's copy the next public server URL paste it in ourv file and this is simply going to be HTTP SL local oops Local Host 3000 there we go and then we can hit save awesome all right that is automatically going to reload or nexts development server it says reload n right here and after that's done great we can simply try this again and now that should really work so let's hit check out that is going to do some stuff here no error post configure beautiful and we are forwarded to the stripe checkout page nice I'm very happy that worked how did I not notice that man uh anyways all right I'm glad we got that fixed now we know that the check out session was created successfully beautiful um we can close out of our Prisma Studio remember to just change this to your actual logged in user that we get from kind awesome and now that gives us all the options to actually pay for or service and because we are in stripe test mode we can simply enter some dummy information here so for the chipping address we're going to say um example town the address is going to be yeah it's Al it's already has some stuff here selected for me some random stuff that I already typed in U so I'm just going to use that you type in I don't know your address or any random address it doesn't really matter um SD card we're going to use a stripe test card that's going to be 42 42 42 42 42 42 42 42 so a bunch of 42s that's just it then any date in the future for the date and then any CVC it doesn't matter and this is a stripe test card so this will always work as long as we are in test mode right this is a card that is prepared by stripe um that we can simply use to test or payment system before actually going live right so we don't have to use our actual credit card in order to test if the payments are working um correctly nice and now we are redirected to the thank you page exactly what we expect to happen because that is our success URL we are redirecting the user to Local Host 3000 or in production this is going to be a different URL slash than you and then the order ID is attached right here so an or case that is right here up in the URL that we can now read on the page that we were sent to beautiful we know that payment system works perfectly now the only thing that's not optimal right now is that we had to go ahead and create a user um ourself right we don't really want to do that and a much better way is to do everything through one site in or app so what's going to happen let's take a look at how this is going to work architecturally right let's give this a lot more space because I again I want you to understand why we're doing what we're about to do so what we're going to do is something called an off callback and maybe that sounds a bit abstract if you're not familiar with what that means but let's take a look at what this means step by step so what's going to happen is that the user will get a popup and that's going to ask them to please log in where is this going to happen this is going to happen right before they check out because in order for the user to check out we want them to be logged in and on the popup they will either have the option to log in if they already have an account so oops let's say login or in the other case where they don't have an account with us yet if they're a firsttime customer it's going to say sign up or register whatever you want to call it right and if they click on the login button basically what's going to happen is that first things first we want to save their configuration right cuz they're going to be taken to a different page once they click the login they're going to be taken to or authentication provider to log in that is kind right and through that page change normally all the progress in their configuration how they dragged around their image and stuff would completely get lost right that is horrible we don't want that therefore the user should continue right where they left off after they log in right on the checkout page of their order and doing that is actually really easy we can simply store their configuration ID that we do already SA save in local storage and then get it from local storage once the user is back on our page so once the user gets back to our own page let's just say or page we can simply check is there a configuration ID in local storage which is just um browser site client s side storage on the user's browser like in Chrome for example and if there is we can simply redirect them to it so they continue right exactly where they left off and same thing for the sign up right this is also going to take the user to kind and why can I not damn dude sometimes xcal draw is seriously weird uh why does this anyways whatever man all right let's copy and paste over this entire Arrow then doesn't matter they're also going to be taken to kind in the signup step they're also going to land on or page after they're done and basically same thing we're also going to put their configuration ID in local storage so the user can check out right away once they're back on our page and logged in right so doing this conceptually it sounds pretty simple if you just ignore that this looks kind of horrible cuz my diagram skills are pretty bad this is basically all that's happening user logs in lands back on our page we check if they already have a configuration and if they do we redirect them right it's pretty simple so let's go ahead and go Implement that together and as you see in code this is also not hard um so in our app folder this is where we're going to create a new folder and we're going to call that folder off- callback and hit enter and in here we're going to create a page. his X that's going to define the content that is shown on the off callback page as always no surprises here that's going to be an arrow function that doesn't return an array there we go and we're going to export default page at the very bottom beautiful now this page is going to be client site we're going to make use of react act query inside of here so we're going to mark this as a client side component now you know what I think before we continue on this page it um makes a bit more sense to actually implement this login model first so you understand this page a bit better yeah I think that makes more sense so let's save this for now we're going to get back to here in like five or 10 minutes we're going to get right back um first things first let's go into our design preview component so in our file tree that's under configure preview design preview right here um because the big thing we're doing here that's not ideal is that at the create payment session right here we are directly calling this function when clicking back down here the checkout button right we're immediately creating the payment session even if and that's the important part even if the user is not logged in and that's not ideal we want the user to be logged in before they can purchase something so instead we're going to create a function down here and we're going to call this handle checkout and this is simply going to be a eror function and the logic in here is going to be extremely straightforward we're simply going to do a check if we have the user in that case and don't worry we're going to get the user here in a second if we have the user then we want to create the payment session let's just put that as a comment for us so we don't forget this and else right conceptually what should happen if the user is not logged in well they need to log in of course right and now very important before we prompt the user to actually log in let's save their current configuration let's go back here um let's click back can we go back to our configuration yes we can we want to save exactly the kind of where they drag the image and which um options they chose which color they chose and so on right so that's not lost and to do that I just mentioned how to do it we're going to say local sto oops local storage do set item that's how we can put something in local storage and we're going to call this the configuration ID and this is going to be the ID now where does this ID come from well basically just from the configuration that is passed in here as a prop from our server right we can simply destructure the ID from oops from that configuration at the very top of the file there we go and if we spelled that right that would actually work that's how we get access to the current configuration ID that we can now simply store in the user's browser right awesome that's how we keep track of that and now how do we know um when a user is logged in or not well very simple our authentication provider provides us the um necessary hook for that so we can simply destructure the user from and that's going to be called use kind browser client and we can go ahead and import that well the Auto Import doesn't seem to work all right let's manually import it maybe for you the Auto Import works I don't know sometimes for me it doesn't we're going to say use kind browser client and import that from at kind OSS SL um kind o nextjs there we go that's the client side hook and we still need to invoke that function there we go and now we either have the kind user if the user is logged in or null if the user is not logged in right which is a totally valid case as well that's why we even have this if statement down here now what should happen if the user does need to log in well in that case we want to open that like model that kind of popup that we talked about to ask them to please log in right how are we going to do that well basically we're going to keep track of a state so let's go to the very top and let's declare a state right here and we're going to say const is login model open very clear name as to what this is for right and set is login model open and this is going to be equal to use state right and by default this is going to be false of course we don't want the model to be open and just because I like it as a personal preference I'm going to put Boolean in here into the state and we forgot to do it here as well um I'm going to put it here once again you don't have to do this but I just think it's a kind of cool um I just like it I I just think it looks nice all right and then we're going to set the model to open in the case that the user does need to log in right so we're simply opening the model and if the user is already logged in we can basically just do everything as before the user is totally fine to actually create the payment session to actually check out and as the config ID this takes we're going to pass in the or actually we can just pass in directly at the ID that we destructured from our configuration right so nothing really changes in this case only if the user is not logged in in that case we want to ask them to now to actually show that model well that model doesn't exist so we need to create it first off let's determine where it will be shown and that's going to live right here below this first div element this is where the login model a component that doesn't quite exist yet is going to live and it's going to be oops self closing as well beautiful so let's go ahead and create that login model and that's going to live in Source components new file login model. TSX right here all right and let's say cons login model once again no surprises this is just a regular error function that we export default at the bottom login model and this is going to return something called a dialogue now if we try to import this then we could right headless UI does have a dialogue but a much nicer version of the dialogue is provided by shaty nuui and that's not the uh dialogue it's not the alert dialogue it's the regular dialogue and if we click that you can see what happens it's kind of this um popup style that we are exactly going for fully accessible if you press Escape or click outside of it it closes that is exactly what we want so let's install that one let's go ahead and stop our Prisma Studio we don't need that anymore and say npx shed cn- UI at latest add dialogue and and then hit enter that's going to install the component into our local project right here so we can simply go ahead and then import it from there we go that's done from or/ ui/ dialogue and go ahead and import that beautiful okay now to make this dialogue control to know when it's open and when it's not open that information actually comes from our parent right the parent determines when should this be open versus not as the is Log and model open state right so we simply need to pass that in this properties to make this dialogue controlled by the parent so we can simply accept two props in here the is open and also the set is open and because this is very simple we can simply inline these types the is open is going to be a Boolean and the set is open is going to be a bit of a different type this is the type of the react State Setter and I already memorized this but if we hover over this we can can see the full type right here is this it's dis patch set State action Boolean so we can simply grab that copy it or um kind of type it out ourself that's totally fine as well and paste it here as the property type and then simply import both the dis patch and the set State action types from react and because fun fact we're only importing types we can also annotate this with type at the start and that's going to basically Tre shake this to make our module smaller because if we didn't include the type you could see um that we actually have some import cost right here of 4.1k versus if we add the type then this is only going to exist at design time and will be stripped at build time you know cuz typescript doesn't exist in runtime okay so now we have the is open and the set is open property we can simply pass them into our dialogue right away so on open change we're going to say set is open and the open state is going to be is open just pass that into our dialogue right away beautiful that's all the logic already done now we can get to the actual jsx what do we want to show in this model first off we're going to render out the dialogue content that is provided to us by our UI library and let's give this a class name and that's going to be a absolute and a zindex of in angled brackets for a custom value just a bunch of nines just very very high this should be above everything else right inside of here goes a dialogue header let's import that and inside of here goes a div element with a class name and that's going to be relative MX Auto a width of 24 a height of 24 and a margin bottom of two and inside of here lives a nextjs image element we can simply import from next SL image this is going to be self closing and this is basically going to be an image of a snake right it's going to look pretty nice so the source for this image is going to be SL snake D one.png the alt is going to be just snake image the class name is going to be object Das contain and lastly we're going to apply a fill property to this image as well now before we continue in the login model let's already see if this works right let's give this a bit more space let's save the log in model and now also import that of course in or design preview now this expects the two properties that we set in the login model so the is open that it expects right these two at once the is open is simply going to be or is login model open and the set is open that this expects is going to be the set is login model open so basically we're straight passing in the state in the state seter into the login model to make it controlled by the parent element and if we now click check out and we are not logged in so let's sign out of our account by clicking the sign out button up here and that sometimes takes really long in development mode especially if you have a s-e old PC there we go okay we're logged out and now if we hit the oh no one thing we still need to change we actually need to say handle checkout wherever we um have the button right currently we're still creating the payment session when we click the button that's not what should happen instead we need to Now call or handle checkout function very important save that reload our page and now we're actually making use of our doic that be wrote right here in the handle checkout now once that's done loading once we click the check out button what we expect to happen is for a model to pop up beautiful and it has the little snake in there awesome so we can see what we're doing in real time let's close out of some of this and continue in our login model because we already know this works now nice now below the um div element that contains the image this is where we're going to create the dialog title also import that from our UI library and this dialogue title is going to get a class name of text 3XL text Center font dbol a tracking Das tight and lastly a text Gray of 900 awesome and the text we're going to put inside of here is going to be log into continue awesome head save on that now if we go below or dialogue title this is where we're going to put our dialogue description and also import that from our UI library now this dialogue description will also get a class name and that class name that class name is going to be a text of Base a text Center and a padding y of two and inside of here we're going to put a span element with a class name of font medium and a text zinc of 900 and inside of the span element we're going to say your configuration was saved exclamation point to make it very clear to the user that hey no progress is being lost because by default you're going to be a little bit annoyed when you see a popup pop up um to ask you to log in right that's just a little bit annoying so we want to make very sure that the user knows hey your configuration was saved no progress at all is being lost here now we're going to insert a JavaScript space after that to maintain one space bar and then we're going to say please log in or create an account to complete your purchase period awesome and that's most of the logic done we can save that and now we only want to offer like two buttons either to log in or to sign up because that's what we put earlier here remember if we go uh kind of back a bit then yeah right here was the sign up button let's get rid of some of this stuff um the login and the sign up is what we want to offer to the user right so let's do that that's going to happen below our dialogue header and still inside of the dialogue content this is where we're going to create one more div with a class name and that's going to be grid grid calls of two a gap of six a divide DX and a divide gray of 200 let's open this div up and the first thing we're going to put in here is the login link let's import that from kind or authentication provider and that's basically a button that takes the user to the login page for us all done right inside of her we're going to say login and this will also get a class name just to make it look good and that's going to be dynamic inside of here we're going to use our button variance so basically we can make this look like a button without it actually being a button but we can still benefit from the beautiful built-in colors of our UI Library by using the button variance function that we import by the way if you're wondering from the same button component that we have right and this takes the variant and let's define this as the outline variant so it's going to look like the outline button pretty much and now right below the log link we're going to create the register link you can probably imagine what this does right right and where we import this from also from kind USS kind of nextjs and this will simply take the user to the um register page the sign up page so the class name will also be dynamic we're going to apply the button variance just the same as for the login link and the variant that we want to pass in here is going to be the default variant awesome and let's also say sign up inside of the register link so typescript is happy because it's now has age child element awesome okay and the phone case it looks a bit big maybe that maybe we need to fix that later um anyways let's go into side by side and we can see login to continue we have the login option we have the signup option and this just looks super professional man with a snake the kind of ratios of the font sizes and the font weights and a this looks so good nice so we can now log in we can sign up and if we actually hit them then you're going to be able to see something very cool and that is how we put the configuration ID into local storage so I want to show you what that looks like um so we can actually see the local storage is a browser side thing um if we go into application and then into local storage this is where a lot of stuff already lives let's clear out of all of this and we can now simply try this again let's click the checkout button and that's going to put right there that you see the configuration ID appearing let's try that again bam right here it just appeared again the configuration ID into our local storage which is right here under storage right and that's how we kind of save the progress that the user has made because this is the actual database ID remember and now if the user logs in what do we want to happen well basically we want to send them to something called the O callback right so or page is going to be the off callback what we kind of got started in creating earlier that uh going to be the off callback and the only purpose again of this all callback page is to get the local search and see if we need to redirect the user back to where they were or not right that's liter it's kind of just a fancy name the all callback for a redirect page cuz that's all it is right we're checking is there something in local storage and if there is redirect the user to this exact page that they are on right now and if there's not then the user is just trying to log in from like the landing page or something maybe and in that case we don't need to redirect the user right that's exactly what the off callback is for um and we're going to go ahead into off callback page and actually make that happen because as you'll see it's really not hard so the first thing we want to do is to actually see if there is something in local storage that's the first course of action the way we do that is going to be in react State let's define a config ID and a set config ID right up here at the top of our page component and this is going to be equal to use State we can simply import that from react and as typescript this will either be string or null and we're going to default it to null right because chances are a config ID either does exist in local storage or it's also very possible that it doesn't right and we're handling both cases um right here so to actually check if it is in local storage we're going to do that inside of use effect it's very important that we do this inside of use effect and to kind of manage side effects because at the very first um initialization of the component that might not actually be defined the local storage right this might be undefined so let's say cons configuration ID is going to be equal to local storage. get item and we're going to get the configuration ID and if we hover over a local storage you can see the type of it is storage if you want further information you can go to the mdn reference right we don't really care about it um all we do care about is that we get the same key the configuration ID that we set in or design preview right it's this one these two need to match and they do right if you're just following along with the video but just for your understanding we need to retrieve the same key that we initially set to check if it was set now if we have a configuration ID in that case we're going to set the config ID state to that conf configuration ID and if we don't then nothing is going to happen this is already null by default right it's going to stay null and to tell the user fact it should only run once and not on every render we're going to pass it an empty dependency array great now we want to see if the user is logged in or not and the reason for this is that we want to make sure that all users that register or log in through kind definitely also exist in or database do you remember earlier when we had to create the user in our database or S that's not what we want to do of course not right when a user logs into our system they should automatically be also put into our database and that's another thing that the all callback is very good for so to do that we're going to say const empty object is going to be equal to use Query this is the first time we're going to make use of use Query also from 10stack query and this takes something called the query key you already know what this is for same thing is for the mutations for caching and invalidation and we're going to call this the O callback it's nothing that we're going to make big use of in this video because we don't need to the query function is going to be an asynchronous error function and this is going to call something called let's say a wait because that's going to be asynchronous something called get off status which is a function that we are going to Define ourself just like we did with the ations earlier this will be a server side action that we can trigger to Simply get the current login status of the user and to see if they are synced with our database so this all callback is going to ensure basically as a kind of question we can put here is user in our database question mark is the like is the user and if they are not then we will create the user entry automatically so we don't of course have to do any of that or self manually that would be just just imagine that would be absolutely horrible now inside of our app folder in the off callback folder right here let's create a new actions dos to actually Define this get off status right remember at the very top as always we're going to say use server because they should only ever be executed on the server side and then from here we're going to say export cons get off status and all this does is get the current login status of the user and sync them with our database so first off we want to get the currently logged in user right we're going to say get user destructure that from our authentication provider get kind server session because this is a server s running function as I just um explained right a server action and now to actually get the user object we're going to say user is equal to await get user just call that function and because we want to do some awaiting we need to mark this function as asynchronous in order to do that and now this is either going to be the logged in user or if the user is not logged in this is going to be null right and if we don't have a user which is optional do ID right the user doesn't have to exist or if we don't have a user. email in that case we are going to throw a new error and we're going to say invalid user data basically we can't handle this case if the user doesn't have an ID or they don't have an email and so we're going to throw in that case right and you're going to see exactly why we throw in a second here that's going to be important and you're going to see why and now we're going to check for the existing user in our database right does the currently logged in user exist we're going to say con existing user is going to be equal to a wait DB let's import our database Prisma do user. find First and now what's the thing that we want to check for of course does this user exist in our database so where the um ID of that table entry is going to be the user. ID and either there is a user with the currently logged in user ID or not and if there is not an existing user in that case we need to create an entry in our database of course right we're going to say await db. user. create that's going to create a new entry and in here we still need to pass the data that we want to create this user um with so that's going to be like the um ID the user. ID that comes from kind and also their email which is going to be the oops the user. email there we go so we're automatically creating this user with the ID and their email in our database and if there is this user already in the database well we don't need to create a new one in that case we're just going to return from this function a success property of true and now comes the part that I teased earlier why do we throw here and that's because we want to retry for as long as the user is not in or database right every user on their first login and all subsequent logins will invoke this function so we want to make sure that user in order to do anything in our entire website they are in our database that's very important right so to do that let's first off import or action that we just created the get off status and then we're going to pass two more things into this use Query the first one is going to be a retry of true right so if the user doesn't have an email or not an ID right so if they're not logged in after all in that case we're going to keep retrying and then the retry delay is going to be 500 that's in milliseconds so between each retry to call this get off status function until it doesn't throw right no matter how long that takes until the user is in our database which is important and we are going to retry in 500 millisecond intervals and we can simply destructure the result from calling this function right here and call it data from our used query and now comes the important part the redirection if we have the data do success oops success property in that case that means right if we check in reaction what does it mean that the user is successfully in our database everything is fine the user is synced this is exactly the perfect case right and now we're going to do one more check if there is a config ID if the user was currently creating their config and click this button and was prompted to log in in that case we can draw that ID from local storage in that case we want to put the user back to where they were so they don't have any interruption in their checkout flow right and in order to do that we're going to need access to the router which comes from the use router Hook from next navigation as we did before you already know how this works and now we can simply call the router. push to actually push a certain URL path into the user's browser and that path is going to be slash configure slash preview and then question mark ID is going to be equal to the config ID so basically the exact same path as they came from when they clicked the login button right and what we're also going to do is clear the local storage so above the router. push not after because afterwards the user is already going to be gone right the URL is already going to be changed so before we push into this and we're going to say local storage. remove item this time the configuration ID right so uh we read it once and then once we push the user we remove it so if they ever log in like two 3 Days Later through the landing page then we don't again push them to an item that they might have already bot right that would make no sense and else if there is no config ID in that case we're simply going to push them to the landing page um right there awesome okay and now to actually give some user feedback while they are on the um or callback page which won't take long right this will run really fast we're doing just one database lookup one conditional check and maybe creating user this will be really fast um so they're going to be on the o callback page for like a second maybe um so but to still provide visual feedback to what's happening we're going to return some very basic loading state for this page so at the top level we're going to return a div and this div is going to get a class name of with full a margin top of 24 a flex and a justify Center let's open this div up and inside of here goes one more div with a class name of flex flex-all items D Center and a gap of two and now let's open this div up inside of here we're going to put a loader Two element or icon rather from Lucid react as kind of a loading spinner the class name of this is going to be a height eight a wi8 an animate Das spin to actually make it spin around in circles and a text zinc of 500 and now below this icon we're going to put an H3 element and this H3 element let's scroll down bit is going to get a class name of font semi bold and lastly text XL for extra large and inside of here we're going to say logging you in dot dot dot to kind of you know let the user know what the hell is going on and lastly we're going to put a P tag here and let's say you will be redirected automatically period because again right here yes they are actually going to be redirected so the user doesn't have to worry about anything all is done for them so let's verify that this works right we're going to expand this page what do we expect to happen currently we are not logged in right so if you want to kind of test this with me make sure you're logged out let's reload the page just to get the freshest version and be sure in nice confetti by the way and then now what do we expect to happen when we click this button the popup is going to appear asking us to log in oh and by the way one thing we should definitely change about this popup is the um Z index deex for the backdrop because currently the Navar is still kind of um like it's overlaying and it doesn't look good and so just to fix that let's navigate into our dialogue component under Source components UI and so that's going to be components UI the dialogue that comes from our UI library and for the dialogue overlay let's add a custom Z index and that's going to be also like a 999999 like a a lot of n so it's very very high much higher than the nav bar everything um and by doing that hopefully when we reload the page then we should be able to see yeah that the overlay does like it does overlay the nav bar now and it look oh wait that's no that's too high that looks weird why does it let's leave it at 999,999 yeah there we go that looks a lot better all right now it does overlay the NF bar but not the actual like model that would look super weird all right perfect so this is exactly what we want to go for we left it at 999,999 um perfect now what we expect to happen right what I wanted to say is that when we click this button the modal opens and now when we log in we're going to get back redirected automatically to the same page not to the homepage to this exact page that's what we expect to happen so let's try that let's hit login I'm going to open my email here on the right hand side there we go let's grab the code copy and paste it over and hit continue and wow okay that didn't oh and that didn't work because we forgot like the most simple thing right and that's going to be in ourv file one thing we need to change is the kind post login redirect URL where we are redirected after we log in that's currently the homepage that's why we land on the homepage that should be of course or all callback that actually handles the synchronization between database and kind or authentication provider and um we need to save that and then we can always know that we synced the user and send them back to where they were so let's kind of navigate back here um to the preview where we just were let's hit sign out again and let's ah that didn't work there we go okay now we are logged out um right right okay perfect so we're logged out we're on the configure page that's exactly where we want to be and now let's try this again so just navigate back to the actual preview page and uh make sure you're logged out that's where we want to be okay now what we expect to happen is to be redirected to the same page after we log in right so let try that let's hit login and once again that's going to send us a code via email that we can simply copy and paste over and let's try this we should now be redirected to the off callback and perfect logging you in very very nice and we are redirected right to where we were there is no interruption in or flow and we're actually logged in properly now very nice awesome that's exactly how we know this works this works through local storage if you've never worked with local storage before it can be very helpful for exactly stuff like this and now you know how to use it how to kind of redirect users around with query parameters how to check if the user is synchronized to our database using the get off status and now the user is synced to our database and already to check out and actually pay us because the payment already works very very good job dude we are making great progress now the user can pay and next up is going to be the M thank you page that thanks the user for placing their order and actually paying us and that's of is going to be fully secure you're going to learn how to read data from stripe and that one is going to be really cool as well we're making absolutely killer progress together right now let's go and uh Implement that part next all right dude very very nice and again for me it's the next day so I'm just uh trying this out once again with the image and uh it it takes sometimes really long in local development especially when you first started up anyways we're going to continue as I just mentioned on the thank you page so let's take a look at how this works conceptually because oh what's the what's the thing here cannot get user details no authentication credential found well yes that's fine because we're not actually logged in so that's not really a problem um so the first I want to tell a little story so the first freelance client I ever got was like two and a half years ago and um basically I put my services on Fiverr right which is like a freelance site where I also hire the illustrator for this project right so I put my my this is not an ad by the way I just put my I just put my services here on Fiverr in hopes that anyone wants to hire me for me to get my first uh freelance client and somebody actually reached out after a couple of days I had no experience no portfolio nothing and they asked me so here's what they asked me okay can you do payment system they asked me if I can Implement a payment system for them and I was like hell no I cannot do that I never did that I have no idea how that works and surely that's a lot of effort with a lot of security considerations and so on and if it would like um if you implemented it from scratch yes it would be but with stripe or any other payment provider essentially like lemon squeezy or paddle or there are good alternatives to stripe as well it's not just stripe actually it's not um hard at all and I want to teach you it like right now not only in this video right now how it works and so if a freelance a potential freelance client ever asks you if you can do it after this video you're going to be able to say like hell yeah I can cuz I learned it from Josh hopefully right if I explain it well enough hopefully uh it's going to work out I'm going to try my best so conceptually how this works a payment flow okay here's what you need to understand how this works conceptually so we have the payment flow and you see the checkout page right we check out here let's quickly sketch this out this is the uh preview page right so let's call this the um preview page when they click the continue button then the user is forwarded let's say continue that's the button they click the user is then forwarded to the hosted check out Page by Stripe Right the check out page that we all already saw where we input like the 42 42 credit card right and when the user has paid on that site all the logic is done by stripe we don't need to care about any of it once the payment is successful stripe will send us something called a web hook and that sounds complicated I know when I was a beginner I was intimidated by this name I didn't know what a web Hook was and I was like what the hell is a web hook basically when Once the user has paid Stripe Right will send a request that is not directly linked to this checkout page to or API and I'm not really sure where I can draw this out let's let's just make a window down here or API and it's not directly related to the checkout flow right cuz the checkout page once the user pays that will make a request to stripe um informing stripe let's say Stripe Right Here informing stripe that the payment was successful and then stripe will make a request to or own API endpoint in something called a web hook and basically a web Hook is just a fancy name let's call this web hook for an API request that's all it is right so what we can do is Define our own API input in next chest nothing no magic right just a regular API endpoint and um when this API endpoint gets called it gets called from stripe we need to make sure of that but that's not very hard um so this API only gets called if a payment was successful so we know that the user now has to receive their phone case right that we need to print it and ship it that's all a web Hook is it's stripe calling any of our API end points that we choose by URL by giving it to stripe and also providing us with a web hook secret so we know this request actually comes from stripe and not from any user right and we can simply handle all the logic what should happen once a payment is successful um on our own API with whatever we want in a next s Handler super straightforward that's all the magic there is right it's not intimidating at all if you know what's happening conceptually all right so this is why it's so important for me that you understand this because it's really not hard and payment systems I had a lot of respect for them and of course you should have respect for them but don't be scared of them as I was when I first got started freelancing that's the story I'm trying to tell um so we're going to take a look at how to do this together and we're going to do it right now by defining that web hook that I told you about so this is going to live in our source folder once again this is pure nexj API if we want or any other API right if you want to use express or python or go whatever doesn't matter um any API endpoint will be used as the web hook that we Define so let's call this the web hooks folder in our API folder and inside of here we are going to create a new file called route. yes that actually handles the um logic when this web Hook is called by well technically by anyone we need to make sure it is actually stripe in the business logic in here and this is a regular plain route Handler and let's zoom in a bit more let's export async function um post right here so this is the HTTP verb that we expect this function to handle and we get access to the request which is of type request or next request next request same thing it doesn't really matter and this function let's open it up will handle all the logic um when this API is called so first off we're going to create a tri catch rapper so in case anything goes wrong during the tri stage and we can simply inform whoever is calling this API that something went wrong now the first thing that we want to do is to get access to the body that is passed um to this web hook Maybe by stripe we're not sure of that yet who is calling this endpoint right so let's receive that body const body is equal to await Rec do and not body or Json or anything we want the raw body as text that's going to be important to validate that this is actually from stripe and now to get the signature that is attached to this request right stripe always sends along something called a signature if it calls a web Hook from us and we need we need to receive the signature and verify that it's actually coming from stripe and we can simply get this signature let's say con signature is going to be equal to headers and these headers come from and I think we need to do it this way come from next SL headers this is a function we can simply invoke and now call the dot get uh on here um to get the specific header that we're looking for which is the let's give this a bit more space the stripe Das signature that's what we're looking for this header and if we don't have such a signature if it's not even passed right in that case we are already going to return a new response and inside of here we can say invalid signature and the status of this response is going to be 400 bad request because that header is not even present it's not only incorrect well it's not even present and now let's verify that this request actually comes from stripe and not from any random user that would be very bad right because essentially that would mean that anyone can for free receive a phone case if they just call this API that would be horrible so let's verify that this is a stripe sent event let's say constant event is equal to and we can use our stripe utility from lip stripe to do this web hooks do construct event that's what this is called and this takes three things first off it takes the raw body of this request which we already have right up here so we can simply pass in the body let's also pass in the signature right that is the header and lastly the secret what is the secret well let's head over to stripe.com that is a value that stripe gives us and let's give this some more space let's sign into stripe and we can simply grab this web hook secret let's say web hook right here in the search bar in stripe go to developer um and then web Hooks and we can simply um add a new endpoint and we can right now just type in HTTP uh Local Host let's say 3000 SL oops 3000 SL apiweb hook web hooks I think that's what we named the folder and this is not going to work right maybe this is not even possible with stripe and this is just so we can get the web hook secret and we can always change this later um and then the event we want to listen to is going to be the checkout session completed this one right here under checkout or you could also search for it the checkout. session. completed event and stripe is already going to give us some code for this which we won't won't really need because we're going to do it ourselves and let's hit add events and add endpoint and yeah okay that that doesn't really work it let's enter a temporary URL right here this is later going to be where we deploy this application um we're going to do that on versel in this video and for now let's just say Cas Cobra U I don't know doc or something that doesn't exist um but we can change this later and then let's hit add endpoint and that is going to right here give us the signing secret that's why we did this this is the web hook secret that stripe expects us to pass um right in here as the third thing however we're not going to pass it as a string right we very well could but this is a very sensitive piece of information and so because it is so sensitive we're going to Define it in ORV and there's a lot of stuff going in going on in our EnV but that is fine it's called this the stripe undor web hook underscore secret and that's going to be equal to the secret we just copi it over and then we can simply use the process c.v. strip web hook secret instead of defining this very sensitive piece of information directly here so let's say process. env. strip web hook secret and we can put an exclamation point at the very end to tell typescript yes we are sure this exists great and now if the event. type is triple equal to and do we get yeah we get full type safety here that's nice check out. session. completed the one event that we are listening to with this web hook right in that case it means the user has actually paid we are sure we have received the money stripe tells us this and all the logic again is handled by stripe we don't need to worry about it and in this case we are absolutely sure that the user has actually paid however one check we still need to do is if not event. dat doob do customer under oops customer uncore details. email and by the way the customer details are optional so we need to put a question mark here in that case we're going to throw a new error and this is going to say missing user email right because we do expect the user to have an email but of course this will be passed um by stripe so this is more for typescript and less for actual runtime problem handling um but I think it's important to add right and then below here we can simply get the payment session so let's say call session is going to be equal to event. dat doob and if we hover over this um object containing the API resource relevant to the event for example an invoice. created event will have a full invoice object and so on basically all this means who cares right is all the important information that we care about um like the user metadata and so on that we passed when creating the payment session is going to be right in here that's all that matters and we're going to cast this type as stripe oops stripe which we still need to import from stripe. checkout. session because we already know what this event data object will be because we're literally only listening to that one event right so this is just nice for type safety for working with typescript and now we can simply destructure the metadata um that we passed in when creating this payment session if we go into our actions let's see what those were um and that will be under preview right here what was the metadata this right here the metadata and we can simply destructure that right now where stripe is calling or web hook right so we're going to get the user ID and the order ID that we passed as the metadata and that's going to be equal to the session. metadata or we're going to default them to null um so the user ID is going to be null and the order ID is also going to be no so either we grab these from the session. metadata if they are present or they are going to be set to null which is of course a case we need to handle um by saying if not user ID or not order ID well we can't handle this request that doesn't make sense this will always be passed by stripe in a correct kind of web hook call so we normally don't need to worry about it if anything goes wrong well in that case we can't process this request we're going to throw a new error saying invalid request metadata data to then handle this case in the catch block here in a second all right now let's grab the shipping and billing address that the user answered on stripe so if we go ahead and actually click check out on our app well we need to log in okay let's do that really quick and because I want to show you um the billing and shipping address how we enter them so I'm going to choose my hello Jos coding.com email once again and you enter your right here that's going to send you the code um so you can just follow along as we do this and I keep refreshing my email here on the second monitor there we go um that's going to send us the code from kind that we can use to log into this app and then we should be forwarded to the page that we were just on because that is saved in local storage beautiful so the off callback still works as expected that is awesome and now when we click checkout we're actually going to get sent to the stripe checkout page now where does the um shipping address and the billing address come from that we need access to here in our web hook well you can see that right here the shipping address and if we enter stuff here like example town and some random stuff that I entered once um by the way if like I hope this is not an actual address I don't know um by default the billing address is going to be the same as shipping if you wanted to pass something different you can right but normally this is going to be the same stuff so um dribe is automatically going to handle that for us and give us access to these values that the user types in here their shipping address in the session customer details so we can simply grab the billing ad oops address right here from the oops is and that's going to be equal to the session. customer details. address and let's just tell a typescript to f off this this exists that's fine and then similarly we're going to grab the Shi shipping address so the shipping address is going to be the session. shipping details um. address so that's where we need to ship to which is often going to be the same as the bilding address as I just uh explained right and then we can simply go ahead and update or order with the stuff that strip provided us where we need to ship this right very important so we can say at the very bottom that's await or database let's import that do order. update and basically we want to update the order where the ID of the order is the order um ID now what you could also do is enforce that the user ID is in fact the user ID we're not going to worry about it because it doesn't really matter this information comes from stripe the user has no way to modify this information um but if you wanted to go the extra mile um you could totally add this as well um it doesn't really matter and now the data that we want to update the order with is going to be the is paid property and said that to True whenever this is called we have done the check this comes officially from stripe um we are sure of that and stripe is telling us hey the user has sent the money we've received it or well you've received it right in your stripe account um so we can simply set the payment status to true and because we know this is the case as for the shipping add oops add RIS we're going to pass in here we are going to create one because it probably doesn't exist yet so we're going to say create and we're going to enter the name of it that's going to be the session. customer details AME right here and let's just tell typescript ay dude this will exist don't worry about it the city is going to be the shipping address. City and let's just put an exclamation point here as well um there we go why why is this giving us an error by the way typ string or null ah typescript man all right let's put more exclamation points here to really tell typescript that this will exist because stripe will literally give it to us um the country is going to be the ship oops C country there we go is going to be the shipping address. country and let's just tell typescript that will exist as well and that will exist as well damn man all right typescript some typescript is very helpful sometimes it's a bit annoying anyways um let's do the same thing for the postal code code postal um code that's going to be the shipping address. postal and code there we go then the street is going to be the shipping ad oops address do and let's tell typescript that will exist do line one and also exclamation point hey oh by the way this needs to be postal code there we go and lastly the state is going to be the shipping address uh. state there we go and dude just ah this will exist don't worry about it all right beautiful and now for the M Building address it's going to be very similar we're going to um alt shift and arrow down the shipping address and just rename it to bilding address billing address there we go as easy as that and now of course we need to change the whole shipping address stuff to the billing address just like so and um again of course these will be similar in most cases but the user can choose for them to not be similar uh so not in every case will they be the exact same just mostly awesome and with that out of the way we are updating or order correctly and now we can simply go ahead and return a next response. Json from our API and simply import the next response from next SL server and inside of the Json we're going to pass the result that's going to be the event and the okay property is going to be true great now what if anything goes wrong right if anything fails up here if we throw an error or the user maybe doesn't have an email which you know really won't happen realistically but it might right we can't just always assume the happy path for or code we need to error handle properly first thing I always do is console oops I didn't mean to do that first thing I always do is console. error the error so it appears in our runtime logs wherever we deploy our service and if we ever need to do debugging then we're going to have the arrrow right there and can um actually address it optionally right what uh we will do at work probably at least at my last Dev job is send this to Sentry Sentry is an error logging tool that helps you to debug um and it's very common in Enterprise applications um to debug user problems we're not going to do that that would add a lot of overhead to this video that we don't want and again I personally also don't do it this is only relevant in enter prise context and not for us because this is not Enterprise and lastly we're going to return in next response. Json and first thing we're going to pass in here is a object with a message of something went wrong and then an okay property of false and lastly we can pass in the status in here status of our response is going to be 500 which is an internal server error to let hopefully stripe calling or web hook know that we didn't receive it properly it wasn't processed and stripe will automatically try um to call or API again at a later interval so it has a delivery guarantee which is really important for web hooks because imagine that wasn't the case and the user actually paid but your web hook fails and the user doesn't get what they ordered right that would be very very bad awesome now we could get into Local web hook testing there are ways to do it for example a tool called enoro which basically allows you to forward Local Host to an actual website um but you probably don't have it installed this is how I usually um host my applications or well test the web hooks at least I think what's a much better idea for us right now is to already deploy or application so we don't need to do it later and we already actually get the web hook called right so what we're going to do is let's already get this deployed and the easiest way probably to deploy nexj is on versel so it makes a lot of sense for a video like this and I also deploy a lot not all but a lot of personal projects on ell okay so in order to get this deployed we're first going to create a GitHub repository for it so I'm going to head over to GitHub and click on your repositories and let's create a new repository right here together I assuming you are familiar with GitHub um you can also use GTH the lab if you want just any place to store your code and the most common by far is GitHub we're going to call this case Cobra and do I have okay no I already have Dev let's just call this case corpa and this is later on going to be the exact repository that I'm going to make available to you and to follow along with this video I'm going to make it private for now I'm going to make it public once I actually publish the video and so it's totally fine to Market as private for now we can still deploy from it and that's going to create or repository awesome now once we have the repository to put our code into let's try building or application and that's basically going to be a final check is everything correct can or project built successfully and if it can great in that case we know we can also successfully deployed in order to test this let's run yarn build and that's or npm run build pnpm run build same thing right and that's going to create a production version of our um entire website it's going to be much faster it's optimized production build you can see that right here and so that normally also takes a bit and did my did my PC crash doing this it it might have just crashed um no it didn't crash it's just really slow and there is an error with or built and I can already imagine what it is yes it's literally the stupidest es lint rule in existence this rule is complete dog water nobody needs it everybody hates it and it has no point cuz it's completely pointless it basically tells you to replace quotes with the HTML element and it's completely stupid we're going to disable it I always do it's the worst es lint rule there is and so in or. eslint rc. Json which is automatically created by um create next app we're going to also pass in the rules and then here this takes an object um we're going to say react no unescaped entities the stupidest es rule in existence we're going to turn it off cuz to hell with it I hate this rule um and then close out of our um es lint rc. Json and we can simply run the build again it's going to warn us that we should use the nextjs elements which we purposefully did not do by the way it it has a purpose that we don't um because while the HTML the normal HTML images um make the page load a bit slower they don't get lazy loaded um once we navigate to the page so it just looks better sometimes to not use the nextjs image okay type error Navar cannot be used as a jsx component well that's fine that's not really an actual error and that happens in our root layout if you're wondering where in the file tree that is that's right here Source app layout that's where the current um problem is and let me look this up this could either be fixed by just updating or um typescript version let's see what's the current version we have in our package.json typescript okay so this is five let's inser the newest typescript version let's say pnpm install um types oops typescript minus D so as a development dependency and we want typescript at latest minus D so the newest version that there is of typescript and the Asin components the server side react components have actually been reworked in typescript so we should be able to just use those and let's see if the typescript update fixed or um issue it doesn't seem like it let's reload our window and we might also need to upgrade or um react types if that didn't fix the issue it might have it might not let's wait for this it did not okay that's fine let's navigate back into our package.json and let's install the latest react types as well so this is nothing wrong with your code it's just typescript not knowing about react server components yet so let's also install um that's going to be at types SL react at latest to install the latest types minus D and then hit install it's going to update or react types and then hopefully wow we get more error whoa what why do we get more oh no what happened that's reload or window that should not have happened there we go after reloading or window now it works okay so yeah sometimes typescript messes it up a bit but um just make sure that you install the newest react types and the us typescript and things will work 100% if for any reason they don't you can always go ahead and like just uh add TS oops at TS ignore this error if if something should still be a problem for you if you can't upgrade your types for any reason this is an error that's absolutely fine to ignore um and then we can simply run or build once again and if there are no errors in our build then we are all ready to all well already to already deploy our project um first to GitHub to push our code to GitHub to remote and then deploy a project on ver cell so that we get an actual production URL for the project that we can use as the web hook source for stripe so let's let this load and then hopefully we won't get any issues during the build great so linting and checking validity of types it gives us the nexj image warning which again is totally fine um this is intentional that we're ignoring that and that's also something we can disable by the way maybe we should it would make the build look nicer and there we go our app build successfully done in 50 seconds beautiful what that means is we're all ready to push our code into remote whatever that might be for you probably GitHub right so we're going to um copy the remote origin from GitHub add that to our project by simply pasting the git remote at origin bam there we go and then we can simply say git add Dot that's going to add every file we have in our entire app to git so to Version Control we're going to say git commit minus M for a comment and we're just going to say initial commit and then we're going to say get push oops get push minus U and so Upstream origin master so we're going to push to the master Branch now I also have a Mech if you're on Mech this might be main instead of Master so main for me that's usually Master and then we can hit enter and that's going to push all of our local changes into the um branch called master in our remote repository great so there we are remote resolving Deltas 100% and we're all ready to go and if we now reload our GitHub we should be able to see that our project is right here in GitHub perfect okay now that means we can deploy this to versel or any other place where you want to deploy probably versel it's the easiest um to follow along at least um there are other places where you can deploy nexr that are very fun like Railway or AWS amplify um they're also great but just for a video it doesn't make a lot of sense and then we we can click add new project and we're just going to select the project we just created okay so I just logged in with GitHub and then we're going to choose the um GitHub you're going to choose the GitHub repo you just created I'm going to choose the case Cobra one I created like a minute ago hit import and now before you click the big ass deploy button that looks so fun to click let's quickly paste in our environment variables this is really important um for our app to properly communicate um with all the services that we need right so let's grab all of ourv values and we can simply copy and paste them right here into the key field um control V that's going to put all our environment variables inside of um brael now there's still Local Host in here we can't change it yet because we didn't receive a domain for a project yet so we can already deploy and then once it's deployed successfully then we get a domain from versell assigned to us that we can then use to change the environment variables to that domain where or app is hosted and we can already then go ahead and use that domain as the basis for our web hook and what that's going to do is even in local development here it's going to allow us to actually have stripe call or live web hook and because it interacts with the same database that use locally right the web hook in the deployed version we're still going to see the changes um locally so once we deploy this once we don't have to test anything um in the deployed version we can just keep on working locally but it will just work with the web hook which is really nice awesome uh while we do this we can kind of take a look at the the build logs we have right here Prisma schema loaded Integrity check passed that's what we want to see that is very very nice so this is basically running the same command that we ran locally yarn build right it's kind of doing the same thing and we get oh of course the Prisma eror so this is very common when you work with Prisma um and deploy it on versel because versel caches Prisma that's not good Prisma has detected that this project was built on verel and essentially we need to run Prisma generate during the build process that's what it tells us and that's a very easy fix we can simply go into our package.json and then Define a new script um at the very top and that's going to be the post install script right here post install and that we need that whenever we deploy Prisma to versel that's going to say Prisma generate so during the build step we will generate the Prisma types and everything will be fine and dude by the way while we are here we can also go into our es Lind and quickly disable this image rule because it just kind of clogs up the build process um so we can quickly grab this name which is the at next next no image element and simply also turn this off in eslint because we don't want that great let's go ahead clear this let's add everything we changed which are only two files by the way then let's say git commit minus M and we're still going to say initial commit doesn't really matter and then we're going to say get push and because this is the second time we push we don't need to pass the minus U which is the Upstream we don't need to pass the branch this will automatically push to the current branch that we're on and it will also automatically trigger a rebuild a redeployment on verell with all our environment variables still in place so if I go to my personal projects here on versil then into the case groper which we just tried to deploy and over to deployments we can see that's automatically going to um rebuild our project but all the environment variables should hopefully still be yep right here um to build with perfect all right great ready that means everything worked and uh if we take a look at the build process then we shouldn't be able to see any image related warnings uh because we turned them off in es great okay so if we now go into our settings then and let's just reload our Pat really quick versel should give us a URL under which this is now hosted great there we are domains casec ca. ver. app and if we go here then we can see our stuff pop up right here exactly what we worked on great so not only do we know hey this actually works in production which is great this is now the URL that we can use for first off or environment variables right because these Still Point to Local Host and of course that's a very bad idea so let's take a look at our environment variables and change all the ones that point to Local Host for example the kind site URL let's addit this one and this needs to be log oops this needs to be well let's basically just replace the existing one with case cobra with no trading slash by the way let's remove that one and hit save on this one then let's take a look at the kind post log out redirect URL where should the user be sent once St log out same thing or develop or deployment domain with no trading slash and let it save uh this one is also still Local Host let's change that one to be the versel domain as well just like so or callback hit save beautiful admin email is of course going to be the same the next public server URL important that we also change this same thing with no trading slash and let's hit save on that this drip is going to be same upload thing is going to be the same database URL is going to be the same and all the rest is going to stay the same so let's do a final check great we have no more Local Host in here we changed all of it and now to actually apply these environment variables we can simply redeploy for project this is necessary for the environment variables to be updated but we're not going to need to do any code changes we can simply redeploy the last deployment that actually went through successfully great and while versel builds a project in the background and does all of that we're going to quickly change our um stripe web hook so on our web hook this one right here casa.com which we just kind of mocked we're actually going up here to update details and then can simply change this URL to our deployment URL /i/ web Hooks and the events to send we're not going to select anything here we're just going to click update endpoint CU we're already listening for that event perfect and once ver sale is now done and redeploying our project we can actually test the stripe payment in production because the web hook that we created um and that's let's give this a bit more space and close out of a lot of these files the web hook that we just created under web hooks route. TS this one will actually now be called by stripe on our production URL and update our database accordingly right which is really great and then we can see the status of the web hook right here in stripe so once this is done let we can already get started and start back up or development server by the way while versel is building our project and then um okay already have the checkout Page open if you uh closed out of this while I was doing all of this and you can open back up your um well you can upload an image and you can click check out that's going to take you to the stripe page right the checkout page that is exactly where we want to be right now to kind of test if the payment worked successfully right so what is verell doing hey can you can you just kind of build this out and by the way no more image related warnings we get some different warnings H image elements must have an ALT prop okay yes for accessibility that's a good idea that's true but it's not a blocker right now for us to try this out and the deployment was successful beautiful okay so once that's done on verell let's go back to our checkout page and let's enter the test card 42 42 42 basically a bunch of 42s any future date and any CVC it does not matter and let's say I don't know just Josh as the card holder name and why can we not oh because we need to select an email we can't click pay yet so let's click the big pay button and see if this works let's hit pay and that should actually um oh wait should it wait in the action did we uh oh maybe uh yeah maybe this doesn't work it we just need to try again if it doesn't work but it might work let's see what happens so what we expect to happen in the ideal case is that we can now see the called payload the called Web hook in or stripe dashboard what might happen is that the process. env. nextt public server URL was still Local Host um while we created the checkout page therefore it doesn't use the upto-date values from our deployment but let's see if this works um let's reload or strip page for the web hook and then ideally okay so we see the web Hook was fired but it did not succeed something went wrong interesting so it tried to call our web hook but something didn't work um so that's already good that it tried to call a webook let's quickly see why this failed and to do so that's why the um console. eror here is so handy we can now go into our versel logs to see what went wrong right here in our API um okay so it says invalid Prisma do order. update invocation argument City must not be know so it seems like we are missing the city argument wherever we try to update or order so it seems like the shipping address. city is not passed in properly was that a field that I forgot to fill out in the checkout form let's try this again and uh maybe I just missed a field in the checkout page that error has not happened before and uh I want to make very sure that I'm leaving you with a working project so that can't happen let's try this again let's customer case and create one more checkout session with everything up to date now with or deployed um kind of version that is then being called by stripe there we go and hopefully that will work so let's hit check out that's going to create our stripe session there we go and let's try this again so the email shipping address let's say example town there we go there we go there we go no so we did fill out the city I'm pretty sure let's try the same thing again card information any future date CVC uh and let's take a very quick look so that's everything we can fill out we're not going to worry about address line too um okay so this should definitely work we filled out everything this should work let's try this again and we expect to see this as another web hook so we have two calls already the next ones are going to be determining if this works or not so let's hit process that is going to call or um web hook under the hood by stripe and then we expect this to work okay so the payment did work we are redirected successfully and now let's refresh or stripe dashboard to see if we get a successful web hook yes we do perfect okay so the issue was that we didn't uh call the checkout session we didn't create it with all the upto-date stuff um so it probably tried to call or local endpoint I'm not sure um but now everything works passes us of course as we expected so right um as it should um it passes us the city and the shipping address and everything and we can also just see the data that stripe called or um web hook endpoint with right that's that might be interesting right the shipping details that the user filled out that we require to fill them out by the way because we have the shipping address collection right here that requires the user to put their shipping information and that is why we can make these assumptions that these will exist right but typescript doesn't know that of course um so if you're interested you can see all the data that stripe calls or endpoint with all we care about is that is actually worked that or endpoint is called successfully and what this also means is even though we're now still in local development where we're going to continue we already know now that this order was actually paid because our production uh web Hook was called and it does interact with the same database that we use locally right that is why this is so helpful now all right dude great we're just flying through this we're making really good progress awesome so what about the thank you page how do we display to the user that their order was successful that we received it they paid and all of that that's all going to happen on this page which currently doesn't exist so let's close out of everything here and let's create that thank you page so that's going to live in our app folder let's create a new folder in here and call it f-u and as always inside of here we're going to create a new page. TSX file that's going to contain the content that's going to be shown on the thank you page so let's define a cons page and this is nothing more than an arrow function that we export default page at the very bottom oops there we go and inside of this page we're going to return something called a react suspense right this is something that comes from react it's a very um popular element if you Google react suspense and basically what it allows us to do is it lets you display a fallback until its children have finished loading now why do we need to do this because the component we're going to wrap inside of the suspense that's going to be the thank you component by the way that's what we're going to call this as a self-closing um custom component is going to use a hook that expect us to put this component in suspense nextjs needs this to happen that's why we do it right um and that's already it that's the page content all of the rest of the logic here will be handled client side inside of this thank you component and that we need to wrap in a suspense in order for it to use the hook that we will need so also inside of our thank you folder let's create the thank you. TSX component that's going to handle all the logic as to what's displ on this page so as always let's close out of the sideb bar let's create the thank you component which is nothing more than an error function that we export default export default at the bottom thank you great and we're also going to declare this as a client side component at the very top with the use client directive to opt out of the server site that it is by default and because we created and exported the component we can already import it in our page. TSX and why is that giving us an error by the way thank you cannot be used as a jsx component its type void is not a valid okay that's probably yep because we didn't return anything yet that's fine we're going to do that so what do we want to return from this component is the question and basically let's take a look at how this will work conceptually this thank you page right the thank you page how will this work there are basically going to be um three states that we need to handle in this component the first one is that we are loading the order data from our database right it's going to be a loading state which means we're asking our database is this order already paid or not right we don't have an answer yet the second state that the order could be in is not paid which means the user is done on the stripe checkout page and they have already been forwarded to or thank you page but stripe has not yet sent us the web hook right the web hook has not yet been called so the stripe checkout window it does doesn't close when the payment actually has been received it closes before that right the uh web Hook is sent along with the user exiting the stripe checkout page that's important to understand right the user will exit the checkout page to or thank you page thank you page and already already be redirected without necessarily this can happen but it doesn't have to without the web hook already having been called so these happen at the same time which um means we can't rely on the web hook already having been called when the user arrives at the thank you page it's not this order not first web hook then thank you page no it happens at the same time which means we need to get the status of our database on the thank you page to ensure that the user has in fact paid or not paid right that's a possibility as well if they land on the thank you page before the web hook has finished executing and then the third state of course is the one that we want the ideal State um it's eventually going to happen that user actually paid that or database was updated um so both the thank you page the user was actually forwarded and the web hook also succeeded and in that case we get the paid state right so these are the three states that we need to handle on this thank you page and the question now of course becomes how do we know which state that the page is currently in and for that that's actually pretty simple we can simply destructure const um empty object something from use Query now use Query comes from T react query and it basically allows us to query some action something from our database and once this page loads right on page load we're immediately going to fire this automatically and um of course as always we're going to need to pass a query key in here that's going to be our payment status and that's actually called it get payment status I think that's a good name for this and then of course the query function what should be executed what are we searching for in or database and as always let's use my favorite pattern for this let's create a new file called the actions dots so you see the pattern right if we go into any other folder we have the page server site the component client site the actions also server site I really really like this pattern I might just do a separate video on it um I'm a big fan as you can tell from the project right because it works very very well now inside of the actions first thing as always let's define the use server directive so we can use it in or thank you component and from these actions let's export a cons and call it get payment status and for now we can leave this as an error function that literally does nothing just so we can already get rid of these arrows here because they look kind of bad right and so we're already going to go ahead and import the um get payment status action in or thank you page and of course this is going to be an asynchronous function that calls the await get payment status just like so and okay this is not a synchronous and so we're getting a little Mark here away it has no effect on the type of this expression because this is a synchronous function and so we need to mark it here as asynchronous right so we can actually await it and now let's already return some basic div from here just so the arrow in the page. TSX also goes away that complained that we didn't return anything right so what is the logic that we want to handle right here for the thank you page basically we need to determine the current state what are we in right so inside of here first off we need the logged in user to check um their orders right if the user is not logged in they're not allowed um to interact with our database very important right and to get the current user we can destructure the get user function from get kind server session or function we can always call on the server s side to get the currently logged in user provided to us by kind and now to get the actual user object let's say con user is equal to await get user so an asynchronous operation that reads the cookies that are passed to this API call which under the hood this is just a post request to an actual um HTTP endpoint it's just kind of abstracted into this syntax with the use server and so on right but under the hood of course this is an actual API that runs serface s side it's just generated for us and if we don't have a user. ID and the user in this case is optional so we need to put a question mark it might not exist or we don't have a user. email in this case we don't need to put the question mark because we already put it here and if this condition fails then this one won't even be evaluated that's just a interesting side note by the way on just kind of how the execution Logic for an if statement works if we have the or operator um so in this case when we don't have an ID or the user email we're going to throw a new error saying you need to be logged into view this page period great and right below this we will actually query our database for this order in order to get the status so we're going to say const order the user order is going to be equal to await DB let's import our database to interact with it do order. find first now what's the condition that we want to search by we specify that in the where clause in Prisma where the ID is equal to well what what should it be equal to of course we don't really know just from this function if depends on what the thanku component was called with as a URL parameter right because if you take a look at this we pass the order ID as a query parameter to our page therefore we should also get that query parameter from um the page and pass it into our action of course as a um argument whenever we call it and receive it as a parameter right here in or get payment status we need to receive the order ID that is passed in from our page and this um order ID to tell typescript what it is is going to be of type string just like that and receiving it in this function means we can now use it in or wear clause for the order where we find it and also where the user ID matches the currently um logged in user. so we're only searching for orders that the currently logged in user owns right that's also important and we're going to say include which basically under the hood does an SQL join for us just in kind of Prisma syntax we're going to include the bill billing address let's get some autoc complete going here that's going to be true we're also going to include the configuration let's say true for that as well then the shipping address we also want to grab that and by the way the reason we grab all of these is to Now display them to the user on or thank you page just so you know um and lastly we also want the user true so basically we join these four tables together with the actual order table and that's all that really happens here and if we don't have an order like that um this could be undefined um or null I guess same thing right it doesn't exist in our database Prisma couldn't find it in that case we're going to throw a new error that says this order does not exist period great and now if the order do oops if the order dot is paid and let's scroll down a bit so you can see this e easier if the order is paid in that case we're going to return the order else if the order is not yet paid in that case we're going to return M false and this statement is really important because it now allows us to pull this function to pull our database under the hood from or thank U component which is really important so just like in the O callback we need to go for a polling approach here where we pass a retry property of Truth telling tanac react query that it should keep retrying until nothing is thrown from this function right that's very important if the order exists only then do we stop retrying to get this order and we can also specify the retry delay in our case I think 500 is a good balance to strike between spamming or database and between actually getting the user the results they're looking for pretty damn fast all right now the get payment status obviously complains that it still needs to receive the order ID where does this order ID come from well at as I just talked about we pass it into the page as a query parameter remember which also means we can now simply get it from let's say const search prems and that's going to be equal to use search prems a hook that we get from next SL navigation and simply invoke that we get the order ID from the search parameters of course and to use them client side this is the hook we need and this Hook is also the reason why in our page. DSX we wrap this entire thing in a suspense boundary right when grabbing these states here these are based on the order ID that we can only grab using this hook now we could also do the server site yes you could also get the order ID server site in the page component and as we did before right that's totally fine as well um in this case grabbing them client side where we also query or backend also makes sense um and then we can simply grab the order ID from the search prems these are going to be equal to the search prams doget which is a really helpful function we can just call on on the search prams and now we can pass in the name of the search pram that we're looking for in our case that's the um order ID right so let's pass that in here or we're going to default this to an empty string if this doesn't exist if there is no order ID in that case uh we're going to default it to an empty string so in any case it is a string that we can poll right and if it's an empty string of course the polling won't work it will keep throwing but that's fine it will still like do what it's intended to do right and to get the result of our get payment status we can simply destructure the data from our use Query that's going to contain whatever is returned from our server s side function whatever is returned right here is going to be the type of our data on the front end which is really nice so we get four stack type safety and now we can handle each of these states separately first let's do the loading State and to know when we're loading we can simply do an if check if the data is triple equal to undefined it in that case we are actually loading and we can return some jsx from here now the data will always be defined if we got any kind of response from or action right um unless we threw beforehand that's totally fine right but um if the order is paid or not if we got to this point then it will be either the order object or it will be false but not undefined so this is actually just the loading State and not any of the other states already included here now what are we going to return first off a div element this div is going to get a class name and that's going to be a width of four let's pass it a margin top of 24 flex and justify Center there we go let's open this div up and inside of here one more div and this div will get a class name and that's going to be Flex flex-all items Das Center and a gap of two and inside of this div let's open that one up we're going to put a loader 2 element or icon rather from Lucid react or icon library and this one will get a class name of height eight a width of eight an animate spin to actually make it spin around and lastly a text zinc not zon of um 500 to give it like a nice kind of gray shade below the loader let's create an H3 aheading three element this is going to get a class name of font semi Boldt and also text XL for extra large saying loading your order dot dot dot to kind of indicate the current loading State and lastly A P tag that's going to say this won't take long period to kind of inform the user what is going on now that's the loading State handled beautiful um we now know about the loading State now let's handle the not paid state right if whatever we get back from our server side function is false which means the order is not yet paid the web Hook from stripe has not yet been executed successfully so we're not 100% sure that the user actually gave us the money that they actually paid as verified by stripe so in that case we want to display also a loading state but not like this loading state but informing them that we're checking their payment that it's being verified right so if the data is triple equal to false in that case we're still waiting for the web hook to update our database so basically we can copy over the same structure that we have in the loading State and just change the text to inform the user what's actually going on here so let's just um copy this over and of course we also need to return that in this if statement and let's change the H3 text to be verifying your pay oops payment dot dot dot and then in the P tag instead of this won't take long chances are this might take like 5 seconds or 10 seconds or whatever how long it takes for stripe to call or web hook endpoint so in this case we're going to say this might take a moment dot or period I guess beautiful and that's literally already it that's both loading set handled that's the not or not yet paid I think is a better way to put it and the not yet paid and then the last thing is the ideal case the happy path which is the payment case right the user has actually paid we know everything is all right so let's handle that happy path so first off to handle it uh we're going to need access to some data that we destructure from well the data all right and what do we want to destructure from here basically um some configuration we care about the configuration then the billing address we care about then the shipping address we care about to display both of these to the user and lastly we care about the amount how much did it cost the user to order this product and what we also want to access to is the color of the um phone case that the user ordered and that's going to come from the configuration um that we destructured right above here so we can use it to actually display their real color phone as a kind of preview with their image and it's just a nice detail you know in some cars when you drive them around and then in the infotainment screen you have the actual car model like literally in the same color as your kind of thing on the dashboard um my car doesn't have it by the way I think only the more luxury cars have it but that's just a really nice attention to detail where the right color is used in the demo and basically we're going to do the same thing um right here in our app just the just a fun side note I guess just a little tangent uh anyways um the top level div that we're going to return is going to get a class name of BG white a white background now inside of here let's create one more div in this one we'll get a class name of MX Auto a maximum width of 3 XL a padding X of four a padding y of 16 on small devices a padding X of six on small devices a padding y of 24 and on large devices a padding X of8 awesome let's open this one up and inside of here lives one more div with a class name and that class name is going to be maximum width of XL for extra large awesome inside of this div let's create and by the way we can save all of our and Pages the page. TSX the thank you component and the actions and if we now reload or thank you page we should actually be able to see what happens loading your order great this won't take long because we're in Local Host this will take long but in production of course it will not and then currently um the order is actually paid which means our stripe web poke worked awesome we saw that before um but currently we're not displaying anything in the state right and let's do that inside of the maximum with XL div let's create an H1 element and this is going to say Thank you exclamation point for your order the class name is going to be a text of Base a font Das medium and a text primary and if you're wondering what this is this basically just applies the primary color that we have set in or global. css so with shaty and themes that we defined below this is going to live a P tag and that's going to oops and that's going to say as the class name margin top two a text of 4 XL a font of Bolt a tracking Das tight and on small devices a text of 5 XL and let's save that and see what happens um if my PC was go faster we will already see it here on the right hand side there we are thank you and um inside of the pag by the way we forgot to say something here let's say your case is on the way exclamation point and also save that and by the way I just noticed um these H1 and PS don't make sense and the reason the text is so small is that I made the typo it should be text for XEL the thing is if we save this you're going to notice that this part is way larger than this part so how about we just switch around the P tag with the H1 tag I think just visually that makes a lot more sense now could you put all of this in a div yes you can should you probably not that's just pretty bad semantic HTML not like many people are going to care anyways but uh I want to teach you how to do it properly you know um awesome and then below the H1 let's create a P tag saying we've received your order and are now processing it period and this ptag will also get a class name and that class name is going to be a margin top of two as well a text base and a text oops a text zinc of 500 there we go and then hit save that's just going to be a pretty subtle color um there we are we've received the order and are now processing it great that is very very nice and now below the two Pags right here on the same level let's create a div element with a class name that's going to be margin top 12 text small font medium there we go and inside of this diff let's create a P tag with a class name of text zinc 900 and inside of this P tag we're going to say order number and let's create one more ptag right below this and this is going to contain the actual order number so the order ID the identifier we get from our database and this ptag with the order ID inside of it will get a class name of top two and a text zinc of 500 as well so the same kind of subtle shade of gray that we had on the ptag above great order number and then it says the actual order ID so if a user has any kind of support needs for their order they can always include the order number and then we know in our database which order that is what their email is um and so on so it's always really helpful to give the user any kind of identifier access um for their order so in case anything goes wrong with the order or something we always know how to identify it beautiful let's create a element with two more closing divs to go that's where we are let's create one more div and this div is going to get a class name that is going to be margin top 10 a border of T and lastly a border zinc of 200 and this is going to be like a visual separator um this top border inside of here one more div with a class name and that's going to be margin top of t 10 as well a flex a flex Das Auto and lastly a flex Das call and inside of here is going to live an H4 element a heading for so a much smaller um kind of heading in which we're going to say you made a great choice exclamation point and this H4 is going to get a class name of font semi bold and also a text zinc of 900 um as well beautiful and now right below this H4 let's create another P tag and this one is going to get a coloss name that's going to be margin top two a text of small and a text zinc of 600 so one shade of gray darker than the others and then here we're going to say we at case Cobra believe that a phone case doesn't there we go doesn't only only need to look good but also last you for oops last you for the years to come period we offer a a 5year print guarantee if your case isn't there we go isn't of the highest quality will uh replace it for free period now do you have to type exactly this text of course not right this is just something that I made up um if you named your project differently or if you want to highlight some different cool aspect of your um SAS that you're launching or not really SAS of your shop that you're launching here um this is the place to do that right this is just something that I came up with and I think it works uh pretty well there we go okay I was about to say that doesn't work pretty well if the spacing is off but sometimes exra s development locally takes a bit at least on an old PC but I don't need to tell you I talked about it too often in this video already okay so with one closing diff to go and then two closing div um let's open this up and then we have two more closing divs until the end that is where we are right now and that is where we're going to create a div element right here and this div will get a class name of flex of space X6 of overflow hidden margin top 4 rounded XL BG gray 900/5 so a very kind of light opacity on it a ring of one a ring Das inser a ring gray 90010 so a very light opacity as well and last ly on large devices we're going to give it a rounded of 2 XL to extra large let's open this up and inside of here is going to live a really cool component and that's going to be basically a phone but not the phone component we already have not this one but one where a person has a phone in their hand and we apply the user image to it basically a very realistic preview of what the phone could look like the like the actual phone phone case if someone held it in their hand that the user configured how cool is that and that phone case preview is going to live inside of its own custom component and that's going to be let's name it the phone preview. TSX and create that great now once again the phone preview is nothing else than a regular error function that at the very bottom we can say export default phone preview great and now very important that this is also marked as a client side component by use client adding that directive at the very top because we're going to make use of some client side react hooks in here now the question is what do we need inside of this component as props in order to properly display a phone exactly as the user has configured it basically and uh so imagine this the phone what do we need in order for this to look like the user made it two things first off the user image right uh the let's name it the image exactly how the user cropped it and where they positioned it very important that this looks like the user phone and then secondly imagine like it was a bottom and then right side how the user configured it that's exactly where we need it to be right now as well and then the color right if the user had a red phone let's make the background red then of course this phone should also be red or if they had a like black phone then this should be black in the actual phone preview right so we need two things the image and the um color of the phone so let's grab them as props very easy let's get the cropped image URL and let's also get the color and now we can inline this type because it's not very long we can simply Define the cropped image URL as a string and we can also accept the color color there we go and these are going to be if we take a look at our Prisma schema well we already have the colors right here defined as an enum right the valid colors how can grab this as a typescript type because Prisma always gives us the typescript types let's try it let's go for case color can we import that uh oh this this works okay oh that I actually didn't I'm going to be real with you I didn't know that worked so what I did in the demo project was um dollar enums that we get from Prisma do case color uh so that's how I did it that's really cool that this actually just wow I didn't know that all right dude I learned something new every day as well that's awesome let's just use the case color that's that's much easier great all right and the first thing that we want to do in this component is Define a state and that will be the rendered width of the phone because how large we want the image to be on this page is going to be very determined by how large the page is rendered on the user device right it's very important we need um we need to know about it so let's define a State and name it the rendered width which will be in pixels how large is this phone on the user device whatever that might look like right on on PC on desktop on phone whatever the user is using doesn't matter the rendered width will be um the correct value in pixels and then also the set rendered with and that's already it we can simply destructure those from the UST State react hook and let's by default pass an object into this US state hook where the height is going to be zero and also the width is going to be zero so these are just going to be the default values that we're going to change um later on and now because we're going to need to know how this is rendered out on the user device this is going to be very similar as to how we saved the image earlier in the video do you remember what we used to do that it was um a ref so we're going to make use of a ref a reference to a certain Dom node in this component as well we're going to say ref is equal to use ref which is just another react hook we can import and to tell typescript what this ref will point to in the Dom this is going to be an HTML div element we can pass in here as a generic and also let's default this to null because once this component first renders the Dom note is not going to be linked or referenced I guess um to this ref yet okay now let's return oops return the jsx from this component at the top level this will return or aspect ratio component from UI aspect ratio this will take or ref this is what we care about right this will be the actual phone how it will be displayed um so we're going to pass our ref into here and then we can also Define the ratio and the ratio is going to be 3,000 / by 2001 where do these come from well these are basically the dimensions of the image that we're going to use um for our phone that I prepared in Photoshop so that's just no magic numbers that's where I got them from and then the class name is going to be relative on the aspect ratio perfect let's open this aspect ratio up and inside of here is going to live a div element and this div is going to get a class name that's going to be absolute a z index of 20 and a scale and that's going to be a custom value in angle brackets a 1.03 52 very specific value I got it through trial and error and this will basically make sure that the background we're going to display in this div um in a second here's an image will actually never exceed the actual phone um Dimensions right so if we display the image kind of right here then it will never be bigger than the phone that's basically what we ensure with the scale so I just did some trial and error that's it it's also not a magic number and this just kind of works beautiful let's add a style property on this div as well and that will be an object this style will take two things first of the left property and now this will get interesting this is going to be the rendered width and actually you know what we have both the height and the width in the state so rendered width doesn't make a whole lot of sense I just noticed that let's call this rendered Dimensions instead so let's kind of improvise here on the Fly I think this is a much better name and uh we want to write good code and that's just a much better name uh because it doesn't only contain the width otherwise it could just be a number and you know that would that would not make sense so for the left property we're going to pass in here as a style we're going to use the rendered Dimensions do width divided by two basically what that's going to do is take the image right so this is going to be let me show you the the image that we're going to use here that's in our public folder under where is it uh it should start with C there is it's the clear phone.png it's basically um a transparent image of a person holding a phone right so imagine that this white image is the entire composition right with the person kind of holding their hand being uh well I'm very bad at drawing but imagine they're holding their hand kind of like like this I guess you know and the phone is right here I don't know looks horrible but uh you get the idea right so this is going to man let's copy this over there we go let's get rid of that this is going to be the entire image there we go and basically what we want to do is to take the user image and move it over the phone that the person has in their hand right and that's basically if we take a look at this that's basically the center of the image so what this means the render Dimensions do width divided by two is that we're taking the user image that's going to be contained in this div right here and moving it right here so the left Edge is on the 50% line of the screen right so it's a bit too far to the right because the left Edge lines and we want it to be right here we want it to be Center Center and not left Edge center right therefore not only this but also minus so to the left we're going to shift it by the rendered width um or rendered Dimensions is what we call it Dimensions do width divided by now in parenthesis 1,216 divided by 121 and that should be uh divided oh I switched my keyboard to English there we go so divided by 121 how did I figure these numbers out basically it kind of depends on the image that you're working with but I measured how large is the phone how is it displayed on our page and then through a bit of trial and error I came up with this ratio right here where this is the um kind of width of the phone and this is the offset um on the image and doing that we actually move our phone from let's zoom in a bit from the left Edge is in the center to it needs to be to the exact position that it needs to be in on the page however currently it would literally be here at the very top in the right position X wise but not y wise because we haven't given this a top property yet so as the top property right here we're also going to give it a rendered Dimensions do height divided by 6.22 and again the process is kind of trial and error in Photoshop where I measured the element and I put a lot of work into this component and this took me quite a bit to figure out um and then this is the ratio that I eventually came up with and the important thing about why we are using ratios instead of pixels is that as we resize the image however it is um kind of sized in the user browser the elements go like they get larger and smaller with how large it's um displayed on the user browser right that's very important if this was fixed pixels then as we drag this around well the content would not scale right that would be horrible and that's why we use um ratios and not pixels so it's much better the image we're going to put in here is going to be the actual user image finally right and the width of this image is going to be equal to the rendered width H rendered Dimensions is what we call Dimensions do with and now divided by and then in parentheses 3,000 divided by 6307 once again the process for coming up with these was in Photoshop kind of measuring stuff doing a bit of trial and error and eventually I found this ratios that work really well to always keep the image exactly where it should be and uh we can just use those because I've already done the trial and error work for us great this class name is going to be for the image element a phone- skew which is a custom class we will create in a second a relative a z index of 20 a rounded top oops top of 15 PX as a custom value a rounded bottom of 10 pixels as a custom value then on medium devices we're going to say rounded top of 30 PX as a custom value and also on medium devices a rounded bottom of 20 pixels instead of 30 as a custom value so we're left with round at top 15 round at bottom 10 medium round at top 30 medium round at bottom 20 and now because this image element will cover the entirety of the phone that is held in this picture so that's going to be um this phone right here it's going to cover the entire section we also want to apply the actual color that the user chose um and let's open up this page the actual color that the user chose for their phone right and I I agree like while we're doing this this all seems a bit abstract but trust me once we get done with this and save the file this is going to look really really good and you're going to see that here in a minute um once we get done with this so in order to apply the user um color we're going to grab the class name and cut it don't worry we're going to put it back but we're going to make the class name Dynamic with our CN helper function and import that and then simply leave this as the default name but we're also going to Merch this with something called the um case background color now of course this doesn't exist the case background color but what we do already have access to is the color that the phone was configured with as a prop into the phone preview component right so what we can simply do is a little check we can say let case background color um is equal to BG zinc of 950 as a string by default right and if the color is triple equal to Blue in that case we're simply going to set the case background color to BG blue of 950 there we go as easy as that and now we can press shift alt and arrow down copy this if statement down and slightly change this where if the color is Rose in that case the case background color is simply going to be BG Rose of 950 or um any other color that you support right that you do Define for your phones and then we simply dynamically merge it with the existing class name right here and lastly this image also takes a source and that for us is going to be the cropped image URL that is also passed as a prop into this component beautiful that's going to be the user image rendered out now the last thing we need to do very simple is actually display the background image as well the clear phone.png and we're going to do that right um above or closing tag for the aspect ratio this is where a div is going to live with a class name that's going to be relative a height of full a width of full and a z index of 40 inside of here let's create an image element this can be self- closing this must be self closing actually um the alt tag is going to be phone The Source is going to be uh as a string slash clear F clear phone.png what I opened up right here that's this image and then lastly as the class name for this image element we're going to pass pointer events none a height of full a width of full an anti alest hardw and a rounded of medium there we go let's save all of our files can close out of some of these and then let's see what pops up here on our thank you page because chances are it's almost going to look perfect um but let's reload the page loading your order this won't take long on local host sometimes it will and um oh did we not did we not import this component uh yeah we didn't all right we forgot to import our phone preview component well of course it's not going to show up let's render out our phone preview component in or thank you component and of course this expects to take two things which is the um cropped image URL and this going to be equal to or configuration. cropped image URL and we can simply pass this an exclamation point to tell typescript don't worry about it this will exist and the color is simply going to be the color and also put little exclamation point there um and we know we can put these exclamation points because all of these infos even though yes in the database they're optional we know that we already set them in step two of the configuration process Which is far done when we get to this page so they will be set in any case right we can save that and now let's see what happens let's restart the page and you're going to see something very cool pop up here on the right and that is well it almost works it kind of works we can see the background image is already there the clear phone that is awesome something that doesn't work yet properly is displaying the um image the user image and we're going to get to why here in a second actually no let's do it right now I mean dude it it doesn't work let's fix it why is this happening well because we never actually set the rendered dimension of the image we have assigned the reference to the Dom node right here the aspect ratio but we never actually updating the rendered Dimension and it's set to width and height of zero so the user image is literally zero pixels high and wide right so it won't be visible to anyone therefore let's um put a use effect into our component and this is going to be a very simple um kind of implementation that we're going to write here basically let's define a function let's say const handle resize and that's just going to be an empty Arrow function for now um above or use effect so we can already get started in using it in our use effect by the way let's pass in dependency array into use effect and this is going to run every time the ref oops ref. current changes when our Dom node link is actually established that is when this function inside of our user is going to run now what do we want to execute here the handle resize and simply call that and now we also want to fire this function not only by default on the first render or when the reference changes but also when the window is resized by the user to refresh how large the user image should be to always maintain the perfect kind of ratios right therefore we're going to say window. addevent listener we're going to listen to the resize event just like so and we're going to pass the handle resize as the Handler for that event so when whenever the user resizes their window this function will get called and of course to avoid memory leaks from our function or from our component we're going to return the window. remove event listener and pass it the same thing we're going to listen to the resize event and also the handle resize as The Listener or the Handler for that event right so if this component unmounts we clean up after ourself which is really important great now the handle resize currently doesn't do anything right we can call this all we want it doesn't do anything so let's fix that basically if we don't have a ref. current in our handle resize let's return and with this guard Clause that's what is called in place we can now assume that the ref. current exists and simply destructure let's worry about the destructuring later from the ref. current. get bounding client rect you're already familiar with this function from the previous this implementation so you already know what this gives us a bunch of really useful properties like the width and also like the height of how this component is rendered out in the user browser right no matter which device they're using no matter uh how high or wide it is it's always going to be this one right here we can rely on that and now simply set the rendered width to exactly that width and the height with d structure from the get bounding client wct and of course that should be set rendered Dimensions we kind of switch that out I think it's a much better way to name things and oh set render Dimensions there we go now that's going to recognize the function we can set that and if we save this then this effect will almost be done right and we're very close to this being done so let's reload this loading your order now the user element will actually be resized properly and well nice that's good it's in the right position but it's it doesn't look perfect yet the user image is kind of off right it's not tilted in the same way as the phone and to fix that that's actually exactly what this phone skew property is for that we're going to create right now ourself in or globals um. CSS so basically the goal here is to match the skew and the perspective of the phone how it is held in this image right and doing that in plan CSS is actually pretty simple we're going to define a custom class called phone- skew exactly how we use it in our component and simply apply a transform property on here which is going to be perspective of 400 PX then a rotate y of 3 degrees a skew X and the skew is basically um imagine if you had a rectangle then you move the right side to the right but you move the top side to the left so it's kind of um if I have to draw this it gets kind of more like uh like uh this you I mean that looks really bad but imagine this happens right from this to this I think you get the idea that's what the QX is for and it's is going to be 11.1 de and lastly the Q Y which basically does the same thing but for the y-axis M that we're going to apply is going to be minus 10.9 degrees hit save let's reload our page and we're going to see wow that looks a lot better it actually looks like the user image is right on the phone that they are holding and uh why is that not working did we phone- skew and we defined the phone- skew why that well that should work let's reload again wh why is that not working oh and I just did some debugging and it seems like there's a typo somewhere yeah we missed the degrease okay so it says invalid property value that's why it's not applied properly um and that will be because we forgot the degrees at the very end right here for the qy and then CSS is like whoa what is going on I don't know what's happening I can't appy this but if we fix that to degrees then you're going to notice wow this looks like it's actually on the phone exactly how the user configured it in the color as the user configured it as a really clean really nice preview of the actual user configuration very very nice and let's close go out of the global. CSS file and we're pretty pretty good progress on the thank you page so um now what do we want to do is to also display the shipping and billing address as well as the payment status to the user as kind of the last thing on the thank you page right the most important part is this one but this is also important and because the user expects these kinds of information to be on the shipping page so it's it would be pretty bad uh to just kind of forget it right that would not be good on the summary kind of thank you page so below the div that surrounds our phone preview with two closing divs to go let's create one more div this one won't get any class name so let's open it up and inside of here lives one more div element this one will get a class name and that's going to be grid grid cuse of two a gap X of six a padding y of 10 and a text of small perfect let's open this div up let's give this a t add more space to work with and inside of this div let's put one more diff this will just serve as an element to be the top of the grid so it's also not going to get a class name but in here is going to be a um something with a class name let me tell you so in here is going to be a P tag right uh and this P tag is going to get a class name and that's going to be a font medium and it's going to be a text Gray of 900 and instead of here we're going to say shipping address so you can probably guess what comes next it's going to be a diff containing the shipping address this div will get a class name and that's going to be a margin top of two to kind of space it out and also a text zinc of 700 let's open this div up and in here we'll live the address and if you didn't know there's actually an HTML address field that we're going to use for this and inside of this address field we can simply create a span element and we're also going to pass a class name into this address saying not italic and if you hover over this you can see font style normal because by default this will be italic and that's not what we want um so inside of the span let's give this a class name of block and the content of this is going to be the shipping address um question mark. name just like so uh just to make typescript happy because if we didn't have that question mark typescript would be like hey where is that like that could be optional maybe the name doesn't exist and it would complain and we don't want that so let's copy this span down using shift alt and arrow down once and twice and the uh second span is going to get the shipping address. Street and the last span element the third one is going to get two things first off the shipping address. postal code and also uh don't forget the question mark here there we go did I misspell this yeah postal code and then right behind that still still in the same span element the shipping address question mark to make it optional do City there we go if we save this we might already see what this looks like here once Local Host is done rendering there we are and uh that why is that still why is that still italic oh there we go it just kind of fixed itself dude yeah I'm really looking forward to my new pc that is faster It's just sometimes a bit annoying to have stuff like that on Local Host where you think hey why is it not working but it actually is working it's just Local Host not really uh catching up you know anyways um let's copy down this div right here that is inside the grid the top level div one more time to display the billing address so we're going to copy it down using shift alt Arrow Down Bam there we go and we're going to change it from the shipping address to the billing address and similarly of course we're going to change everything here I'm using contrl D mark the first one press contrl d a couple of times and you mark all occurrences of the shipping address and replace those with the billing address just like so beautiful okay if we save them they if we save the file that's what I mean these will be displayed side by side so this looks really really nice and lastly below the closing address with one closing div two closing div three closing div that is where we're going to open up so with three more closing divs to go this is where we're going to create one more div and this one is going to get a class name of grid a grid call of two a gap X of six a border t a border zinc of 200 a padding y of 10 and a text of small let's open this div up and inside of here one more div element again as the kind of top level of the grid so this will take up one grid column whatever is in here and in here we're going to put a p oops A P tag there we go that's going to say payment status and this ptag is going to get a class name of font medium and a text zinc of 900 there we go below this P tag just like so we're going to create one more P tag and this will get a class name that is margin top 2 and a text zinc of 700 so a bit less intense than the one above it and inside of this ptag we're going to say um paid because why are we hard coding this because every time that this is visible to the user this entire return statement is triggered we already know that it's not unpaid and we are also not loading but in that case we already know anything that is rendered out here we already know that user actually paid that's stripe verified that we got the money so we can simply hard code this here and because this will always be the case if this is rendered right beautiful and then below this div element we're going to create one more div element and basically it will be the exact same thing so what we can also just do is grab this div containing the both P tags and replace this one with just created so we have two under another with four closing divs after it that's where we are right so the address three closing ones and then the section we are currently working in and for the second one we're going to change the payment status to the shipping method and let's say inside of this P tag DHL takes up to three working days and hit save let's see what it looks like there we are payment status paid shipping method DHL takes up to three working days beautiful the absolutely last thing we're going to do on this page is um displaying the order Price Right how much did the user pay for this just as a nice kind of summary and that's going to happen um let's give this a bit Ah that looks kind of weird the formatting there we go so closing P tag then a closing div another closing div and another closing div so with two more closing divs to go that is where we're going to create one more div element and this one is going to get a class name that's going to be space y of six a border t a border zinc of 200 a padding top of 10 and lastly a text of small let's open this div element up inside of here one more div with a class name of flex and justify between let's open this one up and inside of here lives A P tag with a class name of font medium and a text zinc of 900 saying subtotal and we can just copy this P tag down using shift alt arrow down and change the class name to instead be a text zinc of 700 so again a bit less intense than the one above it and as the subtotal we're going to use our format price utility function to always display the right currency for the um amount that the user paid what is the amount that the user paid well we already already know right here from our database right how much the user paid for this order so we can simply use that amount in our format price function right here beautiful let's mark this div with a flex justify between and simply copy and paste it down once and twice using shift alt arrow down as the second one we're going to say shipping and as the amount we're just going to put zero because we offer free shipping on our service no problem and of course if you wanted to actually charge for shipping then that would be the price that you charge for it and of course don't forget to also tell stripe about it so it actually charges for it right but our service has completely free shipping we're going to use zero and then the total at last is going to be exactly the amount because shipping is zero right so amount plus zero equals amount can save the thank you page and that's going to display how expensive this was to the user subtotal shipping total $14 and that is a beautiful thank you page if I have ever seen one it shows the phone case with the exact user image um exactly how the user configured it your case is on the way with the order number we could use for any optional support request that the user could use with the shipping method the payment status it's paid and when we reload this page we also get a loading state for or order that is very very nice with the price great job all right dude and one more little cut because for me it's the next day again and I just tested the entire checkout flow and it works very very nice we've got our little well that's what I just put on the phone I know it looks silly but whatever um beautiful so we are at like 10 and 1 half hours of 12 um which is awesome that means we have done already most of the work and it also means uh we're almost done with the entire project now one thing I want to show you how to do is how to build the admin dashboard right where we see who ordered from us so we can process the orders um and so on I feel like that's very very important and also that the user gets an email when they purchase from us right that's the second thing um during the checkout flow I noticed one little thing that's the loading State on one button missing we're also going to fix that of course um but for now let's implement this dashboard so when when a user orders from us and we're logged in as the main admin user well okay right now that's that logged us out that of course shouldn't happen if we're logged in as the main admin user we should be able to access the admin dashboard and see all the incoming orders right so we can process them however we want by forwarding them to a processor um or by printing the cases ourself it doesn't really matter um as long as we handle them right let's stay at this zoom level okay okay so let's close out of all of the tabs and let's create this dashboard together and it's going to live in our source folder under app and then let's create a new folder called dashboard right in here that's going to end up as the URL and once again for the actual content we're going to define a page. TSX now let's export a cons page from here well not export yet let's define it first and then export it at the bottom as the default um from this file great now now inside of here at the first level we're going to render out some a div element and this div element is going to represent the main wrapper for this dashboard component and we're going to give it a flex a minimum height of screen a width of full and a background- muted sl40 which is like a builtin kind of color that our UI Library actually gave us and of course we also need to return this div and all that's doing with this top level div is if we now log in as the main admin user so for me that's going to be hello atj trite coding.com and by the way again how do we determine the admin user um that's going to be done with our admin email right here if you remember if we go into our navbar component we are checking the isadmin by the process. env. Admin email so this is the main site admin that's going to only this guy only you on this Emil will be allowed to actually access the dashboard right which is uh the entire point of securing this dashboard so once we log in with our main what we can't offer a quick start a new session oh that what that's weird I'm not going to cut cut that out because maybe that happens for you too that is very weird that that happens um okay so let's try deleting our cookies and then trying again that might have to do uh with the fact that I'm logged in on literally the other Chrome browser um so let's get rid of all of our cookies and try reloading the page let's click log in and if that doesn't work then I'm going to go ahead and log out on the other account so let's try this again let's send an email that's going to send us the code and um I mean maybe you didn't even get that error I have never got it before that's weird um but it might be like a cookie inconsistency or something um let's see if this works let's enter the code let's log in and that did work beautiful okay so that was like a once a once like an error that only happens once English is hard man that's what I'm trying to say anyways um so we can see our dashboard button right here but currently it logs us out right if you see the link like at the very left bottom here it says API off log out that is horrible we don't want the dashboard button to log us out of course not right uh so what we're going to do in the dashboard is instead of linking us to the API o logout this is going to take us to slash the um dashboard great now this would be a very insecure kind of way um of going about things because yes we're only showing the button to the actual admin but anyone can go to slash dashboard right even if they don't see the button if they know that the URL exists they can currently see or secret dashboard page right if we reloaded this even in incognito mode or something where nobody's logged in they would be able to see this very secret data that we uh want to show in here like all the revenue and the incoming orders and the user details and so on that's very very bad now how do we secure that it's actually pretty simple and it works very very nicely first off we need to grab the currently logged in user in this dashboard page and we already know how to do that right we can simply get something from the get kind server session because this is a server site component right this runs server site because we did not use a use client directive at the very top right so don't put it here by the way I'm just showing this to you um because it's important that the this runs on the server side great and now from here we can destructure or get user function and simply um grab the actual logged in user object by awaiting oops is equal to await the get user and simply invoking that function and of course in order to await this we also need to mark this page component as um asynchronous right here great next up let's grab the admin email let's say const admin email in all caps is going to be the process. env. admin email there we go and now let's do the important check right what's going to differentiate between any random user trying to access this page and the actual admin so if we do not have a user if the user is not even logged in or if the user. email is unlike um the admin email that's the Bad Case right in that case this user is not authenticated to access this page so um we're going to pretend this page doesn't even exist in that case we're going to say return not found which comes from next SL navigation right here what are possible Alternatives you could do you could also use the uh redirect I'm just showing this to you you don't need to follow along you could also use the redirect and simply send them back to the homepage if you want to that's also perfectly valid and we're going to stick with the um not found so they're going to think that this page doesn't even exist there is no dash board only when you're an admin are you actually able to even see this page in the first place perfect let's give this a bit more space and maybe we can zoom in a bit more yeah there we go okay now as for the actual content that's going to be shown on the dashboard what is the main thing that we want to show well the orders right everything is going to depend on the orders in our database therefore let's grab them right let's say const orders is going to be equal to await DB let's import or database helper do order. find many so we're going to grab multiple orders um in the server site component now this takes a wear property so which orders do we want to take by which criteria and we're going to put the is paid to True which means we're only going to grab orders that are successfully paid this is kind of personal preference if you want to list all orders you can simply ignore that I'm right but I think it makes sense to only show the ones where we are sure that we actually have received the money but again if you want you can just leave this away and you're going to see all orders no matter if they're paid or not and then the created ad property right here is going to be an object and we're going to say GT instead of here which means the created ad property needs to be greater than or equal right so in JavaScript that would be like this operator right here greater than or equal same thing as the GTE and Prisma a new date that we're going to construct and inside of here we're going to pass a new date once again do set date and we're going to set this date to once again a new date. getet date minus 7 so essentially all this means is last week right get me all the orders from the last week that's why we have the seven um days right here perfect and now right below the wear Clause let's also order these entries St we get back from our database so we're going to say order by and we're going to order by the created at property in a descending order so that means we're going to have the newest ones on top and then the oldest ones um on the bottom I think or did I get it wrong like is it the exact opposite I always get confused between s sending and this sending but I think this sending is newest first oldest last I think that's what it is and then we're also going to say include so basically we're going to join two tables on top of this and first one is going to be the user so we also get the user entry that made this order and also the shipping ad oops address we're going to put the true as well so we're going to join these two tables with the order that we're originally grabbing right great and we can almost get started with our jsx one more very quick thing before we do and that is let's quickly grab the sum of money or Revenue that we made in the last week to do that we're going to say const last week sum is going to be equal to await db. order. aggregate which is something we can call on the order and provided to us by Prisma and we can simply pass a where in here and same thing we're going to pass the is paid true and the created ad basically same thing right last week we can simply copy and paste this down so the we Clause is basically going to be the exact same as before the only difference is that we're now going to say underscore sum and that is an object and now what do we want to aggregate on which property that is the amount right of our orders and we're going to set that to true because that's going to be the actual amount that the user paid meaning that this last week's sum is now going to contain a underscore sum which is going to contain the entire amounts from the last week that have actually been paid verified by strap right and we can already get started with building out our page and actually seeing the results of the data that we just fetched so let's do it let's let create a second div inside of here in this one we'll get a class name of Maximum with 7 XL a width of full an MX of Auto a flex Flex Dash call on small devices a gap of four and there we go it's not complaining anymore and on small devices a padding y of four as well okay let's open up this div and one more div in here this one will get a class name which is going to be flex flex-all and a gap of 16 and let open this one up and one last div in here this one is going to get a class name of grid a gap of four and on small devices a grid calls of two all right let's open up this div and I promise no more div in here inside of here is going to go something called the card component that currently doesn't really exist in our project because it is one that is provided to us by our UI library that we haven't installed yet and it looks pretty nice out of the box and it's ideal for displaying um the table that we're about to display right so basically all it is it's like a simple wrapper with some padding and an outline applied to it and it looks nice out of the box it's not something like very groundbreaking or anything because it's just like a little div I guess with an outline and padding but it looks kind of nice and we can simply add it to our project using the npx command right here so let's open up a new terminal and let's say npx Shad cn- at latest ad car and then simply hit enter that's going to install a bunch of stuff this stuff right here into our own project like the card card content card description and that's the beautiful thing like look at this API right we have the card the card header the card content and then also a footer if we want to um and we are going to make use of all of those so let's see how to do it first off we can already import our card once that's done installing and inside of this card we're now going to put the card header first just like in the documentation here on the right hand side right so let's go onto our page so we can do so we can see what we're doing in real time and let's put the card header right on top as the first element in our card and this will get a class name of padding bottom to great let's open up the card header and inside of here goes the card description let's import that also from our custom UI uh component that's in our own project and this is going to say last week so this is going to be the revenue overview for the last week and to actually display the number the revenue from the last week we can simply grab the card title and put it right below our card description this one is going to get a class name of text 4XL we're going to make it pretty big and inside of this card title let's render out the actual revenue from the um last week and we already did the work for that right we already have the last week's sum that we can now simply build on top of let's simply use our format price Helper because we're going to display a um price amount a money amount and in here we're going to put our last week sum Dot and this now has a property called underscore sum provided by Prisma do amount which is nothing else than um the wear condition that be set which is nothing else than the total revenue for the last week and now let's do a knowledge call lessons if this is undefined or null in that case we're going to display zero right here to use as the input value for or format price function and let's see what we just did let's save the page let's read out this one on the right hand side and um we're going to see what happens there we go $22 perfect let's verify that that is true right we can simply say npx Prisma Studio to go oops studioo there we go we can simply go into our studio to see if that's true right let's open up or Studio here on the right hand side and we expect to see one order inside of here because I recently cleared the database so we're only going to have one order in here and that order should be for $22 perfect very very nice so we can see the revenue of the last week right here and let's also um give this some context let's define a goal for this right we can Define this ourself let's just go above our component above the return statement and let's define something called a weekly goal con weekly underscore goal is going to be equal to let's say 500 so our goal is to make $500 per week with our store and I think it's just kind of nice to have that in mind so we can now use that in our card to actually put this number into context right so below or card title and below the card header let's create the card content and simply import that as well from or UI library and inside of here goes a div element with a class name that's going to be a text of small and a a text muted muted for ground which is also just a color that our UI Library provides to us if we hover over this and inside of this diff we're going to say off and then format price or kind of money helper function weekly uncore goal the constant we have just defined goal right so off $500 goal let's save that and see what happens and while it loads we can do something even more cool arguably and that's going to happen there we go $22 off $500 goal what if we now had like a little progress bar that showed the actual progress from $22 to or goal I think that would be a very nice detail addition and that's going to happen inside of the card footer right here um and inside of here we're going to use a component and by the way we also need to import the card footer now for this to work we also need to import the progress component right here that we already have in our project because we also used it on the upload right the upload status to show that remember um so we can just reuse this this can be self closing and this takes only one thing and that's going to be the value so what the value is basically if we say 50 for example let's see what happens let's rot our page um and that's going to be how far the progress bar will be to the end so 50 would be like literal half 100 would be it's completely full and zero would would be that the progress bar is actually empty right and now we want to set uh $22 in perspective to the $400 or $500 whatever goal right and the way we do that is simply by passing in the last week sum doore sum. amount or zero as Nish call lessons right if this is undefined or null divided by or weekly uncore goal so basically how much is 22 or $500 great but that doesn't look very realistic because the progress bar should be filled up way more therefore let's wrap the entire parenthesis right here again in parenthesis and then say times 100 at the end right so if we pretend that the last week's sum do sum do amount was 500 let's just try this out in that case we expect the progress bar to be full so let's reload our page and see if it is full great it is if it was 250 so half of our goal you would expect it to be at 50% let's verify that that is the case and it is perfect so we now know this calculation is accurate and we can simply save this perfect now we have the actual progress to or goal and we're going to do the same thing for the last month right I think that's also very interesting to include therefore we can simply grab the entire card element and simply copy it down using shift alt Arrow Down Bam there we go and that's going to be for the last month oops last month in this case and the logic to grab the sum for the last month is going to be basically the same as for the last week right we can also simply um copy and paste that down and rename it and only change like the number right here so let's name this the last month sum there we go and then the seven we're going to change to a 30 so the last 30 days is going to be the last month now is that incredibly accurate should you put this in a banking application with just hardcoding 30 days probably not is this a good idea for us because it avoids a lot of complexity yes it definitely is and so it's going to be the last 30 days and we're going to call it the last month and uh that's just much easier than actually calculating like how many days did the month have and the where are we in the current month and so on um if you want that's a challenge for you to implement and but it adds completely unnecessary um complexity for us right now and now let's determine or month oops monthly monthly there we go underscore goal and let's set this to I don't know two 2,500 as the kind of Revenue goal for this month right and that's already it we can simply now use these values just like we did with the last week sum and replace the last week sum using control d by the way to select both with the last month sum and then put that into perspective into our a monthly goal right and then hit save let's reload our page and see what happens and there we go last month $22 of a $2,500 goal but the status bar is filled up too much that doesn't look realistic right it's the same for both and that's because we forgot to divide by the monthly underscore goal and not put it into perspective to the weekly goal that doesn't make a lot of sense so the progress bar should be way lower than this because 22 of 2,500 is not that much yeah there we go that's much more realistic perfect very very nice now let's actually display the incoming orders right so we can process them that's going to happen below the closing div below the closing card with three more closing divs to go this is where we're going to create an H1 element that gets a class name and that's going to be a text of 4 XL a font Das bolt a tracking D tight and that is already it let's open up this H1 element and say incoming orders and let's already hit save now what we're going to use to display the incoming orders is going to be there it is nice is going to be a table component this doesn't make sense to render out in anything else except a table right this is how you display a long list of items that potentially are like hundreds and hundreds of elements right hopefully I hope your shop does that well so it's hundreds and hundreds of elements um right and we're going to display that inside of a table and that's supported by your UI library with the table component that we're simply going to use for this um it works very nicely out of the box so let's install it let's say npx Shad C n-i at latest add table and then hit enter this actually uses tan stack react table under the hood it just kind of styles it 10stack table let me show you that um so this is what shn actually uses under the hood T stack table a headless UI for building powerful tables and data grids it's a really good good table you know that's that's just it it's lightweight um and it has wow I didn't why did this just open wow that was some horrible layout shift uh sorting Global filters it supports a lot of stuff and it's just really fun to work with basically also because of the API that we get to use um to create this and that API let's see how clean it is first off we're going to create a table element from our UI table right inside of here we're going to create a table header and we also need to import this from our UI library and inside of the table header we're going to put a table row that we also need to import there we go and I mean check this out this just this API just makes sense right table then you have the header then you have a row and now we can kind of put in the row um all the headings so let's let me show you uh what we're doing right now I think it's going to be a bit easier so imagine if we have a table right here basically it will have a bun bunch of columns just like so and what we're doing right now in this table header is going to be the text that's going to be like in the top right here right so U I don't know column one column two of course we're going to name it differently but this is what we're doing inside of the table header right just so you understand where we are right now so inside of this table row let's define the table head that's how we table head there we go that's how we Define the column name the First Column is going to be named customer then let's copy and paste this down the second one is going to be named status status that's going to be like is it shipped or whatever and this one is going to get a class name and that's going to be hidden and on small devices we're going to give it a table- cell there we go and let's give this a bit more space let's copy and let's give this even more space so this is one lined there we go let's copy and paste these status Down Bam that one is going to say purchase date so when did the order or when did the customer make the order and then the last one that's copy and paste it down once again but remove the class name and replace it with text- WR this is going to be the amount amount there we go and that's literally all the column names done right we can now actually go ahead and show the data so let's already saved this let's see what this looks like but there won't be any data in here just yet because we haven't kind of implemented that part but let's see what happens there we are customer and amount and if we actually give this more space um on desktop devices so we can process the order we see all the information right here in our table so let's actually go ahead and display the orders let's fill this table right that's going to happen below our table header inside of the table well you can probably guess the body right just like um with CSS you have header and body um same thing in the table API and I really find that very intuitive inside of the table body we can now simply map over or orders we're going to say orders do map so for each order that we receive in the Callback function we're going to return some jsx right away so we're going to open this up in plain parenthesis and from here we're going to return a table row I mean yeah right it just kind of makes sense um this one because we're mapping will get a key which is really important that's going to be the order. ID any unique value and the class name of this is going to be G accent which is also provided by our UI library and automatically kind of Applied beautiful let's open up the table row and inside of a row is a cell right so let's open up a table cell that's exactly what it's named from or you Library I like it and let's open this one up um once again there we go inside of the table cell lives let's create a div with a class name of font D medium this one will contain the order do shipping address there we go that we join together if you remember um question mark. name because as far as types group is concerned hey this might not exist because it's marked optional in our database and then right below this diff one more diff with a class name that's going to be hidden a text of small a text muted foreground and on medium devices this will be displayed as inline so it's actually going to be visible and inside of here we're going to put the order. user. email right just so we can reach out if anything is up um or update them on their order if you want to if it has been shipped and so on great let's take a look at what the next cell will be so we did the customer what is the next one that's going to be the status of the order great so let's create one more table cell right here and this table cell for the status is going to be the only one that's a bit different from the others all others will be def find in this file right here the status one let's just write status for a second here that's going to have Happ inside of a custom component that's actually going to interact with our database so let's just mock this out and go back to this once we did the other ones so let's check out what the third one is that's going to be the purchase date well nothing easier than that let's create a table cell there we go for this purchase date and in here is going to live the order do created ad when this order was um basically put into a database automatically generated by a Prisma do to Lo local date string there we go so it's going to display this date in the format of the user that's viewing it so if you're in the US and I'm in Europe it's going to display differently for both of us right um in your home country date format basically and this table cell will give a class name that's going to be hidden and on medium devices a table cell as well beautiful last one what should that one be it's going to be the amount well also nothing easier than that let's create a new table cell there we go and the class name of this is going to be text right just like the column we did for it and inside of here we're going to use our format price utility function um to format the order. amount there we go and that's already it right that's the table done congratulations very very nice we can simply save this the only thing as I said that we're going to get back to is the status because that will actually interact with our database and now if if we reload our page we should see what happens let's give this a bit more space there we are customer just tried coding there is the email status purchase date in my local kind of format and the amount is $22 very very nice that looks awesome and if we go into like small screen only the important stuff like the customer and the amount will be shown oh and that doesn't look right it shows the status and the dollar sign so the status of course should not be shown in this view we just forgot the class name so let's add a class name to the table cell for the status and this one is going to be hidden and on small devices a table cell oops cell there we go I keep uh misspelling the cell word anyways let's reload this page and that should only show the amount on the right hand side beautiful very very nice we're making great progress and now to actually update the status of the order right from waiting shipment to shipped and so on that's going to happen right here inside of this table cell where we just hardcoded status for now we still need to change that so let's create a new component in order to interact with our database and actually update the shipping status right and that's going to happen let's close out of all of these and we can basically create it in the same folder as our dashboard it's going to be specific to this page so no point in making it reusable let's simply Define a new file in our dashboard folder and call it the status dropdown do DSX as always con status dropdown is going to be equal to an arrow function that we export default at the bottom status drop down there we go and let's already return something from here to already put this in our page right so instead of just hardcoding the status here let's already go ahead and import or status dropdown component from do/ status dropdown beautiful this status drop down will do two things it's going to first display the current status of the order and second it lets us change the status of the order and in order to do that we need two things as properties for this component first off that's going to be the ID that's going to be a string um oh and we can't just do it like that we need to define the ID as a string just like so so typescript is happy right um and that's going to be important if we want to change the status from or for which order do we want to change the status well that's going to be the ID and then secondly what is the current status let's call this the order status we also need to know to display it so the order status is going to be of type order status basically the enum that we have defined in and Prisma so if we take a look at that the order State oops order States let's go to that enum it's basically this as a typescript type right it's either fulfilled or shipped or it's a waiting shipment which it will be by default because it just makes sense and I think I just voice cracked there anyways let's save or state this drop to and let's already give it these props from the parent so in our case the ID is simply going to be the order. ID and then the order status is simply going to be the order. status so whatever order we are mapping over right and why is this giving us an error oh because we probably forgot to return anything from here that's fine that is expected now this is named the status dropdown all right so what do we expect to return from here well probably a drop down and in our UI library that is called the drop- down menu that we import from our custom components right inside of here is going to live the drop-down menu trigger so basically the item when we click it that will open this drop down menu let's open this up and pass it the as child property because we want to define the button ourself and don't want this to automatically be wrapped in another button right so whatever the child is will now be used as the trigger for us that's going to be a button element and this button is going to get a variant which is going to be outline the class name for this button is going to be a width of 52 a flex justify Das between and items Das Center and this button is going to contain the current order status but the thing is if we just put the order status like so into our button let's see what happens let's save all of this because this is going to be exactly what's in our database how we internally identify the order status right so if we give this a bit more space it's going to be like in lower case awaiting underscore shipment like wow that just looks really bad let's make this user friendly let's make this user readable right and that's going to happen above our component this is just going to be a constant a con label uncore map so we're going to put it in all uppercase as the convention that I told told you about right and this will be of type record so basically a map and the first type is going to be the key of type of order status so basically what we're saying here is as the object keys we're going to put in here only these three are valid object keys so you're going to see what that means here in a second and each one of those is going to map to a string which is going to be the same thing in human readable format so let's create this object and because we told typescript that the entries here will be of key of type of order status check this out when we now press control space bar to get the auto completion we can see exactly only the options that we can pass in here it's completely types save how awesome is that and then let's define the user readable format for the awaiting shipment that's going to be um oops awaiting uh shipment and let's capitalize that so that's going to be what the user actually sees instead of this cuz this just looks bad right that's not what we want to display to the user the F fill that we can pass in here is going to be fullfilled and not fil not F filled and then lastly the shipped is just going to say shipped and that's already it that's our label map so these are just the human readable labels that we can now use in our button and to do this we can simply say the label uncore map at the index of the order status right so this is always going to be coming from our database and because typescript knows these are only devel valid types for the label map this will always map to a correct entry and typescript is going to be very happy with us and right below the label map let's put the little icon to indicate that this is a drop down and this is going to be the chevron's up down icon from Lucid react this can be self closing and the class name is going to be margin left two a height of four a width of four a shrink of zero and lastly an opacity of 50 there we go let's save that let's see what happens and we're going to see the beautiful human readable format show up for our status right here that looks awesome great the only thing that doesn't work right now is that this doesn't actually open the drop down menu and well that's because we haven't defined the drop down menu content yet so let's do it let's define the drop down menu content right below our trigger from our custom UI library and the class name we're going to give to this content is going to be a padding of zero inside of the drop down menu content let's map over all of the possible variants that there are right the awaiting shipment the fulfilled and the shipped so basically what we can update the order status to so we're going to say object. keys and pass in the order status there we go and we're going to say do map and for each available status we can now simply render out some jsx right away so this is going to be the actual string of awaiting shipment or fulfilled and so on and for each one we're going to render out the drop-down menu item simply import that from our drop down menu as well and because we're mapping remember to give this a key right otherwise we're going to get a little react complaint and also let's give this drop- down menu item a class name and that's going to be a dynamic class name so let's use our CN helper function instead of making it a string the default class name that we're going to apply to the drop down menu item is going to be Flex a text of small a gap of one items Center padding of 2.5 a cursor of default so it's not going to go into this like um pointer right we don't want that and on Hover this is going to get a background zinc of 100 there we go and now also let's differentiate the active drop- down menu item from the nonactive one so the current status should be highlighted differently than the non-current status and that's going to happen that dynamically in or CN helper function let's create an object right here and let's apply a BG zinc of 100 permanently as the background not only on Hover but all the time if the order status that we get as a prop is triple equal to the status of this current um element that we're mapping over so if it's like uh shipped for example then the Shi is going to get a background zinc of 100 and now inside of this drop down menu item let's open it up so we can already see what we're doing here two things are going to live first off a check mark let's apply a check right here this is an icon that comes from Lucid react and this will get a class name that's also going to be dynamic so let's get rid of the default string and let's pass this or CN helper function the default class name we're going to apply to this check is going to be a margin rate of two a height four with four and text primary so in our case that's going to be like a green tone and as for the dynamic part we are going to apply um if the order status we get as a prop is triple equal to the status so if this is the currently selected element that we're mapping over in that case we're going to say opacity 100 and in the other case we're going to say opacity zero if this is not the active element right so we're kind of keeping space for the check you're going to see that uh in a second here once we actually hit save and then right below here we're simply going to render out the label uncore map at the the index of status and because typescript can't properly infer the status type it just thinks it's a random string and but we we know that it's going to be the correct types one of these three right uh we can simply cast this type as order status there we go and tell typescript hey this is totally fine we know what's happening great and if we save that let's take a look at what we just did right let's read out the page sometimes this takes a bit for me hopefully your PC is faster and then once we open this drop- down menu we can see we reserve the space for each kind of check mark right it's there but it's not visible and only if the item is selected then will it actually become visible so in our case the awaiting shipment is like a little gray background and also the check mark right it just looks really really good beautiful now the only thing that's missing is when we click an item here it well it doesn't actually change the status right and then that's not IDE deal of course we want to actually interact with our database and change the order status and in order to do that it's surprisingly simple let's go into our status drop down at the very top here and that is why we receive the ID as a prop so this will become very easy for us let's destructure something from the use mutation hook that we're going to use for this just as always right this simply takes an object and we can pass it the mutation key this can be for example let's say change- order- status because that's exactly what's going to happen here and as for the mutation function well we want to pass it or server action right what should happen on mutation so let's define a actions file actions. inside of our dashboard folder where we're just going to Define what should happen um in this case and it's going to be really simple so inside of our actions file let's export a const and let's close out of that top bar I think it takes up a lot of space in our editor um so anyways inside of this actions file let's create a new cons and let's call it change order status and this is going to be an asynchronous arrow function there we go and we need to receive two things in here first off is the ID which order do we want to modify the status of and secondly the new status what should the new status be set to right and to make typescript happy let's Define the types right here the ID is going to be a string and the new status is going to be of type order status so any of the three um states that we allow in our Prisma enum right any of these three right here is valid as an argument into our function great and now the actual logic is of course going to be really straightforward we just need to make one database call so let's await the db. order do update and now this always takes a wear property which order do we want to up upate well basically where the ID matches the ID that is input into this function right and lastly let to find the new data that we want to modify that's going to be the status and we're going to set it to the new status as easy as that right four lines of code that's it can already save this action beautiful and now use this change order status in our status dropdown as the mutation function right it's that easy and that just works and now the last thing we want to do is on success when this ran successfully when the status was updated we want to refresh the uh browser for the users so so the result is actually shown right and we can do that inside of the on success callback of this mutation and now to refresh the user browser we can simply grab the router from use router which comes from next navigation not next SL router as I told you about earlier and we can now simply call the router. refresh that's a function we have on the router that's going to refresh the user browser to get all the most recent data right and let's destructure the mutate from our use mutation that we actually used to call this function and now the only thing left to do is to actually call this function whenever we change the drop down option right so that's going to happen on the drop- down menu item when we select any item and we can check for that event with the on click right in in that case we're going to call or mutate function and this expects some arguments we need to pass in here for one that's or ID and for two that's the new status we need to pass in here and that's going to be the status and once again because typescript can't properly infer this it thinks it's just a random string when we know it's one of the three correct options we can simply cast the type here as order status because that's what we know it will actually be perfect let's save or status drop down on and you're going to notice one thing an error that's because this only works in a client side component and of course we still need to mark this as use client for next year to know that we want to use client side hooks in this component beautiful so let's reload the page and let's see what happens there we go and now everything should be fully functional let's check this out we have or last week or last month all the progress bars work the incoming ORD we can see right here the customer status purchase date and amount beautiful and now if we actually change this order um because for example we are done printing the case and we just shipped it bam let's click shipped and let's see what happens that should reload or like a browser which either takes really long or doesn't work why is this that that should work why is it not working let's reload the page once more let's click Shi and did I mess anything up uh let me check okay after like five minutes of very confused debugging because this should definitely work like there's no way this doesn't work I just noticed the problem we didn't say use server at the very top of the actions and next CH just doesn't tell you about it they just they just kind of leave you in the dark to see what happens by yourself and let's try this again let's hit Shi there we go okay finally that was the problem dude I was so confused cuz this should definitely work and now it does beautiful so we can now actually change the status when ever we process the order uh to shipped awaiting shipment shipped and so on very very nice so we can keep track of all the incoming orders stay on top of what the hell is happening and forward them to our supplier update the user actually when we ship the order and so on beautiful work man that's a fully functional admin dashboard we have just built together with the revenue and the table and all the incoming orders that is awesome man very very good job all right dude and we are literally almost done great work and there's only a couple of small details left that I want to implement together with you right now so first um of those is going to be customer email when somebody buys from us we want to send them a small email thanking them for the purchase right that's what we're going to implement first second is going to be a button loading state that we missed earlier button loading State and that is when we um configured our phone when we dragged around the right in step two um there is no loading State on the button yet we forgot to do that I noticed um and then I think we should be oh and the metadata so we don't want this to say create next app and then the versell icon right that's really bad that just looks super generic we want a beautiful custom thumbnail when you send the link to this website as well as a fav icon and the title right so we're going to say metadata and these are a bunch of small things but together they make up a much better website than it is currently and it makes a really really big difference if you implement that small things and the first one is going to be a email that the user is going to get when they received the or when we received the order right and in order to send this email we are going to install a really neat tool and that is called react email it's basically a super modern way to write emails previously it was always um a lot of pain to write emails if you've ever done it yourself and react email makes it a lot easier because we can essentially just write emails as regular react components right it's super intuitive and this is going to happen let's create a separate folder for this in our components and let's simply name this emails right so this going to live next to the UI folder and let's create the email that the user will receive once we have gotten their payment and they order the phone right let's name this the order received email. TSX because again this is nothing else than a regular react component now to actually use react email let's install the package for it let's go into our command line and say pnpm install yarn at npm install doesn't matter at react email/ components and these basically are some pre-made um react components that we can simply use to kind of design and construct or email as we wanted to in basic react syntax it's really cool and you're going to see exactly how it works right now once that that is done installing beautiful and this is nothing else than a regular react component so let's create it let's name it order received email and this is going to be equal to a basic Arrow function that we export default at the bottom order received email perfect and this order received email will receive a single prop and that's going to be the shipping address of the user that they ordered with and we can simply Define that type in line that's a very simple type the shipping address will be of type shipping address basically the type we get back from Prisma there we go let's import that and we're all ready to get started in actually writing this email together at the top level we're going to use the HTML element but let's reload our window in order for the Auto Imports to hopefully work and do they nope they still don't okay so let's manually import this let's import the HTML component from at react email/ components there we go and then we can simply use that no props no nothing just a regular element inside of this HTML we're going to put a self closing head tag and this actually works now we also import that everything we're going to use in here basically we're going to import from at react email/ components right no exceptions um here inside of here let's create the preview and this is the text that will be shown um when the users in their email inbox the main text uh that will be used here and we're going to say in here your order summary and estimated delivery date and that's going to get rid of the aror beautiful and of course by the way we still need to return all of this there we go okay now we can give this a bit more space and now below the preview we're going to create the body this is going to contain the main content of the email and this body is going to get a certain style now this video very important is not a CSS tutorial uh we don't want to get into CSS right that's a whole different topic in and of itself so what I did is I prepared all the email CSS in the copy and paste list for you to Simply grab and then paste uh right below or export default or um above it it doesn't really matter just put it somewhere in this file so we can use it and we don't need to worry about any CSS headaches anymore because again this is not a CSS tutorial right we can just save time by not doing that and this body will get a style of main now inside of the body we're going to open this up let's put a container and that's basically going to be a wrapper for all the content we're going to put in here and please let me all oh man just go away there we go sometimes vs code is just a bit frustrating and this is going to get a style of container right as you can as you can probably guess from the name now inside of this container we're going to create a section element this is not going to be self closing let's open this up and the section will get a style which is going to be the message style perfect now inside of here let's use an IMG an image we also get from react email this is going to be self closing and this will take first of all the source and the source is going to be dynamic and basically now we need to do a check where do we get the image source from and that's going to be different between local development and actual deployment so let's do that check at the very top here we're going to say const base URL which is going to be the basine URL for the image source right here um and that's going to be the check of it's going to be equal to the process. env. nodecore EnV which will automatically give us the environment in which this is currently running right wherever we execute this code and if that is equal to development we get full type safety here because this is a builtin um environment variable in the node.js runtime and if this is equal to development in that case our base URL will be http Local Host 3000 3,000 not 300 there we go and in the other case right where we are in a production setting in that case let's grab the URL that versell gave us right so we deployed this to case ca. ver. app so that's going to be our live deployment URL that we can use as the base for this image and also let's remove the trading slash so we can properly construct the URL um wherever we use the actual image so so let me show you how this works now for the image source we can simply dynamically insert the base URL determined by the environment that we're currently running in and then append the/ snake D 3.png which is basically the snake in a box that's going to be in the image right let's give this a width and that's going to be 65 and let also give this a height and this will be 73 and that's in pixels by the way the all tag is going to be delivery snake and the style this is going to get let's just inline this is going to be a margin of Auto so this will be centered in the email and now of course we also need to import the section that won't happen automatically there we go and just like that we can get rid of the error and display the snake 3 if you're wondering which one that is um it's this one right here in or email beautiful very very nice now below this image let's continue still inside of the section this is where we're going to create a heading that we also get from react email as a component and the style for this is going to be the global. heading let's open this heading up and inside of here say thank you for your order exclamation point and below the setting let's create a text we also get that from react email and the style for this text is going to be the global. text and let's open this text up in here here we're going to say we're oops we were preparing there we go everything for delivery and will notify you once your package has been shipped period delivery usually takes two days and of course this is something I made up right is that true mostly yes most package carriers I would say take about 2 days it I guess it depends on where you live Etc but uh this is just something we can put in there that that's totally fine and let's actually grab this whole text element copy and paste it down using shift alt arrow down there we go then slightly modify the style let's turn this into an object this time and cut out what was there before so we're going to put this in an object then spread in everything we had before right so the global. text so we're applying all the Styles we had before but also one more that's why we do all of this which is going to be the margin top and that's going to be 24 to kind of visually space these two text elements out um so in the second one we're going to say if you have any questions regarding or any questions regarding your order please feel free to contact us uh with your order number and we're here to help period okay so if they want to reach out they should hopefully include their order number which makes it easier for us to look up their order in the database and update them on anything that might happen with the order beautiful after the closing section let's create an HR element also from react email and this is just a horizontal kind of divider which just looks really nice in the email and the style for this is going to be the global. HR that we prepared beautiful let's create one more section down here this section will get a style and that's going to be the global. default padding style let's open this section up and inside of here we're going to create a text element with a style and that's going to be the address title just like that great now inside of this text element let's scroll down a bit there we go let's say shipping to and let's dynamically now include the shipping address that we pass in here as a prop. name there we go beautiful that so that's going to be the name of the customer that bought this product and to actually display the address and not just the name let's copy this text element down once and change the style the style is going to be the dot dot dot global. text so we are spreading in all the properties of the global. text which is this one right here we're just spreading those in right here and let's add one more and that's going to be a font size of 14 right and then inside of this text we're going to say very many Dynamic stuff in here the first one is is going to be the shipping address. Street then comma separate that with the shipping address. City then comma separate that um with the shipping address do state then without comma separation right after that dynamically again the fourth Dynamic element here the shipping address. postal code there we go and that is already it that's going to be the address of the user uh where we will ship this phone case awesome now below the closing text below the closing section let's copy and paste our HR element where did we have it right here we can simply grab and copy that so above the container um no above the container there we go at the end of the section this is how we're going to separate things and now let's open up one more section right down here this section is going to get a style that is going to be the global. default padding let's open this section up and inside of here is going to live a row which is also something we can import from react email components this row is going to get a style and that's going to be a display of inline Flex just like that and a margin bottom of um 40 which is also in pixels by the way if you're wondering what the unit is here um automatically inside of this row let's let's create a column which is something we can import from react email components and this column will get a style and that's going to be a width of 170 let's open up this column and by the way we're almost done don't worry um this is not going to take up too much of our time um and inside of this column we're going to list the actual order number so this going to contain a text element this text element will get a style and that's going to be the global. paragraph with Boldt that we can use for this text and and inside of here we're going to say order number simply copy and paste this text element down using shift alt arrow down and in here we want to include the actual order ID and I forgot to tell you that's what we also need to receive as a prop by the way but we can already give it the style that's going to be our track. number and while we are at the top of the props let's also receive the order ID that will be of type string order ID of type string that we can simply list there and one more thing we want to do is to also receive the um order date and that is also the order date going to be a string that we're going to pass from our database based on the created ad property that we get from our database right and that's it that's all the props um I promise we can simply now insert them here um so as for the track. number class that we have in the second text element this is where the order ID is going to go that's the order number for the user and now we can simply grab this column paste it down using shift alt arrow down once the second column doesn't even need the same class name as the first one and we're simply going to change the order ID with the order date that we receive as a prop in this component perfect now the footer we're almost done with this component let's one more time grab the HR and simply paste it below or section just as a kind of visual separator and by the way you're of course going to see how this email looks like and we're going to deploy this together in the end and then take a look if everything is really working correctly and to ensure that I'm leaving you with a fully working project so of course you're going to see the email in action so let's create one last section here absolutely last one and this will get a style which is going to be the padding y there we go let's open this section up inside of here lives a row no class name no Style no nothing and inside of here lives a text element this one is going to get a style and that's going to be an object where we spread in the footer. text class add a padding top of 30 and a padding bottom of 30 as well and inside of here we're going to say please contact us if you have any questions and then in parenthesis if you reply to this email we won't be able to see it because uh in most cases you're going to send this from a no reply email um automatically and you're not going to see if they literally reply to a no reply email right you want them to email you directly um in that case of course not in every case right if you send the email from your personal email um then they could reply but in most cases uh in company context and Enterprise context you're going to have a no reply email uh for sending out customer emails great and now below the closing text and below the closing row let's add one last row of this component very simple one um not even with style or any anything and inside of here one last text element the text element here is going to get a style and that's going to be the footer. text and inside of this text we're going to say um with a little Copyright symbol that you can simply grab from Google probably Copyright symbol or maybe if you have a Mac you could also just uh do it on your keyboard let's just copy the copyright symbol or I think you could also use and copy like that I think that should also work but I'm just going to put it here and we're going to say case Cobra and by the way if you can't find it doesn't matter right it's not very important um case Cobra Inc incorporated All Rights Reserved and of course I made this up right there is no case cpra Inc uh I just made it up but I think it makes the email look a lot more serious than it really is which is kind of cool awesome and that is or order received email now when do we want to send this email and how do we send this email very simple when do we want to send it in our web hook so when the user actually paid and we are sure that they did um by stripe then we want to send them this email and to send it to them we're going to go to res.com and there are 10 million ways to send an email right I think resent is the easiest one and we're going to create an API key at resend. comom so this is a basically if we're not logged in let's go into Incognito rent.com it's basically very easy to send emails to them it's email for Dev Vel opers basically um using AWS under the hood you could just use AWS yourself if you wanted to and but it's a very easy way to send emails to your users so we're going to use it in this video not sponsored of course uh I just think it makes a lot of sense for this video so the API key name let's name it case Cobra the permission is going to be full access and the domain all domains that is totally fine let's copy our key and then paste it into ourv file um at the very bottom let's say resore API uncore key is going to be equal to that key that we just grabbed from the resent dashboard and by the way if you don't have an account with them of course you can log in with GitHub and uh that's going to give you your own API key for resent beautiful and resent makes it really easy to interact with their apis with their own SDK so let's install it let's say pnpm install resend and that's already it the package is very lightweight it's very small um that's going to be done installing really fast great and then at the top of our stripe web hook so by the way if you're wondering where that is the web hook that we're just in its under Source app API web hooks route. Ts that is exactly where we are right now and we're going to create a new resent right here we're going to say cons resend is equal to a new resend and pass in the process. env. resent API Key by the way if you're wondering where resent comes from where that class is import from we can simply import resent from resent the package we have just installed there SDK right and now this makes it incredibly easy to send or own email to our user let me show you how it works let's go down all the way below creating the or updating or order in Prisma right the DB order update below that is where we're going to say await resend. emails. send and this takes a bunch of options like for example the from this is going to be the center of our email and in our case let's say case Cobra and then in these angled kind of brackets like HTML and we're going to say hello at josht coding.com and now please don't use my email here you won't be able to it will just throw an error and use the email that you used to connect to recent that you logged in with right either your GitHub email if you logged in through GitHub or your email that the login to token was sent right it's very important that you do that um because by default on the free plan either you have to verify your own um email to send from a custom email that's what I did or you can only send from the email that you logged in with for security reasons of course um so use the email that you logged in with to resend um right here and that's going to work okay and now the email to where do we want to send this email to that's going to be the event. data do object. customer details. email there we go very very long uh kind of thing right kind of text but that's going to be the actual email of the customer then the subject we can pass in here that's going to be thanks for your order exclamation point awesome and lastly let's pass in the react property the actual content of the email that we want to send and that's nothing then or order order received email that we just created in that other component remember we can simply now invoke this with the props that it expects right so that's going to be for example the order ID so if we take a look at where this comes from let's see where is the order d uh here it is we're destructuring it from the session metadata then the second thing this expects let's see is going to be the order date which is going to be a string right and in order to get this order date we can simply say um this is going to be a new date to local uh where is it to local date string um and that's going to automatically convert this state to a string what we could also do um right is we could also receive the cons updated order from or await DB order update call and then simply use the created ad property um of that updated order right so in that case I think that is a bit better then this this totally works I just wanted to show it to you and but if we actually use the created ad property and convert that to a local date string in that case it's always going to match or database and I just think it's a a bit of a nicer approach right and lastly as for the shipping address we can simply um use the same thing that we passed into our database call this one right here so all the stuff that's in the create right here we can simply grab that and paste it into or shipping address that we're sending to our component that is going to give us a typescript error I know this also happened in My Demo project but we can simply go ahead and ignore this error because everything is all right with this and typescript uh should just stop complaining uh because that's exactly what we put into our database as well we know this works and we can safely ignore this error we don't we just don't care about the error men just uh send the email please and that's going to work beautifully great very very nice and that's all we need to send send the email once the purchase is verified by stripe perfect Let's cross this off our list let's just uh strike that through uh and just let me change this to Red there we go okay now next up is a button loading state that we missed and that actually happens um in step two of when we create a case right so while this loads in local development where I want to show you where this happens let's already navigate to the corporate which is the um design configurator component where we currently don't have the loading State and dude this takes ridiculously long 20 seconds on my PC wow all right anyways let's just upload an image and I want to show you where exactly this happens and that is right down here in the button that we use to proceed from the design configurator where is that let's contrl F button there we go and this button right here the continue button it doesn't have a loading state right but we've already already implemented all the logic we need um in order to properly handle this loading State and dude I wish I could show you but it just takes ridiculously long to do U yeah screw it let's just implement the button loading State uh as you can see it's not here right um so that's going to be well how do we how do we do the loading state right if we check out the button what do we need to pass in here uh wait that's the what that's the status drop down it takes me why does it take me to the status drop down dude my PC is doing some weird stuff what the hell let's go to the button component finally that's the page I'm very frustrated with my PC as you might be able to tell um anyways but I'm not um yeah 14,000 milliseconds H anyways dude I'm not uh going to let that affect this video so inside of the button what do we need to pass in order to make the loading State happen well basically two things right the is loading prop and loading text so let's do that where do we get this loading state from very easy we get it in our design configurator from react query so where are we doing this where do we have the use Query uh let's just uh use mutation screw it let's just uh control F it there we are in line 47 right here that's where we can simply grab something called the is pending from and this is pending is essentially the loading state it just means that we are currently executing what ever is in our mutation function in our case the save configuration also the underscore save config right so we can simply use that as the loading state for our button um right down here in our JX so the is loading is going to be equal to is pending and also the disabled is going to be disabled is going to be equal to is pending as well because we want to disable the button while it is loading and lastly the loading text we can pass in here is just going to be saving right and because this is already loaded it was this button right here uh that I wanted to show you where the loading State doesn't happen but I guess we already fixed it so it's uh fine anyways great we fixed the button loading State when we now hit the button there we are saving dot dot dot we see a beautiful loading State here now perfect that's already the second item we can strike that through and we're almost done with this entire project man one last thing missing that is the metadata we don't want this to say create next app or anything that just looks really bad instead what we want to do is to oh wow and it finally compiled the page wow D and by the way one thing we can do is give this your iPhone case a bit more space to the top I don't yeah I'm not too happy with how that looks uh so let's just go ahead and fix that let's navigate into our design preview so as you can tell we're almost done we're just fixing a bunch of small things that we just noticed on the way so on this diff right below or login model this is what we're going to change first the margin top 20 is going to remain but we're going to give it a flex a flex-all and an items Das Center and of course that is going to conflict with our grid so we're only going to apply the grid from medium and onwards and we're also going to remove the grid calls one that just doesn't make a lot of sense here and that's that class done for the div above the phone we're going to remove the call span 4 on small devices so all we're left are these three right here the phone itself we're going to join the class them together with a maximum width of 150 PX and then on medium devices we're going to remove that um limitation again so medium devices and upwards we're going to apply a maximum width of full once again so it can fill up all the space that it needs to then we're going to remove the small margin top zero right here from the your model label case basically the space between the iPhone model and the actual title right here that we talked about before that just doesn't look good and we we don't want it um in app and let's save that and already preview um of the changes that we made so let's reload the page that's going to take a bit once again on my PC and then is that now the most upto-date version let's see um let's preview this there we go if we're on full screen then the phone is going to expand there we go that looks great now if we zoom in there we go there we go there we go the phone is not way too huge anymore like it was before and that looks so much nicer all right perfect I'm really happy with this um you could yeah maybe maybe the phone should take up like a bit more space here maybe should take up what does it take up right now let's see um so on exactly this size right here this maybe looks a bit weird right now the phone is very small so we might want to say where is this this is medium devices end up uh we're going to say call span 4 um in this case so it's going to take up more space right here yes that looks much better and then on large devices we can say call span three once again and that will just look a lot nicer all right awesome dude I'm very happy we got this fixed this looks a lot better than it did before just kind of on the fly together with you here beautiful and oh that yeah wasn't even an item we just wanted to fix that to make it look better that's totally cool and now the last thing that's going to be our metadata and that's really simple by the way and to make this look good with the actual title and image um and so on the fav icon rather um we're going to create that in our source folder under lib under uos this is where we're going to create a utility function to handle the metadata let's call this function export function there we go construct metadata and we can close out of the sidebar great this will take a couple of parameters first of the title and let's default this to case Cobra oh and if we want to default this we have to use an equal sign and not the object syntax so we're saying the default value of this property will be case cpra custom high-quality phone cases now the description this takes is going to be also with an equal sign equal to create custom high quality phone cases in seconds beautiful now the image um the preview image when you send a link to this application is going going to be/ thumbnail.png something we have in our public folder if we take a look it will be right here the thumbnail hell yeah that looks absolutely great that's going to be basically the image when we send a link to our application beautiful and then lastly we're going to put the icons and default that to slash fav icon do I so basically do icon and now to tell typescript typescript is very confused right now what is going on here to tell typescript the types of these um let's inline type these the title optional is going to be a string the description also optional is going to be a string the image optional is going to be a string and lastly the icons also optional is of course also going to be a string beautiful let's say this is equal to an empty object just like so and this function will return meta data and that's a Tye we get from next to tell next is hey this will actually return the metadata that you expect and then let's open up this function right here and now the only thing that typescript is complaining about is that we're not actually returning the metadata so let's do it in here let's return an object containing the title that we defaulted to this one right up here the description then afterwards the open graph this is something the metadata also takes and right now my PC is super slow again so screw it I can't show you anyways uh this is going to contain the title the description as well and also the images and the images are nothing else than an array of objects containing a URL property and we can just say it's going to be the image that is passed into the construct metadata or the thumbnail.png that we have set to be shared is the open graph image when you share a link to this application perfect we can also Define a Twitter card that's going to be shown when you share a link to this app on Twitter basically this takes a card and we can set this to summary large image as a string this also takes the title the description and the images property which is an array of strings in our case we also want to use the image right so or thumbnail to be used when somebody shares a link to this app on Twitter and lastly we can pass the Creator if you want so in my case that's going to be at Josh trade that's how I like my name on Twitter you can put your name or if you don't have Twitter just don't worry about it right you don't need to pass it beautiful and lastly let's pass in the icons also passed into this function and we're receiving it as a parameter up here and that's it right that's how we construct metadata and now let's go into our main layout to Simply use this because right now we have all the default next stuff here so the verell image and the create next app as the name that's not really what we want right let's simply export con metadata is going to be equal to or construct metadata utility function that we just created and because we defaulted all the values we can simply call it and don't worry about the rest because all the default values are great if you ever want to change them for any sub page that you have you can always do that by simply passing in an object here and overwriting the defaults that we have just defined perfect and if we now go ahead and reload our page a couple of times hard reload we should be able to event see the fav icon pop up oh by the way we probably won't because the fav icon is still the verell icon as so go ahead into the GitHub repository for this project and you're going to find the actual fav icon under Source app um and then fav icon. IO in the GitHub repository so I've got the actual project open here on my right hand side so just pretend this is the GitHub repo okay you're going to go into Source app and then here you're going to find the fav icon that you can simply download to your device um and you can just drag it in here and overwrite the Fab icon that is all already present in the app folder right just replace it with this Cobra image and that's going to be the fav icon for or app and if we now hard reload or development server a couple of times it normally doesn't do it on the first try just two three four times sometimes it takes and then it's actually eventually going to replace or fav icon and it seems to still have cached the other version it's not really that important if we deploy this then we will actually be able to see this fav icon awesome dude let's try this we are done let's deploy this to production and see if everything works correctly and in order to do this let's head over to versel really quick and um yeah let's head over to verel we're going to check out our deployment together let's go into our project and let's update the environment variables for example with the resent key right let's go into our environment variables um there we go let's scroll down and let's add the resent key um that we have in our project that we just added so let's copy it from or. EnV file paste it in here and hit save and one more thing we want to update and that is going to be the um upload thing uncore URL this is to make upload thing work properly with or deployed version and this is simply going to be the versel domain so or production domain that we have let just just visit a website copy the URL and simply paste it in as the upload thing URL really no magic here but just so uploading knows um or app and can communicate it or communicate with it properly right perfect with those environment variables in place that's great let's build our project one last time uh what do we get here JWT expired well that's that's fair enough because the login expired that's no problem um and let's build or app one last time to see if we get any errors so let's say yarn build or npm run build and see if everything builds correctly and if it does then chances are it will also do that on verell and then we're going to give this a final run to check if everything works correctly all right so let's give this a second and see um what happens okay so it compiled successfully now it's linting um image elements must have an ALT prop yeah okay so sometimes we forgot to pass Like An All Tech into an image or something uh but that's not a biggie metadata base property is not set for resolving social open graph or Twitter images using local oh okay that's actually a really useful hint um right here and basically all it means is that in our construct metadata we forgot to pass one thing that is going to be the metadata base uh we need to pass in here very simple U not a lot of work this is just a new URL we can create and simply pass in our production URL right here U it's important that this is production we don't want to use Local Host for this of course and then let's try the build again this time or actually we don't really need to try it again we know this will just work now uh the error is going to be gone because I've did this like previously but just to show you that this is actually going to fix the error let's just do it and then deploy to verell together and give this whole app a final run so that's going to lint check the valid validity of types collect page data great now the error is gone we have a valid metadata base that the open graph image can now pull from and there it goes rendered or entire app correctly that is beautiful all right let's say get at dot let's say git commit minus M and let's just say update stuff uh it doesn't really matter what you put in here is just a personal project for us and then let's say get push if you were like working in a software team this would be a horrible commit message um but this is just a personal project as I just said so it's totally fine to do that I don't care about commit messages at all when I'm just working on stuff that nobody else will really see um but if it's like a public repo or something then it is more uh important anyways okay that's going to create or build why do we have this open twice we don't even need that that's going to deploy our project to versel we can see the build process right here that is great and then uh let's let this load and give it a final check all right dude there we are it deployed successfully so let's check this out let's go to case cobra. vl. app let's reload a couple times and hopefully we should see the Fab icon pop up and I think my browser just caches these uh normally of course the fa icon should be there there is literally no alternative um because we if we take a look at the public folder there is literally no versel icon anywhere in here right is that true versel if we search for versel yeah no we did deleted all that stuff it can't know about the for sale stuff anymore uh so this is probably just a caching thing in my browser I would have loved to show you that this works but maybe I need to switch browser or something for this to work and anyways chances are you're probably going to see the fa icon here and of course like for all the apps that I deployed to the web like profanity. Dev and so on they all have a custom fav icon just like this so this works um but I think it's just cached in my browser which is kind of unfortunate but whatever anyways there is our landing page beautiful what our customers say what people are buying upload your own photo and get your own case now awesome very very nice let's click actually let's let's try logging in yeah let's try logging oh and good that we did invalid callback URL and that's because we still need to configure um this URL to be used in or um well with kind right so let's log in with kind I'm going to do that with GitHub really quick and then we should be able to just set this URL right here um in as a valid callback URL so let's log into kind let's switch our business to case Cobra Dev which is our um case Cobra project and then let's head over to settings and is it here no is it ah here it is um applications under environment let's open up or nextjs app and then we should be able to set the yeah call back url's application homepage UR I uh let's just put case cora. ver. app there application login U that's fine let's put the allowed callback URL and let's include that here and maybe we don't even need this uh yeah let's just remove it I don't think we need it let's just put the Callback URL right here and also include um kopa vl. app as the allow logout redirect URL and that should already do the trick let's save this let's go back into our app and let's try logging in again and now that should work with that callback URL configured beautiful and since we're already logged in that's automatically going to log us into our app awesome okay beautiful let's click create case let's upload any image I'm going to choose this one right here the little snake that's going to upload and then um if everything works correctly we should be redirected here in a second let's go into our console to see what happens fail to load 44 that's fine uh that's not a big problem it just didn't find the API or log out but it doesn't need to uh if we click this button that's going to log us out um okay so let's try configuring this let's drag the snake around let's make the phone case I like black the most so I'm going to stay with black uh let's go iPhone 13 uh this one right here and let's click all the premium options cuz why not it's our app we can do whatever we want without having to ask and then let's hit continue we can see a beautiful loading State on the button beautiful very very nice and now we should be forwarded to your iPhone 13 case with your actual image as we configured it popping up right here on the left hand side beautiful okay now let's hit check out that's going to summarize all the order details and that's going to forward us to stripe with the actual product price beautiful I'm going to use my hello atj coding.com um email let's uh just use Josh as the shipping address and then the random kind of stuff that I always used before um as for the credit card information let's use a bunch of four twos right here descripe a test card that we always use any random date and any CVC and then that's it pay and let's see what happens two things should happen first off we should be redirected to the thank you page where it actually pulls for the payment status and then secondly we should get an email right so let's wait for loading your order this won't take long your case is on the way beautiful there is your Live phone case preview with a snake on it awesome and the shipping address and the billing address uh the actual uh verified by stripe that we received the payment and the shipping method and then a summary of the cost beautiful your case is on the way now let's check my email and yes we did actually get an email if I just drag this over right here uh you can see what happens right here from case Cobra thanks for your order uh we're preparing everything for delivery and we'll notify you once your package has been delivered uh delivery usually takes two days blah blah blah perfect and then this little like footer at the bottom that is Al oh and I think oh yeah order number order number that's what we forgot to change this should be the order date of course and then uh a little padding I'm going to yeah I'm just going to fix that in the or actually should we just fix it together let's just fix it together uh why not let's go into our email and let's just fix it so we should have order number here twice right let's see yeah we forgot to change that this should be the order date and then let's also give this um a let's give the entire column a style and that's going to be a margin left of let's say 20 uh because right now it looks like this is a bit close to the left side here you know it's just not ideal so we can kind of space it out and actually change this to the order date which is today right the 3rd of May 2024 beautiful shipping to Josh and then the random address I entered thank you for your order dude this is awesome I can move my email out of the screen again and uh that's it now we can see this order pop up in or dashboard from our customer we can see our uh Revenue has increased right we're closer to our goal of $500 very very nice by $22 uh from the second order and the status is of course going to be awaiting shipment with or order details right here the purchase date the amount beautiful and now once we actually forwarded this uh case printing request to our supplier or you printed it yourself whatever you want you can change the status to shipped and you can also change the status to fulfilled once the order is actually done so A beautiful admin dashboard where you can keep track of your financials and manage all the customers and the order statuses dude this is awesome I know this was a really long video but big respect for following along honestly uh I really hope you learned a lot this app works so nice let's log out again that's going to refresh the complete page and take us to the landing page and there we are that just looks beautiful it was really important for me to have everything in the single video no extras no payments needed no credit card and I really hope you agreed that I kept my promise in the beginning to show you all the conceptual stuff that we go in depth into the stuff um and I really really hope you enjoyed this build process enjoy the artwork that the illustrator made specifically for this project that I hired and uh thank you for watching I really appreciate it and then I'm going to do the actual outro in person right now hey man I really hope you enjoyed this video you cannot imagine how much work this was to make it it was a lot but uh it's really worth it I love seeing your reactions to it um thanks again uh big time for kind or to kind for sponsoring this video and by the way since finishing the video I forgot one thing and that's like the gradient that's on the homepage I forgot we didn't do it in the video but I added it in the GitHub repository it takes like 1 minute to implement it's very simple uh I just forgot to do it in the video but that's the only thing we forgot all else is implemented and I really appreciate you following along and I hope you agree that I kept my promise um of the three things you're going to learn in this video how to write good software how to write good UI and so on hey so thanks for following along it really means a lot and I hope you enjoy this video a ton and that's it that's all I have to say thank you and I'm going to see you in the next video Until then have a good one and bye-bye it feels weird just ending a 12-hour video I spend so much time on like this I'm going to see you in the next one have a good one bye-bye
Info
Channel: Josh tried coding
Views: 97,551
Rating: undefined out of 5
Keywords: react, nextjs, course, full course, 2024, nextjs full course 2024, react full course 2024, typescript, tailwind, tailwindcss, postgres, postgresql, next.js shop, e commerce, nextjs e commerce, react ecommerce, ecommerce, nextjs ecommerce, modern, drag and drop, casecobra, joshtriedcoding, josh tried coding
Id: SG82Aqcaaa0
Channel Id: undefined
Length: 720min 0sec (43200 seconds)
Published: Sun May 05 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.