Build and Deploy a Full Stack AI SaaS Platform with Next js 14, TypeScript, Stripe

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
have you ever wanted to build a real software as a service application with AI features and payments and credit system that you might even turn into a side income or a business idea well today you'll have the chance to build an app with AI generative fill image restoration recoloring and object and background removal by watching this video you'll learn the fundamentals of building AI SAS applications and you can even use it as a blueprint for your own SAS or as a great addition to your portfolio and skill set now let me show you how the app works the homepage showcases image Transformations that people in the community have made at the bottom we have a pagination component to navigate between different pages but what's even more interesting is the search feature at the top it's not your typical search where you look for images by their title instead you search based on what's in the image let's say you wanted to find all the images containing the sky just type it in and there you have it isn't that incredible this is an advanced search functionality you probably have never implemented before now to be able to create your own image Transformations you need to log in and for that you'll develop an entire user management system with clerk if I click on one of these Community images created by someone else it will take me to its Details page and here I can compare the image changes and even download it but where's the fun in looking at other people's images let's create our own unique images instead first we have ai imagy store where we simply provide a title upload our image and VOA we have a beautifully restored image we can save it for future use or download and discard it moving on we have generative fill where we can adjust the aspect ratio of the uploaded image and generatively fill the space to resemble the original image it's perfect when you want to turn a horizontal image into a vertical one and vice versa see how nicely AI did this next we have object removal where we can specify Which object we want to remove from the image by describing it and boom it's gone the fourth one is object object recolor which works similarly to object removal by the way you can upload your own images or select from a wide range of freeo use images from unsplash or any other platform here simply describe the object you want to recolor and watch the magic happen all thanks to AI last but not least we have a straightforward AI background removal for any image you can save these images and update them whenever you want by just adjusting your image on this spt pretty neat right implementing these complex features is all possible thanks to cloud and incredible AI tools which we'll use to build this app and the amazing part is that it is absolutely free to use besides cloudinary we'll also use nextjs tailwind and chaten typescript Clerk mongodb and stripe to build this app so make sure you have a good grasp of JavaScript and react before watching the tutorial if not check out our crash courses on YouTube back to the app there's also a profile page to view all the Transformations you've saved now here's the feature that makes this a SAS as I showcase these demos I used up all my free credits so if I try to create something new you'll see a nice looking model saying we don't have enough credits we can either buy more or just leave it if you click on it we'll go to the credits page and there we can choose how many credits we want to buy and then we'll head to our payment processor stripe after paying we'll see how many credits we have back on the profile page and now when I try to create something it works pretty cool right all these amazing AI features are bundled with a robust payment credit system remember you can use this app as a blueprint for building your own SAS applications I bet you can't wait to start developing it so let's get started to get started with building our phenomenal project we'll start from bare beginnings and that is by creating a new empty folder on our desktop I'm going to call it IM imagine if I but feel free to choose a better name then simply drag and drop it into your empty Visual Studio code window once you're in there open up your terminal and we are ready to initialize nextjs the react framework for the web used by some of the world's largest companies it enables you to create highquality web applications so simply follow my steps by running this command that is MPX create next app add latest / to initialize it in the current directory it will ask you a couple of questions and you want to just tap enter because we're going to use all of the default options which are recommended now this process takes about a minute so feel free to pause this video and I'll be right back and there we go success all of the files and folders have been initialized and set up right within our folder we're going to check them out soon but before we do that let's install and set up shaten shaten allows you to build your own component Library it provides a lot of components out of the box for dashboards cards tasks playgrounds forms and more and the best thing is they're all Bare Bones very simple but shaten uses Tailwind CSS behind the scenes so you can naturally style them to match any design you want so to setup shats n simply run MPX shat cn- UI add latest space in it this will run the shaten configuration choose which style you would like in this case it's default slate color will do just fine and we can also choose the default option of using CSS variables for colors it will take just a moment and there we go shaten has been installed as well now that the Bare Bones of our project have been installed let's run mpm randev to check it out in the browser you can hold command or control and then click this link to open it up within your browser once you're in there you should be able to see something that looks like this it's just a default nextjs page now let's work around it to provide all of the necessary fonts colors and styles that we'll need for building out our application to do that we can start with the layout file of our application which is right here we have some metadata and in this case here you can add the title of your application I'm going to go with imagine IFI of course I invite you to choose a better name and we can also add a description in this case it's going to be an AI powered image generator instead of using the basic inter font we want to use a font called IBM Plex Sans so right at the top you can import ibmor plexor s hands coming from next font Google we can then initialize its Constructor right here by running IBM Plex Sans and we're going to choose the subset of ldin but we're going to also add a couple of additional things in here such as different weights that's going to be of 400 we can also get 500 600 and 700 and you don't have to say weights just weight is fine finally we can choose variable this is going to be a variable that will trigger or turn on our font so we can say-- font D IBM DPX and then we can use it right here within the body so under the class name we can call a special utility function called CN you can see that the intellisense will allow you to automatically import it from lib utils and if we quickly check it out right here you'll notice that this utils file was created for us by shaten it simply allows you to merge a couple of class names together so with that in mind let's also rename our font to IBM Plex and we can now use it right here within our class names by saying class name is CN which we call as a function and then we can pass a couple of different class names such as font Das IBM Plex and then anti alest this is a CSS property that simply makes some fonts easier to read finally right next to that string we want to call the IBM Plex Sans font and then the dot variable on it which will apply the actual font and we can put children in a new line so it's easier to see what's happening essentially we've just modified the metadata and applied our font the next thing we want to do is set up our B base Tailwind CSS config and we can do that by going to tailwind config.txt win. config.txt and then override everything we have here you'll notice that in here we simply set some background images and the font family we declared as well as some default colors as you can see right here so we don't have to refer to all of these absolute values we can just use these nice color names once you have the tailin config set up the other thing we want to set up is the global. CSS file that is our second Styles file so you can navigate to it and similarly like you copied the table config you can also copy and override the global. CSS here we have some class names that are mostly utility classes that will will help us style our application keep in mind you will still be doing all of the logic and styling yourself these are just some helper classes that we can use within our application to make her life easier and of course what would our app be without some images that's why I've prepared a complete folder of all of the assets we'll be using throughout this project for you just go to the same readme download the zipped public folder unzip it delete the existing one that we have have here and then simply drag and drop the new one right here and say copy folder you'll notice that in here we have a lot of assets such as icons which are all svgs and also some images like banners and stacked coins which we're going to use for credits later on another thing we have in here is the favicon which we want to move over to our app and say replace this is a new favicon which has these imaginify icons right here like Sparks which means that something AI related is happening and once you do that we can check out our application in the browser it seems that not too much has changed but if you open up your tab you can see that we are using a proper title as well as the favicon for our application now you might be wondering which browser this is this is called The Arc browser it's a bit different from traditional browsers and if you haven't tried it out yet I would highly recommend to do so they're not sponsoring this video or anything but as you guys know I always want to teach you things that can improve your developer productivity and using Arc is one of them with that said we have now imported all of the necessary CSS files and assets which means that we can finally start focusing on the initial structure of our application now even though I like looking at this nextjs template let's remove it and let's turning this Local Host 3000 into something something special we'll do that by first learning about nextjs routing creating route groups and creating different layouts for each one of these route groups I want to teach you the proper file and folder structure within nextjs so let's get started by creating a couple of Route groups in the app directory nested folders are normally mapped to different URL pads for example if you have a folder named users you'll be able to go to for SL users however you can Mark a folder as a route group to prevent the folder from being included into the routes URL and this allows you to organize your route segments and project files into different logical groups without affecting the URL structure it's very useful when you want to organize routes into groups for example by authentication user management and so on or if you want to enable nested layouts for all both the routes within the same route group and I'm going to teach you both of these Concepts right here so how do we create a route group you simply have to create a folder wrapped in parentheses that is it and in practice that looks something like this you can have marketing shop and then different pages within it enough Theory let's get into practice the first route group we'll create will be for our authentication so let's create a new folder and let's call it parentheses o and right within it we'll create a new layout. TSX file within there we can run rafc which will just create a basic structure of a react component if rafc didn't work for you that must mean that you don't have the es7 plus react Redux react native Snippets extension installed so just install it and then it will work flawlessly now let's rename this layout to a capital layout like this and within here we can return an HTML 5 semantic main tag to indicate that we'll have some main content within here we can accept children right here through props and immediately Define the type of those children as children is of a type react. react node that's because layouts always have to export some children within them so our main will have a class name of O and right within it we want to render children great now since we talked about route groups let's also talk a bit about layouts so I can simply go to nextjs documentation which is great by the way and search for layout and we can do a command F and search for layout one more time right here we can see more information about what a layout is and I know that I could rush through this video and just teach you how to build this phenomenal application but I always like to take my time and explain things in detail I am not here to teach you how to build this specific app I'm here to teach you how to build any app you know that's saying give a man a fish and feeding for a day teach a man how to fish and feed him for a lifetime and that's exactly what I want to do in this case as well I want to teach teach you how to read documentation how to be an independent developer and learn on your own so that's why we're taking the time to go through the docs together and this content will always be free on YouTube but in case you want to dive a step further we also have the official JS mastery. proo platform where if you want to advance your nextjs skills you can take our ultimate nextjs course and only if you're already a junior developer and you want to get to Mid and Senior position positions you can sign up for the master class right now there's only a waiting list and that's because big things are coming but with that said let me explain what a layout is a layout will typically export the children and then allow you to put some shared UI for different routes here such as the navigation bar or the footer so for example you don't want your authentication routes to have the footer and the navbar because they're off pages in that case you will create a layout without anything but then for your homepage routes you want to have the Navar and footer within every single route so instead of Simply duplicating it within every single route you will just place it within its layout so let's check it out in practice our o layout will be completely empty whereas we'll create a new route group called root which is going to be for our home routes and then within there we can create a new layout. TSX we can start by duplicating the one we already have here and pasting it but this one will have a class name of root and it won't immediately render children rather will have some additional structure within it such as a div that will have a class name equal to root- container we will also have another div right within it which will have a class name equal to rapper and finally we can put the children right within that wrapper so let's place it here there we go we have the wrapper and we have the children finally every route group also has to render a page so let's move this basic page that was created for us by nextjs into the root layout let's remove everything within it run rafc and just call it home this will be our homepage and the reason we moved it here is because we want the homepage to have this specific layout so now that that is done I'm going to close all the files to have a cleaner working environment and collapse it I did that by holding command and then pressing W and let's check out our app within the browser there we go you should be able to see just a simple home on a wi screen now that we have that let's also create a couple more routes within the root route group so let's create a new folder called credits and within it create a new page. TSX where you can simply run rafc and rename it to credit page we'll use this later on to purchase the credits for AI image generation the second route we'll have within the root route group will be a profile route where you'll be able ble to see your profile so we can also create a page. TSX run RFC and call it profile page the next route we will create is a new folder called Transformations so let's do it like this and here we'll showcase the detail of the created image transformation so let's create a new page. TSX where we can run our rafc and we can call it transformations page now here's a key part with these Transformations each transformation will be different so it won't simply happen on Local Host 3000 SL Transformations like this that's not going to be the case rather what will happen is that each transformation will have its own ID so it's going to look something like this and to implement this we need to use a concept known as Dynamic routes within nextjs and doing Dynamic routes is pretty simple you basically have to wrap your folder's name within square brackets instead of parentheses this time for example square brackets ID and then you will get access to that specific ID per Ram through the perams prop passed into your page so how would that look like in practice we can create a new folder within Transformations called ID within square brackets and then move this page within that folder that's going to look something like this and then as we discussed you will get access to that specific ID by getting the params and then getting the ID which we can talk about later on this is the most important part right now as right now we're in the process of creating a routing structure this will be a route for the get request but we also want to have one like that for the update request so we can create a new folder within the square brackets ID which is going to be called update and within the update we want to create a new page. TSX where we can run ource and we can call it update transformation page there we go and finally there's one more which will be added just to the Transformations folder so we can create a new folder on it called add and then within ad we'll create a new dynamic route of type within square brackets which will allow us to create different types of image Transformations and within the type we can create a new page. DSX in which we can run ource and call it add transformation type page there we go better descriptive than shorter trust me because if you come back to this code you'll know exactly what it does but if you use a shorter name then you might be a bit confused but with that said we now have a complete routing structure for our application and how does that look like in practice but the question is did that change anything in the browser and the answer is yes if I zoom this in you'll see the homepage and we can now navigate to SL profile and it changes to profile page we can also navigate to Transformations and then forward /1 to3 because this is a specific transformation page we can also navigate to Transformations and then ID and then update in case you want to update that specific transformation and you can see that we have a route for that as well and all of that was done automatically for us by using the next js's file based routing you create folders and pages and instantly you have the complete routing without having to set up everything like you usually would by using a react router package if you were using only react so with that in mind you've now learned the basics as well as more advanced concepts of nextjs routing like file based routing route groups and creating different layouts for each one of these route groups and you've learned a bit about creating a proper file and folder structure great work and now that you've set up your routing it's time that we Implement authentication and for that we'll use the most comprehensive user management platform clerk it's not just a signin box it allows you to embed different uis apis and admin dashboards to authenticate and manage your users so to follow my process exactly click the clerk Link in the description of this video and then start building for free you can keep using clerk completely for free until you pass 10,000 monthly active users and at that point you can also pay them a bit because you'll be making money from your app as well so let's start building for free it will ask you to sign in or create an account and once you're in you can add a new application then you can enter your application name I'm going to use JS smore imaginify you can use JSM uncore any other name and immediately you can choose from a dozen of different signin options in this case we can go with email Google and let's also add GitHub since we're developers finally press create application and immediately from the Kickstart copy your environment variables back within our application create a new file in the root of your directory called env. looc and then paste what you copied this allows us to keep your secrets private back within clerk go to configure users and authentication and then email phone and username name here we can turn on the username so we know how to call our users once they sign into our application and don't forget to click apply changes let's go back to home and let's click continuing docs to follow the clerk documentation that allows you to add off and user management to your app within 7 minutes this will allow us to install clerk set up our keys wrap our app with a clerk provider limit access to authenticated users which is very important and and then embed the user button so let's go ahead and follow the steps first things first let's install at clerk for/ nextjs back in our repo we can open up the terminal split it and then simply paste mpm install at clerk nextjs while that is installing let's scroll through the next steps we have set invironment Keys which we have already done and then the next step is to wrap our app within the clerk provider provider and before we do that let's switch this over to dark mode so everything is in dark mode so your eyes don't burn as a switch from code over to the docs so the clerk provider component provides active session and user context to Clerk's Hooks and other components simply import it by importing clerk Provider from clerk next to the top of your file and in this case we need to do it within the layout so let's not copy anything as we already have our layouts but let's just figure out what we need to do import Clerk and then wrap it and now the question is within which of the three layouts that we already have the O layout the root layout and the base layout will we wrap our o provider that's a tough question and it's a bit misleading if you said within o You' be wrong and that's because yes even though will have some of our o Pages within here we want to wrap the entire app with a clerk provider to know which Pages we can't access and to be able to get access to the user data within all the pages no matter where we at so let's open up the base layout TSX right here and let's wrap our app with a clerk provider it's going to automatically import it if you simply press enter we can wrap it and properly close it right here at the bottom and indent this the import should have automatically happened from Clerk nextjs that step was simple let's see what's happening at step four now that clerk is installed and mounted in your application you can decide which pages are public and which should require authentication to access and to make this happen we need to create a middleware dots file so let's copy it from the docs and create it right here within the root of our directory right next Tov dolo by creating a new file called middleware dots and we can paste what we copied in this case we won't be modifying anything we'll just keep it default as it is now let's see if it works as they say it does what happens if you try accessing your app by visiting Local Host 3000 if I reload on Local Host 3000 you'll see that we are redirected to clerk authentication which is exactly what we wanted now these off pages are not within our Local Host right now which means that we have to create route Pages within our routing structure so that this window appears directly within our routing structure and we can do that by going to our app our o and creating a new folder right here called sign up like this and then within the sign up we can create a new folder which is going to be called double square bracket do sign- up and close it with a double square bracket and within it create a new page. TSX a bit of a weird structure but it works and then run rafc within it call it sign up page and within it the only thing we want to return will be sign up which is a self-closing component which we can automatically import the directly from at clerk SL nextjs now you can copy this entire file because we're going to also use it for the sign in so let's create the same structure for sign in within our op we can create a new folder called sign- in within it another folder D- do do dot sign- in close it and within it create a new page. TSX where you can paste what we copied of course you want to rename it to sign in there we go and rename sign up page to sign in page this is the only thing you have to do to create your own OD Pages within clerk now the last thing you need to do to make it work is head to env. local and add a few more variables and those will be the paths to your pages so you need to call them next public clerk signin URL is equal to forward SL sign- in duplicate it rename this to sign up URL and then replace this with up and then do two more which is going to be next public Clerk and then add the word after sign up and after sign in where do we want to navigate and in this case it's just going to be forward slash in both of these cases finally to test this out we can follow one of the last steps from Clerk's docs which is using their pre-built component of user button that allows users to manage their account information completing this full authentication Circle So within our homepage let's use this user button I'm going to navigate over to root page which is this one right here by the way to quickly switch the files I use the command P keystroke which allows you to start typing the name of the file you want to go to press enter and you're immediately there that's just another Pro tip now within here we can wrap everything in a div we can modify this home to be a P tag like this and right within it we can render the user button component coming from Clerk nextjs and we can give it a prop of after sign out URL is going to be forward slash so this is the URL that the users navigated to once they sign out and if we check out the docs now you can sign out of your application and sign in so that technically means that you are authenticated let's go ahead and test it in practice let's go ahead and test it within our Local Host 3000 we immediately get redirected to our signin page which is within our own application now the signin window is at the top left right now but don't worry about that we're going to fix it soon for now let's continue with GitHub Google or just your email I remember how hard it is to usually turn Google authentication on you have to go to the Google Cloud developer console get your ID and do all sorts of other things and right here it just works out of the box I just signed in with my Gmail account and it's also asking me for my username so right here I'm going to say JavaScript Mastery and press continue and immediately we are within our homepage you can see that now we're authenticated and we also have this icon pulled directly from Google that allows us to open up this clerk window and where you can sign out see your account details dets as well as manage your account out of the box you can manage your account and change profile information update your username email address and even see connected accounts as well as manage your password think about how long it would take you to develop all of this manually it's just not worth the effort as even if you did it manually first of all you're dealing with security here which is not a good thing to quickly get done and second of all you you wouldn't be able to have a beautiful UI such as this one so when clerk said 7 minutes they truly meant it now while we're finalizing the O Let's customize Clerk's appearance with The Branding of our application that's also something I want to teach you as it's very important to have that custom UI feel no matter the design of your application so right here on the left side go to customization and then navigate to branding here you can modify your application name and upload the logo as well as the icon you can find all of these within the assets folder that I provided to you they'll be right within the public folder of our application so let's choose the logo for the first one there we go and let's choose the icon for the second one there we go that's better now before we check out how that looks in action let's apply our brand Coler to all clerk components simply by passing the appearance prop to the clerk CL provider the cler provider is within our base layout and it can accept the appearance property which can take in an object of variables and here we can provide a color primary which is going to be hash 624 cf5 and you can save it make sure not to have any spaces here and now let's test it in our browser back on our Local Host we can now sign out and on the next sign in you can see that now there's a logo here and if you've been paying close attention the color of the button also changed and to finalize it let's Zoom it out and let's Center it as well as change the background color we can do that by navigating to our global. CSS and then right here below the layer base we can add a new class of the dot o and and we can apply a couple of Tailwind Properties or Tailwind classes such as Flex Das Center to center it Min Das h- screen so it takes the full height of the screen W full so it takes the full width and BG purple 100 this will apply a o class to our o layout which will result in this beautiful signin interface and we can also move between sign in and sign up and we can seamlessly log in into our application so in this module you've learned a lot you've learned what clerk is and how to integrate it within our application you've learned how to protect specific routes in this case all routes are protected you learn how to set up the default signin and signup clerk routes and I've even taught you something that a lot of people think is impossible which is styling your cler components with that said the only thing we have right here is an empty screen so the next thing we'll do is create the entire front-end interface for application components such as sidebar navbar and even mobile nav to get started creating our layout we can navigate to The Roots layout DS file because this is where all of the pages within the application will be here we have the main with the root and then we have all the children but on top of that we want to render two things and the first one is a sidebar component self closing like this and the second one will be a mobile navigation so let's create both of these components one by one starting with a sidebar component we can create it by going to our components and right here creating a new folder within components called shared components shared across the entire application Within in shared we can create a file called sidebar. DSX within there we can run RFC to initialize our sidebar component then we can go back to the layout uncommon the sidebar and imported from components shared sidebar finally let's start developing the sidebar by turning it into an aside element this is an HTML 5 semantic tag that usually means that something is on the side as it says right here and since it's a sidebar it makes sense we can give it a class name of sidebar and this class name is coming directly from our globals so in case you're wondering which Styles exactly will be applied once you add this class name you can go right here to search search for the sidebar and then find it right here it will apply property of hidden H screen for full height width of 72 white background some padding shadow and then more shadows and flex on large devices within the aside we want to create a second wrapper div with a class name equal to flex size- full which is going to give it a width of 100 and a height of 100% Flex Das column and a gap of four within it we can render our first link this link has to be imported from next link and it will have an atra of forward slash pointing to the homepage with a class name equal to sidebar Das logo which automatically lets us know what do we put within it and that's going to be the image which is a self-closing tag coming from next image and we can give it a source equal to slash assets slash images SL logo Das text.svg with an Al tag of logo a width of 180 and a height of 28 if we save it go back and reload you'll be able to see our wonderful sidebar on the left side now within the sidebar we'll want to mention a lot of different links pointing to different features of our app as you usually do with any kind of navigation component and I don't want to necessarily just type all of these right here such as link one link 2 3 and so forth we want to declare the links in a file called constants so let's create a new folder within the root of our directory called constants and within it create a new index.ts file in the same read me where you found other code blocks you'll also be able to find the constants file it is a 200 lines long file which is seemingly a lot but it is incredibly simple it is basically just exports of all different objects that will be using throughout our application specifically nav links which we need right now it is just an array of different links which point to different routes that we created they have a label route and the icon this makes it so much easier now to consume it within the sidebar rather than to keep all of this right here within the component within the components only focus on the jsx keep the logic and data in other files so now that we have these nav links we can use them right here below this link by creating a new nav component with a class name equal to sidebar dnav but we only want to render them in case our user is signed in and for that we can use the signed in property from clerk nextjs the code within will only show if the user is signed in we can then create a new UL which is an unordered list with a class name equal to sidebar Davore elements and within we can finally map over our nav items by saying nav links coming from constants map where we get each individual link and for each one we open up a function block and first f figure out if that is the link on which we're currently on and we can do that by saying const is active is equal to if link. route is triple equal to to the path name that means that we're currently on that link the path name of course is coming from use path name hook provided by nextjs so we can say const path name is equal to use path name coming from next navigation finally a map is missing a return so now that we know whether we're active or not we can return a new Li item for each one of these links since we're mapping over it each Li has to have a key which is going to be equal to link. Route since they're unique and we can also give it a class name of it's going to be a template string where it will always have a sidebar Das naavor element property it will have a group class name as well and then only if it is active it will have a BG DP purple DG gradient and a text white else it will simply have a color of text- g-700 within the LI for now let's simply display a link. label now we can save it and check it out in action but in action we get a look at this a meaningful error coming from xjs that says use path name only works in client components add the use client directive at the top of the file to use it that is pretty simple it means that we're using browser functionalities and whenever you do that you have to add the use client directive at the top which turn this component from a serers side rendered component by default to a client side rendered one and if we go back you can see that we have home which is currently High lighted and then we have all of the other features which we will soon Implement let's finalize our Li by not only returning the link label rather by rendering another link so we can actually Point somewhere by clicking it and this link will have a class name equal to sidebar dlink and it will have an href of link. Route within it we'll render the image of course it's not going to be a basic image it will be a nextjs image which has to have a source equal to link. ion it has to have an Al tag of logo it can also have a width of 24 a height of 24 and a class name now we have to be careful we're going to give it a bit of brightness but only if it is active so we can say if is active and and apply brightness off 200 and then below that image we'll render the link. label so now if we save it you can see how well this looks we have a home which is currently selected and we can immediately navigate to all of the different routes simply by clicking on them that works because we have already implemented the routes for all of these pages and it is so simple to do because we have also created this constant which is an array containing all of our routes with an icon route and a label so you can see how easy it was to Simply map over it now this is what we will see if we're signed in but if we're signed out we need to do something different in a similar fashion we're going to use the signed out component coming from clerk nextjs which simplifies the way of showing some code if the user is signed out no more checks at the top of the file and then ensuring if the user is logged in or not a lot of if statements we simply put it within the component here we want to render a shat CN button component and looking at the docs it's just a simple button that displays a component that looks like a button if you click it it clicks as well so the reason why I'm showing you this documentation page is to let you know that this will be the first shatan component we will install yes install even though we've set chaten up it doesn't automatically provide you all of those components out of the box which is a good thing because we won't necessarily need to use all of them what we will need to do is just install the ones that we will be using by copying this command or you can type it out with me that is MPX shaten D UI add latest add button this component will add a new button within our codebase you have full control over it you can see that a new folder called UI has been created within components and there we have a button this might seem like a lot of code for a button but don't worry you never have to go through these files created by shaten you simply use them within our application so right here we can render that button with a capital B importing it from slui button within it we can Rend the a link and that link will have an href pointing to sign in so if we're signed out we want to point to login we can also give this button the as child property which means that it will be rendered as a link which is exactly what it is and we can give it a class name equal to button BG DP purple D gradient and BG Das cover now we cannot see it yet because we're not loged out so before we check it out let's collapse it and let's create a button that allows us to log out so still within the UL right below this map we can create a final Li that will have a class name of flex Das Center cursor Das pointer Gap -2 and padding off four within it we can render the user button coming from clerk nextjs it is a self closing component which we have used before already so we know that we can give it the after sign out URL of just forward slash and we can also say show name which will show the name now we can collapse both the signed in and the signed out so it's easier to see what's happening we have the logo we have all of the list of the elements if we signed in and we have the button to log in if we're signed out so let's go ahead and test it all together back within our application we no longer have the button to check the user profile on the left in the nav bar but rather now it's right here as part of the sidebar which allows us to sign out as well now at this point in time we cannot even go back to Local Host to check it out if we're signed out currently it's needed to be signed in to check it out but later on we can allow users to just check out what's happening without needing to log in so with that in mind let's just simply sign back in now even though the current sidebar looks great there's just so many things to look at we don't know what to click which things to check out first so usually what you can see in all of the apps that have a lot of actions is that the top left part are for the actions you can take within the application and then the bottom left is usually for some kind of administration like logging out buying credits or checking out your profile so let's turn this into two separate lists we can do that by navigating into the signed in and then slicing the links before we display them by saying slice from 0 to Sixth Element and then map we do everything the same and we close the UL right here so we close it a bit before then keep in mind we still have this link right here and for that one we want to open up a new UL element because we want to separate them into two parts so that will be UL class name sidebar nav elements where we have Just An Li with a user button so if you go back that will now push it to the bottom which looks better but we're missing the last two elements from the list because we've sliced only the first six one so what we can do is we can copy all of the nav links one more time not including the UL and paste them right here within the second UL and this time we want to slice from the element six until the end and of course we want to indent it properly there we go and indent this so now if we do that and save you'll see that on top left you can see different things that we can do and on the bottom left you can do some admin stuff like checking out your profile buying credits or signing out wonderful that's it for our sidebar now let's go ahead and move into creating a mobile navigation bar because what happens if a user is on a mobile phone they won't have enough space to show the entire sidebar to demonstrate that I've put my browser to the right side and my visual studio code on the left and you can see that our sidebar automatically gets hidden after it loses a specific amount of width this is for tablet and then if we go here it is gone so let's create this mobile navigation bar we can do that by navigating over to Shared components and creating a new file called mobile na. DSX there we can run our afce save it and we can go back to our layout of the root and we can uncomment the mobile nav and import it from components shared mobile nav immediately it appears right here on top of the website the next thing we want to do is install another shatan component called sheet a sheet extends the dialogue component and displays the content that complement the main content of the screen something like this you click it and it shows up from the right side so we can install it by running this command that is MPX shaten UI latest add sheet and immediately here we can copy its usage as well so go to shaty and docs and search for sheet and there you have the usage you can copy all of the Imports that they have shared right here and use them within the mobile nav we can also copy the entire sheet don't just paste it yet first wrap the entire mobile nav into a semantic header component that has a class name of header that's going to make it look like this within the header create a link which of course has to be imported from next link with an HF of forward slash pointing to homepage and a class name equal to flex items Das Center gap of two and on medium devices padding y of two as well within there we can render a self-closing image component of course being imported from next image that will have a source equal to SL assets SL images SL logo dt. SVG with an Al tag of logo a width of about 180 and also a height of about 28 if we save it you can see this great logo up here on top finally we want to create a navigation bar right below the link so we can call it a nav that will have a class name equal to flex and a gap of two within it we can render the signed in component meaning that this only shows if we're signed in immediately we want to render the user button coming from clerk it will have the after sign out URL to forward slash as well if we save it that's immediately looking good there we go and then we want to render the sheet component that we copied from shaten this thing right here simply paste it indent it properly and save it this is how a sheet looks like you click open and it's there now let's modify the sheet trigger from just a piece of text that says open to an image by saying image self-closing one that has a source of for/ asset SL ions SL menu. SVG with an Al tag off menu a width of about 32 and a height of 32 and a class name of cursor Das pointer so we know that it's clickable there we go that's better we have the menu we click on it and it opens now right below the sheet trigger we have the sheet content to which we can give a class name equal to sheet Das content and on small devices width of 64 if we save it and open it up there we go that's looking good we can remove everything that is currently within the sheet content and start from scratch by rendering an empty react fragment and within it we can render an image this image will have a source equal to SL assets slimes SL lg-ex SVG with an Al tag of logo a width of about 152 and a height of about 23 if we save it you can see just the logo because the logo here is being hidden so we want to display it again so you know which app you're using right below the image we want to render all of the links we've had before so for that we can go over to our sidebar by using command p and then pressing sidebar and then enter and we can copy the way that we map over the naving elements so we can copy essentially the entire UL right here there we go and then we can paste it right here below the image of course indented properly and don't forget to import nav links from constants in this case we don't want to slice them we want to show all of them at once and we also need to get access to the path name and for doing that you know what you need to do const path name is equal to use path name coming from next navigation now if we save it you're going to get an error saying that use path name only works in client components so let's immediately declare this component as use client let's remove the Imports which we don't need from UI sheet and put it all in one line there we go and now if we scroll down and click on it you can see that we thought we would be able to see something but nothing is there so let's modify the class names in this case our UL right here won't have the sidebar nav elements rather it will have a class name of header nav elements also we can completely remodify the class names of this Li so let's expand it in multiple lines and let's give it a class name equal to a template string immediately putting a dynamic block saying if is active and and then give it a class name of gradient text like this and then outside of the dynamic block give it a padding of a Flex wh space no wrap Intex D dark -700 if we save it that's going to make it look quite different and in this case we don't even need to render an image because there's not a lot of space in mobile devices so just rendering a label is fine and we don't need to provide it a class name of sidebar link but now that I think about it we might want to keep the image as it did look better with it so let's keep the class name of sidebar link as well as the image right here but I believe we can remove the class name so that all of the images are the same and then the text is the only thing that changes and also let's give the entire link a cursor of pointer if we save it you can hover over them and you can also rigate to different screens that is great finally if we're logged out we want to do almost the same exact thing as we've done in the sidebar which is simply checking if the user is signed out and rendering a button to sign back in so going all the way down below the sheet content and the sheet and the signed in we want to display the signed out which we need to import from clerk nextjs we want to import the button as well and if we're signed out we can point back to sign sign in and now if we click on a specific link such as the image restore we can check that it actually modifies in the URL and even though we cannot see it right now because the header is covering the title of the page we're on we can see that the URL changes which means that our mobile navbar is working as well also notice that if you go back to home you can see double the user button that's because we have never removed it from the page which now we can safely do by removing this user button because we always have one user button either in the nav bar or if we expand we have it right here at the bottom which means that we have successfully implemented the left side bar on desktop devices and the full Mobile navbar on mobile devices and in the process of doing so we've learned a couple of things we've learned how to use a sheet component coming from shaten as as well as learned how to use the signed out and signed in components coming from clerk to only show code that should be show to either signed in or signed out users phenomenal work so far but we're only getting started now is the time that we focus on implementing our database doing a complete mongodb database setup connection connection caching learning about mongodb models creating them and more so let's Dive Right In to set up our database we'll use mongodb Atlas which is a Cloud solution for spinning up mongodb databases you can just go to mongodb atlas and click try for free then you can either sign up or sign in then you can create your organization if you don't yet have one and create a new project I'm going to call my imagine ify just add yourself as the project owner and click create project next you'll have to to create a deployment so click create and then of course we'll go with the free version and click create again then you'll be able to choose your username and password I'm going to go with something simple like JSM and 123123 and create a user oh there we go it's too weak so let's autogenerate it and copy it just so I have it for reference and click create user after that your IP address will already be added so that's good so let's click finish and close and then go to overview you can see that our cluster is being created and while it's being created we can go to network access add an IP address and click allow access from anywhere which will allow us to more easily deploy our application to production while still having access to our database then you can go to overview connect and select drivers here it will give you the necess necessary steps such as the installation of bongod DB and this connection string that you have to copy make sure not to lose the password that you created before and close now that you've done that you can navigate over to the env. loal file we can also add some comments to make it more readable such as here we can add clerk then the second one can be clerk URLs and we can also say mongod DB and here with you can create a mongodb uncore URL is equal to and then you can paste your connection string in my case it's this one and the only thing I have to do is change the password so I'm going to use the one that was automatically generated for me hopefully you remember yours as well now we need to install mongodb and and we can do that by running mpm install mongodb and now that we have everything installed let's set up our mongodb connection we can do that by going to our lib creating a new folder called database and within database we can create a new file called mongus dots inside of here we'll make the connection to our mongodb database so to get started with that we can first import Mongoose as well as Mongoose with a capital M within Square within curly braces from mongus we can also set up our mongodb URL as a variable right here coming from process. env. mongodb URL and just to make typescript happy we can create a new interface of connection just so we know how that connection will look like it's essentially an object consisted of con which is connection of a type Mong Goose or null and it will also have a promise which is of a type promise Mongoose or Nal once again but now is where the tricky part happens usually in Express applications you might have seen that we directly connect to mongodb within the application only once but in nextjs it's the other way around we have to call it on each and every server action or API request that we do but in nextjs unlike in traditional server-based applications like those using Express and mongodb you connect to the database on every request or server action because nextjs runs in a serverless environment serverless functions are stateless meaning that they start up to handle a request and shut down right after without maintaining a continuous connection to databases this approach ensures that that each request is handled independently allowing for better scalability and reliability as there's no need to manage persistent connections across many instances which works well with scalable and flexible nature of next year applications but doing that without any optimization would mean too many mongodb connections open for each and every action will perform on the server side so to optimize our process will resort to caching our connections to properly Implement caching we can create a new variable called cached which will be of a type connection and it will be equal to Global as any. we're doing this to make typescript happy to know that this Global is coming from the global scope then if we're calling this file for the first time we're going to say if not cached before then we want to set the cached to to be equal to Global as any and then we want to set the connection and the promise to null then we want to create a new function so export con connect to database which is an async function and here we'll do the connection if a cached connection already exists so if cached con is there then we'll simply return. cached connection and immediately exit out of the function this is the optimization we're making and also if there is no mongodb URL in that case we can throw a new error saying mongodb URL is not defined or simply missing mongodb URL finally if there is no cached promise of a connection we want to create a new cached promise so we can say cached do promise is equal to either the ex existing cached promise or the new connection to the mongodb database by calling the mongus do connect to which we need to pass the mongodb URL as well as some additional options such as the DB name which in this case will be imaginify as well as an option of buffer commands of false like this let's now put it in multiple lines so it's easier to understand we're doing the cash promise or connect to which we then pass the options of DB name and buffer commands false and once we have this promise it will resolve in a connection so we can say cash. con is equal to await c. promise and finally we can return that cached do connection so once again every time that we try to connect to our database first we'll check if we already have a cached connection and if we do have it we'll exit out immediately therefore optimizing our application if not we'll try to make a new connection to mongodb I know this is a bit tricky due to the serverless nature of nextjs but I hope that this explanation helped you just a bit more to recognize the difference between server full and serverless architectures they both have their advantages and disadvantages in this case serverless is more scalable now that we have our connection we are ready to create our first models so let's navigate over to lib database and create a new folder called models then we can create a new file called image. model. TS this will be a model for one of our image transformation that we'll do so let me teach you how to create our first Mongoose model first we we can say const image schema is equal to new schema coming from Mongoose and then we passing the definition of the schema for that model in this case it will have a title of a type string and requir to true because we need to know what is the title of that image it will have its own transformation which will also be a type string required to true it has to have its public ID which will be a type string required to true it has to have its secure URL which is of a type string required to true and we can also use a type URL right here makes a bit more sense now our images will have a lot of properties such as a width which will be of a type number and it won't be required we can also do height right we need a height too so we can say height is of a type number then we can have a config which is going to be of a type object we'll have the transformation URL to know when we transformed a specific image which will be of a type URL not required we can also keep track of the aspect ratio which will be of a type string in this case we can keep track of the color of the image so we can have a type string we can have a prompt used to generate that image which will be also of a type string very important we need to have the author that generated that image which will be of a type schema do types doob ID with a reference to the user of course this object ID is the type of the author right here we want to have a created ad date to keep track of the date which will be just a type date with a default of date. now and we also want to have the updated ad which will be exactly the same thing in this gives us our image schema but now we want to turn this schema into a model and we can do that by saying const image is equal to we check if it already exists under the models so models. image and this models is coming from as well so we can import it or if it doesn't exist we can create a new model with a name of image based off of the image schema and this model also has to be imported from Mongoose finally we want to export default that Constructor or the model in this case called image now since we'll be working with typescript what do you say that we immediately create the type for this image so that our front end immediately knows what properties do we have on the documents built off of the image schema we can do that by creating a new interface for the image what I like to do in this case is just leverage chat GPT to create it for me so I can say create an i image interface based off of the following image schema and then we can feed it all of this information and we'll see what it will come up with there we go here is how we can Define it and immediately it started giving us all of the properties so let's wait until it is done and let's copy this code and paste it right here above of course you can type it out with me in case you're not using chat GPT for this one we have to export this interface as well as we'll be using it within our front end and we're going to make it extend the document which is imported from this will automatically give it Fields likeor ID let's see if chat GPT got it right we need we need a title of a type string we need a transformation type of a type string as well public ID string secure URL which is also going to be a string width which is of a type number height number as well config is going to be an object of course we could make it a bit more specific if you want to we then also have the transformation URL aspect ratio color prompt author which won't be just a string rather it will be an object where the underscore D is of a type string first name is also of a type string and last name is of a type string and created at and updated at our dates great so now we have our interface as well as our schema and based off of the schema we're also getting our model or we can call it a Constructor for future documents belonging to this schema now I have a challenge for you and that is to create a new model this time for the user so create a new file called user. model. TS and start creating the same structure like we have here for the user schema make sure that it has Fields like clerk ID because we'll be modifying it with clerk also make sure that it has an email username photo first name last name plan ID because they can be a paying user and the credit balance to know how many more image Transformations can they make pause this video and take some time to try to do it yourself I'm going to provide you with a solution in 3 2 1 the complete solution to this exercise or the final user. model. TS file can be found in the readme of this video so simply compare Your solution to the final one essentially here we create a new schema with some fields and in this case I didn't even create a typescript interface for it which you can do and plus points for you if you did that but let's just try to follow this structure for now just so we don't have any issues later on once we create these models make sure that it has a clerk ID with a type string required to true and unique to true as well then we have the email username photo first name last name plan ID and the credit balance once again great job for trying it out yourself but for now just pays the final solution so we're on the same page and the last Model that we need will be called transaction model. TS I will also provide this one to you in the read me down below so simply copy it and paste it here a transaction is used as an additional reference between the user and the image creation because we have to keep track of the credits so each trans action is essentially a stripe conversion that we make to turn credits into images I hope that makes sense if it doesn't it will make much more sense once we start using it on the front end and with that you've successfully learned how to set up a mongodb database you've learned how to create a mongodb and connection and cach it as well for serverless architecture that nextjs offers and finally you've learned how to create create three separate mongodb models now in the next module we'll be able to start using these models to create their instances such as create new users create new images and make transactions we'll do all of that using NEX js's server actions and when creating a user We sync Clerk's user data to our backend using something known as a web hook so let's get started and the first thing we can do are going to be the crud functionalities for user actions meaning creating viewing updating and deleting users and we can do that by starting to create our first actions file we can do it within lib and create a new folder called actions utilizing the nextjs server actions in there we can create our first file called user. actions. TS and the most important part to make this a server actions file is to add a use server directive at the top of the file this will Mark all of the export functions from this file as server actions because they're within a file that uses the used server directive and what are server actions in the first place well they are simply a synchronous functions that are executed on the server they can be used both in server and client component onon to handle form submission and data mutations but we can also use them for get requests too the only convention is to use the use server directive at the top of the file declare whatever you want to do and then use it within your pages server actions are a simpler alternative to API routes we don't necessarily need to have many API routes we can just use Simple functions now since user Crow actions are something that we do in almost every single video to quickly get us started on it you can find the complete user. action DS file in the read me of this video copy it and paste it right here you'll notice that we have four separate functions delete user update user read user or get user by ID and create user that's it now before I explain all of these in detail you can see that we're missing some dependencies to this file such as the handle error function coming from from utils as well as some of the type properties right here navigate over to the readme and override the complete u. TS file here you'll notice that we have our handle error which is a very simple function that takes in an error and then simply consol logs it appropriately so we know where is it coming from there are also some additional things like different loaders string that modifies the Base to Base 64 and functions that form are URL query as with the user actions file you can also notice that we're missing some types so let's also get access to those types to create a types go right here to the root of your directory create a new folder called type or we can even call it types and within it create a new index. D.S in the read me down below you can also find full Declarations of types that will use across the application for example here we have create user prams indicating what we need to pass into the function that will then create a user if you want to you can review these but as with any of the files that I provide to you I will explain them in detail as soon as you add them so now our user. action DS file is no longer complaining and we can check it out and see exactly what it does there are four different server actions create read update and delete let's look into create first since we're using a serverless architecture we have to call the function that we created in the dots file a bit before the one that cashes the connection to the database and we have to call it on every single call because remember the connection to the database doesn't persist rather we have to make another request every time that we want to get something or do something in this case create a user but thankfully we're caching our connection so the next time we try to create a user it will already know what it needs to do and it won't waste any time actually connecting it so how are we connecting a user well we simply say cons new user and we await a call to the user model where we call the do create method on it that creates a new document to which we pass the user data coming from the front end and what data will it be it will have a clerk ID email username first name last name and a photo similarly we can read the user details like that by using the model and then the method. find one on it where we find one by the clerk ID where it's equal to the user ID if we don't find it we throw an error else we return the user data the update is very similar as well we find one and update the user and we return it back and delete is even simpler we find a user if there is no user to delete we throw an error else we delete a user and send it back this is it these are the credit operations for our users so now let's go into a bit more detail into the most important function here which is the one that creates a user well you might be wondering why do we have to create a database user if clerk is already doing that for us here we have our user right well yes but at the same time no we have to have access to the user in the database as well we have to know has that user created any images we have to make references from the user to these images and how do we do that well we have to sync the data between a clerk user and our newly created database user and we'll do that by using the concept known as web hooks a web Hook is a concept where when something happens an event is triggered in our case what will trigger an event well in our case clerk will trigger an event once a user signs up with a new clerk account then it will make a request with a payload containing all of that juicy clerk user data such as the username first name last name hashed password and more then it will send that data over to event processing directly to our database so that we can then create a new user within our database and sing those users up so let me show you how to connect clerk with web hooks to our mongodb database the easiest way in which we can do that will require us to immediately deploy our application right now it's weird right we don't yet have a lot of stuff to show but we need to deploy it so that we can expose our application endpoints to the Internet so that once we create a new clerk user it can ping that endpoint and therefore automatically create a new user in our database so let's get our application deployed we can do that by going over to GitHub and then entering our repository name we can leave it to public for now and click create repository make sure to not take add read me because here we have some useful information open up your terminal let's stop it from running so we have more space right here and before we deploy it we just have to make sure that our app works so in this case it looks like we have to install an additional package called Qs as in query string by running mpm install Qs and then reloading back here it seems to be working fine so now we can stop our terminal from running by pressing contrl C and then clear we can run get init giad dot git commit dasm initial commit git Branch DM main git remote at origin and then git push U origin Main in a second our new code base will be pushed directly over to GitHub at the same time we can head over to versel here you can see all of the great projects that we're building over many of these belong to the masterclass program where we help where we teach people how to develop apps themselves completely independently but if you click add new go to Project our new project should be right here at the top and you can click import you can modify the project name and this is important head over over to environment variables go back to your envs copy them all and then simply paste them right here it will automatically figure out what they are and you can click deploy this deployment process will take about a minute so let's give it some time and I'll be right back and there we go in about a minute our project got deployed so you can click right here and it will lead you to your newly deployed project now on the internet and you can see clerk automatically opens up we can see that it is under our new domain and we can now sign in as we usually would and we are in now we don't necessarily need this page right here but what we do need is the URL of our deployed application so let's copy it or save it somewhere for later because we'll need it once it save it we can open up a documentation page for syncing clerk data to your backend with web hugs you can find this page just by Googling it and just by reading the title it seems like this is exactly what we want to do a common setup for applications that involves a frontend for customers to interact with a backend that includes a database since authentication in user management happens on Clerk's side data eventually needs to reach the application's back end and the recommended way to sync that data is via web hooks so let's figure out how to make it happen first we have to go to our clerk dashboard and navigate to web hooks it is right here on the left side then click add endpoint and here you can enter the endpoint URL in our case the URL will be the URL of our deployed application but we have to point to a specific route so that will be SL API SL web hooks SL clerk make sure to type make sure to spell this EX exactly as I have it right here API web hooks clerk because the way it works is clerks web hooks will ping our application upon specific events and in our case the event will have something to do with users specifically when a user is created deleted or updated and click create there we go now we can keep track of all of the events that are happening you'll also notice that there is a signing secret right here so make sure to reveal it and copy it we then need to go back to our application and add it to our env. loal by saying clerk web hook secret and we can paste it by saying web hook uncore secret is equal to this string right here and then if we keep going through the docs the second step is to add the secret to ourv which we have done and the third thing is to understand how does the web hook event payload or in other words data look like it has a data object that holds the information for the events payload the object which is always the event and then the type of the event the fourth step is to install the SX package this is a package that verifies the web hug signature making it easy to verify the authenticity of the web hug event so we simply need to run mpm installs fix which we can do by running this command in our terminal and then we have to create the endpoint of our application we can copy it here directly from the docs but in the case of our application I will provide you with a complete web hook file in the readme down below so copy it from there go back to our code and create a new file under app new folder of API another folder within it of web hooks another folder within it of Clerk and then within clerk create a new file called route. TS this is our first API route and within here you can paste the complete file that's going to look something like this it feels like there's a lot happening over 117 lines but let me show you what is truly happening first we are importing the same user actions that we have created within lib actions user actions and it looks like it's having trouble accessing this file but if you simply remove something from it and then manually go to the user actions you'll notice that now it's not going to complain the way that this web hook file works is that it's always accepting a web hook based off of the web hook secret coming from clerk it gets all the headers the signatures to ensure that we are the real sender of the hook and then we are looking for different types of events if the event is user created then we want to create the user within the database if the event type is updated then we want to update and if the event type is deleted then we want to delete it that is more or less it and of course we'll dive deeper into each one of these three if statements to figure out exactly what's happening in there but for now let's just ensure that they can actually get cold and we can do that by following the last step of our documentation here which is to add our endpoint to middleware so let's see what they're doing here adding public routes so navigating over to our middleware dots file we can specify public routes which is an array where we can say SL API SL web hooks SL clerk this means that we're allowing public access to this API endpoint so clerk can ping it with new events so Let's test it out by publishing all of this new code to production we can do that by opening up the terminal clearing it and running git add. git commit DM Implement clerk web hooks get push this will push you directly over to GitHub which means that versell will also take notes and start building it now keep in mind we added one additional environment variable so we have to make sure that it gets added to the final version of the application as well so let's go over to settings and then environment variables and let's add the secret key going back to ourv that is this one right here web hook secret and let's paste it and click save now an important note is that whenever you add a new environment variable you have to redeploy deoy your latest commit so it actually recognizes it so let's give it a minute until our latest deployment is building and then I'll be right back there we go the latest version of our app has been built in a minute and 8 seconds and now we can close all of the files besides the Local Host 3000 so we're not going to be doing testing on the live deployed version rather Local Host which right now is unreachable so let's go back over to the code and run mpm run Dev to run our application on Local Host 3000 there we go we're back online now before we go ahead and test anything related to what we've just done let me ensure to explain it all in detail so the way this works is as soon as we create a new clerk user let me signed out for now so I can show you how that works as soon as the new user is created click clerk will ping our new endpoint that we created which is this one right here and tell it hey there's a new event and it's of a type user. created let's see what do we do then all of the information about that user is saved within event data things such as ID email addresses image URL first name last name username and more we extract all of that data and set it to a new user object now once we have a complete user object we call create user but what is this this is the server action that we have created before that takes in all of that user data and then creates a new user in our database then once we do that we simply merge the clerk ID with our own user ID and return back a message saying okay Sim similar things happen for update and delete as well if the event is user updated we simply call the update user server action and if it is delete we simply delete it so what do you say that we give it a shot let's try to continue with Google on our local host and see what happens make sure to choose a new account that you haven't used before and of course you'll need to choose a different username such as the real JSM and press continue now this works exactly as it worked before we are back here we have this wonderful looking navigation we can see our user information and it all works so what did we even do well here's the difference behind the scenes clerk this time let our application know hey a new user is created please add it to your database as well so now if we check out our mongodb Atlas database enter the cluster and go to collections you should be able to see a users collection with one object or two looks like a user has already been testing the application but hey the user that I created is right here it has all of the necessary information such as the first name and last name picked directly from clerk as well as a photo username email clerk ID and the real object ID it works exactly as we imagined so this module was interesting you learned how to create server actions you just create them in a special file that has a used server directive and every function that you export from there is called a server action from those functions we have to then every time connect to the database but don't fret we're doing it in an optimized way because we have a cached connection which we created before then we introduced the concept of web hooks so we created and exposed a new route where we gave permission to Clerk's web hook to Ping us whenever a new event gets created once it does we figure out the event type and call our server action which then of course creates the user in the database so now we're syncing the clerk user data with our database user data you learned about crud you learned about server actions you learned about web hooks this one was great next up now that we finally have a real user within our database that user can do stuff we'll create the add image form which will allow our user to add new image Transformations so let's do that right away we can start by heading over to our app Root Transformations specifically the ad type page right right here and then within here instead of rendering this add transformation type page which we cannot even see at this point because it's being covered by a navbar we want to render something different and that something will be a header component so let's quickly head over to our components and create a new shared component called header. TSX run rafc right within it and then import it and return it as the only thing within our ad transformation type page that's going to be a simple self-closing header which we can import from components shared great to this header we can pass two props the first one is a title and we can do something like transformation title and the second one is a subtitle like this which can be a transformation sub title there we go that's good let's also put it in a new line so it's easier to see what's happening and you can see our header component now is complaining because if we head to it it's not accepting any props so let's fix it let's make it accept a title as well as a subtitle and now that I'm checking this out it might make more sense to spell out subtitle with a lowercase T it looks just a bit better so make sure that it is exactly the same as how you spell it there and we can also provide the types where the title will be of a type string and the subtitle will be optional and it will also be of a type string now now that we have that information we can wrap everything within a react fragment which will look something like this and then we can return an H2 element that will have a class name equal to H2 D bold as well as text- dark- 600 and within it we can render the title and also right below the H2 if the subtitle exists and only if it does we want to render a P tag that's going to render the subtitle and we can also give it a class name of something like p-16 d and Mt of four for margin top to divide it a bit from the H2 and that is our header now why is nothing being shown on the screen well that's because we are on our homepage so if we navigate to a specific transformation like image restore you can just start seeing something barely here let's ensure we're in the right one we're on the ADD and then restore yes so this is looking good but as you can see our navbar is hiding the header so to fix it we might need to see what's happening not within the header but within the root layout here we have a root container class and a wrapper class which should provide us some space for the header to show below the knv bar so let's navigate over to our globals CSS and right here below the O Let's also add the root and the container classes we can start with a DOT root where we can apply a couple of Tailwind properties a flex Min dh- screen so it takes the full height of the screen w- full for full width Flex Das column so elements appear one below another BG white in EN large devices Flex Das row we can also create the root container so that root- container and here we can apply the margin top of 16 which will provide the necessary space SP for it to show Flex of one overflow of Auto padding y of 8 on large devices margin top of zero because there we don't have the knv bar and on large devices Max dh- screen as well as on large devices padding y off 10 if we now save this you can see that the header appears properly now we can go back to our header and we can see exactly what we're showing the transformation title and the transformation subtitle but the whole goal of this page is for it to display things dynamically so if we are on the generative fill we want to say something about generative fill same thing for object remove recolor and so on so how will we make that happen well to make it happen we can make use of the params that we have here you can notice that right now we are under the route ad and then restore and if you remember before I mentioned that whatever you put in these square brackets you will have access to within the props so specifically in this case it's the type so what we can do in this case is the structure the params and then from the params destructure the type and of course we have to ensure to specify its type which will be of a search peram props and now now that we know on which type we are for example generative fill we want to figure out what to show in the header and for that we can import transformation types coming from add SL constants this constants file is a simple object that contains the type that we have in our URL and based off of the type it provides us with a title and a subtitle as as well as the icon so it will be very easy to show what we need to show here simply by taking it out out of that object by saying con transformation and it's equal to transformation types and then we enter that specific type for example if we are under restore we can go into the transformation types restore and now we can grab the title and the subtitle so let's say right here that's going to be a transformation. tile and similarly we can call the transformation do subtitle back in the constants I'm using the capital T so let's also modify the capital T right here and now you can see that we get a nice header that says restore image refine images by removing noise and Imperfections and we can go to generative fill and that also changes the header changing the title and the subtitle this works incredibly well not to mention that it also works on our desktop devices so if we go here you can see home image restore gen to fill object remove object recaller and background remove as well now that we have the header let's focus on a component that is much more important that also has to be shown on this page and that is the form so right next to the header within our shared components we can create a new file called transformation form. TSX inside of which we can run rafc and then we can go back to the page wrap everything in a react fragment put the header on top right here and then immediately after render the transformation form coming from shared transformation form immediately if you reload you should be able to see transformation form right here so to get started with building our transformation form we'll check out the docs coming directly from shaten they say react hook form building forms with react hook form and Zod forms are tricky they are one of the most common things you'll build in a web application but also one of the most complex well-designed forms are well structured easy to use and navigate accessible have support for client and server side validation and well styled and consistent so in this guide we'll take a look at building forms with react hook form and Zod in this case we're going to use the form component as the wrapper we'll use the form field for an individual field and a library called Zod for form validation so let's scroll down a bit and check out the installation process first we have to add the form so we can copy the installation command go to the code split it and add it it is as simple as that next we can navigate over to the transformation form and we can import what they say we need to import in this case that is Z from Zod and then a form schema so to get started with this we can just import everything from the form schema we can just import everything from the first step of the docs and just paste it right here at the top first of all it's a used client component because it has to manage keyboard and key press and submit events and then we import Z from Zod and we Define our form schema and then below the import we have the form schema for now just referring to the username later on we're going to update it let's go to the Second Step where we have to import the Zod resolver and the used form right here at the top and then we can Define the form and Define a submit Handler which we can do right here at the top of our component after that we we can build out our form by first importing the input all of the things from the form and the button so right here at the top we can paste all of those Imports and you can notice that we haven't yet installed an input from shaten so we can do that by saying add input right here after we actually render out the entire form so let's copy the form and paste it right here as the return of our transformation form component and let's make sure to indent it properly there we go and that's it you have a fully accessible form so to verify that truly to be the case we can see that this looks wonderful we have a username input with some helper fields and a submit button with a complete validation right out of the box with shaten but now we want to turn this very simple form that has one single username field into something much more complex so let's get started with with the validation where we can Define what kind of fields or inputs we want to have well we can start with a title field of a type z. string then we can have the aspect ratio of our image which will be of a type z. string and then optional we can also do something like color which will also be a z. string and that optional as well as a prompt which will also be a Z string optional and finally we need a public ID which will be a z. string but not optional now we have all of our fields and we also have to define the default values so right here at the top of our component let's say const initial values is equal to and now in case we're doing the editing on a specific image not creating then we might immediately have some data from before four in that case we can check if data exists and if action is triple equal to update with a capital u then we can define an object containing some default values else we can simply set it to True default values which are coming from constants that's going to look something like this where we have all of these empty Fields Title aspect ratio caller prompt and public ID which we can copy for now and we can use them as the default values in case if we already have some values but in this case we will already have access to these pieces of data so let's hold the Windows key or the command or the option key to select multiple cursors and then you can keep holding it to move through these words while you're still holding it hold a shift key to select it command or control C to copy it remove the empty string like this and then say data question mark Dot and then paste it this way we immediately can fill in all the data or if that keyboard shortcut combination seemed like magic to you in that case you can simply type out what I have right here so this will only populate the data of the form in case we're updating the already existing image but now I'm sure you can notice the error we have on the right side and that is that the data and action doesn't exist so what do you say that we get it through props we can say action and data which by default can be set to null is of a type transformation form props there we go and now it's no longer complaining now when we're defining the default values of our form we can simply set the default values to be equal to initial values and later on once we call this form component you'll see in which cases we'll pass the data in of course if it creat creating the image for the first time it's going to be empty therefore the values will be empty but if we're editing then it will pull the values from the data and finally for now in the submit Handler let's simply consol loock the values we're getting back now going back to where we're calling this transformation form let's give it some props let's give it an action which in this case will be equal to add like this because we're for the first time adding or creating an image not editing it because we are in which page the ad page let's also give it a user ID so we know which user is creating this specific image so we can say user ID is equal to and now we have to figure out where this user is coming from and with clerk it's quite easy to get access to the currently logged in user we can do that right at the top by saying const user ID destructuring it from o which you import from Clerk nextjs and call as a function and instead of passing it directly right here which would maybe make sense user ID match with the user ID in this case we need to pass a real user ID the underscore ID coming from the user in the database not the clerk ID so what we need to do first is say const user is equal to await get user by ID which is coming from user actions and then we pass the user ID like this and of course we have to turn the entire function into an async function since this is an await call and this user ID sometimes might be of a type null and now that we have the user we can pass the user doore ID right here which is the real ID of the user in the database we can also pass the type of the transformation we're doing in this case that's the transformation. type as transformation type key which is just the type and we can pass the credit balance which is equal to user. credit balance now we're passing everything we need to to the transformation form you can see that we no longer have an error but the question is why is typescript complaining right here that the user ID could possibly be null well the only thing we have to do to resolve it is say something like if there is no user ID in that case just do a redirect coming from next navigation to forward slash sign in like this there we go now everybody's happy and we're now calling our transformation form from the ad page so finally we can close that page we can close the header and we can focus only on implement lenting our form currently we have one form field right here but let's delete it because we'll create our own we can leave the form empty for now to save us some time and copying from shatan Doc I'm going to provide you with a complete custom field component which you can get from the read me down below copy it and then paste it over to components shared and create a new file called custom field. ttsx and paste right here it's just a 43 line long file where we're using chatsi and form field but then we're also allowing for some modifications by passing some special props into it such as allowing us to dynamically change its name class names labels and more you can notice that it's complaining that it cannot get the form schema from the transformation form so what we have to do is go back to the form and from here we can also just say export const form schema so now we can use it both within the transformation form as well as within the custom input great now within the transformation form we can use that custom input we created so let's use it for the first time ever by creating a custom field or rendering it as a self-closing component that has to have a control equal to form. control so we can actually control that field it can have a name equal to title a form label equal to image title a class name equal to w-o as well as a render property so we can say what it will render and here we can destructure the field from the values we're getting and we can automatically return an input property self-closing component to which we can spread all of the props coming from the field and we can also pass it a class name off input-field so now if we save it you can see a wonderful looking theme matching image input field next we want to create a similar custom field for choosing the aspect ratio and that won't be an input rather it will be a select this is how it looks like you click it and then you get a couple couple of options to choose from so we first need to add select to our code by pasting this command MPX chat CN UI latest add select and then we can figure out its usage we can from the doc page we can copy all of the Imports right here to the top and we can copy the actual use so let's go down and let's paste it right below our custom field if and only if the type is triple equal to fill then we want to display this custom input or custom field like this that will have a render property where we get the field it will return what we copied from shaten which is this entire select field so now if we save it it will look something like this and it looks like it cannot find this type the type in this case should come from props as we're passing it into this component so in the props we can also declare the user ID which we're passing the type and the credit balance we can also get access to the current transformation type we're doing by saying const transformation type is equal to transformation types coming from constants where we tap into that specific type that looks something like this it can be restore remove but in this case we're looking for fill also we need two new States soon enough we'll implement the image upload so we can say use State use State snippet called image set image and at the start it will be set to data this will contain all of the information about our image of course it starts with a lowercase letter and don't forget to import use state from react alongside image we also want to keep track of the new transformation so what are we doing with that image and we can create a new use State snippet called new transformation and at the start it's going to be equal to null and just to satisfy tab script we can also specify that it will be of a type Transformations like this or null but instead of using the double real logical or we are going to use the single or which is for typescript there we go so now that that is out of the way let's also just create a simple function to handle our select called const on select field Handler pretty descriptive right it's going to take in the value of a type string as well as the onchange field which will be of a type a function that accepts a value of string and then returns void meaning nothing and then we can open up the function block here and fill it in later for now we just need it so we can use it within the select field below so now with this custom field we cannot yet see it because we're not on the fill page but now if we navigate over to generative fill then we should be able to see the aspect ratio as well which we cannot because we're not passing all of the necessary fields to the custom field so let's pass it a control equal to form. control let's pass it a name equal to aspect ratio let's give it a form label equal to aspect ratio and let's give it a class name equal to w- fo if we save it we can see a nice looking picker that's not choosing the aspect ratio rather the light and dark theme that's because we are yet to modif y this default select coming from shaten docs and we can do that by passing our select Handler to the select so on value change will be equal to we get in the value and then we call the on select field Handler to which we pass the value and the field on change then we have the select trigger which in this case will have a class name equal to select Das field and it will render the select value with a placeholder of Select size now within the select content we want to remove all of the selected items and render a dynamic block of code getting the object. keys of the aspect ratio options being dynamically imported from constants let's check it out what this aspect ratio options is as you can see it is an object that has different properties 1 by one which is a square typical 9 by 16 and 3x4 and each one has widths Heights labels and more so we first want to extract the keys which are the names of these options and then say map where we get each individual key like this and then for each key we autom automatically return a select item that select item has a key equal to key a value equal to key and a class name equal to select Dash item finally within it we can show Dynamic block aspect ratio options and then we get into a specific option key as aspect ratio key like this and of course let's spell it properly aspect ratio key and then we say do label to only pick the label in this case it looks like typescript is complaining a bit because it cannot find name aspect ratio key and that's because if you copy that name and paste it in the search you'll see that we are referring to that aspect ratio key right here within utils but we're never exporting it so right here we can say export aspect ratio key and then here we can import it from lib utils and now we're good so let's see what we just created we have the image title and then we have the aspect ratio which is a nice select field where we can see all of these different types of ratios we can select such as Square portrait and phone portrait the reason why we have the title as well is we have it here under label and we're rendering this label this is not the first time that we have some pieces of data or the text within constants and then we simply use it within our code it is so much easier like this imagine if here we had to type standard portrait phone portrait and then render different select items no we simply map over them coming from constants now let's move on to some other custom fields we have this one that only shows if we are on type fill we have the title which always shows but now we're going to have two more which only show on some of these pages right here so let's start creating them by saying if in parentheses type is triple equal to remove or type triple equal to recolor only then render a div that will have a class name equal to prompt Das field so only if we're doing a special action of removing objects from images then we want to know what we want to remove and recolor to tell it to which color we want to change it to so in those cases we want to render a prompt field that's going to be a custom field self-closing component with a control equal to form. control a name equal to prompt a form label equal to if if type is triple equal to remove then it will say object to remove else it will say object to recolor that is our form label and right here tab script gave us an interesting warning saying this comparison seems to be unintentional because the types recolor and remove have no overlap but who said that the type will only be recolor uh let's see what type is it referring to it's referring to this type right here coming from our transformation form and let's see what that type can be well it can be any of the transformation type Keys it can be restore fill remove recolor so why does our typescript think that it is only going to be recolor that's interesting we can leave it for now and then see why is it saying that later on for now let's move on to the class name which will be W-4 and let's render the actual field as before we destructure the field data and then we immediately return in this case an input but of course it's a shaten input like this so if we save it we cannot see anything here but if we move over to object remove we cannot see it there either but on recolor we see it right here object to recolor that's good oh I think I know what it is I think typescript just saved our ass that's because if I go to object remove you can indeed see that there's no field and TP script is saying hey but this will never be removed it's always going to be recolor why because I said if type is recaller then show this what I meant to say is if type is remove or type is recolor so I need to merge these two ifs to together so now it shows both an object to remove and object to recolor great now to this input we can give a value of field. value we can give it a class name equal to input-field we can also give it an on change property equal to where we get the event and then we call on input change Handler to which we want to pass first of all the string of the field we're changing such as prompt then we want to pass it the E Target value so it knows the data that we're trying to type the type are we trying to remove an element from an image or are we changing the color and then the field.on change itself so we can create this on change input Handler right here at the top const on input change Handler is equal to where we get the field name of a type string with we get the value of a type string a type of a type string and an unchange field of a type function that takes in the value of a type string and returns void and finally we open up that function block so now we have the on input change Handler and we have our object to remove which is just a regular input and it seems like if I reload the page it doesn't allow me to type into it but in the image title I can that's because we'll manually modify the way to update this input within this function before we do that let's add another input field this one specifically will only show if we're trying to recolor something so let's go all the way down below this second closing statement and let's say if type is triple equal to recolor then render a custom field that has a control equal to form. control and let's switch it over to recolor so we can actually see it there we go we cannot see it yet because we have to pass the name equal to color the form label equal to replacement color the class name equal to w-o and then finally what we want to render which we will get access to the field and we will return the self-closing input tag just like so so now if we save it we have the replacement color right here this input will have almost exactly the same Fields as the above one does so we can copy them paste them here and just change the prompt right here to color and instead of type we can say recolor right here as well finally let's not forget a submit button right here above our closing form so we can say button and it's going to say submit and it will have a type equal to submit there we go that's more like it but let's make that button suit our design a bit better by expanding it right here so we can pass it a couple more props let's also give it a class name equal to submit Das button and capitalize there we go that's much more like it and let's make it disabled in case we're currently submitting and to be able to know if we're currently submitting we have to add a new used State field at the top so let's add a new Ed State snippet is submitting set is submitting at the start set to false let's also add a couple of other Ed states which we will definitely need such as use state of is transforming so are we currently doing something with the image at the start set to false as well and let's also create a new use state of transformation config set transformation config at the start equal to config and that config will be coming through props and by default it can be set to null now that we have those fields we can set the disabled state if we currently are submitting so our users don't press it multiple times there we go that's great and let's also add a button to apply a transformation so this is a button to submit the transformation but above it we need to have one to apply the desired transformation so we can create a div to wrap those two buttons in with a class name of flex flex-all and a gap of four within here we can put this button right above to place it within this div and we can also duplicate it so the first one will not be a type submit rather just a type button with a class name of submit button and capitalize disabled if we are is transforming or if new transformation is triple equal to null and onclick we of course want to call the on trans form Handler which will be the actual function that handles the logic of doing something to the image so we can say const on transform Handler make it look just like this for now and that button can say something like if is transforming then it can say transforming dot dot else it can say apply transformation and the second button is not going to say submit if we are submitting so if is submitting it will say submitting else it will say save image great so now let's save it to see how does it look like and would you look at that we now have a real form where we can apply different Transformations enter the images recolor them do replacement color and that's only for the recolor but if you go to image restore it's going to look completely different as we don't need all of these additional fields for background remove we will have the image title and for object remove we also have the object to remove so it's fully Dynamic and you have the action Fields now let's also apply a bit of a margin here to divide the form from the header we can do that within the add page so that will be page. TSX off ad right here where we have the header so we can wrap the transformation form within a new section like this put this a bit up indent it and give it a class name equal to margin top of 10 there we go that's more like it and finally the last thing that is remaining is we have to handle these three or four functions we have what happens on select what happens on input change what happens on transform Handler and finally what happens on submit so let's go ahead and handle these three first so we know what happens on input clicks and then we will handle on submit last because it allows us to take all of those changes from those inputs and turn it into something great starting with the on select field Handler we can first move into where we have access to it I believe it's within let's let me think about it maybe Phil yes because we have to figure out which aspect ratio of the image we want so let's implement the functionality for the on select field changer first we need to get the image size by saying const image size is equal to aspect ratio options and then we get the specific value that we have selected which is going to look something like this and we can use typescript as aspect ratio key there we go so there we get the size then we want to set the image by calling set image where we get previous state so prev State and we want to immediately return something for immediate return you need to wrap it in parentheses and then put an object within it where we spread the previous state non-intentionally we specify the aspect ratio which will be equal to image size do aspect ratio we specify the width which will be equal to image size. width and height which is image size. height and for now we can set the prev state as any finally once we have the image we can set new transformation to be equal to transformation type do config because now we know the configuration of the transformation we're doing and then finally we can return the onchange field with the modified value so this is how we're handling those inputs within our form so now we can actually select it and it will be selected and later on we'll be able to apply a transformation now let's focus on the input Handler so we can select a new input which is within object remove there we go and let's modify it by first using a function known as de bounds so right here we can say de bounds which can be imported from lib utils and to it we'll pass an empty callback function and as the second parameter a debounce value of 1,000 milliseconds or 1 second if you don't know what a debounced input is it looks something like this where it waits a specific amount of time and then submits your entry whereas with the regular input every single keystroke will be registered and imagine if you sent all of those requests to your backend or to the cloudinary API to actually make modifications it would put stress on your server and it would just cause too many unnecessary requests because you weren't even finished with the typing so that's why we want to Implement a 1second debounce so right within it we're going to also call the set new transformation to which we're going to get the previous state of a type any and then we will immediately return an object where we spread the previous state and modify the type of the transformation to daa do prev state question mark Dot and then type like this so what we're doing is spreading out the entire State and then tapping into a specific property a specific type of change like recaller or in this case remove spreading all of the properties that it has and then if field name is triple equal to prompt then we return prompt else we return two like this and we can modify that to Value because if it's a prompt that's the prompt that you specify and and I believe the other alternative is the recoloring option finally we want to return on change field with the value two down one more to go this is the transform image Handler this one will be asynchronous and it will modify a new react field this time we won't use a State field as we usually do this time we'll use a use transition hook coming from react by saying use transition coming from react and it looks a lot like a typical state where you say const is pending and then start transition like this but it's not rather what the used transition react hook does is it lets you update the state without blocking the UI so moving down we can start with set is transforming to true then we can set transformation config to now bear with me we want to call a function called Deep merge objects coming from lib utils and we want to merge the new transformation with the transformation config and this deep merge objects function is a function generated by chat GPT where it essentially merges all of the keys of both of these objects to Ure sure that all of them end up in a newly created object which we then set to transformation config finally we can set new transformation to null as we're not doing it anymore and we can start a transition so start transition which takes in an asynchronous callback function that looks like this and in there we await the update of the credits so later on we'll be able to update the credits as soon as we start a transformation of course we want to charge credits to a specific user ID and we want to take a specific credit fee but for now we don't have access to these properties so we can simply comment it out and we'll uncomment it later on so I will add a to-do right here so we know that we need to return back to it so I'm going to say return to update credits but with that we can collapse this on transform Handler and with that our entire form is now fully functional we have different fields that show on different occasions or rather should I say on different pages and we are properly handling all the inputs selects but what we're not handling right now is the onsubmit and this is where the magic happens so now that we're collecting different things we need to transform the image such as the title objects to remove um all the other informations Fields aspect ratios and so on we need Cloud in's real superpowers for doing all of these Transformations such as restoring our images doing generative fill removing objects recoloring them removing backgrounds and more because even though our code is great we need to leverage Cloud dinner's AI feature to make our app come to life so let's summarize what we have done up to this point point you learned how Zod Works how you can specify different validations on your form you learned a bit about how react hook form works by passing specific controls and rendering different fields on our custom field and at the end of the day you learn how to create a completely functional form using shaten Zod and react hook form so now that the structure is set up we can start learning about cloudinary handle the image upload which will be the single most important part of our form because right now what can we do just select the image title change the aspect ratio we need to upload our images and then make transformations to them so let's do that next learn about cloud in learn how we can upload images with it and how to do transformations to get started with using cloud in Click the special link in the description that will allow you to replicate everything you're seeing in the video once you're there click get started and sign up for an account and immediately after the sign up we are on our getting started guide if this is not the screen you're seeing but something else try signing up with a different account I noticed that with my previous account I was able to see something completely different after signing up so try a different one and you should be able to see this so let's get started with setting up our environment here they say to install just cloudin but we'll install the next GS version of cloudin so we can open up our terminal and run mpm install next- cloudinary and press enter the second step is to get all of our keys and we can get our keys by clicking view credentials and we can copy one by one so let's start with the API key and we're going to put it within our EnV local so we can create a new section called cloudinary and we can put a cloud underscore API uncore key is equal to this key right here next we have the API secret so we can say cloudinary uncore aior secret and finally we also need the cloud name so right at the top this can be public so we can say nextore uncore cloud inore cloud underscore name and we can paste it right here and close it now we almost have everything we need so the first thing we can do is head over to settings right here on bottom right let me Zoom it in so you can better see it and once you're here you want to navigate over to upload then scroll a bit down and click enable unsigned uploading if this didn't work for you that could mean that you previously use this account and hit its limits so if that is the case simply go ahead and sign out and then sign up with a new account now that we have this go to add upload preset and in this case start with JSM uncore and then the name of your app I'm going to do imagine if I and change the signing mode to unsigned and add a folder of the same name as your app in this case imagine if I then then head over to Media analysis and AI turn on Google autot tagging and set it to somewhere around 0.5 and press save you'll see a new upload preset appear right here then you want to go to explore and click on add-ons and you want to turn on two features first is the cloudinary AI background removal you can click free and you'll be subscribed to a free free plan there we go it should be blue once it's done and we can also select a second one which is Google auto tagging and you can also go for the free plan and click agree there we go so now if you reload and go to add-ons you'll see that you have two of these Pro features enabled with a completely free plan we'll use the cloudinary AI background removal for removing the background from the image of course and then we'll use Google autoab tagging for the descriptive search feature that's quite exciting so with that in mind you've just learned how to set cloudinary app and now we'll use two of their very important components the cloudin upload widget which will allow you to upload images as the name says and then the cloud Nary image which will allow you to optimize the way that that image is viewed so to get started with that let's create a new file under our components shared and let's call it media uploader do TSX within here we'll use a shaten toast a toast is a succinct message displayed temporarily you click it and you get a notification so let's quickly add it to our code by running MPX shat CN UI latest at toast and then to use it you can run rafc in the media uploader import the used toast coming from components UI use toast and we'll get a function that allows us to call that toast on a button click we'll use this directly within the cloud enner upload widget so let's talk a bit about that widget and let's talk about using cloud iny with nextjs at the start of this segment we installed a package called Next cloudin it is a community-built solution for using cloud eny in a nextjs project it includes tools like clld image compos compon social cards and most importantly an upload widget so we have to install it we have to add our keys which we have done and then we can immediately use it within our project let's try to find the uploader because that's what we want to do right now and if we do a command f for clld upload we can click right here to navigate to it and here we have information directly from nextcloud iny that tells us how to get started with a CLD upload widget so so let's see its usage use the following to generate an unsigned upload widget we can expand it and you know what there's not a lot of code so let's go ahead and write it by hand going back to our app we can use this media uploader within the transformation form so that's going to be right here specifically we want to put it above our submit button so here we have our buttons within this div and right above it we want to put a div that will have a class name equal to media- uploader dfield within here we can render believe it or not yet another custom field yes we will mask Cloud en's media uploader widget within a custom field so that we immediately get its data within our form so let's give it a control equal to form. control let's also give it a a name equal to public ID of the image will upload let's give it a class name equal to flex size- full and flex Das call so elements appear one below another and finally in most importantly let's run the render where we get that field and we immediately return something and that something is the media uploader component which we just created which we have to import from that slash to that media uploader we can pass a couple of props we can pass the on value change equal to field that on change we can pass the set image like this is equal to set image coming from the state we can pass the public ID equal to field. Value we can pass the image equal to image and we can pass the type equal to type now if we save it this should appear on on all transformation pages and there we go you can see the media uploader here which means that now we can navigate over to the media uploader and start working on it so let's immediately turn this div into a clld upload widget it won't be a self-closing component and it will only have one G this will be coming from next Cloud iny so we can say import clld upload widget coming from next- cloudinary then we need to pass a couple of props to it we'll pass the upload preset which we created and here the name has to be exactly the same as what you Ed within your Cloud dashboard so I'm going to say JSM imagin IFI if you don't remember it go to settings upload presets and see how you named yours then we can provide some extra options such as multiple is false so we only allow for one image upload and then resource type in this case will be just a string of image now we of course have to handle success and failure so let's say on success we're going to call the on upload success Handler which is a function which we can declare right here above const on upload success Handler it will accept the result equal to any and we can just open up a block of code similarly we can create the on error that's going to be on error and it will be on upload error Handler and we can just duplicate this function we don't have to pass anything in it in this case and just say on upload error Handler later on on success we'll do some additional things to to it of course but for now we can handle the error by showing up a toast element so something like toast where a title of the toast is something went wrong while uploading like that we can also give it a description of something like please try again duration of about 5,000 milliseconds and a class name equal to error Das toast great we can also duplicate this toast and put it over within the success Handler where it will say something like image uploaded successfully in the description we're going to say one credit was deducted from your account duration 5,000 and success toast great so now that we have the error and success we can move over to what will we show within this upload widget and what we can do with the upload widget is open a dynamic block of code and immediately have a function inside of which we can destructure the open State and then we can automatically return something so within here we can return a div that div will have a class name equal to flex flex-core and a gap of four immediately within we can create an H3 that will have a class name equal to H3 Das bold text- dark- 600 and we can give it a title of original so this is the original image that we're uploading and we can display that image if we have a public ID and this public ID will be coming from props as we're passing it into this component so right here we can accept all of the props that we need such as on value change set image image public ID and the type and we can say that all of that is of a type media uploader props and we can Define that type right above by saying type media uploader props is equal to an object where we can set the on value change to be equal to a function that accepts a value of a type string and returns void meaning nothing we then have set image which is a react. dispatch that's how you specify a type for react set States and the D is capitalize then we have a public ID which is of a type string we have the image of a type any and we have a type of a type string now right here within the image we can check if we have access to a public ID and if we do we can render an empty react fragment and if we don't we can render a div at the start we won't have anything right so here we can say here is the image because we have a public ID and here we can say here is no image and you can see that we get here is no image so let's get started with the case where we don't have a public ID yet because this is where we'll actually upload the image so we can give a class name to this div equal to media- uploader unor CTA as a call to action to upload the image and on click we can call the open function coming from cloudinary but in this case it will have to be a callback function that then calls the open looking like this right within that div we'll create another div which will look like this and it will have a class name equal to media- uploader ca- image and within here we can show kind of a placeholder for the image by rendering a nextjs image tag importing it from next image that will have a source equal to slash assets SL icons SL add. SVG we can give it an ALT tag of add image a width of about 24 and a height of about 24 as well and right below the image we can do a P tag that has a class name equal to p-14 DM medium that will say click here to upload image and save and there we go now we have a little plus sign where we can upload the image and this doesn't look the best so we might want to put this P tag below this div containing the image and now it's much better so now we can clearly see that we can upload an image and if you expand this let me show you what you get with Cloud en's upload image widget out of the box you click here and immediately you get this widget where you can upload your own files search from different web app addresses turn on the camera Google Drive Dropbox shatter stalk gety images and so much more let me try unsplash to get some free images and let's take this one right here a mountain covered in snow and click next we can select the format and the size you can see that you immediately get different options I'm going to choose medium and I'm going to click upload and as soon as you click upload it will upload it and bring you back to to your homepage now we didn't yet get either an error or a success message so let's keep working on our code back here we have to implement the part that happens after we upload the image so in this case let's wrap everything in a div that will have a class name equal to cursor Das pointer overflow Das hidden rounded d10 pixels of course in square brackets because we to make sure that Tailwind reads it as a pixel value now right within there we'll use a special clld image coming from next cloudin this image allows you to pass a width which can be equal to now we can dynamically calculate a width by using the get image size coming from Leb utils and passing it a type the image and finally the width property the way this works is this is a special utility function that takes our aspect ratio and then Returns the dimensions based off of that aspect ratio we can do the same thing for the height by saying height is get image size that's going to look something like this we can also pass it a source equal to public ID the AL tag equal to image we can pass it sizes equal to in parentheses Max width of 7 767 PX and then close parentheses 100 VW for width and then comma 50 VW we can also give it a placeholder equal to data URL as placeholder value like this and let's spell it properly placeholder and this data URL will also be coming from utils that basically gets the URL of our shimmering effect which means that it will slowly load up until the real image gets there and we can give it a class name equal to media- uploader uncore clld image like this now it doesn't seem that our image was recognized though so let's finalize our on success upload right here what will happen once we successfully upload the image well we can set the image to the state so we can say set image where we get a previous state that's of a type any and then we immediately return like this a new object where we spread the previous state we update the public ID with result question mark. info question mark. ID we can update the width to be result info width the height result result info height and then a secure URL by saying result info secure URL and finally we want to call a very important function called on value change which will take into account that the form changed and then it will update the public ID within our form that will let our application know that the public ID indeed has changed and then we can display this newly uploaded image let's save it and give it a shot I'll try to re-upload the image by selecting the size medium and clicking upload and it doesn't look like it got it one thing that we might have missed is adding a use client directive at the top of this file now even though we're not manually using any of the hooks ourselves we are using used toast which is a hook and we're also using the media uploader which of course has to do something with a browser and the user actually uploading the image so this definitely needs to be a use client component it is possible that nextjs missed reminding us about the we needed but thankfully I remembered and let's see what happens if we reload and try to re-upload that image I'm going to do the same one mountain covered in snow with a pink sky and click upload there we go that's a nice Shimmer and immediately we can see the image right here so this is the media uploader component that we were working on and if you try to collapse it a bit you can see that it still looks great even on mobile devices and one thing that I noticed that we're missing is I didn't see the toast letting us know that the image has been uploaded successfully and that's because for the toast to work yes we definitely need to call it like this but we also need to put it right here within our layout. TSX we need to put this toaster component right below the main so going over to our root layout we can put the toaster right here at the bottom so toaster importing from components UI toaster now if we save it go back and try to re-upload you can see that this cloudin interface looks good on mobile devices as well let's go with this one this time next medium and upload that's looking good original and there we go one image uploaded successfully one credit deducted this is looking great so let's do a quick recap on the media uploader component in here we're using two of the next Cloud enies very important components clld upload image which is the entire featureful widget allowing you to upload images from different sources and then the CL image that allows you to render the most optimized version of the image depending on exact widths and Heights you want to provide to it and you can also do this nice placeholder you saw how we're doing a shimmer effect while the image is loading so when you pair those two you get the uploader for the first time when you don't hit upload the image and then you get the image showcase once the image is there now what's happening is that on success we are modifying all of this state data which will be able to utilize later on within our form but for now it looks like the right side of the screen is a bit empty here right we have the original but the whole point of our app is that we can do crazy things with the images we upload such as removing different objects maybe like this cabin right here to make it full of nature or doing Rec coloring restoring the image filling out the image to get more of its surroundings so let's do that right away let's add the transformed image component on the right side so that we can finally apply different transformations to the original image to do that we can create a new component within our components shared called transformed image. DSX we can run RFC within there and we immediately use that image within our form so we can go to the transformation form and right below where we call this custom field still within the same div we can render the transformed image we'll pass a couple of props to it such as the image itself we can then pass the type to it as well let's pass a title of the image in this case we can get it from form. get value vales and then we can call the dot title on it we can pass the state if it's currently being transformed so we can say if is transforming we can set is transforming as well so we know when to modify that state and we can pass the transformation config so we know exactly what we have done to it make sure to spell it properly transformation config now we can move over to that transformed image and accept all of the these nice problems that we just passed into it so that will be image type title transformation config is transforming and set is transforming like this later on we'll also have a special property of has download which we can default to false and all of that can be of a type transformed image props which we have already defined within the index DTS and we can start creating the layout of our transformed image I'm going to pull the code just a bit here and I do believe that we should be able to see the outcome of this transformed image so let's start with wrapping everything in a div that has a class name equal to flex flex-all and gap of four within it we can create another div that will have a class name equal to flex between and right within that div we can create an H3 that will have a class name equal to H3 D bold and text- dark- 600 there we can say transformed I hope this is starting to make more sense now we have the original and we have the transformation sometimes we might also have a download button so we can say if has as download and end in that case we want to render a button like this that will have a class name equal to download Das BTN and on click it will call a callback function with an event called download Handler to which we can pass that event I think we can also shorten that and call it like this button class name on click and then download Handler like so which of course we have to Define right here at the top const download Handler for now we can leave it empty within that button we can render an image that will come from next image it will have a source equal to/ assets SL ions SL down.svg it will have an Al tag of download a width of 24 a height of 24 four and a class name equal to padding bottom of 6 pixels within square brackets we cannot see it now because it's set to false but if you were to enable it by setting it to true you can see a little download button appear here for now I'm going to bring it back to false now finally let's show the transformed image so we can do that right here below this div containing the button and we can check if the image exists by saying image question mark dopu ID and transformation config exists as well if that is the case then return a div and properly close it that has a class name equal to relative and if we don't yet have the image then we can simply render a div which we can also close it will have a class last name equal to transformed Das placeholder and it can simply say transformed image if we save it there we go we simply have a div that says transformed image but now what happens once we actually upload our image if we go back to our unsplash and select an image you'll notice that it will get uploaded but nothing will yet happen to the right side Although our CSS works well that it changes the size immediately the transformed image is not doing anything yet and that's because we haven't yet clicked apply transformation for that we'll need to do some extra logic calling Cloud's apis to do actual changes on the image and that can be as I said restoring the image removing some objects filling out the image so we get more of the environment and so on we'll do that soon enough but just to Ure that we immediately see something once the transformation is done let's implement the cloud image to show that transformation to get started we can use the same one from the media uploader which is this clld image right here so let's go ahead and copy it and then paste it directly within this div of course we have to import it from nextcloud iny and it would be nice if we just indent it properly as well so I'm going to make a selection and in indented right here there we go let's fix this thing that I messed up data URL as placeholder let's immediately import everything we need such as the get image size from lib utils data URL from utils and placeholder URL from utils as well now instead of passing the public ID as the source we'll pass something else we'll pass image question mark. public ID because now that public ID ID is already stored within the image State the AL tag will be image. tile the sizes can remain as they are same thing for the placeholder for the class name we can give it a transformed Das image class and now this is important we can give it the onload property where we can call a specific callback function as well as on error Handler so what will happen if we experience an error while trying to load it on load we want to turn on the set is transforming state so here we can check if we have access to the set is transforming function and if that is the case then we can call set is transforming to false because the image has been loaded on error we can call a debounce function from lib utils we can call it as a callback function set it to something like 8 seconds which is is 8,000 milliseconds and then we can call the same thing right here set is transforming to false because if nothing happens after 8 seconds then we can surely say that it has failed and maybe the most important thing of this entire clld image which we can do at the end right here is to spread the entire transformation config because this config will contain all of the image Transformations that we want to apply to that IM image and finally right below that image while we are transforming we want to show some loaders so here we can render a div we can close that div as well and that div will have a class name equal to transforming Das loader and within it we can render nextjs image which of course has to be imported first from next image and then we can give it a source equal to SL assets SL ions SLS spinner. SVG we can give it a width of about 50 a height of about 50 and an Al tag of transforming now if we click save absolutely nothing will happen that's because we are still under this placeholder of transformed image as we haven't yet applied the transformation so with that in mind what do you say that now we go back to our transformation form and fix this issue first it should be a lowercase s see how easy it is to notice these typos once you have typescript enabled and scroll all the way to the top where we want to actually deduct the credits from the user and we want to do something on submit which is to apply the transformation so let's get started with updating these credits first we can do that by navigating over to user. actions. TS where right now we have create get update and delete and the last user action will be to use credits and it will follow the same form such as export a sync function update credits that accepts a user ID of a typ string as well as a credit Fe which is equal to a number and we open up a try and catch Block in the catch we call the handle error function and pass the error to it while in the try we immediately try to await connect to database once we connect we want to update the user credits by saying const updated user credits is equal to a wait user. find one and update we want to find the user by the underscore ID property which is equal to user ID then we want to increment by using the Inc command the credits and we want to apply it a credit fee which will most likely be something like minus one and finally we want to give it options of new to true so we can recreate that user if there's an error like if there is no updated user credits we say error credits update failed and then we simply return Json pars Json stringify updated user credits so we know exactly what has happened and that is our server action for updating the credits so we can now go back and we can uncomment this line right here and import it from update credits coming from user actions and for now we can hardcode this credit fee to minus one I'm going to leave a to-do right here to potentially update credit fee to something else if needed or to make it a bit more Dynamic but for now it can be minus one great so now before we can actually submit the form and do the transformation we have to implement the server actions that will call Cloud in's apis so to do that let's close our form go right here to our lib actions and create a new file called image. actions. TS and of course immediately add the use server directive at the top as this will be a file containing many different server actions to add images update them delete them and get image information by ID let's start with the first server action of the day it's going to be the one to add the image to our database so we can say export async function add image and it can accept a couple of programs such as image user ID of the user who's uploading the image as well as the path to which we need to navigate after the image is added and these will be of a type add image params immediately within it we can open up a new try and catch block you know the drill within the catch block we can create a new handle error where we calog the error and then within the try we can simply await connect to database like this at the end of every single server action we also want to do revalidate path which is coming from next cache which will allow us to actually show the new image that was created and not just keep what was cached rather we want to revalidate the path to show this new added image and at the end we can simply return Json parse Json stringify and then we pass in the actual image now this is just a typical boilerplate structure for almost every single server action so what do you say that we duplicate this three times below 1 2 3 now the first one is to add the image the second one will be to update the image so in instead of calling it add image we can call it update image and it's going to be accepting the same three props and we can also change it to update image perams immediately after we have the one to delete so right here we can say delete image and we can rename the function to delete image this one simply needs the image ID to delete it so we can remove this entire object and just say image ID ID of a type string there we go and finally the last one is to get it by ID so we can say get image and this one will be get image by ID of course we need the image ID and that's the only thing we need so we can say image ID of a type string so now let's start from top to bottom and let's implement the these different server actions in the first one we need to connect a specific image to an author who created it so right here we can say const author is equal to await user which is imported from database models user model find by ID and then we pass in the user ID of course ID is capitalized then we want to check if maybe we didn't get the author and if we did get it we can form a new image by saying con New Image is equal to await image. create inside of which we spread all of the image data and also pass in the author specifically the underscore ID of the author that created it this image should be coming from the models and it looks like we imported it from the wrong place so rather let's say image and let's import it from database models image model there we go and finally at the end we want to return a newly created image that we added to the database this was add image now for the update it will be very similar after we connect to the database we want to find the image to update by first running the dot find so we can say const image to update is equal to a we image. findind by ID and into it we pass the image doore ID after we find it we need to check if it actually exists and whether our user has the permission to update it so we can say or image to update. author do2x string so we're getting its ID is not triple equal to user ID in that case we can say unauthorized or image not found finally we want to update it by saying const updated image is equal to await image. find by ID and update not delete we want to do the delete in the next one and we want to pass it three different perams first the image to update doore ID so we know which one we're updating that's not within an object then the image object itself meaning the data that we want to update and then new to true to create a new instance of that document once we have this image to update we simply want to pass it over right here and return it and with that the update image is done as well delete will be fairly simple we need to connect to the database and we need to run a wait image. find by ID and delete and we pass the image ID in this case we don't need to revalidate anything because the image is gone and we also don't need to return anything because there's nothing to return so this is pretty simple to this one though we want to add a finally Clause so what's going to happen once the image has been deleted we simply want to use the redirect property coming from next navigation and just redirect to forward slash meaning the homepage that is it for our delete and now for our get we want to connect to the database and then get that image we're looking for by saying const image is equal to a wait and now bear with me we don't want to Simply get the data of the image we want to get the data about the author that created that image as well and to do that we can call a function called populate user like this and to it we need to pass the image. find by ID and then the image ID so we know who is the user that created this image so let's create this function called populate user right here at the top const populate user accepts a query of a type any and immediately returns query. populate where we populate with an object of path is author model is user and we select which Fields want to populate such as the underscore ID the first name and the last name as well so now going back to get image we are getting which user created it and then we're populating that image so now it also contains the data about the user that created it finally if there is no image we can simply throw a new error something like image not found and at the end we don't need to revalidate we simply need to return this image and that is it these are our four server actions that will allow us to add update delete and get images from our database which means that we can finally go back to our transformation form and focus on implementing the onsubmit Handler this one will be a bit longer so I'm going to extend our code editor and let's get started the first thing we can do is see if we're actually getting back any values and if we are how do those value look like once we submit the form so let's try to submit it a good form to test is the generative fill so let's try to add the title something like expand the image and then the aspect ratio can be like a phone portrait and we can choose the original let's let's get one from unsplash connect and let's choose one that is horizontal so we can actually expand it to be vertical which would be very very cool for social media use I'm going to upload it there we go one image successfully uploaded and let's see if we got a conso log currently there's nothing there I'm guessing that's because I have to click save image so let me click save and you can see what we get back this this is that same exact conso log on line 67 we get back the aspect ratio the color The Prompt the public ID which is the actual image hosted for us by cloud iny and then the title of the image so that's what we're getting back in the values of our form and if you think about it that's everything we need so immediately let's set submitting state to be equal to true and let's check if we have data or if we have a new image right here in that case we can proceed with the upload now we want to transform our image that is the whole idea so let's say const transformation URL is equal to get clld image URL coming from next cloudin you can import that and pass it the options object within that object give it a width equal to image question mark. width give it a height equal to image question mark. height give it a source equal to image questionmark dopu ID and most importantly spread out all of the transformation configuration so transformation config I believe this should be good and there we go then we get back the transformation URL provided to us directly by cloudin next we will want to form all of the image data by creating a new object and then giving it a title such as values. title coming directly from the form a public ID coming from the image a transformation type which in this case can be just type because we're storing it is it a recaller is it a generative fill or something else then we pass the width equal to image width the height equal to image height the conf config which will be equal to transformation config the secure URL to where that image is stored which is equal to image. secure URL like this then the transformation URL the aspect ratio equal to values. aspect ratio that's coming from the form the prompt in case there is a prompt equal to values. prompt and the color in case we doing a recaller this is all that we need for the image data finally we can see if the action is triple equal to add that means that we want to add the image for the first time in that case we can open up a new try and catch Block in the catch we can simply consol log the error but if we are in the try we can get this newly generated image by saying const New Image is equal to a weight add image which we can call and then provide an object and give it exactly what it needs now this add image has to be imported from image actions and this is what we created not that long ago so we know that it accepts the image the user ID and the path so moving back first we need to turn this function into async right here so async function on submit and we can pass it the image equal to image data the user ID and the path of just forward slash in this case it looks like there's a type mismatch so let's see in our add image add image per Rams the image has the following Fields Title public ID transformation type with hide config secure URL transformation URL aspect ratio prompt and Coler and let's see what we're passing to it title public ID transformation type with hide config secure oh yeah I think the URL here was supposed to be all uppercase secure URL same thing goes for the transformation URL right here equal to transformation URL there we go and now tapescript is no longer complaining it knows exactly what it needs to get finally if we successfully create a new image what do we want to do well we want to run the form. reset to do a bit of a cleanup we want to set image data or set image to be just the data and we want to enable the routing functionalities which we can do right at the top by saying const router is equal to use router and this has to be imported from next navigation so now at the end we can say router dot push and we want to push to a template string of for SL Transformations forward slash and then Dynamic code New Image doore ID so we're going to push to that specific URL also what's going to happen if the action is not add rather if it's update so to replicate that we can copy this entire try and catch block and then go below this if and add another if if action is triple equal to update in that case we want to paste that same block but this time we'll say update image pass it the user ID pass it the image but not equal to image data rather we want to spread what the image data the image currently has and then also pass the ID of the data doore ID and want to pass a path which will be equal to for slash Transformations with the S at the end we want to make it a template string and then do forward slash Dynamic block data. ID because now we already know to which ID we want to go to and don't forget to import update image from image actions once you do that we're going to call it not New Image rather updated image and in this case we don't have to reset the form or set the image data we simply want to push over to that existing transformation and at the end of both of these ifs in just one space before we close this entire function we want to set is submitting to fals because hopefully we have successfully submitted our form and with that our submit is now done so let's go ahead and collapse it right here so let's give it a spin back in our form we can try with generative fill first this is a very exciting one let's first select an image by going here and going to unsplash let's do some interesting scenery so if I go to Scenery for example let's do these natural Rock Bridge and click next I'm going to choose a medium size and click upload I'll say a natural Rock Bridge and I'm going to select a size let's say that I want to make it a phone portrait which this is obviously not so let's apply a transformation we have this nice looking Shimmer effect while it is loading and you can see that it actually expanded the image it gave it more depth and it looks like we took it from a different vantage point we have more sky and we have more rocks at the bottom pretty extraordinary right so what would happen if we click save image I'm going to click it and something quickly happened but let's inspect our console to see in more details it looks like we have an invalid schema configuration URL is not a valid type at secure URL so we have to be careful with this URL uppercased or lowercased we make sure that what we have on the front end matches what our backend and our database are expecting so let's fix it we can first look into where we're submitting this function and how we're sending it over to the back end I can see that here we say secure URL and once we're saving it into the image it is SEC cure URL like this and these are the only two mentions of this property in this file so we're then passing this image data over to add image and over to update image as well so let's see what those functions are accepting the ad image if we look into the ad image for Rams is expecting a secure URL with the URL capitalized and I believe it's the same thing right here secure URL for the update per Rams so if we look into this means that we're good because we're indeed passing it as the secure URL all capitalized but the next question is now that we create this image and we pass all these properties to image. create we have to see what the image model itself wants and in this case if we check it out looks like we have it as secure Ur L where the r and l are not capitalized same thing for the interface right here here so let's go ahead and fix it and make it secure URL like this there we go now if we search across entire code base for secure URL you can see that all instances of secure URL are all uppercased besides this one which is interesting where we have the on upload image Handler where we set it to the state so what we can do is we can also make it uppercased here secure URL and don't forget there is one final instance then considering that we changed that one as well and that is the image. secure URL right here how did I miss that one it's interesting why it didn't show up here but here it does show up image. secure URL anyway I'm going to modify that one to be secure URL as well so in absolutely all instances where we're mentioning secure URL we want it to be secure URL like this great now with that in mind let's try to recreate a transformation and save our image to the database going back to our app we can select an image we can choose some of the unsplash images in this case we're doing generative fill so let's try to use this one some new images appear today and let's select a medium size uploaded this appears to be some kind of a person in the desert so that's how we can call it and the aspect ratio we're going for can be a phone portrait let's say we want to use this image for social Med media so we can apply a transformation you can see this nice loading animation and there we go you can see how it expanded it you even see some steps right here it's looking phenomenal you can also see her legs now that is great so now let's open up our inspect element go to the console and clear it and let's try to click save image to see what happens now I click it and we get another error saying that inv valid schema configuration you are is not a valid type at pad secure URL okay okay that's my bad if we go back to image. model. TS I noticed that I typed URL but URL is not a valid type we should have just used a string both here in the secure URL as well as the transformation URL my bad I thought that also supports URL types but looks like I was mistaken so we can just use a string if we fix that and click save image one more time this time it looks like we get navigated to the Transformations page which is a good sign because that happens on success and if we revisit our mongodb Atlas Cloud database you'll see that we have a new image created within our database it's a transformation type of a type fill with a person in the desert with a specific width height and the configuration which in this case is fill background secure URL of the image that we modified and it's also connected to the author that created it and we can just cross verify that it's ff4 if I check out users you can see that my user right here also ends in ff4 which means that they are really nicely connected with that in mind before we go ahead and create this transformation page let's just ensure that the user can indeed do an image transformation and to do that we have to make sure that they have enough credits don't forget soon enough we're going to implement the entire credit system where the user will be able to purchase credits to then use to do Transformations so let's go back over to generative fill and let's Implement a model that will let the user know in case they have insufficient credits so back in our application we can head over to components and create a new shared component called insufficient credits model. TSX and of course we could just copy and paste the entire model coming from shaten in this case I will provide you with a complete insufficient credits model down in the description so you can simply copy and paste it right here as you can notice it's a very simple model where you wrap everything into an alert belonging to shaten precisely the alert dialogue which is a dialogue that interrupts the user with important content and expects a response so here you can say are you absolutely sure and then continue or cancel we've done a slight modification to it where it will actually say Hey you run out of the credits no worries you can grab some more and then you have the buttons to go back to the profile and cancel or to proceed of course to be able to use this we have to install the alert dialogue coming from shaten so to do that we can clear terminal and run MPX Shad cn- UI add latest ADD alert D dialogue great and as soon as it is installed you can see that it's no longer complaining now another thing we need to do is go back to the user. actions. DS Where we have this update credits function remember what we're doing here we essentially just update the credits by modifying the credit fee depending on which action user decides to to take and then we return the updated user credits so now we have to go back to the form which is the transformation form and we have to add a condition where this new model will show so right below the form which is right here within this regular form element we can create a new property where we can check for credit balance so we can say credit balance and if it is lower than math. apps this is going to get the absolute value so if it's like minus 5 it will bring back five then we pass the credit fee which will usually be negative and this credit fee is coming from constants so basically currently we're sending it to minus one if that is the case then we want to render the insufficient credits model and of course we want to render it as a self-closing component there we go now to quickly check out how many credits we have we can go back over to users and we can see that our credit balance is 10 I don't believe we have deducted any credits so far and I can see some other people also creating their accounts in the website which is pretty cool uh but yeah right now it seems that my amount of credits is still 10 so let's see if it actually gets deducted right now to test it we can see what happens on submit specifically on transform Handler and we even have a to do for ourselves here update credit fee to something else so right here we are updating the credits to be credit fee there we go which is coming from constants and it's basically the same minus one number and immediately as soon as we start the transformation one credit should be deducted from our list of credits because we are incrementing the minus one number but in this case it shouldn't really be credits it should be credit balance yes that's why it wasn't deducted for for the first time we have to be careful about how we call it so I'm going to Simply say credit balance there we go and now we're properly updating the credits now so far we have only tested generative fill but there are many other features to test generative fill Works where you just click the button and it does the job then it takes the credits but for the background remove and image restore features we need to set the new transformation and enable the apply transformation by Buton right after the image is uploaded it's going to make more sense soon to do this we can use the use effect to watch for changes so let's go to image restore go back to our code specifically the transformation form and navigate over just at the top of our form where we can add a new use effect this use of course has to be imported from react and it accepts a callback function as well as a dependency array in this case in the dependency array we want to look for when the image changes when the transformation type. config changes and when the type itself changes then we want to say if the image exists and in parentheses type is triple equal to restore or type is triple equal to remove background in that case case we want to immediately apply the set new transformation equal to transformation type. config like this so now to check out how this works let's try to do either a restore or remove background transformation together I'll do a restore first let's try to search for an image I would want to find a very bad quality image so very pixelated let's try to see pixelated image yeah all of these are pretty good not pixelated in any sense uh let me see bad quality image maybe this quart image right here seems a bit of a lower quality let's see yeah we can go for the small version and click upload you can see that one credit was immediately taken we can give it a title such as let's do court image restore or let's just say chord and click apply transformation oh my God you can immediately medely see the quality difference right here the image on the right is much much sharper I hope you can see it if not I'm going to zoom it in just a bit more and it definitely has a lot more detail so the image restore feature works wonderfully well as well I can even open both side by side and just look at the difference in quality let's say here and here the left image has much more detail and depth and color and contrast as well and if if we paying attention as soon as we uploaded the image we should have spent one credit if we go back to our database and refresh we can see that now we have nine credits remaining that should be enough for us to test the application to the point until we Implement stripe to purchase new credits now let's also test out the background remove which is a very cool feature so let's go here and let's try to find something where we might want to remove the background from such as some kind of a portrait of a person this one is cool but there's a lot of color coming in these are interesting portraits uh but let's try to find just a typical um LinkedIn type portrait right here like this guy right here that's pretty interesting and let's click next select a format let's go with large and click upload there we go we're loading up the original we can say portrait and we can run apply transformation but in this case it still says transforming right here because the background remove does take some time and there we go you can see that now we're getting the transformed version and it did a phenomenal job you can see there's not a lot of lost content right here around the edges it's not looking bad at all and we can click save immediately after we're led to the transformation page which will'll develop later and if it's still not clear why for the restore and the remove we had to add that use effect that's because we have only a single field this one right here and these will not allow us to apply transformation so once the image is uploaded we can immediately allow this button to be clicked whereas with other ones there's also some additional fields that will allow us to then apply the transformation and yes I know that so far we have tested the restore the fill and the background remove we're going to play with the object remove later on we cannot do that for now because we cannot type into this input and same things goes for the recoiler we'll work on that very soon but for now let's focus on implementing the homepage the primary page of our application where we can see all of the Transformations that you and other people have made pretty exciting stuff so let's get started our homepage won't be just a collection of cards we show to the user or all of the Transformations we have created so far it will be a call to action to start doing these actions it will show you the Transformations themselves and we will also use cloud inner search API to allow for incredibly detailed searches so to develop it we can collapse this over to the side and navigate over to root and then page within the root which is our homepage first we want to create our homepage Banner so right here at the top we can wrap everything in an empty react fragment and create create a section that will form our Banner we can give it a class name equal to home and right within it we can give it an H1 that will say something like Unleash Your Creative Vision with imagine IFI and if we save it nothing appears yet that's because we can give it a class name equal to home dashe heading which should apply a bit of margin and then if you reload we still cannot see anything that's because we don't want this to clutter the mobile view rather we only want this to appear on desktop devices but already we can collapse it once again because now we can start focusing on the main content which will be a UL an unordered list with a class name equal to flex D Center w-o and a gap of 20 right within this Ur we can map over all of our nav links by saying nav links. map but before we map over them we first want to slice them so we're going to use a slice from one to five and then we can map over each one of these individual links and immediately for each link return a link component which we can import from next link each link will have a key equal to link. Route it will also have an href equal to link. route and it will also have a class name equal to flex Das Center flex-all and a gap of two finally what do we show within the link that will be an Li and below the LI we can create a P tag that will render the link. label so now if we save it we again cannot see anything on mobile but if we go to tablet or desktop you can immediately see that we can unleash our creativity and also click on one of these transformations to get started this is a common practice that you'll see that on mobile devices you want to remove some content because you don't have enough space to show it properly so it's better to focus just on the content but in this case if we do have the space why not just put it in there so let's create an image within this Li which will be a nextjs next image tag that can have a source equal to link that icon it will also have an Al tag of image a width of 24 and a height of 24 but now let's also style this Li by giving it a class name of flex Das Center w- fit rounded Das full to turn it into a circle BG white and a padding of four that's much better let's also style the P tag by giving it a class name equal to to p-14 D medium text- Center text- White and if we save it looks good but I just have to add a t to the text and this is now much better and now that we have this Banner which already navigates you to different pages we want to focus on displaying some of the finished Transformations that our users have utilized and to display all of these properties we'll create a new component called collection within shared components by creating a new collection. TSX and just to save you a bit of time and so we can focus on what matters the most which is cloud Ander research and also purchasing credits later on using stripe and making transactions you can find the complete collection. TSX file in the read me down below so you can simply copy it and paste it here here we don't do anything we haven't done yet so far we simply simply have a link where we render the CL image and then also display a title for that specific image as well as render the image belonging of the type of the transformation we made to it that is it and let's not forget a part of this specific collection also has to be the search component so let's create another component right here under shared called search. TSX and you'll also be able to find a complete search in the read me down below this component couldn't be any simpler we're simply using one single input where we modify the query for the search and what we're doing here is we're using the debounce function so that we don't automatically search through the API for every keystroke rather we wait for 300 milliseconds before we send the response and what we're doing here is we're not just updating the state rather we'll also be up updating the URL so as you type for example test right here and press enter you'll notice that that test gets added as the query to our URL query params so you can also paste the search right here and just so I don't forget our collection component will also be using pagination so right here we're just importing the basic pagination coming from shaten and that means that we have to install the pagination coming from shaten UI so let's do that right away by clearing it out and saying MPX shat cn- UI latest add pagination and press enter there we go it automatically got installed and we don't have an error anymore you can also notice that we're using the search component right here within the collection there we go so now will be the perfect time to check out how our collection component looks like on the homepage and and we can do that by going over to root and then page going below our links and below our section and we can create a new section that will have a class name equal to on small devices margin top of 12 within it we can render The self-closing Collection component which of course we need to import from components shared collection and we also need to pass a couple of props to it specifically props relating to the pagination so let me show you how we'll Implement pagination in our app nextjs makes it so easy you want to grab the page number directly from the search query params and you can do that by destructuring the search perams directly from the props and giving it a type of search peram props once you have that you can say const page is equal to turn it into a number and say search params question mark. page or set it equal to one by default we can do the same thing for the search query by saying const search query is equal to you wrap it in a string and say search params question mark. query as string or we can leave it as an empty string by default let's expand it so it fits in one line and the next thing we have to do is of course fetch all the images we have created so far so that we can then pass them into our collection to do that we'll have to navigate over to our image actions. TS remember we have actions to add the image update delete and get a single image but we don't yet have one to get all images so we can create one this will be the most complicated of them all and this is exactly why I'm sometimes providing iding specific pieces of the UI because that allows us to focus on what truly matters which is more complicated pieces of code that you don't get to see in every YouTube video you get to see them in JSM videos because I want to make sure that we try to replicate the real work environment as much as possible to replicate how real life projects look like with all of the little incases and details so let's create it together first we can follow the typical structure by copying what we have here from the get image and pasting it here we can rename it to get all images and it can accept a couple of params that we can destructure that's going to be a limit which by default can be set to nine a page which by default can be set to one and a search query which by default can be set to an empty string and all of that will be of a type limit is optional of a type number page optional of a type number two and search query of a type string and I believe we don't have to make page optional we can always pass it at least the number one then we're connecting to the database and we're handling the error but now we can focus on the most important part which is to get all images but not only that also to search through those images to make the search work but not only that also to make the pagination work all together so to make that happen we'll have to config our Cloud inter instance to be able to pull the images from somewhere and for that reason we have to install a new package called cloudinary mpm install Cloud inary remember so far we have just installed next cloud inary and now that we have installed it we can just import it right here at the top by saying import V2 as cloud inary from cloudinary and we can scroll down and then say cloudinary do config and now we can pass a couple of our environment Keys into it such as the cloud name which we have stored in process. env. next corpu cloudinary Cloud name the API key which we have also passed as cloudinary API key and API secret which is cloud API secret also we can say secure is true to make sure that we have a secure connection now you might have noticed that as I was typing this new lines immediately appeared for me and all of that is done by GitHub co-pilot I'm not paid to say this but it truly speeds up my workflow so I would recommend checking it out I do believe they have a free version for students maybe even free one Monon version so you can try it out but it's definitely a great help now now that we have a cloud in config we can also set up our initial expression by saying let expression is equal to and here we can say folder is equal to and you need to make this equal to the name of your folder I believe I use just imagine IFI but if you go back right here to your dashboard increase the size a bit and go to right here to your specific upload preset you can see the folder name right here and just copy and paste it next the reason why we just set this expression like this is because now if there is a search query then we can append the search to it by saying if search query exists in that case we can say expression plus equal to a template string of space and and then we can put the search query right here which will search only for specific images finally we can get the resources back by destructuring them from the call we'll just make and that is equal to to await cloudinary do search and then we can pass the do expression pass the expression in and then say execute this will give us back all the resources we need now that we have gotten back those resources we need to get back the resource IDs so we can also get them from our database and we can do that by saying const resource IDs is equal to resources. map where we get each individual resource and for each resource we return the public ID we can also add a type any for this right here now that we have our resource IDs we can form a new query which we'll use for querying our own database and we can say if search query exists that's coming from the front end then we want to modify the query to go over the public IDs and say in included resource IDs so we're now modifying our query search to include only the ones that we got back from cloudin then to implement the pagination we can define a skip amount which is just a number of cards we want to skip because we're on the second page for example and we can Define this by wrapping the page into a number like this and then saying minus one and then multiply it by the limit the limit of of course being the limit of cards per page so this will give us the specific skip amount and finally we can now fetch back the images by saying const images is equal to await populate user because we also want to get back the user data for each image so we know who created it and then we want to run the image. find to which we pass the query like this we can also run the do sort on it to sort it by up upd dat at minus one so the newer ones appear on top we can also run the do skip skip amount for the pagination and that limit of limit finally we need to define the number of total images so we know how many there are by saying const total images is equal to await image. find where we pass the query and we call the count documents like this and we also want to get the total number of all images in general by saying con saved images is equal to await image. find and we call it without a query and then we call the count documents once we have this we can return an object where the data is equal to json. Parts json. stringify images where we have the total Pages which is equal to math. seal so this tries to return these smallest number that's greater than or equal to the argument that you pass in and that's the total images divided by the limit so we know how many total Pages there are and then we pass over all of the saved images and it looks like I misspelled it right here so if we fix it we're good I know this wasn't easy and for that reason the complete image. action. TS file will also be provided in the readme in case you miss something but I definitely wanted to take my time time to go through this with you to ensure that you understand how now we get this get all images function to show the elements on the homepage do the pagination and do the search so back in here we can say const images is equal to await get all images to which we pass an object that has the page and the search query coming from query params of course we have to make the function asynchron eress as we're using a weight and we have to import the get all images action coming from image actions now that we know the page the search query and all the images we can pass it all to our collection which will ultimately render it all so let's pass the has search which will be equal to true because in some other cases where we call the collection we won't have that has search the true we can pass the images which is going to be equal to images question mark. dat we can pass the total Pages which will be equal to images question mark. total pages and we can pass the page we're currently on which will be equal to just page and you can see the collection component is no longer complaining it's getting exactly what it wants to get to show the pagination the search and the collections so let's close all the files expand the browser and check this out we have our portrait from which we remove the background and we have the person in the desert which we use the generative fill transformation on then we have our phenomenal cloud and powered search here that allows you to search not only by the title such as portrait in this case but by what's on the image so if you search for something like a human you can see that here a human is obviously visible in the picture we can also maybe search for rocks no that's not these specific sometimes we might need to be a bit more precise but just to give you a glimpse of what it can do you can search for something like sky in our deployed application and you can immediately see that it figured out that this is a sky at a beach party this is a night sky and this is a sky within the park without a bench we can also search for something like a human here as well and immediately we get back this woman and also we can search for a computer and it will will nicely recognize that this is the old computer so I just wanted to quickly walk you through the fact that once we have more posts and images you'll be able to do so much more with this search and let's not forget the fact that we also have our pagination right here at the bottom right now you cannot see it you will only be able to see it if you have more than nine elements in total then you can paginate to the second page with that said let's focus on the next very important part of our application which is the image Details page so if I click into this person in the desert I want to see exactly what happened here such as if I click the night sky here in the deployed application you can see what kind of transformation was used the aspect ratio and then you can see the original transformed as well as download the transformed result so to get started with it we can navigate to our ID page specifically ID of the transformation this is the the transformation Details page once again you know how much I value your time and I want to ensure that this little time that we spent together is used to its fullest potential so here we're just using some images and components we have already used before and for that reason I'm going to provide you with a completed Transformations ID file page you can copy from the read me and paste it right here you can quickly see that this is nothing more than a simple section where we show the transformation type The Prompt if there is one the color if there is one the aspect ratio if there is one and then we show the image before and image after that is it we also need to import the header from shared components header here we're doing the default export and we just have to make it the default import as well and we're good and now if we go to the browser we're going an error saying that we need to convert some kind of a component into a client component specifically referring to some kind of a button here this is a great lesson to look into this is an Asing component so it surely has to be server component or server page but some of the components here apparently some kind of a button should be client side and here we have a regular button this is not a button with some kind of a function that we have seen so this is not it but if we navigate into the transformed image component you can notice that this component has a button that has an onclick listener and whenever you have onclick listeners and click handlers that means that this component has to be a client component and the reason why we have seen this error just now is because on our image Details page we have the download button so it actually renders for the first time ever we'll implement this soon with that in mind if we go back this time we get an error saying invalid Source prop host name rest cloud.com is not configured under your next config JS so let's copy this restcloud diner.com this is the host hosting our images go to next. config.js and right within say images then Define the remote patterns then an array with an object in between with a protocol of https and make sure to spell it correctly then give it a host name like this res. cloud.com and we can leave the port as empty now if we save this and recompile our application we can see our product details it is as simple as it gets we just say some stuff right here and then we show the original and the transformed image let's just make sure that this download button Works to fix it we know where we have to go in the transformed image and we have to implement this download Handler right here we can accept the event as the first and only parameter and say that it's of a type react. mouseevent of a specific HTML button element type and it has a mouse event as well like this this immediately we want to run the e. prevent default to prevent the default behavior of the browser which is to do a Reload and then we can say download this is coming from lib utils to it we need to pass which image we want to download specifically the URL of the image and we can do that by saying get clld image URL coming from next cloud in to which we pass an object where we Define the width of the image in this case image question mark. width the height of the image image question mark. height and the source of the image which is public ID and then we pass over all of the transformation config just like this finally we have to pass the second parameter to the download function which is the title as you can see now it's happy and we can look into the download basically the way it works is it does a fetch to a URL from the response it gets access to something known as a blob which basically contains the image forms it as a URL and then downloads it I believe this is a function that I created using chat GPT sometimes you don't want to write these functions by hand and now if we go back and click right here this great looking image just got downloaded great so with that in mind our image Details page is now done you can see the before and after so the next thing we'll do is we'll implement the image update page where we can update a specific transformation this will be incredibly easy as we have already built the original form the update form will in most cases be almost exactly the same as the create form but it will have the information pre-filled such as the image title or original transformed and so on so you can immediately see what did you do on that image before so we can create that update form by going to Pages root Transformations ID and then update page since we have already built this form together before you can guess it I'll provide this code to you in the reading down below so copy it and paste it right here believe it or not it has only 40 lines because it's using the header component which we have already created as well as the entire transformation form which is that biggest component that you have created completely on your own while watching this video yep so we're simply reusing it here in this case we have to fix a couple of imports such as this header needs to be a default import and then we need to see if we have access to the get user by ID and get image by ID actions coming from our actions looks like we cannot find this module right now so what if we go into image. actions and search for it well we do have get image by ID and if we go into the user actions we have the get user byid so it looks like just the Imports are wrong let's delete them and where we're calling them let's just automatically import them by pressing control space and clicking right here and looks like the Imports are now good which is a bit weird because I think they were exactly the same before we deleted them but here it looks like they're good and also the subtitle will be renamed to a lowercase T but if we do this go to the image details and then navigate over and then append the forward slash update to the URL you see that you'll be navigated to this form like page where you can now modify where you can now modify this trans information for example let's say I want to make this Square click apply but it looks like we're stuck on transforming so let's see what's wrong back in our code we can go back to the transformation form right here and specifically go to where we're working with the fill property right here so if type is fill let's see what's happening well in this case we have this select field that has the on value change but it also needs to have the actual value so we know once the value has changed so we can pass it field. value then also going to the prompt field right here we are destructuring the field but it looks like I have an extra pair of parentheses right here so I can just remove that one extra pair and close it properly it should look something like this also quickly navigating to the transformation image or transformed image right here where we have the debounce this function that we declare right here won't be called on its own so to invoke it we have to make it into a self- invoked function expression which will look something like this I noticed that I missed this before also we need to fix this loading for the transformation image where I noticed we're calling it a transforming loader but if we search for classes there's not going to be one rather I misspelled it in the globals as transforming so rather it should be a transforming loader so add an S right here here and we can also call this an Al tag of spinner and below it we can give a P tag of something like please wait so that we also know that something is happening with a class name equal to text- White over 80 to make it a bit grayish and the actual transformation might have worked for you even before but I just wanted to make sure that we do a couple of these quality of life changes as I like to call them so with that in mind I created another one but we can go back to the person in the desert we can navigate over to update and make sure that you have enough credit balance if not just manually increase it right here otherwise you would be able to see a model to purchase more which we will Implement later but with that in mind if we now try to change it to a square and click apply transformation the please weight now is much better so we can see some visual use that something is happening as well as it says transforming right here for you it might have worked before as well but for now it's visually being shown and after doing a bit of browsing I managed to find a situation where I said aspect ratio instead of ratio so if I now remove this n at the end this was in the on select field Handler hopefully that will be one last obstacle we have to go over before we can fully test out our updating function fun alties so now if I go back and if I go to the person in the desert and I go to update we get navigated to the update form and I'll try to turn it into a square and as you can see it even saved it in the cache because I was testing this out before so it works automatically but I can click apply transformation one more time to regenerate it now sometimes and depending on some regions this generation can take a bit longer but we can always wait for it because now we have this nice looking loader and after some time we got back to square now one issue that we have in our app right now is that if you check we have to manually type update in the URL bar to go to the update page but a regular user might not even know that this update page exists so to fix that what we want to do is immediately once we visit the details page if we are the creator of this post we want to create a button to update or delete that post and to get to that button we can go to the Transformations ID page that is this one right here not update but just the details and then if you scroll to the bottom of that page you should notice that we already have this part that should allow us to update the Page by navigating to the update page but it looks like we're not getting back the user ID at least not in the way we're current Curr ly trying to call it so instead of getting the user ID from the session claims which would be getting it from the clerk metadata we can try to get it directly from OD by saying user ID and then we get it immediately so here we're getting the user ID back from clerk but then our image only knows the user by its database ID so we have to go into the get image by ID and then where we populate the user right here at the top we also have to add the clerk ID that way if we go back this image will now have access to the clerk ID so if we go down and now if we change this over to image. author. clerk ID now we have access to it and our check should be proper so now we should be able to see the update image and if we go back there it is and now it navigates us over to the update page immediately see how this caching works really nicely in case you want to really quickly modify it and you can save it immediately that is great the update button works with that in mind let's also implement the delete button if we are the user that of course created the post so to implement it we can Implement a model similar to the one we have had so far which will be within components shared and we can call it delete confirmation. TSX as this is just an alert dialogue similar to the one we've used before the full code will be provided in the read me down below so copy it and paste it here about 60 lines where we simply use the default example from shaten where we say delete image are you sure you want to delete it yes and then we call the server action which we have created before great now of course we want to call this delete confirmation component somewhere and specifically we want to call it on the same page where we just showed that update and that is on the Transformations ID page this one right here right below the update specifically below the button we can render a self-closing delete confirmation component and we need to pass to it the image ID equal to image doore ID so we know which image to delete of course this is only also being shown if we are the Creator or the author of that transformation so now you can see the delete image and if we click it you can see this great looking but also very simplistic model that allows you to delete that transformation let's give it a shot I click delete and we're rated back to the home and we can see that some users also added some new posts so all of this is looking great but don't forget so far we have tested only three out of five functionalities the background remove the generative fill and image restore but finally let's make our Fields within the object remove and object recolor work so we can also test out those amazing functionalities fixing it will be fairly straightforward we of course have to navigate back to our transformation form and then we have to navigate to the on input change Handler where we're calling this transformation in this case I noticed that I closed this debounce a bit too soon rather the return of the onchange field should have happened after the debounce so we do it like this and I believe this is the only change we need to make to make our Fields work because now we are returning the onchange field with a new value so now if you go back and if you try typing something it works wonderfully so let's give it a shot let's try to remove an object and recolor an object let's go to our great cloudinary image upload go to unsplash and try to find something that we can remove hm this one will be interesting three Surfers walking in let's see in what into the ocean at Sunset okay that's an interesting one what would you say if we just made this you know ocean at a sunset without these three Surfers let's see if we can make that happen I'm going to upload this image and say this is ocean at the sunset hopefully without the servers and what do we want to remove from this let's say people and unfortunately the apply transformation button hasn't yet turned on the reason why we cannot see it is because on input change Handler this debounce is never actually getting cold in the way that it's structured what we need to do is we need to make it a self-invoking function like this at the end you can just put a pair of parentheses and it will self- invoke and then set this new transformation so that now if we try it again let's say sunset at the ocean object to remove will be people and then we can actually take the same one and quickly uploaded you can see now that we can actually apply the transformation so let's give it a shot we can see this great loading please wait and it looks like it removed one person so you know what I'm actually happy with that it looks like that person was never actually there so I'm going to say two people watching the sunset at the ocean that's actually good and now we can save the image great we can see the original we can see the transformed we we can download that image as well update it and delete it and we can see exactly what transformation happened and it also showed on our homepage this is great now finally Let's test out the object recaller by choosing an image that has an interesting color inside it let's search for bright colors or bright color and let's try to go with these flowers right here purple orchids I think that might be interesting to recolor we can say flowers object to recolor would be a flower right and I'm going to choose a color of red and click apply transformation and there we go we had very very bright red colors and we can click save image wonderful so all of the AI transformation functionalities are now fully functional let's not also forget that we have a search so if we search for flower we can see it if we search for Sky we can see some sky in the picture as well ice cream maybe there we go that works as well so we have this phenomenal cloudinary powered search based off of the context that it recognizes in the image we have background remove generative fill and more let me know down in the comments which one of these functionalities do you like the most I find the generative fill the best because you can truly change the image and it expands it and provides you with more context and now with that in mind would you look at this it feels like our app is completely done right all of the functionalities are working but to turn it into a complete SAS we want to implement payments yep once you run out of credits we want you to be able to purchase credits to be able to do more transformations in the deployed application that will look something like this we have a header and a subtitle that we reuse but then we have a single card which is also a reusable component which we reuse three times and we create three different tiers of pricing and of course all of this will be connected to your clerk account and through stripe will process those payments and then finally we'll create the profile page where we'll be able to show the credits available the image manipulations you have done so far as well as all of your recent edits exciting stuff ahead but now we'll move away bit from our app the uiu and the frontend and we'll focus on completely setting up our entire stripe infrastructure and our server actions for processing the payments so let's get started with that next first things first we need to create an account so click Start now go through the entire sign up process and then turn on the test mode then head over to developers and go to API Keys you'll need your public publishable key so let's copy it and going back to our code base we can go to env. loal and we can add a new sub chapter of stripe and we can call it nextore public stripe uncore publishable uncore key it's always better to be descriptive than to shorten it great going back to the stripe dashboard the second thing we need after the publishable key is the secret key which we might need later so simply copy it and back in our EnV we can call it stripe unor secretor key and we can add it here finally we can install stripe by going to our terminal expanding it and running mpm install stripe and add stripe slash stripe DJs now that we have installed it we want to create our first transaction server action so let's head over to our lib actions and create a new file called transaction. action. TS and inside of here we'll create a connection between a strip transaction to our database meaning we'll purchase the credits so you know the drill we have to Define it as the used server and we can create two new actions export async function check out credits this is to buy the credits where we'll get the transaction right here of a type checkout transaction for Rams and that's it here we can do everything related to stripe first we need to set up a stripe instance by saying con stripe is equal to new stripe like this and we have to import this stripe at the top Top by saying import stripe coming from stripe and then we can call it as a function or as a Constructor and to it we need to provide our stripe Secret Key by saying process. env. stripe secretor key and we can add an exclamation mark at the end because we know that it actually exists so typescript has nothing to worry about then we want to get the amount that trying to charge by saying con amount is equal to we turn it into a number from the transaction that amount and then we multiply it by 100 because stripe processes the transactions in sense once we do that we want to create a new stripe checkout session by saying con session is equal to await stripe. checkout. sessions. create and we want to pass in param first we want to define the line items we're trying to purchase which is an array that has one object and here we can Define the price undor data which is an object where we can Define the currency in this case we can go with USD the unit amount will be the amount we have declared above and the productor data will be a name of transaction. plan so we'll have many different plans to choose from and then after this price data we can Define the quantity right here after quantity of one then after this array we want to define the metadata of this order where we can set the plan to be equal to transaction. plan we can Define the credits which will be equal to transaction. credits and the buyer ID so we know who to attribute the credits too by saying transaction. buyer ID after that object we can Define the mode of the payment which will be payment and very important we need to define the successor URL which will be a dynamic template string of process. env. next corpu unor server URL SL profile so we want to go over to the profile if if we're successful and on cancel URL we simply want to go back to home meaning forward slash like this and after all of that is done we simply want to redirect over to a session. URL like this and of course this redirect we have to import at the top from next navigation so we can say import redirect from next navigation there we go that is good and this is the function that will process our payment and on top of that we also want to create a new transaction in our database so to do that we can say export async function create transaction which will get in the transaction as a pram and it's of a type create transaction params this one will be more similar to our typical server actions where we can say try and catch in the catch we call the handle error like this and provided the error and then in the try we await connect to database like this then we want to create a new transaction with a buyer ID that's going to look something like this const new transaction is equal to await transaction. create to which we pass the entire transaction we can spread it out and we also pass in the buyer as the transaction. buyer ID and of course this transaction has to be imported from transaction models finally we want to update the buyer's credits by saying await update credits coming from user actions to which we pass the transaction. buyer ID and the number of credits we want to add finally at the end we want to return json.parse and json. stringify this new transaction there we go these are our two server actions one to process the checkout and the payment and one to actually create a transaction record in our database and to attribute the credits to the user so now that we have these we are ready to set up our stripe web hook remember our Clerk's web hooks where they listen for specific events and then they trigger some actions similarly we also want to trigger a web Hook from stripe once the payment is done so that we know to call this create transaction and then attribute the credits to the user so going back to the dashboard we can go to web Hooks and create a new endpoint we want to set the endpoint URL similar to what we have done in clerk so if we go back to Clerk and to web hooks we want to take this exact same URL paste it here but instead of saying clerk at the end we'll say stripe because this will be a completely different web hook we want to subscribe to one event right here similar to what we have done on clerk this will be a checkout. session. not completed so we know when a checkout session has been completed so we can do something so let's click add events and let's click add and point there we go it's waiting for events and here we get a signing secret from this web hook so we can copy it we can go back to the code specifically to our envs and we can create a new EnV called stripe uncore web hook undor secret and that's equal to this wh SEC and then we have a specific key once you do that we can create this web hook file by going over to app API web Hooks and then we want to create a new folder called stripe within web Hooks and create a new route. TS within it I'll provide you this simple web hook in the readme so you can simply copy it and the reason why I did that is because I want to explain the most important Concepts that we can see here so once you have it let's make sure that we fix the Imports by once again automatically importing this create transaction I think it will fix it then there we go and let me explain what's happening here so we have a post request on our application and we're allowing this endpoint to be pinged by a web Hook Once it's pinged we try to access the body of the request we process the signatures so we know that they are legit then we specify the event and we construct it by using the stripe. web hooks. construct event then we figure out the event type and if the event type is the only one we signed up for which in this case will be checkout. session. completed we destructure the ID the amount total and the metadata from that transaction for form it in a way that suits our create transaction function that we have created not that long ago and then pass this newly crafted transaction object with the plan credits buyer ID and all of that to create a new transaction in our database and then we simply return it now to test all of this out we of course have to have the page from which we can purchase the credits so let's go to app Root credits and then page. TSX since this is just the UI you can find it in the read me down below and you can paste it right away we have a couple of things we have to fix the header is just a default import component and of course with this file we also need a shared checkout component so let's navigate over to components shared and create a new checkout. TSX and then get access to it from the readme and and paste it right here you'll notice that it is about a 10 line long form with a simple button that says bu credit what matters most is what is happening on the on checkout function well here we get the transaction information and call the checkout credits with the transaction and do remember what the checkout credits is a function we have created before this one right here where we actually trigger the stripe checkout process and if you look above you'll see that we have a use effect to check where we have to redirect to and that is it with that in mind we can properly import this shared check on component so that it doesn't complain that is right here there we go and we called it a subtitle with a lowercase T so now we have a complete credits page so what do you say that we go ahead and check it out going back to our app and going to buy credits we now have this wonderful looking buy credits page which is quite simple it's just one card that we replicate three times and you can see that as well we are reusing the header we're mapping over the plans and for each one we're simply showing An Li that has an image and a label that is it now what happens if I try to click Buy credit we get an invalid URL undefined profile URLs must begin with HTTP or https and the reason this is happening is if you go to our transaction I believe that's a transaction action in checkout credits you notice that we are using the next public server URL to navigate over somewhere so we have to add this to our envs as well so if we navigate over to our envs we can call this something like other and then we can say next public server URL in this case it will be equal to Local Host 3000 because for now we're testing this out locally so if we try it one more time and click Buy credit you can see that now we are redirected to a beautiful typical stripe checkout you can easily process the payment by simply saying 424242 until the end and then also type a random numbers for CVV add a card holder and click pay if you do this it will process there we go and once it does you'll be redirected to the profile page completing the cycle but of course on the profile page we cannot yet see how many credits we have so back in our code we can go over to our app Root profile and then go to the page and I'll provide you with this quick profile page U Y and ux you have gone this far into the video so I doubt you'd learn anything from just writing a couple of lines of jsx so you can copy and paste it here from the read me once you have it we can fix the header if it's not already fixed and we need to implement one more server action within lib image actions called get user images which will allow you to extract all of the images that a specific user has created in this case we have get all images that you can see here and this one will be incredibly similar if not even easier so you can also find it in the readme down below copy it and paste it here the way it works is we try to connect to the database we figure out how many pages we want to skip based of the pagination then we only find the images by a specific author we get all the images return them and that is it your typical server action using the nextjs best practices now back in here we can use it to extract all of the user images and which you look at this we are reusing the header and this component couldn't be any simpler it is just a section that shows how much credits we have available by showing the credit balance shows how many images we have manipulated so far and then we render the same same collection of images which we have created before this is the same component we show on the homepage that lists out all of the other user images but this time we provide the images only created by that user so now if we go back you can see that we have two credits available and we have done three image manipulations and you can also visit all of these three posts and perform cred operations on them now there's one gotcha here and that is that you can see that we tried to purchase many credits and it didn't really work that's because we haven't yet published our web hook so if you go back to stripe you'll see that two events have been triggered but they both failed because our URL ofweb hook SL stripe isn't yet deployed it's not online we haven't pushed it to verell so to finalize this and to truly make the purchasing work as with Clerk we have to publish this endpoint to the Internet so that web hooks can call it so with that in mind and checking out our application and seeing that it is fully done we are ready to do a final deployment of our application and then we can go ahead and test out its live version on the internet the only thing we have to do now is just go to the source control right here we can use this GitHub co-pilot trick that will simply generate a commit message say commit and sync changes or or you can just push it manually by stopping the terminal from running and then running something like get add dot get commit dasm commit message and then get push with that in mind if we go back to versell and back to our project we can see that it is now building but if we're being really careful you'll know that this is not enough if we try it out after it builds even though it will most likely be successful and ready it is missing something that will make it fully work and that is it's missing the newest environment variables that we have added every time you add new ones locally you also have to add them right here so what we can do is go back to our envs right here and before I go ahead and copy all of these and add them to our final deployed project I want to make sure to duplicate this line and update it for deployed version here you want to put the URL of your new deployed application the same one that you had under web hooks in my case it is https Colin SL imagine if I knew. verel doapp and you can remove this ending part right here now I can do command a and then command C to copy all of it I can go back here and then just paste them within this field it will automatically add all the new ones but we can see it's saying that this key might expose sensitive information share it publicly okay that's good same thing for these right here so verell is just making sure that we know what we're doing and let's click save added environment variable successfully but it looks like it cannot read the ones we already have which is okay so let's just ensure that the ones we need indeed got added like the web hook secret let's try to find it yeah web hook secret is here but this one was belonging to clerk I think but there we go these ones were added just now which means that everything is great so now we can go back to deployments and we can just redeploy it one more time for get measure we can give it a minute until it renders and I'll be right back there we go our app is live let's go back to our project and click visit you'll notice that nothing changed besides that now at the top it doesn't say Local Host because we are looking at the deployed imaginify this time in a live deployed environment we can perform all kinds of different actions search for the images we have edited so far like flowers so let's go ahead and test out some of the features for one last time I'm going to go with generative fill and it looks like we get an error this time it is a server side error but we are now on the deployed side so we cannot simply go to our terminal and check it out so I'm very very happy that I've gotten a chance to see this error with you to teach you how to debug server errors once you're on the deployed site by going to server logs to go to server logs you can visit your project and then click logs right here you'll see a lot of different logs and at the bottom there's going to be a button to load new ones now in this case we're getting an error saying user not found and another user not found and then if you scroll a bit down you'll also see an error from clerk saying that we cannot access the forward slash because there's no signed in user to fix this we can go back to our code specifically to the middleware dots file where we Define our public routes and in this case I want to make two more routes public first one is maybe the most important one which is just the four slash so our homepage will always be public but if we go to some different pages then they will be private because we have to have an account to be able to create Transformations but in case you want to just see the Transformations other people have made maybe to Peak your interest so we can urge you to buy some credits then that's going to be publicly available and then similar to our API web hooks clerk we also want to add the API web hooks Stripe Right here so that we can freely ping it and so that our database gets updated once you make those changes make sure to commit and push them one more time and sync the changes now let's wait until our deployment is out so we can test all of the functionalities of our application and hopefully see them fully working now if we go back before you go ahead and try to test out some of these functionalities just to be 100% sure we're logged in I'm going to go back to Clerk and sign out because now we're on the final version and we want to try to truly log in through the deployed version as well so let's log in clerk works as well you can see that we are on this new domain now so you can sign in or sign up and once you're in head over to imag restore or whatever else you want to test out and now it works now I want to test out one more thing before we test out the Transformations which have done a couple of times so far and that is check out how many credits I have and try to see what happens if we don't have enough credits to purchase new transformations to test that out we can be a bit sneaky and go to our mongod DP Atlas collections go to users and then I will remove all of the credits right here of course I could add them as well but in this case I will just reset myself to zero now if I go back and reload load we can see that all of this is working real time it's pulling it from the live Cloud database and now if I go to let's say generative fill we get this nice looking model that says insufficient credits looks like you run out of these free credits no worries you can grab more so I'm going to click yes proceed and we are redirected to buy credits page now you can notice that here we're offering crazy amounts of credits like you can even get free consumables like for 20 but immediately with 40 bucks it's 120 and for 200 bucks it's 2,000 of course since you will use this knowledge right here by building this video to build your own SAS you can figure out your own pricing model and then price it accordingly of course you want to go into Cloud en figure out how much they charge and then still make it profitable for yourself by charging for credits a bit more that's basically the only thing that this guy is doing levels I io on Twitter he's creating a SAS after a SAS after a SAS after a SAS and all of them are performing well one of the better ones he created lately is photo ai.com basically it allows you to generate AI portraits similar to what we are doing right you can also expand Cloud ander's AI transformation tools to build a customized and feature specific SAS like he has built right here and then you'll be able to charge even more for these credits with that said let's go ahead and test out the functionality let's try to get 120 by clicking Buy credit since we updated our envs now after we complete the transaction it knows where it needs to redirect so enter your information use 4242 for the demo stripe card and click pay it will process your payment immediately triggered the web hook which will then call our API our API will create a action the database and Grant you the credits that's how it works and you can see we are immediately redirected back to our profile with 120 credits available with that in mind you can now choose to do whatever you want I'm going to go to generative fill and I will try to select a new image from unsplash but of course you can also upload your images and let's try to go with some animals I'm going to go with this horse right here as it is a very nice horizontal image and I want to make it vertical so I'm going to upload it go to phone portrait if we want to use it for social media call it a horsey and I'm going to click apply transformation we immediately get this nice looking loader and now that we see all of this in action it truly feels like a complete SAS application that you can just optimize and sell credits on it is that that good look at this it even added the legs and it added a fence right here which is quite interesting that is cool and it also expanded the sky so let's go ahead and save this image immediately we are redirected back to that image details where we can update or delete it and it also immediately got added on our homepage as well as on our profile you can also see that credits got deducted and image manipulations done have increased so with that in mind my friends you have completed imaginify or however you decided to call it an AI image transformation tool of course without even mentioning it all of this is made possible by cloudinary AI that allowed us to upload images optimize them and also do all of these transformation on these images which essentially made our application possible and we used clerk to optimize the user management with that in mind if you came to the end of this video you truly are the perfect fit for what we offer on JS mastery. proo in this course you explored only a bit of what nextjs has to offer but in the course we dive deeper I teach you how all of these biggest companies truly use nextjs behind the scenes and not just use use it like good old react rather I teach you how to think in nextjs and resolve all of these biggest and most common nextg issues and bring your performance from this to something like this it all happens through deep code Dives and truly understanding how things work with this detailed and animated graphics you build and deploy an app that is multiple times more complex than what you've done right now and we also included active lessons where you not only have to follow the videos rather you are given a task with examples thought process resources and even hints so you need to follow along to be able to learn how nextjs truly works and all of that is done in the process of building the amazing Dev overflow which is a complete modern stack Overflow application so if this sounds like something You' be interested in check it out and and if you're already a working developer in your Junior role then I have something even better for you within our Master Class you will not only follow the recorded courses rather you will build apps completely independently and on your own with its sole purpose of increasing your skills and getting a higher paying job and leveling up in your career from Junior to Mid and even senior positions so if what I've just said sounds interesting definitely check out jm.pro with that in mind huge thanks to you for coming to the end of this video building it out you're truly amazing for staying all the way until the end not many people do but you did so great work and I'll see you in the next one have a wonderful day
Info
Channel: JavaScript Mastery
Views: 250,750
Rating: undefined out of 5
Keywords: javascript, javascript mastery, js mastery, master javascript, next js, next js tutorial, next js project, full stack, nextjs 14, full stack web development course, next js 14, next js 14 api, next js 14 projects, next js 14 auth, full stack course, next js 14 crash course, next js 14 tutorial, next js 14 course, next js 14 app, javascript project, next js tutorial for beginners, full stack web development project, full stack web development, full stack developer course
Id: Ahwoks_dawU
Channel Id: undefined
Length: 282min 55sec (16975 seconds)
Published: Fri Feb 16 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.