Learn Next.js 14 Server Actions With This One Project (UseFormStatus, UseFormState, Error Handling)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this tutorial you will learn everything you need to know about server actions in nextjs and in the process you will build a fully functional amazing jobboard application but first of all what are server actions maybe it would be useful to clarify that server actions are a relatively newer feature in nextjs they have been marked stable in nextjs 14 which is the current version as of making this video and they provide a way to write server post end points as normal functions post requests are mostly used to update something in our database for example when a user creates a new comment or a blog post or some other kind of resource through our website then we send a post request from the front end to the back end and then our server updates the database with this new data for example by inserting a new comment into the database or a blog post or whatever normally we have to create a fully fledged server endpoint for this if we use nextjs as our back end this would be a route Handler where we set up a post end point and then to recal this post endpoint from the front end we need to send a fetch request and all of this is not necessary anymore instead of the route Handler we can just write a sub action which is a normal function and instead of making this fetch request we can just call this function like any other function directly from our frontend code under the hood nextjs automatically turns this into a post request but the benefit is that our code is much more concise and another big benefit is that this doesn't require JavaScript so we can call our server action even if JavaScript is not enabled in the browser or which is more likely if the user has a slow internet connection and JavaScript just hasn't loaded yet this is the idea behind Progressive enhancement but if this is all confusing to you don't worry you will learn this step by step throughout this tutorial now you might also be wondering what's the difference between react server components and and server actions and when do we use which the difference is that in react server components we just fetch data from our server to render our page so we might have a page that shows a list of blog post and in our server component we can directly make a request to our database because the zver component is executed on the server and then we can render this page directly with this data but server components by themselves don't provide a way to update data on our server to send a post request this is where we usually would fall back to yeah a post endpoint with a fetch request as I explained before but now we can do this much simpler with server actions we will use both server components and server actions in this tutorial so again don't worry if you don't understand this yet you will understand it when we are done with this there is one prerequisite for this tutorial you need to know the basics about the nextjs app rouer I have a full tutorial on this which you can watch first if you are new newer to nextjs or haven't used the app router before I will put a link to this video into the info card box in the top right corner you can watch this first and then come back to this video okay now let's take a look at the project we will be building here okay so we will build a really cool jobboard application here we have an overview of the different jobs available right now we also have pagination we can show the details of a job and here we have Rich Text formatting so we have stuff like writing in bold or lists or italic and we do this by storing these entries in markdown format on the front page we can also filter our jobs for example we can enter a search term we can look for different types or available locations and the cool thing about the location filter is that this only shows the locations that actually exists in the entries showing here so let's try this out let's filter for kutino or however you pronounce this we can then filter the jobs and here is the one job aailable in copertino the title as you can see also adapts to the filter we have applied which is nice for SEO and also user experience and we store all the filters in the URL as you can see up here the location kutino became a URL search param and if we add more filters let's add apple as the search term here then they will also be added to the URL and this is a really good practice because now you can copy this URL and you can share it with someone else or save it for later and we will get the exact same search here again let's try this out let's open this in a newer window with the same URL and we get the same results with the same headline and the same filters applied we can also post new jobs directly via our website now to send this job data to our back end we use of course a server action and for the form validation we use react hook form which is the most popular form library for re react right now and this form here is really cool and advanced in my opinion so we have this validation let's fill out one just with some dummy data we can select what type of job this is we have to provide a company we can even upload a logo and this logo will be stored in Vel blob which is a file storage service directly provided by Vel of course you can also replace this for Amazon S3 for example if you prefer but resel blob is really easy to use so this is why I want to use here let's just use this logo here for now from a previous project then we have to select if this is a remote or onsite job and this form has Dynamic validation to show you what I mean if I select remote here we don't need an office location so the office location is not required but if we select onsite or hybrid and try to submit this then now we need an office location because for an onsite job we have to know where the job location is right so and here we can search through a list of cities so for example let's search for Berlin in Germany select one it will be put here now we have a location selected and then we have to enter either an email address or a website where a user can apply for this job so if I type something in here we don't have to provide an email anymore if I don't provide a website URL we have to provide an email instead so one of the two or both so for example applications at bl.com this would be a valid email and here we have our Rich Text Editor which supports different formatting for example we can make this text here bold and then we also have to provide a number down here for the salary we submit this again this is handled via a server action then we get redirected to this confirmation page and we will even add our own admin backends to this project so when we look at the front page our new job isn't there yet because it hasn't been approved first to approve it we go to/ admin and here we now have to sign in with our admin account which is Florian at coding andf flow.com Let's see we log in we get to our admin back end and here we see our new job which is unapproved right now when we click it we see the details page again here's the markdown here's all the meta information we provided and then we can either delete or approve the job and again we execute both these actions via server actions so we really learn how to use server action in all these different combinations from server components from client components with react hook form without react talk form we will really cover everything here and error handling will be covered as well so when we approve this job it will be shown on our front page here if we delete it it will beun let's delete it because it's just yeah giber so delete and after a short moment this is removed from our database our website will also be progressively enhanced wherever possible Progressive enhancement means that certain functions work even if JavaScript is disabled or hasn't loaded yet which is important for users with a bad internet connection because sometimes the page has already loaded but it hasn't been hydrated yet so JavaScript features are not available yet many people build forms that rely on JavaScript so if JavaScript is not available yet we can't submit our form the idea behind Progressive enhancement is that we can submit our form even if JavaScript hasn't loaded yet the only thing that we won't have is this loading indicator here because this requires State and State requires JavaScript but we can still submit our form without this loading indicator let's try this out I open the Chrome def tools here I press control shift p and then I can disable JavaScript and this will stay disabled as long as this chrome def tool S Bar here is opened but I have to refresh the page first so now JavaScript is disabled but our filter form over here still works the only difference that we will see is that we don't have a loading spinner here in our button but we still get to the new URL and we still see the filter results and this is the idea behind Progressive enhancement make your website functional with minimum requirements so before what JavaScript has loaded but make it look even better enhance it if JavaScript is aailable in this case the only difference between JavaScript and no JavaScript is that we have a loading spinner on our filter button when JavaScript is aailable the rest works the same no matter if JavaScript is aailable or not now again it's very unlikely that someone these days serves the internet completely without JavaScript but it's much more likely that someone has a slow internet connection and JavaScript just takes a while to loadad and with Progressive enhancement we make sure that our page is interactive as soon as the user can zal it and they don't have to wait until JavaScript has loaded this is the ideal situation of course this is not always possible on our newer job form for example on this page we have so much Dynamic validation and JavaScript logic that it would be really really difficult to get this to work with JavaScript for example input fields that depend on other put fields or our location search this would not really be possible without JavaScript and this Rich Text Editor would also not work without JavaScript so this form here will not be progressively enhanced this will require JavaScript to work but I think this is fine because this is not a core functionality of our website and it's very unlikely that someone has JavaScript disabled fills out this entire form and then tries to send it this is very unlikely and there's only like 1% of users on the internet internet that don't have JavaScript enabled at all and these people already know that most of the internet doesn't work withjs so this form here is not really that important for Progressive enhancement it's much more important that our filter on the front works because this is really the core functionality of our website that users see immediately when they open the website so getting this to work withjs is a big win this website is also fully responsive so when the screen gets smaller this filter Side Bar turns into a top bar but it's sticky at the top so it's always visible on the screen you can modify this if you want and these other elements are also responsive pay attention to this time stamp here and this badge in the top right on large screens they are on the right side but when the screen gets smaller they move here into this list together with these other icons and all the other pages are fully responsive as well and we also show the appropriate metadata for example here in the tab title so we will take care of all of this okay let's quickly talk about some other technologies that we will use in this project we will store our data in Vel postest which is a postest database provided directly by Vel it's very easy to add to a nextjs project post or MySQL or mongodb doesn't really matter you can use any database for our project I just want to bring some variation into my tutorials because I have been using mongodb all the time this time we use postgress but we also use the Prisma om to interact with our database so the code is almost identical to if you use mongodb or something else because Prisma abstracts away most of the lowlevel database code as you saw we can also upload files company logos and we will store them in theel Blob which is a file storage provided Again by Vel we will use Tailwind CSS for styling and the shyn component Library we will install all of this in a moment and I will also show you how to set up prer together with tawin CSS prer is a code formatter and it's especially useful together with tnd because it can order all the classes for you which is really useful we will use a few more libraries and learn a lot of important topics in this tutorial I'm really excited to start this and in the end we will also deploy this whole project so you can open it under a real URL yeah and then I wish you a lot of fun with this tutorial please leave a like on this video Happy coding okay time to set up our project and the first thing we do is we open the command line and we check which version of note we have installed with node minus V we need node 20 or higher for this project because otherwise one of the types we use in our backend code the file type is not available it has been added in Noe 20 so make sure you use at least note 20 if not updated by going to nodejs.org and downloading the latest LTS ver version then as usual I have prepared the starting code for this project I will put a link to this repository into the video description below in this starting code I already installed all the packages we will use and added some other fils like the logo that we will use later the benefit of this starting code is that all your packages will have the exact same version numbers as I use in this tutorial so everything will work exactly the same even if you watch this tutorial like two years into the future I give you the choice you can either download the starting code or create the project from scratch I will show you how to do this in a moment if you want to use the styling code then just clone the repository open the project inbs code and then you have to run npm install which will install all the packages listed in the package.json file so just run npm install and then you should be good to go if you prefer to set up the project yourself you can also do this I will provide all the files and packages that we need throughout this tutorial but then your version numbers will be different and you might have to make some changes in your code if you're using newer versions of packages if you want to create the project yourself then open the folder where you want to put this project here we want to open the command line on Windows that's the Powershell and here as usual we create a new nextjs project with npx create minus next minus app at latest then we confirm this with Y and set up this project what is your project named I'm going to call this nextjs jobboard enter would you like to use typescript yes because we are not lunatics would you like to use esland yes as well tawin zss as well we also want to use the srz directory which is just a better way to organize our code and we want to use the app router would you like to customize the default import Alias no and then it sets up a new project again this is only necessary if you didn't download the starting code this will set up a completely newer project after this has finished we should see our folder somewhere here nextjs jobboard there it is we open this in BS code and then we have to install some packages and again this is only necessary if you are not using the starting code in the starting code all these packages are already installed if you ran npm install earlier okay but if you set up the project yourself then we have to install all the different packages that we will use here so we have to list all of them the first one is Prisma that's the OM we will use to interact with our database and to Prisma belongs also this at Prisma SL client make sure to spell this correctly then we add add the zel SL blob which we use to interact with the zel blob where we store our image files later then we need react minus draft minus W Ys i w y g this stands for what you see is what you get this is how you call these editors where you can change the text style directly in the input field space the next one is Mark down minus draft minus JS this is responsible for turning this what you see is what you get add to input into markdown so we can store it in our database then we also install react minus markdown which then formats the markdown so that we can show it on the screen we also need Nano ID to create unique idas and we almost done the next one is date FNS for date formatting and last mostly at clerk SL nextjs clerk is what we will use for authentication there are a few more packages that we will use but later when we set up shety n they will be installed automatically so for now we just press enter to install all of these packages when this is done we should see these packages listed in the package.json here Prisma what you see is what you get all the stuff is in here now we also need to install a few de dependencies Dev dependencies are installed separately because they will only be used in development they will not be included in the production code so we type npmi again which by the way is short for install and then we type dh- save Dash Dev like this make sure to add two dashes here in the front and then we can install the def dependencies which are a bunch of types so the first one is ADD types slash markdown minus draft minus JS the next one is ADD types SL react minus draft minus what you Z is what you get space then prer without types this time space es config prer space prier minus plugin minus Tailwind CSS those are all we need so we install them as well okay I misspelled T when CSS I typed Tailwind wind for some reason so let's correct this and then install them okay then we can close the command line and then we have to do some editor setup because we use tailwind and prer here and we want to make sure that our IDE works properly with it and you have to do these steps even if you don't know the starting code because this is configuration of your IDE I can't do this for you first of all we make sure that we have this official twin CSS intelligence extension installed this is really important for Tailwind because it provides this autoc completion where we can see the available classes there is a little bit of configuration we have to do to make sure this works properly we go into the settings so file preferences settings and here we search for files Association here you want to add an item that has this value star. CSS and then the value of Tailwind CSS like you can see it here this makes sure that we get Tailwind support inside CSS files then also important we search for quick suggestions and we want to turn them on inside strings because this way we get autocomplete for Tailwind classes inside these class name strings then we also use prer in this tutorial because prier is just an amazing code formatter it's very opinionated which means that it makes all the decisions for us where do we add semons where do we add line breaks where do we add parentheses briia takes care of all of this so we don't have to worry but even more important there is a Tailwind preter plugin that automatically zorts all the class names for us this is very useful because otherwise it's easy to lose overview of these classes we already installed the dependency we just need this little piece of cod here you can type this out by hand I'm going to copy this go to our project then we create a file in the root of our project so right here new file and we call it prettier do config.js and the name has to be exactly this and then we paste this piece of code or just type it out by hand like you can see it here okay we are almost done next we go into the es lint ours Z Json file which comes with a new nextjs project by default we change this value here into an array and add a second string which just says prettier this makes sure that eent and prettier don't interfere then I also recommend that you install the BS code extension for Pria again this is an official one so you can trust it this way we can run code formatting without having to do this via the command line we can just do this via the format shortcut in vs code so I recommend that you install this and again there is a little configuration change we have to make in the settings we search for default formatter and res set this right here to prettier to use this as a default format when we press the format code shortcut which on Windows by default is shift altf on Mac and Linux this might be different so I recommend that you install this extension I also recommend that you install the Prisma extension because this way we get auto complete and code formatting and syntax highlighting in our Prisma schema files it's not absolutely necessary but it's very useful we don't have to do any configuration here this just works and lastly if you haven't yet it's also useful to install the es lint extension which shows you all the lint warnings directly in your code editor and these extensions here es l or prer they automatically pick up these packages we have installed and they basically work together with them so usually you execute prettier or ES L via the command line but by installing these vs code extensions we can use them more easily directly in our editor okay and in the starting code I also included this assets folder which contains two images that we will use in our project and I also replace this fa icon for the same logo if you are not using the Starling code then I will put links to these files into the video description below you can copy them from there put the two pngs into srz SL assets and then right into this folder and the fa icon belongs directly into the app folder where you replace the existing fa icon so just delete the old one and add the new one here again you can find these files in the video description all right next we will set up the shety N components shety n is pretty much the most popular component library for nextjs right now the cool thing about these components is that we don't install them as a package instead we run a command that basically copy pastes the code into our project and this is pretty cool because there is nothing hidden from us we have access to all the code we can change it we can update it whenever we want and these components are based on radx UI which is yeah a library that basically provides all the base components and chatsy N adds styling on top of them but again we can change these Tailwind classes in here we can change the code of these components this is why this library is so popular right now we will use a bunch of these components because then we don't have to style them ourselves and especially the form components are really tricky to get right yourself because you need to make sure that the label is properly connected to the input field and any error messages or description messages needs to be connected to the input field as well for accessibility like you can see here and a good form component Library like shn takes care of all of this for you so we will add a bunch of them and there are installation instructions here for nextjs now I already added the shety N components to the starting code so if you downloaded a studying project you don't need to follow these steps the reason I already added them is because again these commands here can change over time the generated component code can change and I want to make sure that this project doesn't break if you watch it like 2 years in the future so yeah only install these components if you did not download the starting code and I will show you how to do this so we need this command here npx shed the Nui at latest in it we need to execute this in our project so let's open the command line paste this here and then run it and this will ask us a few questions oops I already confirmed the first one but this is correct would you like to use typescript yes which style would you like to use we use the default style which color as the base color slate you can pick whatever you want this just changes the default color where is your Global CSS file we have to type in Sr ZM because we have this srz directory slapp SL glob builds with an S at the end. CSS to point shed CN to This Global CSS file you can see here in the app folder enter would you like to use CSS variables yes are you using a custom Tailwind prefix no so we leave this blank where is your Tailwind config JS located again we have to type this out ourselves because we have a Tailwind config TS file not JS so we type in twin. config.sys we keep the default one we keep the default one for utils as well are you using react server components yes right configuration blah blah blah we confirm this with Y and then this generates some files and updates some existing ones let's take a look at some of the files it has generated so it created this components. Json and whenever we add a new shed ZN component to our project it R this configuration file so it knows how to set up this component where to put it and where it finds related files and so on this is what this is responsible for it also installed some additional dependencies like these helper packages here clsx and so on the Tailwind config was updated for us you had add some color variables here you don't have to understand all of this in detail but this will just work and it also changed the global CSS here i' added different color variables so we have a primary color which is a blackish color by default we have muted and secondary colors we also have dark mode Colors by default and you will later see how we can yeah access these colors in our code and then it also added this utils function which is responsible for combining Tailwind classes again it uses this internally and we will later see how we can also use this ourselves when we create our own components okay again this is already contained in this starting code and then we add a fewer components and I wanted to minimize the number of components we add here because yeah I don't want to use a ton of third party code because we don't learn much by copy pasting others code right but nevertheless we will need a few of them we need this button here so we go to the shyn components select button and then copy this installation command again this is already contained in the starting code we execute this in the command line and then it adds this button component there it is it puts this into the component slui folder again it reads this from the configuration file and in here is the button code and again we can change this if we want okay back to the documentation we need two more we need the input which is this styled input field again here is the installation command again we run this in the command line again this adds a new component there it is and one more the form which is here this allows us to use shed CN components with react hook form which is this validation library that we will use later on our job submission form looks a bit complicated but you will learn how to do this step by step so we add this as well copy command line run this and this added this form file here which contains a bunch of code and it also installed a few more packages namely a react hook form and zot which is the validation library that we will use so now we should have all the packages that we need all right so I will push this starting Cod Branch at this exact point right now there are a few more fights that I added here that we will need later but if you set up the project manually I will link all of these fights in the video description and point you to them when we need them the next step is to set up our postgress Database The Blob storage and Prisma next we initialize Prisma for this we open the command line again and we write npx Prisma in it like this and we run this this is described in the Prisma documentation and this creates two files it creates this schema. Prisma file where we put our database schema with our tables and columns and as you can see I get syntax highlighting here and other features like autoc completion because I'm using the Prisma vs code extension otherwise this will probably just look like plain text to you and then it also created this end file where we put the database URL later this is just a placeholder here first of all we go into our GI ignore file dog ignore and at the end we add n so that if you decide to push this project to GitHub you don't push yourn file because we don't want want to push our database credentials next we go to v.com Vel is the hosting provider that also created nextjs and they provide a database themselves and you should get one free database in the free hobby tier so you don't have to pay anything for it we click on storage here in the B dashboard and here we want to create a postest database we click accept we give this database a name I'm going to use the same name again nextjs jobboard you can select the region I keep the default create and this creates this new database and it also helps you configure all of this properly first of all we click here on nf. loc then we copy this this contains our database credentials and we paste this into the N file we can replace the existing stuff in here again this data here is sensitive so you shouldn't share this with anyone that you don't trust because it contains your database password then we go back to the page here and it also provides the Prisma connection code which is really cool so we copy this part here go back to our project go into our Prisma schema file and we replace this part in here so the provider is actually the same but the URL gets replaced for our n variable here we can also delete these commments because they're unnecessary and again code formatting probably only works in here if you install the Prisma vs code extension and then we create our database table in here and we do this in Prisma by defining a model which we call job so this will contain one job entry in our database and then we add a bunch of fields for the different columns that our database should have first of all every entry needs a unique idea so we write idea which is of type int and these types automatically get converted to the appropriate postgress types Prisma takes care of this for us normally you create a table in post Crest via an SQL command create table job and so on but in Prisma we do this by defining such a schema file okay so ID space in space then we type at ID which makes this the unique ID of this table and then we type add default and in here we call this Auto increment function and again I get Auto completion because of the Prisma extension so I really recommend that you install it so this is an ID that automatically increments with each entry then each entry should also have a slug which is of type string in postgress this will have the text type again Prisma automatically converts this here we add add unique so each slack has to be unique then we need a few more strings title string type string this is the data that belongs to each job location type which is also a string location which is the city itself which is also a string but we make this one optional with a question mark because remember remote jobs don't require a location the other fields here are not optional if we didn't add a question mark so we have to provide them to create a new entry then description which is also an optional string theoretically we could put a job in there without a description usually you want to have one but I keep it optional here we store a salary as an INT which supports numbers up to nine digits should be enough for a salary then company name which is a string app application email which is an optional string because we can also provide an application URL instead we have to provide one of the tour so we make them both optional I just press the formatting shortcut to put this all in place and we are almost done company logo URL which is the URL where this company logo is stored which is an optional string then we add a Boolean called approved and here we add another default value and we set this to faults by default so when we create a new job this is set to faults we don't have to pass faults explicitly and then we have to approve this entry before it will be shown in our front end and lastly we need to time stems created at which is of type date time we set the default value to this Now function I think this is self-explaining and then update at which is also a date time and here we set this is to update at and these values will be set automatically for us when we create or update an entry one more thing our model is called job because in our Cod we want to work with this singular name job but the naming convention for SQL tables is usually lowercase plural so we don't want our table to be called job with an uppercase j we want it to be called lowercase jobs so we could either change the model name but this is not great instead that we can also map the name of the model to a different table Name by going down here below the last field we write two ad signs map and the name we want to give the table jobs one more thing to change in here we go up into this client block and we add this preview features field and here we type full text search because we want to use post gr's full text search later to be able to search for jobs and this is how you activate it in Prisma and now we actually want to create this table in our database for this we open the command line again entry run npx Prisma DB push which will push this to our Vel postgress database and then after this is done we run npx Prisma generate to generate a Prisma client from this newer table here that we can then use in our code and every time you make a change to your database schema you have to create a new Prisma client with generate and in SQL databases you also have to either clear your database when the schema has changed or provide a migration we don't have to do this here because we are not going to change our schema anymore but if you want to change the schema of a database that is already in production then you have to provide a migration this is described in the Prisma documentation okay great next we go into our lip folder which was created automatically by shet CN and in here we create a new file called Prisma dots which will contain our initialized Prisma client now in nextjs we have to set up the Prisma client in a specific way otherwise we get an error message because it creates a new client every time we restart our app and development there is this page here in the Prisma documentation that provides the code for such a client I'm going to copy paste this in here this basically creates a Prisma client Singleton that we can reuse throughout our app please pause the video right now and just type this out by hand because this will help you understand what is going on in here and I think it's a bit more useful than just copy pasting this so pause the video type this out okay and then I want to fill the database with some dummy data and for this I added this scripts folder to the starting code I will also link this scripts folder Below in the video description you can copy these two files just create a scripts folder directly in the root of your project this contains some placeholder job data and this seed script which inserts this placeholder data into our database so again if you're not using the starting code then you need to copy these two files then we go into our package.json and we add this seed script here which executes this CJs file then we open the command line again entry run npm not npx this time npm run Z which runs our script that we set up here so let's try this this should insert all the dummy data into our database okay if you're getting an error here make sure that the field names in our placeholder data actually match to the column names you used in your database schema just check the error message if you're getting an error it should give you a hint now our website doesn't have a UI yet right to still Z our data we can use Prisma Studio which we run with npx Prisma Studio this is a feature provided by Prisma and then we can go to this address Here Local Host 5555 and in here should be our job table with the eight placeholder entries we just in inserted and there they are they all have a slack title type and so on and we will later display them in our UI and Via this Prisma Studio you can also change records you can delete them it's just a nice utility tool okay and then we also want to set up Vel blob right so again we go to the storage menu in our Vel back end and here we want to create a blob storage as well so let's select blob here create store again I call this nextjs jobboard okay The Blob star needs a different name than the post grass database so let's call this files so we create this as well again we get some setup instructions we get this one n variable that we also paste into our project so here at the bottom of our n file okay now our databases are connected we have set up Prisma and all the packages we need and now we are finally ready to start writing code okay now that we already have a bunch of jobs in our database let's show them on the screen so we open the command line you can open a new command line if you want to keep Prisma Studio running I just stopped it for now because we don't really need it and then we run npm runev then we can open the website on Local Host 3000 and as usual by default we have this yeah dummy page that comes with a new create next app project and here on the front page I want to show the available jobs so let's close the command line and open the page. TSX file in the app folder we delete this whole main tag here with the default layout we will replace this for our own main tag so if you have watched my nextjs app router beginner tutorial then you know that components in nextjs are server components by default which means that we can make this function async and then we can fetch data directly in here so let's do this let's create a cons jobs and then we call await Prisma but we have to import this manually up here we type import Prisma from and we put this into our lip folder right into this Prisma file here this is where we get our client from and now we get Auto completion and we have this job type on here this is available because we have a job model in our schema and we generated the Prisma client earlier right and this is when this job field here was generated and on this we get nice Auto completion so we want to fetch all our available jobs so we call find many and in here we can pass a filter where CIB braces approved is true we only want to show approv jobs comma entry also want to order them so you should get auto complete here also for the available fields we have a created ad field in our job model and we want to order them in descending order with the newest one at the top okay for now let's just jon. string if I this this data in here to see that we are actually getting something so we stringify the jobs and then when npm run def is running we should be able to refresh our page and here are our jobs from the database so this is the dummy data we inserted earlier but of course a page that looks like this is not a great user experience so let's create a job list item that we can put on our screen I want to put this into the components folder so in the s r z folder there is a components folder here where also our UI components are in we right click on the components folder create a new file which we call Job list item but you can give this any name you want and then in here we export a default function with the same name every job list item will need a job from our database to display right so we create an interface for the prop s let's call it job list item props and in here we will pass a job off type job and job is an import from the Prisma client again this type is automatically generated from our database schema so this is one single entry in our job table with all the fields and then we pass this job as prop down here to the function job list item props we have to put this between curly braces because we are destructuring the props here and then I also want to destructure this field further which we can do by adding a colon behind job curly braces and in here we can destructure the single fields that we need we type them all out by hand title company name type location type location salary the company logo URL and the created add timestamp okay and then in the function block here we return the layout for each job we wrap this into an HTML article tag you could also use a diff but article is a bit more idiomatic it's better for AO if this has the appropriate tag we're going to add some class names here to style this but for now maybe let's just put the title in here so that we can use this component in our page TSX file and then we finish the styling later so let's replace this expression here where we stringified the data into another expression that takes each job and it Maps each of them to an instance of our job list item which looks like this here we create a job list item we pass the job in this map called to it and since we are creating a list here we also have to provide a unique key for for which we can use the unique idea of the job or the slug which is unique as well now when we save this we already see a very rudiment list of our jobs and now we go back into the job list item component and we finish styling this okay so we go to the article class name and then we use Tailwind to style this we turn this into a flex box with flex and we add a gap here which adds some spacing between the single elements we put into this article we also add a border and then we set rounded LG yeah which gives these elements rounded border we also want to add some pading with P5 which looks like this and when we hover over an element I want to change the background color for this we use the hover modifier in tailwind and then we set the color to BG muted which is one of the the color variables that sheds the end installed and it's better to use these variables instead of hardcoding colors because this way we can more easily change them and we can also add support for dark mode so the muted color looks like this but it's a bit too dark I want to add some transparency which we can do with Slash and number slash 60 so now it's a bit lighter and now when we press the formatting shortcut it should automatically order our class name here appropriately because we installed the Pria Tailwind extension right to try this out let's actually put them into a messed up order I'm going to put the flex here and now when I press shift alt f it puts them into the correct order so we keep a consistent style throughout our whole project and Pria also automatically adds stuff like semicolons or parentheses to see this I'm going to change this a bit so we could write our code like this press the formatting shortcut and everything looks great again this is why I'm really a fan of prer okay let's continue re remove the title for now and as the first item here we put an image which is an import from next image here I want to show either the company logo URL or this placeholder image I added let's import this placeholder image first import company logo placeholder you can give this any name you want from at/ assets SL company logo placeholder then to our image we add the srz prop and here we want to use the company logo URL or if it's null the company logo placeholder we also add an ALT text for accessibility in here we added template string which will contain the company name followed by the word logo I think this makes sense we also need to define a width and height let's set them both to 100 there are the placeholder images because the dummy data didn't contain any actual company logos we will upload some later and I also want to style this a little bit we make this rounded with round LG which looks better if the image is an actual rectangular then it will have rounded corners and then we add this self Center class which makes sure that this image stays in the middle vertically even if this item gets bigger okay then directly below the image inside the article we create a diff which will take up the remaining space next to the image to this diff we add class names as well with flex grow we make make it take up the available space as I just explained and then we add space r three so that the single items we put in here have some spacing between them then we go inside this div and create another div in here I want to put the title of this job entry so we create an H to and in here we put the title I also want to style this H tool I want to make the text a bit bigger with text XL and then I want to make the text a bit thicker with font medium okay on my screen this all looks a bit big because I have set a zoom level on this monitor but on your screen it probably has around this size okay and still in the same inner diff right below this headline I want to put a paragraph tag that shows the company name and I want to set the color to a text muted foreground which is this grayish color and again this is a color variable that chyn created for us okay so this is the title and the company name and below this inner div here we put another diff which contains the metadata like the type of the job or the location we read a class name on this diff as well again the text muted foreground because all these fields should have this grayish color okay and in here we put a bunch of paragraph tags which each show a little icon and a text next to it and the icons are provided by lit react which was also installed when we set up shety n earlier okay so in here we put this briefcase icon but autocomplete doesn't work so I have to import this manually import a briefcase from loit react now it should work briefcase we set the size prop to 16 and then we add this class name shrink zero this makes sure that this icon never shrinks if the available space gets too small because this would look weird and next to it we want to render the type value so fulltime part-time and so on but we want to put that next to each other so we style this paragraph tag with flex item Center we add a little gap of 1.5 and then we also add SM colon hidden remember we make our layout responsive when there is enough available horizontal space we want to have the type here as a badge in the top right corner only on small screens we want to show the type here in this list but if there's enough space we want to hide it so we use this screen size modifier SM which will height this element if the screen is bigger than 640 pixels okay and then we need a bunch more of these paragraphs here with icons and some text so let's duplicate this four times we can either just copy and paste or you can also hold down shift alt and press the down key four times at least this is how it works on Windows then we change the second briefcase icon to a map pin now I get autoc completion if you don't then you have to add this icon here to the import by hand so a map pin and here we want to render the location type and this one should not be hidden on large screens only the type and then the date here at the bottom so we delete this SM hidden and then the next one and this one here as well so we remove these three SM hiddens here just delete them but keep them at the bottom one and at the top one of course if you want to build a different layout you're welcome to do so okay then we change this briefcase icon to Globe tour and we change type to the location itself and if there is no location we want to render the string worldwide instead so if we don't provide a z this will just say worldwide let's see if we have an example yet on here a remote job that doesn't have a location then we change the next briefcase icon to the bank note icon and here I want to render the salary but we store this as a normal int as a number I want to show this as a US dollar value with proper currency formatting for this we will create the little utility function so let's go into our utils file again shed CN automatically generated this file when we set it up here we export another function called format money and to it we pass the amount of money as a number yeah and then we format it with a heler that we have available by default in JavaScript we return a newer inter which is not a package reinstalled this is available by default in JS and here we have this number format class then we pass a local string for which we pass e n minus us with an uppercase us like this and this will format this number in US dollar there are also other formats available like Euro for example but we stay with us dollar then the second argument is this JavaScript configuration block where we can pass a style for which we pass currency then there's also a currency field for which we pass USD and then outside of this closing parenthesis here we call. format pass the amount to it and then we return this whole value and this will be of type string now while we are here we will also need another utility function to format the date so let's create a right now export function relative date to which we pass a from date again we return this value right away and here we use format distance blah blah blah again Auto Import doesn't work this function is coming from the date FNS package reinstalled earlier because writing this logic ourselves would be very verbose and I really don't want to do this here so we import this manually from date FNS and in here should be a format distance to now strict function that's a mouthful this is the one we want to use format distance to now strict to which we pass the from date and then another configuration object where we can set add suffix suffix to true the suffix is the word that will be added behind the so either ago or I don't know before probably okay then let's go back into our job list item here so where we have the bank note we want to format our money so we call our format money function and pass the salary value from our database to it and there it is so we get the dollar zone we get the comma and the decimal point at the correct place in the database this is just in int but we format this properly then the last icon down here is this Glock icon and here we call our relative date utility function and pass the created ad time stamp to it and this one is also hidden on larg screens so we will only see this on small screens there it is we added all of them 51 minutes ago okay so two of these icons are hidden on larger screens because I want to put them on the right side for this we create another div below our last div closing tag here above the article closing tag so here we put another div we add some class names this one is hidden by default so on small screens but on SM screens we set this to flex and SM is also where we hide these other icons here right so below SM we show these icons and above SM we show these icons this is how you achieve this responsiveness with Tailwind we also need a bunch of other classes here we want to make this a flex column so the elements are below each other we want to add shrink zero again so these icons don't shrink when there's not much space aailable we want to put these icons all the way to the right side of this element we do this with items end and with justify between screen we put one item here at the top and one at the bottom you will see this in a moment now for the type I want to create a little badge because it looks good for this we need a new component so let's right click on our components folder and create a badge. TSX export default function badge and of course this batch needs the children as prop so whatever we put inside this batch so we create an interface batch props in here we pass the children which is of type react. react node this way we can put anything in there not only text but also another kind of element if we want we destructure this children prop down here from the badge props and then we return a simple span that renders these children and we add some styling to this span of course we could also put the span directly into our job list item but this way we keep it reusable let's save this for now we take care of The Styling in a moment first let's add this badge to our job list item so here we want to render this new badge component which will show the type of job and when we save it it's there but it's not sty yet let's let's do it now let's go back to the batg TSX file and we add the following class names we add the border which we make around it then we need some padding we set the horizontal padding with px2 and a smaller vertical padding with py 0.5 we set the background to BG muted which is this grayish color and the text to muted foreground which is a darker gray then we add text ASM which makes the text a bit smaller and then we add font medium which makes the text a bit thicker so those are all the classes we need going to format this and if you need you can pause the video and type them out then we go back to the job list item and finish this so here is our badge and below it we want to show another span this time it's not a badge it's just a normal span here we want to show the Glock icon again with the same size of 16 as we also use here we close this tag and then we again yeah render the reative date next to it right here but we want to style this span here as well Flex items Center Gap 1.5 and text muted foreground yeah and this is how it looks and again this is responsive this is only shown on the right side on large screens if the screen gets smaller it moves here into this list okay I think our job list items look pretty cool we still have to add some styling to to our front page here so first of all I want to give this whole page a Max width and then sender it on the screen so we add some class names to the main tag we set the max W to 5xl and then we want to Center this horizontally with M Auto which adds the margin Auto CSS attribute I also want to add a horizontal padding of three with PX3 and a vertical margin of 10 so there's some space here above and below where we later add the footer and then we add space y 10 which will affect the elements we put into this tag however I don't want this 10 spacing between the single job list items so we wrap this into another diff like this which gets its own space wire of four so we create this smaller Gap then we also wrap this inner diff here into a section section again is an HTML tag yeah which tells search engine crawlers and also accessibility tools that whatever is down here is a separate section again this is not so important you could also make this a diff but it's a little bit better and the reason I create a separate section here is later there will also be some other elements in here the sight bar and the pagination bar this is where we add this additional SE but Above This section we put another div this div will contain a headline for this page so to this new diff we add some more class names space y5 this time and text Center in here we put an H1 headline which will just say Developer jobs later we will make this headline Dynamic so it changes with the different search filters but for now we just hardcore this but by default headlines are unstyled in TN CSS so we add our own styling text for XL font extra bolt draging tight lowers the spacing between the single letters which just looks a bit better for headlines and on large screens so LG colon we make the text even bigger text 5 XEL so now when we have a lot of space the text is a little bit bigger than on smaller screens and when we hover over LG we can see the break point 1,24 pixels Above This value this class here will be active and below the class without the modifier will be active then below this H1 still inside this diff I want to add another paragraph which says find your dream job so this is just a subtitle and we change the text color to text muted foreground again yeah looks pretty professional if you ask me okay and then I also want to add a minwidth to this whole website so that we can't squish this together indefinitely because at a certain point it's difficult to read even with a responsive layout I want to set a minwidth of 350 pixels for this layout out and the best place to do this is in our root layout because then it will be applied to the whole website so we change the class name of this body tag in here we are already passing the font class name we surround this with back tcks wrap this into an expression so it stays active and then we also add minw but instead of one of these fixed values we set this to an arbitrary value which we can do like this with the scrap brackets 350 pixels and now our layout will never get smaller than these 350 pixels which I think is reasonable okay now I want to set up the filter S Bar and there we will already call our first server action so let's create a new component for this in the components folder job filter siteb bar. TSX we create this file and we export this function here the HTML tag we want to use in here is aide again we could use a normal diff but aide is a bit more idiomatic for a sidebar for now I just want to put uh a text in here and put it into the layout so let's save this and then go into the root layout we put the bar here into the section above the list so job filter Side Bar and there it is it's just a text for now and then we add some classes to the section because we want to align them horizontally right so we make this a flex Box by default this will be a flex column on small screens this will be a below each other but from the MD break point onwards we set this to a flex row so that they are aligned like this left and right we also add some spacing with gap four and then we want the J list to take up the remaining available width right so we go into this div here which wraps the job list items and we set this to grow so now it takes up the available space and here's our Zar then let's go back to the Zar component so now let's style this Zite tag here with class names on the MD break point which is when we show it on the left side I want to give this a fixed width of 260 pixels so now it takes up a bit more space I want to make this stick to the top for which we can use this sticky class and now when we scroll down uh it doesn't work yet because we also have to add top zero for the position yeah still doesn't work there's still something missing I think this is because this right now takes up the full height which we can see by setting a background color I want to set this to H fit so it's only as high as the content in it and now sticky works okay let's remove this background color here instead reset the background color to BG background which again is a color variable in our Global fire here and by not hardcoding the background color we can support light and dark theme then I want to give this a border again and set it to rounded LG so it looks like this and on small screens it's at the top on larger screens it's on the left side then inside this asite tag let's remove this text and instead we want to add a form with a lowercase f in here we put a single form field and forms can call server actions even inside server components remember in server components we don't have JavaScript features we can't execute an onclick Handler on a button for example but the forms action prop works even in server components because this is one of the oldest native HTML features and in nextjs we can pass a server action in here which is just a function and this function will automatically be turned into a post end point which this form can then call let's call this function filter jobs it's not available yet because we haven't created it yet let's do so now we do this in the same file above the component itself we could also put this into a separate file but if this file is not a client component we can put the server action directly in here so we create an async function which we give the same name filter jobs and these ser actions that we call from forms receive the form data as input which is whatever we typed into the form Fields before we submitted it we will take care of the server action in a moment for now let's finish the layout into this form tag we put a diff and on this diff we add some vertical spacing with space y4 between the single form Fields the reason why we add the space here and not to the form itself is because when we use a server action inside a server component nextjs actually automatically adds a hidden input field here that contains the ID of the server component and this will mess up our spacing this is why I created this additional rer okay and in here we put our first input field first we add the label which we import from /. Lael it's important that you don't import the redic one because this one is unstyled we want to import the one that we have in our own components folder and this label component was added by shed CN earlier when we installed the input component we close this label and in here we write search and when we save this and refresh the page we get an error because nextjs doesn't know that this is a server action so spoiler alert we declare a server action by adding this is use server string at the top of the function this way we turn this into a server action and now this should work and here is our label which has some styling applied to it and Below we add an input with an uppercase i again this is an import from our UI components folder this gets a Zelf closing tag and this is already styled because this is coming from shed ZN and I structured this tutorial in a way that we start with the most simple approach of building and submitting our form without react hook form and any complicated logic this is basically just a normal HTML form that sends form data and this works by giving this input field a name I set this to Q which is short for query but you can give this any name you want and when we submit this form this Q field will be contained inside this form data then we can also give this a placeholder text so here I write title comma company comma Etc this is just what will be shown here before we type something in then we should also connect the label to the input field so that when we click the label the input field is automatically selected this is also important for accessibility we do this we have this HTML 4 prop which we also set to q and then we give the input the ID Q as well so this HTML 4 and this ID have to match because this way the browser knows that this label belongs to this input field and now when we click this label the input field is selected but I want to align them properly so we WRA the label and the input field into another diff like this and then we style this diff with flex Flex call and GAP to so now they are closer to each other and then let's also add add some padding to the Side Bar here on the aite tag we add a P4 okay looks much better and then we need a few more form Fields here but the other ones are not inputs they are select fields and shed CN provides such a select component the problem is this one here doesn't work with our JavaScript but we want our filter form to work withjs this is why we didn't add this one we will create our own component which doesn't have this nice styling for this drop down menu here it will look a bit worse but it will work with our JS and that's the important part and I just stole the styling of the select component itself Yeah by copy pasting it from the chn component we can do this so what we do is we create our own select component I put this into the UI folder in the components folder this time because it's a pretty lowlevel component in my op opinion so I pulled it into this nested folder but this is just personal preference this UI folder was created by shed CN earlier so we put a select in here this time I gave it the lowercase name because this is how sheden also names these components and I want to keep it consistent so in here we export a default function called select and in here I want to render a select component for now let's save this and pull it into our sidebar here so that we see how it looks so below this inner diff with the label and the input we put another div with the same styling again Flex Flex call and a gap of two again in here we put a label with an uppercase L this time the HTML 4 is set to location the label text with Z location and in here we want to put our select field let's see how this looks yeah it's completely unstyled by default so let's style this now so back into the select TSX file first of all I want this to take the same props as the normal HTML select so we can customize this from the outside if we want for this repass the props here which we don't destructure this time and we set the type to react dot HTML props and then we add a pair of angle brackets and in here we have HTML select element as a type this way our select component takes the same props as a normal select component including for example the IDE that reset to location and the name that we set to location as well and again now the label is connected to the select field okay back to the select component now we want to style this with some class names but we also want to be able to add additional class names from the outside when we call this component if we want to change the styling we don't want to completely hardcoded for this we change the class name into an expression and then we use this CN function here which shed CN earlier put into our utils file this is responsible for passing different Tailwind classes that intelligently override each other otherwise we have the problem that we might set a different color from the outside but it's not applied because the order of these Tailwind classes actually doesn't matter just because we pass a class to the right of another class in the class name String it doesn't mean that it will overwrite the other class because this is not how a CSS works but CN makes sure that whatever we pass second will overwrite the first value so the first value in the N will be our default styling that we hardcored here and the second value after a comma is props do class name those are the class names we pass to the select from the outside this way we keep this component customizable and here in this first string we want to add a default styling we set a fixed height of 10 which has a certain RAM and pixel value as usual we add a border which we make rounded with rounded MD and again I copy The Styling from shyn then we actually want to hide this downwards carrot icon here because we can't add padding to it by default it just stays at this position so we replace this for our own one we can remove this with appearance none now it's gun then we add the truncate class which makes sure that if the text in here becomes too long it will be cut off and there will be an ellipses at the end this is what truncate takes care of then we set it to BG background again so it's not transparent then we add this border input class which changes the color of the Border if I'm not mistaken and we need a few more classes let me make this big so you can see them better vertical padding of two then we add the padding on the left with pl3 and the larger padding on the right pr8 we add the larger padding on the right because there will be our drop- down icon and we don't want the text to Overlay it then we set the text to a small with text SM and then we need a few more classes that add this Focus ring here on the outside and if you get confused by all these classes we have to add you can also copy them from the GitHub repository of this project but it's better if you type them out by hand so we add ring offset background space Focus colon which is another modifier so only when the select field is focused we want to set the outline To None then we add Focus colon again we want to set a ring of tour then again space and we are almost done the next one is focus colum ring ring uh whatever this does yeah it Styles the ring color I guess then Focus colum ring offset to then disabled colum cursor not allowed which changes the cursor I just want to keep this consistent with shedi 's input field and the last one is disabled or opacity 50 which makes this a bit transparent then we can reorder these classes and now this looks very similar to this input field with the same Focus ring but again since we also pass this class name prop we can customize this from the outside if we want so if I want to set the background color to Red then this will overwrite the internal background color but we are not quite done yet first of all we want to have our drop down icon here right because we removed the existing one because we can't change the position of that one instead we add our own Chevron down icon from Led react and we style this with some class names and then close this tag here I want to give this an absolute positioning on the right with right three and on top three then we set width and height with H4 and width four this is an alternative to using the size prop that we used earlier we can also change to width and height via CSS doesn't really matter and we want to add some transparency to it now since we used the absolute positioning here we have to wrap this whole select into a div to which we add relative positioning for the children and we also need to add the W full class to the input field so it takes up the full available space and I made a little mistake here so this Chef run down icon should not be inside the select it should be below and actually the select can get a Zelf closing tag so we delete this closing tag and make it Zelf closing like this and now it works here's the down icon then we also have to make sure that the props we pass here here are actually applied to the select field right because right now we only use the class name but we also want to apply the ID and all the other stuff repairs and we do this by spreading these props here into this component uh one more change we have to make this way we also overwrite this class name if we pass a class name from the outside so we don't apply our default styling this is why we need the class name separately so we go up here to props we surround this with cly braces in here we want to destructure the class name comma and then we write dot dot dot props this is the rest props syntax this way the class name will not be contained in these props and not be applied down here and we change this one here to just class name because now we destructured this separately and now when we pass a different styling from the outside it should not overwrite our default styling perfect okay and there is one more change we have to make to this component and you can already see why it's useful to use a third party component library because setting up components and making them properly reusable and accessible is actually not that easy later we will also use this select field with react hook form and react hook form needs a ref to this component a ref is a direct reference with which we can access this component and this has a special syntax in react we can see the syntax when we take a look into one of the shed end components somewhere here is this forward ref and this forward ref wrapper is necessary to get this ref inside here it's not a normal prop we want to add such a forward ref here as well it's just a few changes that we have to make but you have to make sure to make them at the correct Place otherwise the code will not compile so we go behind export default before function here we write forward ref then we add a pair of angle brackets the first argument here between these angle brackets is HTML select element and the second argument is this props type that we already used here so here this one we can cut this out and put it here after a comma so now you should have two closing angles brackets here we go behind this closing angle bracket then we wrap this whole component all the way to the end of the file into parentheses because the function is now an argument to the forward rev call that we do here then we go down here and remove this colum and instead we write comma and in here we get our ref as the second argument so this is how the whole syntax looks now we still get our props as before and separately we get this ref and this ref we pass to this component as a prop the normal select component here the default one already handles this ref for us the problem is we wrap this select component into our own component and to keep this ref intact we have to pass it ourselves with this forward ref syntax but the good news is now we are done with the select component and now we have a nice select component that looks good and works properly and we can reuse it in different places okay now we can go back into our job filter sidebar and fill this select down here with values and just like in a normal select we can now put different options in here with a lower case o this works because our select component takes all the same props of a normal select and the children are one of these props so we can pass children here let's add the first option which was say all locations and now it's contained in our dropdown menu here again the drop-down menu doesn't have this nice styling that chyn has but only this one here this plain one works without JavaScript so we have to use this one the one Chad CN uses is technically not a real HTML select it's a custom drop-down component okay so the label is already connected to the select right so when we click this it focuses the select Field track accessibility I also want to set a default value on the select we set this to an empty string and then we set the value of the first option also to an empty string so this is the default value which will not apply any filters and then we want to add an option for each location right but instead of just putting a huge list with all locations in here I only want to show the locations that are actually in our data set which means that we we need to fetch them from our database and this is still a server component so we can make this an asnc function and fetch the data that we need here let's create a con called distinct locations then we call await Prisma again we have to import this up here import Prisma from at SL lip Prisma okay so Prisma do job. find menu again we add a filter again we only want to show it the approved ones so where approved is true then we also don't need most of the data of these jobs right we only need the location so we can add the select field and here we set location to true this way Prisma will only return this one column in our database and not all the other data that we don't care about which is a bit more efficient and then we have this distinct modifier here and in here we add an array with scrap brackets and inside this array we put location as a string and this is the cool thing about Prisma it has these utility functions and filters that we canuse use which are easier to use than writing raw SQL queries but this is not an array of strings it's an array of objects that contains the location we care about so we also have to map this into an array of strings so we could create a separate variable but to keep this more concise we can also directly chain then on here to do something when this promise has resolved so after the closing parenthesis here rewrite then and here we get past all the locations right and we want to map these locations so we write locations right arrow I'm going to continue in the next line just for readability now we take these locations sc. map because we want to take each location object and turn it into a normal string so we make two pairs of parentheses and one pair of curly braces because this way we can destructure this object remember in here is our location we don't get autocomplete here but this field is in there then we want to map this object to the location itself and if this is confusing to you then you can also write this as separate statements you can first fetch this data and then map it but all this does is it takes this location out of each object and it turns this array into an array of strings one more car we attach here we also call do filter and pass a Boolean to it what does this do this removes all the N values in here because remember not every job has a location so the type of location is string or null and with this filter call we remove all the null values so we format this you can pause the video and make sure that your code matches and the only problem with this filter C here is that typescript doesn't actually recognized that we removed the null values so the type of distinct locations is still string or null array so we have to assert the type ourselves which is a bit unfortunate but it's necessary so we remove this closing zolon here we wrap this whole await call from here before await to after the last closing parentheses into another pair of parentheses and then we yeah cast this into a string array like this because now we tell typescript the type of distinct locations is a string array okay and now we have this string array of distinct locations and we can create options in our drop-down menu from them so we add an expression here with curly braises we take our distinct locations and we want to map them to option objects so in here we get past each location and for each location we want to create an option so we write option again we need a key since we are creating a list the key will be the location itself because the location is unique and the value is the location as well the location string and the text be show in here is also the location okay and now we have our drop down menu with our distinct locations isn't this cool cool okay we need another drop down menu for the different job types part time fulltime and so on but this time we don't need to fetch any data from our database those values will be hardcoded but I want to put them into a separate file I want to put them into the lip folder in here let's create a file called job types. TS and here I want to export a const called job types which is a simple string array okay and in here I paste these different values again you can pause the video and type them out but what you add in here is up to you you can also use different values if you prefer we just store them as strings in our database I want to add one more array in here so another export const location types we later need them when we create a new job in here we put three values remote on site and hybrid okay back into our job filter SAR and I want to put the type filter actually above the location filter but below the search input so right here again we created div with the same styling as we already used for the other tour Flex Flex call Gap toour in here we put another label HTML 4 type the label will also z type and Below we put the another of our uppercase as select the IDE is set to type the name is type and again we set the default value to an empty string and again the default value will say all locations and then again we need to map let's actually copy this one down here this distinct locations map call paste it here but we want a map from our job types now so job types we change the variable name with F2 to type and the rest should stay the same yeah now here are our different job types okay there's one more field I want to add and that's a checkbox So Below this diff here right here we create another diff and again we add some class names this time they are a bit different because the checkbox and the label are aligned horizontally so we set this to flex item Center Gap two we want to have the label on the right side so now we put the checkbox first and the checkbox is just another input field which as usual we give an idea which will be a remote we set the name to remote as well and by setting the type to checkbox we create a checkbox field let's see and there it is yeah it's Blue by default let's change the color and let's make it a bit bigger with class names with scale 125 we can make it bigger because we can't use the width and height on here doesn't work on a checkbox and with accent black we make the checkbox black via this accent color attribute here but again you can style this however you want and now I want to put an uppercase label to the right of it with HTML 4 remote and this will say remote jobs and there it is and now we can click the label and it's properly connected to the input field just like like all the other labels okay and lastly we need a button to submit this form remember we are still inside a server component there are no onclick handlers available in here but when we have a form we don't need an onclick Handler to submit it we just need a button with the type submit set on it so here we put an uppercase button again the import from /ui because this is the button that has the appropriate styling we set it type to submit and this tells the browser that when we click this button this form should be submitted which then triggers our server action and again no JavaScript is required for this and the only class we add on here is W full to give it the full available width and this will say filter jobs and there's our button nice and now when we submit this form it will take all the values and put it into this form data and pass it to our server action and we can get these values by their names so each field has a name and this is where we can get these values from let's try this out for now let's just lock one of these values to the console so normally we get a value like this form data. getet to which we pass the name for example CER which is the search input that we pass and by default the type of this is form data entry value which is basically either a string or a file so normally when you work with this you also have to cast this to a string but we will handle this in a better way in a moment for now I just want to lock this to the console and since this is running on the server we will see these loog messages in our console not in the browser so let's try this out we should be able to submit our server action so let's type something into the search field filter jobs and there's our query this is passed to our server action and now in this server action we can execute any backend code we can make changes to our database we can do anything that we can do on the normal server but for our first server action here we will keep it simple we will just redirect to a new URL with the appropriate filters in the search params again I wanted to start with a simple example so that you can step by step learn about these server actions without getting over rammed and by the way if you are enjoying this tutorial then please leave a like And subscribe to the channel especially the like is important because it helps this video find more people and this in turn motivates me to make more of these videos in the future now again we could extract all these values like this from the form data one by one cast them and then check if they are available but the cleaner way is to set up a proper validation schema the most popular validation library right now is sort which was installed together with shyn earlier so let's create such a validation Library I want to put this into a separate file again in the lip folder validation. TS let's put it in here from here we export a const called job filter schema and then we call Z but we have to import this sometimes autocomplete Works some times it doesn't so we want to import Z from Z I forgot the from keyword and on this C variable we can create such an object which defines a sort schema and we want this object to have the same shape as our job filter here on the left and for this we have a bunch of different validation rules the sort documentation is pretty huge you can take a look in here there are all these different rules you can use but we only need a handful of them so we need to add all the names of the fields that we expect right so the first one is Q because Q is one of the names in our form here so the first one is Q we expect this to be a string so we write z do string which is a function and then we append do optional which means that we don't have to pass this string it can also be undefined then we can duplicate this line two times because type and location follow the same rules and again those are the names that we also use in our filter form and the last one is remote this one should actually be a Boolean but URL search params are always strings but we can let sort automatically turn the string into a Boolean so if the string has a value then it's Trophy and it will be turned into a Boolean for this we write z do c which is not a function this time so we don't add parentheses then we write Boolean which again is a function do optional again this takes the remote string in the URL search params and if the value is there it turns it into the Boolean value true and all the other values are just noral strings we will later create another schema for our new job form which will more complex but this is a very simple one and one cool feature of s is that we can automatically create a type from the schema which is really useful later in different places where we want to pass around these filters so we export a type here called job filter values equals z do infer angle brackets and in between the angle brackets we write type of job filter schema and this takes the schema and it turns it into a typescript type with the same types and optionality as we defined in our schema here isn't this cool so for now we can close this file and now we basically want to tell zot take all this form data and compare it with the schema so let's remove this line first of all we have to turn the form data into a normal JavaScript object which we do the following way const values equals object with an uppercase o from entries and to this we pass form data. entries which is a function so we have to call it this simply turns these form values into a JavaScript object which we can then pass to zot so in the next line we write const pass result equals then we take our job filter schema which re export from our validation file call. pass and to this we pass this values object so now sort will compare these values with the job filter schema if they don't match it will throw an error but if they match we now have these validated values in this pass result and now we have the appropriate types and the field names in here and they are validated so we know that these values are available whereas if we access them via the form data directly we don't have this guarantee because we can put anything into this form data again we can do all the backend logic in the server action but for now we just want to redirect here this is all the work we want to do here now let's destructure the pass results so we remove the variable name and instead we add curly braces and in here we find our different values so C type location and the remote Boolean which now have all the appropriate types then we want to construct search params and redirect to the URL with these search params so we create a const search params equals new URL search params which doesn't require an import to this rep pass a JavaScript object and in here we want to put each of these values as a search peram but only if it's defined because all of them are optional we can pass some of these filters or we can pass none or all of them so they are all possibly undefined this is why we add them the following way we make a pair of parentheses and write dot dot dot in front of it in here we write Q to emberson signs then we create a JavaScript object like this and then here we write Q column q. trim trim simply removes all the weight spaces so if I type something in like this space space space stripe then of course we don't want to include the white spaces in our search so trim removes them and with this weird syntax here we just pass this part here conditionally if Q is defined then we will put this into the search params if Q is not defined then it will be omitted this is what the syntax here does then we do the same in the next line so we spread this expression type to erson science and since we don't need to change this value we can just use the short form like this instead of type colum type okay let's duplicate this line we also do the same for the location and one more time below if remote has a value then we also want to add remote but remote is a Boolean we have to turn this into to a string and if remote is true so if our checkbox here is checked we add the string true to the search params you will see how exactly this looks in just a bit and then we just want to redirect to this new search params for this we use redirect from next SL navigation make sure to add the correct import and here we add the newer URL in back Tex which is slash through our root URL and in here we put our search params variable to which we call to a string which turns this into the URL search param syntax so in case you don't know what URL search params are they always look like this they start with a question mark after the URL then the name of the field for example Q our query and then the value then the ERS s location equals and then the location value and so on this is what we get from these search params to string here okay but instead of just talking about this let's just try it out so I refresh the page after saving all the changes and now whatever I add in here should become part of the URL when we send this we don't have a loading indicator yet but it already works okay but we have to add the question mark ourselves I didn't notice this so here after this slash in our redirect col we add the question mark because this indicates the start of the search params let's try this again so again I type in flow and the filters don't work yet because we didn't actually filter our results yet but the values are already added to the UR let's also try the remote filter remote true and this is how this looks now when we take a look into the Chrome def tools and into the network tab we should see a post request when we sent s our filters and there it is as you can see here it's an HTTP post request and this post request is sent to the same URL as the page we are currently on so this is some magic that nextjs does for us it creates this post endpoint basically and to this post endpoint we pass all the form data which you can see here and here is the coolest thing in the response of this post object is actually already the content of the new page after our redirect and this is important because it would be very wasteful if we call a server endpoint and then make a redirect just to apply some filters to the URL this would be two requests and there are other ways to execute this with a single request but these server actions are optimized so that it already Returns the new page in the response of the post request itself so we actually only have one request to get get our new page and since we use this inside a server component without any client side features this will also work with JavaScript disabled so let's try this out disable JavaScript I refresh the page we don't have our default values set from the URL yet but we will implement this later let's try this out again I want to go to flow and this works withjs and this is a really good thing because again some people have a slow internet connection and JavaScript might take a while to load and this way our page here is interactive immediately the user doesn't have to wait if we would do the redirect operation client s side via the router for example then we would require JavaScript to execute this but by using a form action and a server action it doesn't require JavaScript okay and the next step is to actually apply the filters and filter our results here just one more thing TI uh the type says all locations at the moment this should say all types so we change this one here to all types okay better okay now we want to make sure that our filters here actually work and we also want to add a loading state for our filter button here right because right now we don't have one but this will be interesting because we are inside a server component and we don't have state inside server component so how are we supposed to get the loading State into this button you will see it in just a moment but first of all I want to add an error page because these server actions can throw errors we don't expect any errors here and there shouldn't be any happening unless maybe if we insert invalid values for any of these filters but I think even this should not be possible because yeah those are just strings but still we add an error page in case we get an error anywhere in our app basically so we open our site bar we go into our app folder and directly into the app folder we put in error. TSX this is a special file in the nextjs app router I covered this in my nextjs beginner tutorial but this is simply a page that will be shown if there is an error thrown in our component that we don't catch ourselves so the error page has to be a client component so we add to use client directive at the top and then I want to ort a simple page here so export default function you can give this any name you want I'm going to call this error or error page and again in here we put a main tag and in here I want to put a headline and I want to give this headline the same styling as we have here on our front page but I don't want to keep copying these classes over and over again so what we can do is we can create an H1 component that contains this styling so let's do this let let's copy these class names here then create a new component and let's put it into the UI folder here let's call it H1 TSX and here we export a default function that we call H1 with an uppercase H this will receive the same props as a normal H1 tag for which we again can use this existing react type so react HTML props angle brackets HTML heading element this one here not head element but heading element this way we can pass the same props to this component as we can also pass to the normal H1 and in here we simply want to render in H1 we can give this a self closing tag we spread the props so we can pass anything to this component component that we want as long as it's part of the heading element props and then we want to set the class name here to the same ones as here so we add a class name but again we turn this into an expression and use this CN function then we pass these class names as the first argument as a string and the second one is props do class name again so that we can change the styling from the outside okay then we replace it in our front page here so we don't have to style this anymore instead we use the uppercase H1 and import it from our UI folder that should have the same styling as before and then I also want to use the same headline on the Arrow page so into this main tag we put an H1 tag with an uppercase H the headline will just say error eror and below a text that says an unexpected error occurred and some styling on this main tag here so class name M Auto to center it on the screen we add the same Max width of 5xl that we also used on the front page the same vertical margin and horizontal padding and reset text Center here as well and one more space y5 to add some spacing between the headline and the paragraph okay bimo and now to see this error page we can throw an error here in our server action just for testing purposes throw new [Music] error by Zinger and we also have to comment out this part here below otherwise it won't compile okay let's try this out let's save it and then try to submit our SAR and here's our error page later when we add the nafar and the photo to our page they will also be shown on the error page but again in normal us of our website we never expect the user to even get here okay and then to actually apply the filters we have to add some logic to our page where we fetch all the jobs now I don't want to make this component here to to Big so let's extract the results into a separate component so let's cut out this whole diff here below the job filter SAR with the grow and space y4 let's cut this out and put this into a seate component which we call Job results. TSX so here export default function job results and and then we paste this div in here we import the Jo list item component and of course we need our jobs here so we take this find many call from our homepage and we move it into this component so it's properly encapsulated we also have to make this function async again this is still a server component and we need Prisma over here now we can save this and we can render our job results here and we should still see the same as before okay and remember we get the filter values from the URL they are these URL search params and a server component in the nextjs app router can receive these search params as props for this we create another props interface let's just call it page props and in here are these search params and you have to spell them like this in ch case because this is a special name in nextjs and in here next JS we put whatever we put into the URL search params and in here we expect the query which is optional in form of a string and three more optional strings the type the location make sure the spelling fits to the name of your sibar Fields here your input fields and remote but remote is not a Boolean because all search params are strings we just turn it into a Boolean later when we pass it okay then our homepage can receive these search params so we destructure the props with C Braes and reset the type to page props so now in here are the search params which we can destructure into a single components Q type location and remote then we can pass these search params to our job results to filter the results and also to the site bar to set the default values when we refresh the page but again remote should be a Boolean right but the search param is a string so let's convert it here for this recreate a new object above called filter values and we set the type to job filter values where is this coming from this is the zot type recreated earlier which is generated from our schema now we get autoc completion in here this expect cure which we just pass directly from the search params type as well location only remote we have to convert because the search param again is a string but we expect the Boolean so if remote is equal to true then we pass true here otherwise we pass false and remember in our job filter sit bar we added remote true as the search param in form of a string if the remote checkbox in our form was checked so we receive this here and here we turn it back into a Boolean to get the job filter values then let's go back to the job results component and make it accept the filter values as props so interface job results props which will take the filter values of type job filter values which again is our Z generated type so now in these props here job results props we will find our filter values which again I want a destructure so in here Q type location and remote which is now a Boolean now for the search query we want to use post cr's full text search capability because this is just a bit more powerful than a rare query this is described in the Prisma documentation we already added this full text search preview feature here and then we have different operators available for an end quer so text that contains multiple words we use this erson sign and this is what I want to use for our search string so all searge terms have to match so let's go back into our code add this start of our component before we execute the find many query we create our search string so we create a con search string and what we do is we take our cure which is just a single string but it can contain multiple words we call do split on it this automatically adds the save call Operator because it can be undefined we want to split it at empty spaces because I want to replace these spaces for the ENT operator so we search for separate words so we split it then we call. filter to filter out empty spaces in filter we write word white Arrow word. length is greater than zero so for example when I type in word one and then a lot of spaces and word two I don't want to create a separate entry in our search string array for every single space right this is why we filter out the ones that are empty and then finally We join this string again because split turns it into an array and then in here we write as a string space Amon sign space which again is this postgress search operator so to recap what this does let's say we type into the search stripe front end for example two words then we don't want to search for this exact string in our database because it's very unlikely that we have this combination of words in there instead we want to add this ERS operator in there so that we search for stripe and front end but as separate words so they can be in separate positions and this is what we do here by first splitting at empty spaces and adding this ENT operator and then the way we use the search filter looks like this so in this sare filter here we can search for different fields like title for example curly braces and then here we have search and to this we can pass our search string now I don't only want to search in the title I also want to search in other fields and to not make this expression here too big I want to store this filter in a separate variable which I put above so let's create a const search filter and we give it the types so that we get Auto completion and for these wear Clauses here we can use this Prisma which we have to import from the Prisma client uppercase P do job Weare input type which is automatically generated from our schema and when we use this we get the same order complete that we have down here in this wear block okay but we only want to pass the search filter if search string has a value right so we write search string question mark then we put an object in here with the filters and Below column if search string is empty we pass an empty object and the object here at the bottom stays empty but we put our filters in here if the search string is defined okay I showed you the search syntax a moment ago we want to execute this in here but on a bunch of different fields and we start this with this all uppercase or filter to which we have to pass an an array this way we say we want to search in the title and in the company name and some other places but it's enough if only one of these places match they don't all have to match this is why we add an or filter here okay and then curly Braes and then we get autocomplete in here because we use this job wear input type and then we use the same syntax I showed you a moment ago title colon curly bracers search colon search Str and this is how the syntax looks but to this or array we add a few more so we just duplicate this line four times so that we have five in total and then all the other fields you also want to search with your search string in I want to search in the company name the type so that we can type in a full stack job for example and research in this type field in our table I also want to search in the location type and in the location but you can customize this if you want different Behavior okay those are the search filters but we still want to apply the other filters of our Zite bar which are these drop- down menus right for the type and the location and the remote checkbox so we create another variable below which we just call wear and again this is of type PR job wear input to get the same Auto completion as up here and I put this into a separate variable again for organization because when we put the code directly in here it will be harder to read so instead I put it into this variable here this time we use the end operator because all the filters of our SAR have to be applied when we type in a search and we select a certain location for example then they all have to match in order to show the result so in this end filter we first pass our search filter which is the thing we constructed up here and then our type filter again we only want to put it in here if it is defined which we do with this syntax because now we are in an array and not in an object so this means if type is defined put this type filter in here instead put this empty object in there which doesn't have any effect at all comma then we do the same for the location which we also put in here conditionally comma and remote remote question mark and if remote is true we want to return only entries that have the location type remote which is a string because we store this as a string value and again if remote is false we don't want to add a filter so if the remote checkbox is not checked we actually return turn remote and onsite jobs because I think this makes more sense and then we also only want to show jobs that are approved right this is the filter that we already had down here but now we replace this for this newer more complex wear filter and since this variable has the same name as this key here we can use the short syntax again and just write where and this will now be applied to this find many call together with the search filters and these other filters here let's try this out let's save this and then pass these filters to a job results so here we have our job filter values that we read from the URL search params and we pass them to the job results and then they should be applied to the Prisma query and this works because we have this query active flow and it's searched in the title here let's search search for stripe for example and there's our stripe job we can also search for cities uh cotino was one that we had right or use one of these other filters here should show the same result or maybe let's search for part-time jobs if we have any filter and there they are nice later we will add pagination and metadata to this page like an appropriate page title for now I also want to add an empty view because if we add filters for which we don't have any results right now this is just empty so let's go into our job results component again down here to the return block below our map call inside the diff we add another expression where we check if jobs. length is equal to zero then we want to render in empty view so this is just a paragraph with some class class names text Center and M Auto to Center this this will say no jobs found try adjusting your search filters and let's see there it is yeah pretty cool I think now I also want to use these filter values from the URL as the default values on the sites bar so that when we refresh the page or open this URL directly we have them applied and we don't get reset to this default state so let's go into the job filter sidebar once again which now needs some props so here below our server action interface job filter zbar props and again we will pass the job filter values to it but this time I'm going to name them default values because it's a more fitting name because those are just the default values for the input fields we destructure them down here as usual so job filter zbar props and in here are the default values and then we apply them to our different form Fields so let's start with the title input field which is here default value equals default values. Q if it doesn't have a value the field will stay empty if it has a value it will be put into this search field then our select below which already has this empty string as the default value we change this to a default values do type to Vertical bars empty string so that if we don't have a value in the URL we still use our empty string for the all types as the default value we do the same in the next select field again default values. location or an empty string and on the check box down here this is called default checked which again we set to default values. remote if remote is true then we want this checkbox to be checked of course we also have to pass this prop here to the job filter SAR on our our page and then our page should render again and we can already see that it works because it applies the search query and our type filter to the site bar after refreshing the page and again this is especially important when we share this URL with someone because when they open the page they should see the filters already applied right it's just a better user experience okay then we want to add a loading state to our submit button but again we are inside a server component in our page here and in server components we don't have state or effects so we can't add a loading State here so how do we do this this is where this new react hook use form status comes into play this is a pretty new hook and it has just been marked stable in next sh S14 this allows us to create a small child component that can be rendered on the client which receives the form status of the p parent so we can keep the page as a server component and only this button will be a client component and then here we can have our loading State there's also another new hook with a very similar name use form state which we will also use later in this tutorial so what we basically do is we want to extract this button here into a separate component which can then read this form state so we create a new component in the components folder I'm going to call it form submit button I think this is a fitting name and I put this into the components folder and not into the UI components folder because I think this is a bit higher level than a normal button but this is up to personal preference this has to be a client component because in here we want to call our use form status Hooks and we can only call hooks inside client components we cannot use hooks inside server components this is also why we can't use State there okay below we write export default function with the same name form submit button this button will take the same arguments as a normal button so we write props and again we use this react type react do button HTML attributes angle brackets HTML L button element this way again we can pass all the same props to this form submit button that we can also pass to a normal button like the type or class name okay and at the top of the component we destructure a value which we get by calling this use form status hook which is an import from reactor doesn't take any arguments and in here we have four different values actually data method pending usually the pending value is the only one you care about this is the loading State for the parent form so when we submit our job filter SAR form pending will be true and we can use this in here to show a loading indicator so in here we want to render a button uppercase B from our UI folder because this button should be styled here we first of all spread all the props that we passed to it so they are applied we hardcor the type to submit because this will always be a submit button this is why we called it form submit button and then we can use our loading state in here for example we can make this button disabled if pending is true but buttons also have this disabled prop that we can pass right and we want to take this into consideration as well so we say if props do disabled is true or if pending is true we want to disable this button so that we can still disable it manually if we want to in here we want to render the children which is just the text that we put into the button but also a loading indicator next to it so we create a span for styling to align these two elements horizontally we add some classes to the span Flex item Center justify Center and and a gap of one in here we put props do children which is whatever we put between the opening and the closing tag of the button so this text and above it so to the left of it we want to show our loading indicator but only if pending is true right so we write in an expression pending to ENT science and for the loading spinner we can use this loader to icon from lit react we set it size to 16 and we can make it spin with this animate spin class which again is coming from Tailwind this time we don't need to forward the ref because we only use this inside forms with this submit value and this doesn't require a ref okay great now this button is finished and we replace the button that we have here for this form submit button and since the type is already hardcoded we can remove it from here but we keep the class name okay and now this should work and to recap our job filter sidebar is still a server component only the form submit button is a client component this is why we can have our loading state in here but this use form status hook reads the form status of the parent which is our job filter setar server component and while this form is submitting pending will be true and we should see our loading indicator so let's try this out yeah and there it is whenever I click this button we see our loading indicator which makes this website feel much more responsive right because now the user can see that something is processing but again Progressive enhancement still works because this whole page still works with our JavaScript if I disable JavaScript we don't see our loading indicator anymore but the filter still works and this is really the magic Behind These newer us form status and US form State hooks from react they enable Progressive enhancement where before we would always use JavaScript okay and next let's add the enough bar and the fold to our page and the metadata like the page title and then we will add another form through which we can submit a newer job this will be super interesting because next time we call our server action not from a server component but from a client component and we will use react hook form together with it okay let's create a enough bar over which we can navigate to our new job form I'm going to put it into the components folder call it nafar and here we just return a a layout and again the idiomatic HTML tag to use for such a enough bar is the header tag and in here we put enough which indicates that we have navigation links here and into the snu bar I want to put two elements on the left side we want to have the logo and the name of the website and on the right side I want to have a link that looks like a button that brings us to the new job form so for now let's just put um element one and element two in here so that we can get the layout right let's save the Nar for now and go into the root layout. TSX file we put the N bar right here above the children inside the body tag this is where it belongs children is the page itself and the snuf bar should be shared on each page so there's element one and element two up there okay back to the Nar file let's style this so first we go to the header tag and add the class name Shadow to get this very subtle Shadow here at the bottom then I want to give the nbar content a fixed width otherwise this will spend the full available width we do this on the enough because the shadow should take up the full width only the content should be limited to a Max width of 5 XL just like the content and we also have to add M Auto to Center this on the screen and now the shadow takes up the full width but the content has a limited width let's also add a pading I want a horizontal pading of three and a vertical padding of five then we make this a flex box at item Center and justify between justify between will push one element to the left side side and the other one to the right but I think this only works if they are actually wrapped into text yeah now it works but of course we want to replace these placeholders here for the actual content so again on the left side should be the logo of the website and the name and we wrap this into a next link because when we click them we want to get back to the homepage so we add an AG ref to slash the homeage page then we style this link with flex item Center and a gap of three in here we first put a next image again this is an import from next here I want to show the logo which was part of the starting code we need to import this import logo from at/ assets SL logo PNG here now we can display this here there it is it's just a bit big we can constrain the width and height and we should also give it an ALT text for accessibility which describes this image flow jobs logo because flow jobs is the name of the website and I also want to have a text with the website name next to it so inside this link which is a flex box we put a span for the text which will say flow jobs again there it is and we want to style this text we make it bigger with text XL we also make it bold and again we use tracking tight to reduce the space between the characters yeah there's is our logo and when we click it we get back to the front page which removes the URL search param which is a nice way to reset these filters okay and still inside the enough on the other side I want to put a link that looks like a button so we use a button but we add this as child prop this is a feature of shed CN you can see how this is implemented inside the component because again we own the code this feature is pretty cool because this way we can make this element look like a button but the actual element we render there the actual HTML element will be whatever we put as the children here and this is important because if you have a link on your page then it should be a link and not an HTML button for accessibility reasons so let's try this out let's put a link in here which will Point to/ jobs SL new we haven't created this page yet but we will do this in a bit this will say post a job so up here is our button but when we inspect this in Chrome def tools we can see that this is an AAG this is a normal link this is not a button and this again is better for accessibility you should always use the appropriate HTML Texs okay very nice and I also want to have a footer just because it looks more professional the phoo is not that important but it looks good so let's put it into the components folder as well for. TSX and this time I'm just going to copy paste it because this is just boilerplate code there's nothing special in here and it's a bit a waste of time to build this in this video if you want you can pause the video and type this out you can also copy it from the GitHub repository or you can leave it out completely because again the footer is just there for aesthetic reason since none of these links here actually lead anywhere this is just because it looks better so again you can add this if you want I'm going to save this and then put it below our children in the root layout right here and there's our phoo nice okay next I want to fix the meta data of the page because the page title still says create next app which is defined here in the root layout let's change this now on each page I want to have a page title and then afterwards I want to have a vertical bar and then flow jobs I want this on each page for this we can use a title template because this way we don't have to repeat this pattern for every single page instead of a string we pass an object to title and in here we can define a template where we can add a placeholder with percentage sign s this will contain the title of the child page and then vertical bar flow jobs so when we have a page yeah privacy policy for example with the title privacy policy then this will say privacy policy vertical bar flow jobs but we also have to define a default value in here for all the pages that don't have their own title in this case it will just say fla jobs and here we have to put a comma okay so on the front page we can now see the default value let's also change the description to find your dream developer job of course what you put in here is up to you this description is not visible in the browser but it would be visible if we pasted a link to our website on social media for example I also want a more specific title on the front page so in our page TSX file I don't want to hardcoded title like we did here instead I want this to change depending on the filters we have applied and I want this H one to be the same as the meta title of the page so let's create a reusable function that generates this title for us we put it into the same file let's call it get title and to it we will pass the job filter values which remember contains the query the type location and the remote Boolean and we use this to generate our page title then we create a const title prefix which will check if we have a query then I use a Turner operator if we have a query then the title prefix will look like this first the query at the start of the string and then the word jobs so if we search for few flow then it will say flow jobs then we add the column of the tary operator if we don't have a query we check if we have a type filter in which case we want to have a similar title which has the type at the beginning and then says Developer jobs so if the type is parttime then this will say part-time Developer jobs again colon now there are different opinions about these nested operators and their readability but if every branch is just one single line then I think they are fine so if we don't have a type filter then we check if there's a remote filter which again we do with colum remote question mark then it will just say remote Developer jobs and the last one the file back is all Developer jobs that's the prefix it will be one of these strings and as the second part of the title I'm going to call it title suffix I want to show the location if there is one so if we filtered for a specific location I want this to say space [Music] in location and if not I want this to be an empty string then we don't append anything and lastly we just return the full title again put this into a backtick string and then here we put two variables the first one is the title prefix and then immediately afterwards without a space the title suffix because the space is already in here but this should be called suffix and not suff okay let's try this out down here where it currently says Developer jobs let's now put an expression call get title and pass the filter values to it and that is all Developer jobs remote Developer jobs let's search for flow flow jobs if we don't have a query but we have a type then it should say contract Developer jobs and the location should be appended to the end isn't this cool and now I also want to use this for the meta title so in case you don't know how to define dynamic meta data I cover this in my nextjs beginner tutorial and in my nextjs SEO tutorial when we have static metadata like here we can just export this metadata object but now we have Dynamic metadata because it depends on the search param we pass to the page and to generate metadata dynamically we have to export a function called generate metadata and the name has to be exact otherwise nextjs will not recognize it this function can receive the same props as our page so we set the page props here and in there are our search parameters which again we want to D structure and the return type of the function is the same type as this metadata object here so let's add it so we get autoc completion metadata from next and now we have to return such a metadata object here and we get auto complete in here we can set the title and for this we want to call our get title function again but this time we have search parameters we don't have filter values remember the difference is that remote is a string in the search params but a Boolean in the filter values so we have to do the same as down here we have to create these filter values from the search params let's try this out and now it says contract Developer jobs in aova the only problem is this placeholder here doesn't work on the front page it only works on child pages so we have to add this manually here so let's wrap this whole get title call into a back Tex string let's put it into an expression like this and then at the end of the string we add the same vertical bar flow jobs again so that we have this as part of our page title again this is only necessary on the front page all child pages of this root layout will have this applied automatically and this meta title is also great for SEO because this can actually show up in Google like this contract Developer jobs in otoa if someone is searching for these kind of jobs one more little thing I want to fix right now so now we have filters applied here in our search bar right now when we we click the front page we remove these filters from our URL but they are not removed from our sidebar this is because react automatically maintains the state of this component but there is an easy fix for this we go into the job filter sitar component again we scroll up to the form and we want to force react to rerender this form if our filters change and throw away all the existing state that is saved in these Dom elements and we know that our filter has changed when the default values have changed right because if our search parameters are removed the default values change and then we want to render a new form we can do this by adding a key to the component and when this key changes react will throw away the Old State of this component and instantiate a new one the easiest way to do this here is to just turn our filters into a string with Json stringify because that's the easiest way to compare the old state with the new state if one filter changes this whole string will be different and then it will be rendered from scratch so let's try this out let's apply a filter PA time when we refresh the page the filter is still there but when we click the front page link the filter disappears amazing next we will build our new job form which is a more complex form than our sit bar filter here and there we will also use react hook form and call our server action client side but this is a really important topic to learn okay let's start by creating a new validation schema for creating a new job we put it into the validation file I'm going to put it above the job filter schema so we export another const called create job schema and the cool thing is that we will reuse the schema on the front end end and on the back end to validate both places again this is a z. object just like our job filter schema but it will be a little bit more complex so every job needs a title right we have to put the same fields in here that we sent to our back end that we want to store in our database so all of these here basically except for the idea and these time stamps the title is a z. string but this time it's not optional the title is required right so we don't append do optional instead we append Min one if we don't set Min one then if the string is not undefined but an empty string it will still count this way we make sure that this string actually contains content and as the second argument to Min we can also customize the error message let's set this to required because this is more readable than the default error message now we will reuse this required string pattern a couple of times so let's actually cut this out and put this into a separate variable so that we can reuse it above we create a const required string and then we assign it to this Z schema so this is a little schema basically and now here we can just say weire string and I also want to give the string a Max size so that we can't pass a huge title so let's aent do Max 100 characters okay the type which again is part of our schema because this is the data we expect is also a required string and we can leave it like this but we can also check that it's actually included in our job types here so that we can't pass any arbitrary value for this we can either make this a sort enom but it's very difficult to customize the error message of enome so instead I use a normal string but we can append this refine function where we get past the value so that's the type string we are passing and inside refine we can Define our own rules and this just has to return a Boolean if this passes it should return true otherwise false so here we can check if job types which is our array from our job types file include this value if it doesn't include it then again this section here will fail and Z will throw an error again we can customize the error message that's the second argument to Define I want this to say invalid job type okay that's the type field company name is the next one which is also a required string with a max length of 100 then we have the company logo and we don't get autocomplete in here so we have to be careful that these Keys here actually match all the keys in our database schema and also the keys in our formulator because this is the data we then actually pass so the company logo is a file right it's actually not a string because what we pass to the back end is the file itself which we then save to The Blob storage and then we get a string back but here we expect a file let's put this into a separate variable for organization because this file schema is a bit larger we create a const company logo schema which is z. custom this way we can create our custom validation type we set this to file or undefiled with a vertical bar like this between angle brackets this is the type we expect in the schema why undefined because we don't have to provide a company logo right we can also leave it empty okay so this custom is a function so we call this but then we still need to apply the rules that we want to check for so no semicolon here instead we append do refine again with which we can check for our own rules here we get pass this file and to this we pass a function and again we have to return true if the file is valid now in the beginning I told you that you have to install nodejs 20 and this is the reason this file type is not available on the back end in njs versions below TR this is why it's important that you update your njs version okay let's take care of the re fine we want to pass this if file is is false here so if we don't pass a file remember the file is optional or to Vertical bars if we provide a file then we want to check that file is an instance of this file type so that we don't pass any other data type for this company logo field and we can also check that this is actually an image file and not something else like a PDF for example which we do with file. type do starts with and in here we pass image/ as a string and then we wrap this part here after the two vertical bars into parentheses because these two conditions should be checked together now on the front end later we will only allow selecting image files but front end validation alone is not enough you can still call our back end with any arbitrary value and they could send another kind of file that's not an image or or not a file at all so we have to enforce this rule on our back end and check for the data that we actually expect and the reason we have to write all this logic is because out of the box zort doesn't really have a type that checks for an image file this is why we write our own logic with this refine function and again the second argument to refine is the error message we want to show which will say must be an image file so if we try to upload something that's not an image file then we will see this error message and I also want to check for the file SI so we append another refine below where again we get pass the file object and then we can validate it again we want to return true if file is false here because again we have to allow not uploading a file at all but if the file is defined we want to check that the file size is less than 1,00 24 * 1,24 * 2 so this is the number of bytes this makes it 1 kilobyte * 1,000 is 1 Megabyte * 2 is 2 megabytes so we allow a Max file size of 2 megabytes and again we Define an error message as the second argument file must be less than 2 MP so this is our company logo schema and now we can pass it down here for the company logo field the next one is the description which remember is not required so let's use a normal z. string I want to set a max length on it let's set it to 5,000 characters so it doesn't become too big and then we append do optional because we don't have to pass a description the next field is the saler which will also be a string not a number because form Fields always contain strings even if the input type is number what we actually submit is a string but we can check that this string looks like a number for this I want to create another little string schema which again I put into a separate variable above let's call it const numeric required string because it's both numeric and required we use our required string schema and then we can append a r x yay reg X in here we add the following reg X which looks like this pause the video and type it out this makes sure that our string looks like a number so it only contains digits and again we can customize the error message must be a number okay that's our numeric required string let's let's use it down here for the salary and let's also make sure that this number is not too long because otherwise it will exceed the limit of an integer when we pass it to a number so the max we will allow here are nine digits this way it will fit into an INT and it's also large enough for any salary because who makes a billion a year and then we Define the error message again number can't be longer than nine digits and the user will actually see this error message in the form or as the response from our back end if they send the data directly to our back end okay still missing are our application email and application URL but we need some special rules here because even though they are both optional one of them should be required so we have to check that for this we create a separate schema again so below the company logo schema for organization we create a const application schema which again is its own C doob that contains the application email again make sure the spelling is correct which is a z. string with a max length of 100 characters then we can use this do email function which is provided by zot this makes sure that this actually looks like an email with an add symbol and the domain ending but this should be optional right so we append do optional as well but the way this works is a little yeah kind of buggy because even though this is optional an empty string will not pass our email filter here to fix this we also have to append do or which is a function that we call and two or we pass z. literal and to this we pass an empty string this is necessary to make our field optional and still check for this email pattern if we pass something so this is how this looks let's duplicate this line for the application URL inside our application schema just that we want to check for the URL pattern instead okay but we also want to check that one of these two is actually submitted right we can do this by appending another refine to our application schema where again we get past the data but this time data contains both the application email and application URL whatever we append this refine on and here we check that either data. application email is defined or data. application URL is defined we will return true if one of these has a a value then as the second argument to refine this time we don't pass a string we pass an object which gives us more control over the error message here we can set a message in form of a field which will say email or URL is required and the reason we pass an object this time is that we can now also say to which field do we want to add this error message and we pass this in form of an array and here we pass application email so if we trigger this error message it will be shown for the application email field so in our formulator the error message will be shown below the email input field okay now we have the separate application schema and we want to merge this into our create job schema so that we put all of this logic directly in here for this we go at the end of our schema remove the semicolon and we can use this end function and pass the application schema to it this merges this into this schema what we could have done instead is we could have taken these two Fields put them directly into the create job schema and add the refine to the end of the create job schema but there is a difference in Behavior if we add the refine to the end of the schema then the refine will only be triggered if all of these values above passed but we want to trigger everything at the same time because otherwise we will only see this error message after we already typed in valid values for all this other stuff but we want to see all the error message in our formulator at the same time and we can achieve this by putting this into a separate schema and merging it with this end operator and the only thing that's missing now is the location type and the location again we put them into a separate schema because they need their own refine logic locations schema C doob the first field is the location type which is a required string but again we want to make sure that it's a valid location type so that it's part of our location types array here so we again we append refine where we check the value we check that location types do includ includes this [Music] value and if it doesn't we want to show a string error invalid location type and this should be an arrow here and below the location type we add the field for the location which is an optional string so z. string Max 100 optional and then we add the refine to this location schema as well where again we get past the data which contains the location type and the location and remember if the location type is set to a onsite we want to require a location if it's set to remote the location is not required again we have to write this kind of custom validation logic ourselves this should pass if we don't have a location type selected because then we don't want to show an error in our front end vertical bar it should also pass if the location type is set to remote with an uppercase R so if it's set to remote this will pass in any case no matter what we selected because then we don't need a location but if it's not set to remote and the value is defined then the location has to be truthy so it needs a value because this means we have either onsite or hybrid selected which both require a location again for the error we pass an object withi the message location is required for Onsite jobs and we Define the path where we want to show this arrow on we want to show this directly on the location field okay so this is the last schema we needed we append this here at the end with end location schema and this is quite a complex thought schema we created here but it's necessary because our form is quite complex but again the quol thing is that we can now reuse the schema on the front end and on the back end just one little mistake up we have a function block but not a return keyword so either we add return here or even better we turn this into a oneliner by removing the semicolon so this is the correct syntax because it's important that we return a Boolean here and now I also want to create a type for the schema that we can then later reuse similar to our job filter values type down here let's put it below our create job schema export type create job values and again we use Z do and type of create job schema and when we hover over this we now have this new type that fits exactly the description of our schema and this is the data we expect through our form later you can see the company logo is a file because we defined this earlier we have our non-optional strings optional strings and this type will be really useful later okay and now I want to set up a new page with our new job form where we pass all these values now this form will live inside the client component because we use react hook form which requires JavaScript but I want the page to still have its own title and we can only do this in a server component so what we do is we go to our app folder here rightclick new file jobs SL newer SL page. ttsx this way we create this newer jobs and new folders and in here we export a page export default function page and this will just be a wrapper around our form client component and here I want to export a page title so export con metadata of type metadata and we hard code the title to post in new job then we create another file in the same folder called new job form. TSX you can name this whatever you want this will contain the actual form and again this has to be a client component now we can't make this page a client component because we can only export metadata from a server component this is why we create this separate component and we basically just return our new job form from here but for this we first have to export a function here so export default function new job form and when we save this we should be able to import this here our form doesn't contain any content but we should already be able to open a page yeah and there it is here's the page title and here is the flowj jobs template see we didn't add it to this title it's coming from our root layout okay and now we put our actual form in here okay let's start with some styling so this needs a main tag because this is basically the whole page let's put some placeholder in here for now and of course we have to return this I want to give this form a Max width of 3 XL this time to not make it too big and I also want to Center this we add a vertical margin of 10 again to get some space on the top and bottom and then we add space y10 for some spacing of the elements inside this tag in here we put a diff which just like on the front page contains the headline for which we use our uppercase H1 headline component because this one is already styled find your perfect developer and Below we put a paragraph tag get your job posting seen by thousands of job Seekers of course you can put whatever you want in here I want to make this paragraph text muted foreground and to the diff that wraps the headline and this subheading we add space y5 and text Center I think we already Ed the same styling on the front page and below the sff still inside the main tag we put the form itself inside another diff to which we also add some classes space way six for the spacing we add a border and rounded Corners with rounded LG and some padding so this is the box where our form will be placed in at the top I want to put another little headline and text so we wrap this into another diff then we have an H2 in here which is say job details I also want to style this in font zimi Bol and another paragraph below again with text muted foreground which will say provide a job description and details so we have some text in here and then our form input Fields okay and then we go between these two diff closing text so we are still inside this diff with this spacing of six and here we put an uppercase form this time and here you have to pay attention because they are different Imports we don't want to import this from react hook form we want to import it from our own components folder this form component is provided by shed ZN and this is what we installed earlier so why do we now need this special form component and can't use a normal lowercase form like we did in our filter sidebar this is because now we want to integrate this with react hook form which takes care of validating and managing the form input Fields because our new chop form is more complex and this uppercase form component basically orchestrates all of this it stores the values for us it gives us a way to access the different form fields for validation and together with it we also have this form field component and we use this to render a single form input or a select field or a checkbox or whatever kind of form element so this form field even though this is a lot of code is actually only a single input field in our formulator so we will have a bunch of these why is this necessary well again first of all this manages the form State and passes it to react hook form this is what this control part here is for the form item provides some layout and styling so that the form label the form input field and the description and the error message are aligned properly with the correct spacing and and this form control reer here takes care that the label and the input field and the description and the error message are connected properly remember in our job filter sidebar earlier we set the HTML 4 attribute to tell the browser that this label is connected to this input field in a server component you have to do it like this in a client component we can use these helpers that do this automatically for us via this form control wrapper the reason this only Works in a client component is because this internally uses context to pass these values to the different components and context is only available in client components but the benefit is that when we use this form field reer we don't have to add these accessibility attributes which is especially useful when we also have error messages because they also need to be connected to the input field and they have a bunch of different accessibility attributes so again these form field components take care of this for us but they only work in client components you can also use react hook form without this shety end form field rapper but I like this one here because for one it's already styled but it also has nice usability in my opinion all right I'll show you how to implement this so first of all we have to initialize react hook form again react hook form stores the form input in state for us and this is what allows this Dynamic validation with error messages that pop up before we submit our form or input fields that depend on other input Fields this is all orchestrated by react talk form so at the top of this component we create a const form and we call this use form Hook from react hook form to this we can pass a type argument with the kind of data we expect in our form this will give us type safety later and for this we can use our create job values this is the z type we inferred from our create job schema we can use this here then we call this and between the parentheses we pass a configuration object here we can pass a resolver which is responsible for validation and to this we can pass a zort resolver but it looks like we have to import this manually import D structure from at hook form resolvers SL zot and in here should be the zot resolver which we want to use as the resolver for this form and to this we pass our create job schema which is the Z schema we set up in the validation file okay let's go down to our form component here we want to spread these form values into this form this is described in the shyn documentation this makes this form yeah work properly together with react hook form then into this form reer we put a lowercase form which is the actual HTML form itself to this form we add some props first of all we style this with space y4 to get some spacing between the form input fields we also set this to a no validate this way we disable the native br browser validation because we want to use our own JavaScript based validation with our own error message and not the browser messages and then we also need a function that will be called when we submit this form this time we don't use the action prop because this time we have more Dynamic validation which requires JavaScript to run for this we use the onsubmit function because this gives us more flexibility what we execute when our form is submitted instead of just sending a post request again the downside of onsubmit is that this requires JavaScript but as that the form we set up here is just not feasible without JavaScript so let's create an asnc function for that called onsubmit to this we get past the create job values which are the input Fields so when we use react hook form we don't get past form data we get the resolved values with the correct types and for now let's just show an alert message to see that we actually received the correct values so we stringify these values comma n comma two this part here just formats this Json string so that it's a bit better readable for now this is all we want to do later we will call a server action in here and actually create a new job what I also want to do is I want to destructure this form object so that we don't have to write form dot all the time when we use the different values so in here we the structure handle submit watch you don't have to know what any of these mean you will learn about all of them in a moment control set value set focus and the form State and we destructure the form state because in here is this on submitting field okay these are all values that are contained inside this form object we go down to our unsubmit function and we don't pass our unsubmit function here directly instead we wrap this into handle submit which is what we just destructured from the form object we call this and then we pass our own onsubmit function like this why because this part here is responsible for triggering our form validation through react hook form and this also extracts the data and passes it to our unsubmit function so that we receive these create job values okay and then inside this lowercase form we put these different form field objects that I showed you in the shyn documentation they get a Zelf closing tag but a bunch of different props the first one is control which we destructured from the form object Above This basically allows react hook form to manage the value of this input field we have to give it the name and here we will get autoc completion why because those are all parts of our zot schema that we passed to the use form call here to the zot resolver this is what gives us autoc completion for these fields down here and then we have have to add this render prop and this will render the actual input field in here we write parentheses and another pair of curly braces because we want to destructure a value here which is called field and then we make this an arror function and we want to return something so we write a pair of parentheses not curly braces but parentheses because we want to return this part here from this render function okay and in here we put a form item which is an import from our components folder again again this is responsible for laying out the components properly inside this form item we put a form label which is the same as our normal label just that this one is automatically connected to our input field and this will say job title then below we put a form cont control wrapper close this and in here we put an actual input field from our components folder so this is the same input field as we also used in our sidebar the same component but because we wrap this into a form control we don't have to set the ID ourselves the form control does this for us and the form label and the error message will have the same idea set on them for accessibility let's set a placeholder here that will say EG front end developer so this is a placeholder text and we can already see it here then after this placeholder text we want to spread this field object here because this contains a bunch of different props that we want to apply on this input field when we write a DOT we can see them this applies the name which is the same as up here on change and value to manage the value and a bunch of other stuff so we spread this here and then below the form control we render a form message and the form message will automatically contain the error message of our sort validation schema if a value is not valid because we passed our schema here so for example if we type in a title that is too long then we will see the approach appropriate error message or if the title is missing we will see this required message but you will see this in a moment this is automatically put into this form message because the shed CN form repper handles this for us yeah and when we click our label here we automatically put focus into the input field this shows you that this label is properly connected to this input field even though we didn't set the HTML 4 attribute again this is managed automatically for us now this all might sound a bit complicated but forms in react are just complicated because we need accessibility we need to manage the state and helper libraries like react hook form do this for you the reason why we didn't need this in our filter sidebar is just because our filter sidebar was so simple and didn't have any validation inside it but for a form that's a bit more complex you usually want to use a helper Library when we save this we can already try this out we don't have a submit button yet but when we not type anything in here and press enter we see the required error message now I actually want to get rid of this red title color here because when we have multiple input Fields this will look very busy and confusing in my opinion and since we use shed CN we own all the code right so we can go into the form component that we created via shed CN earlier then we can search for the form label component in here and this is the the code that makes the text red if there's an error I want to get rid of this instead I only want to pass the class name here and actually we can remove this completely because this props spread down here should already take care of this right but only if we also remove the separate class name destructuring up here so instead of destructuring this let's get the whole props object and destructure it here and this should automatically apply the class name as well and then we also don't need this error value here anymore so let's clean this up a bit when we save the changes our title is not readed anymore and I think this will look better later okay and now we need a couple more form Fields So Below the first form field still inside the form we add another form field and they all follow the same structure we pass the control to it we give it the name this one is the type and then we need to render the form field itself again we want this field object here destructured and in here we put a form item and a form label at the top which will say job type this one will be a select field so we use our select component that we created earlier we spread the field props in here and we set the default value to an empty string just like we did in the sidebar and then we put the options in here the first one will be a placeholder that gets the value of this empty string but we set this to Hidden and this will say select an option and this is pretty cool because this way we can create such a placeholder this now says select an option but when we click it this option is not in the dropdown menu because this is not actually a valid selection this is just a placeholder and we achieved this with this hidden option if this was not hidden it would be an actual option to select and then below like in our filter sit bar we want to create an option for each job type so we take our job types array and we want to map it into option objects so in here we have each job type and we want to render an option for each of them the key is the job type itself the value is also the job job type and the text inside the option is also the job type we have done this before now we have an option for each type I forgot the form control rer so we go between the label and the select tag put a form control here and we put the closing tag below the select tag just like we had up here for our input field and again this form control automatically passes an idea to to the select field and it connects it to the label and the error message that we will put below the form control so here we put the form message which again is automatically filled with the appropriate error message and again we can see that the label is properly connected by clicking the label and it selects the select field okay the next one is a normal input field again so maybe let's copy paste our first input field the title below our second input field just change the name to a company name change the label content to company and this doesn't need a placeholder below I want have the file upload button and this is also a normal input just with a special type set on it so let's paste another input form field let's set the name to a company logo the label as well then we get rid of this placeholder here and instead we set the type to a file which turns this into such a file input that we can click and it opens our Explorer we can Define that we only want to accept images with this accept prop to which we pass the string image slisk the arisk means any image file type so PNG JPEG and so on but we also validate this on our back end via our validation schema right because only doing this on the front end is not secure the user could still send any data this is just for usability and now when we click this we can see down here that we can only select image files by default now the reason this complains is because this is trying to set the the value prop via this field that we spread here because in there is the value but on file inputs we actually can't set the value it's not allowed I think for security reasons so we have to get rid of this we could either set the value to undefined down here but a cleaner way to do this is to spread this field object up here then we extract the value like this comma dot dot dot and remain values which we call field values this name here is up to us we have used this syntax earlier this way we D structure the value separately and it will not be contained in the field values so now when we spread the field values down here we don't spread the value anymore and our error disappears we still have one problem though such a file input uses a type called file list which can contain multiple files even if we only select one and then we can extract our file from this file list but our validation schema expects a single file because this is also what we receive on the back end so we could either split up the schema and create one variation that uses the file type for the back end and one variation that uses the file list type for the front end but I want to keep one and the same schema what we can do instead is we can handle the onchange function ourselves by default this is handled from these field values and here is on change we spread it into the input so this is handled by react hook form but we can also overwrite this to handle this ourselves so what we do is here we pass a function that gets this onchange event passed to it we make an error function like this and then we want to handle onchange ourselves because you want to extract our file from this file list and then pass this to react hook form because then it fits to our schema that we have here because react hook form expects whatever we defined here which is a single file not a file list so let's do this we create a const file in here and those are curly Braes not parentheses because this is an actual function block okay we can get the file from e. target. files which is this file list I was talking about and here we have to use this save call Operator because this can be undefined we want to access the first file like this because we only expect one file and then we want to pass this file to react hook form via the field values on change event so here we pass this file to it so all we do is we take the first file out of the file list and we give it to react hook form it's actually not that complicated when you think about it okay the next form form field is a select field again so let's copy our type select field so that we don't have to write everything from scratch paste it below this should be location type the label will just say location we keep the default value but we want to map our options from the location types array and let's also to rename this variable to location type okay and now we have our select Fields down here by the way we can also check if our file validation works properly remember we only allow image files right to check that this works let's remove this image filter for a moment and try to select another kind of file for example an ex file as you can see this immediately say must be an image and this will be validated on the back end as well but let's add this back in so that we can only select images now we can't even see the exra file in here okay next comes our location search field and this will be really interesting because we build our own search input field for this let's create a new component in the components folder I'm going to call it location input or location search or whatever and we export a component here this component will also take some props location input props it will take the same props as a normal input field so what we do is we extend this interface with react. input HTML attributes and again we add this type here in angle brackets which is HTML input element this way this interface accepts the same props as a normal HTML input and one additional prop which we Define in here on location selected this will be triggered when we actually clicked a result in our search and this function will forward the selected location which is a string and it has no return value so we declare it like this now this component will also need a ref because a ref is necessary for react hook form to automatically put focus on an input field that's invalid and this requires a ref remember we already forwarded a ref in our select component earlier and we want to do the same for our location input so what we do is we go behind export default like we did earlier we call forward ref to which we pass two type arguments the first one is HTML input element that's the type of element we have here and the second one are the location props right then again we wrap this whole function into parentheses and this location input receives two arguments the first one are the props that we wanted D structure here on location selected and the remaining props and the second argument is the ref for now let's just return an input field with an uppercase eye because this should be styled and re import this from our components folder so into this input field we spread the input field props that we get past because now I just want to put this into our layout so we can see it while we are building it so below the location type we need another input now let's copy one of the existing input Fields again put it here below the last one the name of this is the location field the label with the office location and inside the form control we don't want a normal input we want our location input to which we have to pass the on location selected callback and when a location was selected I want to call field do onchange so we don't store the input of this input field in react hook form we don't call field.on change in the inputs onchange function instead we call unchange when we trigger this on location selected callback which will be triggered when we actually select a Zid then we store this value in react hook form but you will see how exactly this works in a moment and the other value we want to pass here is the ref which we get from field. R and inside our location input we forward it via forward ref but we also have to actually pass it here like this this time we don't about any of these other field values we don't need on plur or the value or the name or anything we only need on change and the ref so this time we don't spread the remaining field values so right now this is nothing special this is basically just a plain input field but now we want to implement our actual search functionality so when we type something in here we want to show a list of results and for this we will need a state in here that keeps the current input and and then we use this dat to filter our list so we create a const with scrap brackets let's call it location search input and Z location search input and this is a react state which we initialize with an empty string then I have prepared this City's list which is a huge array with different cities I put this file already into the studing code if you did download the starting code I will also put a link to this file into the video description and fun fact my PC fan is actually getting very loud right now because it has to load this huge array now in the real app you usually want to use something like Google Maps search the problem is to create an account there to use the API you have to provide a credit card which makes it not very nice to use in a tutorial so instead we will use our own list of cities again the link to the cities list is in the video description you can put it into the lip folder or somewhere else doesn't matter and then we want to create a variable called cities where we basically filter this huge array to only show the results we care about now what we also do is we wrap this into use memo so that this filter will not be executed on every single render because it's such a huge array and the filter logic is actually expensive here so we only want to execute this when we actually have to this is what we achieve with use memo to this repair an error function like this and from use memo we want to return our filtered list that we actually show in the UI so first of all we check if exclamation mark location search input. trim so if the location search input is empty or only contains white spaces then we just want to return an empty array because then we don't want to show any results then we go down here to the closing parenthesis of use memo and we add the dependency array just like in use effect and this now expects the location search input because we use this variable in here so this cities array will be calculated again only when the location search input changes on every other render it will use the previous value this way we avoid calculating this in every single render okay and here we now want to implement our search Logic so again we create this search words away for which we take our input and we split it at empty wh spaces so that we can search for each word separately okay and then we take the cities list here and we filter it first of all I want to put a city name together with the subcount and the country into the result string here here I want to return City's list which we have to import import City's list from and it's located inside the lip folder and I want to map this as I explained earlier I want to take each City object and map this into a string this string will contain the name of the city comma then the subcount then another comma and then City dot country so we take these three different values and put them into one string and we map this array of objects into an array of strings okay and then we add our search filter directly after the map call so we remove the semicolon and instead a pent filter on it here we get past each zity and we have to return a Boolean true or false depending if we want to keep this value in the array okay and this is the zge logic I came up with I want to take the Zid I want to make it a lower case to make the searge not case sensitive if city. lowercase and the city in this case is this string that REM map here this should start with the first search words so the search word at index zero which we also make lower case because the city is what we're actually searching for and when I type in B for example it should show cities like Berlin and not countries that start with the word Berlin but you can modify this search Logic if you want then we add to erson signs and to be able to also search in these other words like the country and the subcount rewrite search words do every to this we pass a function where we get past each word right arrow city. to lowercase again do includes and in here we pass word do to lower case and word is every single word in our search words array and this way we basically check that all words in our search are contained anywhere in our city string so anywhere in the name subcount or country lastly I only want to return five values at most so again we remove this semicolon entry append do slice to which we pass 0 comma 5 so we create a new array that starts at index zero but it's only at most five elements long and this is our search Logic and again since we WRA this into use memo this will only execute when our location search input changes to save CPU Cycles okay to see that this works let's wrap this input here into a diff like this then below the input we want to render our search results inside a div so for now maybe let's just render json. stringify cities just to keep it simple and now we also have to tell react that whatever we type into this input field should be stored in this location input right so we add this as props we do this by adding the onchange Handler where again we get past this unchange event short e and then we want to set this input on the location search input State e. target. value and we also want to set the value of this input field to whatever is the current search input like this so now when we search here yeah we already see our list of results isn't this cool so let's try Berlin Germany and there it is now we just want to render this properly so what we do is we remove this diff here again and instead we add an expression we want to check that our location search input actually contains something because only then we want to render our results box so we trim this value again to remove white spaces we check that this has a value like this it's trophy if it has a value and if this is the case we run a render a diff that contains the results right so first of all if we don't have any results which we can check with exclamation mark cities. length so if length is zero or undefined then we want to render a paragraph that says no results found and I also want to style this a little bit I just want to give this a padding of three but if we have results we want to map them into a list items So Below we add another expression we call cities. map we get past each City but city is now a string because city is is a string array because this is what we return here right so we want to take each City string and just render a simple list item now actually each list item is a button because when we click a list item we want to trigger our on location selected carb back here and we do this via a button but this time it's a lowercase be button because we don't want any of our primary buttons Di we want a button that is completely unstyled the content of the button will be the name of the zity here this of course needs a key because we are mapping a list let's see what we got now when we save this yeah now we have buttons but they are still rendered directly below I want this to be a popup for this we go to the outer div that reps the input and this inner div and we add the class name relative so that we can align the child's relative to each other then we go to this results div here and add the class name absolute for absolute positioning when we save this you can see that it's now on top of the remaining content and now we want to style this like a popup menu with BG background now it has a background color we can also give it a shadow Shadow XL and now it already looks almost like a popup I also want a border but not at the top only left and right and bottom so we add a Border X for the horizontal axis and a border bottom but none at the top similarly I want rounded corners but only at the bottom which we can do with rounded blg now only the bottom corners are rounded I also want to add a z index of 20 which makes sure that this is always on top of other elements we do this with this C minus 20 class and then I want to add divide Y which adds vertical dividers between the single items that's this little divider line you can see here but each list item should be in its own row right so we have to style the button as well with block each one takes up its own line we also add the width full class to both the diff wrapper and the button so this takes up the available width but I want to have the text on the left side which we do with text start and I also want to have some padding here padding of T and I think this looks already pretty good so this is our search field and there is the no results found text okay I think it's pretty cool how easily we can build our own search input field there is still a bit fine tuning I want to do first of all I want to set a default placeholder which will say search for a Zia but since we spread these props here we can still overwrite this from the outside if we want to because we spread the props after this value they will take precedence if we pass a placeholder from the outside we can also set the type of this input field to search this way we get this little X icon here which clears the input there's one more thing when the search results are shown and we click somewhere else they actually stay shown we only want to show them when this input field is actually focused for this we can manage the focus in a state so up here we create another state called has focus and Z has Focus which we initialize with a use state of false then down here in our input field let's put it below the onchange prop we can add on focus when this is called it will set Z has Focus to true this will be triggered when we focus this input field and the opposite is on blur which is called when we unfocus this input field so when we remove the focus then we want to set has Focus back to false and then we want to show our results down here only if has focus is true so now when we click somewhere else it disappears but when we click on it again it's there isn't this cool and now we only have to handle C clicks on these search results we want to call our on location selected callback for this of course we need an on click Handler on our list item button down here but we don't use onclick we use on Mouse down instead why because when we use onclick then we actually lose focus before the click is receiv received because when we lose focus we remove the results and then onclick won't work one Mouse down is triggered a bit earlier before we lose focus this is what we use here again we get past this event on this event we call e. prevent default this way we avoid losing focus then we want to trigger our callback on location selected to which we pass this C here from which we mapped this button and then we want to set the location search input to an empty string again so when we click something the input field is empty I want to show the selected City below this input field yeah but our location search input here is finished and again this is a reusable component which we can use in different places and even in different projects okay in our new job form we already wired up the location input with react hook form as soon as we click a search result and Trigger this callback we pass this value to a react hook form and we can then later submit this value and as I said below this input field I want to show the city that's currently selected and we can get the current values of our input from this watch function here which is part of the form that we destructured earlier so let's go to our office location input and below the form control but above the form message we want to put an expression here we want to watch the location so this will always contain the latest value of location if there is a value in here then we want to render this location we want to show it on the screen to indicate that we actually have something selected so maybe for now let's just render the value itself which again we get from this watch function we will stay is in a moment but here you can already see how this works when I select Berlin Berlin is written down here because this is the value that's currently stored inside react hook form copertino this works I want to style this a little bit and I also want to add a delete button next to it so that we can yeah remove this value so instead of just watch in here I want to render it if we style this with flex item Center and a gap of one in here we first put a lowercase button again because we don't want our default button styling we set the type of this button to button this is necessary because we are inside a form and if we don't set this type then the browser will think that this is a submit button and try to submit our form we don't want this this is a normal button and inside these button text we render this x icon which is coming from lit react with a size of 20 there it is next to this button inside this Flex div of course we want to render the city name again we wrap this into a span with text smm to make the text a bit smaller and in here we want to call what location to render this here but the button of course also needs an onclick Handler to which we pass an arrow function when we click this button we want to remove the currently selected location which we can do with select value which again is part of what we D structured from the form those are all functions that belong to react hook form the first argument to this function is the field we want to change the second argument is the value we want to set to it we want to set this to an empty string and the third argument is configuration here we can pass short validate and set this to true so that the validation for this location field runs again so if we delete the location but we have onsite selected then this will trigger the error message that says we need a location for onside jobs okay let's try this out now we can click this and the city gets deleted cool okay but we still need input fields for the application email application URL the salary and our description editor okay below this form item we put the next one the application email and application URL this will have a bit of a different layout because I want toow them horizontally and I want to have one single label above them so we wrap them into a div not a form item here we set the class name space y to for vertical spacing and at the top we add a label from our components folder not a form label but a normal label because we don't put this inside a form item so it doesn't get the HTML 4 prop set automatically instead we have to set this ourselves to application email to connect this to the email field this will say how to apply below this we put another div and in here we put the actual form Fields so form field control we already know the drill this one is the application email again we have this render function in which we destructure the field and we return a component and let's close this form field down here inside we put the form item in the form item we put the form control just like before and in here we put the input for the email this time we set the IDE ourselves because we want to connect this to the label so we set the same ID as the HTML 4 attribute again this is necessary because the label is this time outside of the form control so it's not set automatically the placeholder will say email we set the type of the input field to email and then we spread the field values just like on the other input fields and let's see there it is below still inside this diff we want to put another such form field so let's duplicate this this of course is the application URL we don't need an ID this time because this one doesn't have a label but the placeholder will say URL and the type is URL as well so now they are aligned vertically we want to align them horizontally this is where we have this diff wrapper below our label which res set to a flex box and justify between so these elements get put on the left and on the right side to make each of them take up the available space we set a class on each form item set this to grow and we do the same down here form item grow now they take up the available width and in between these two I want to put or as a text but for alignment it's easier if we don't put this text right here between them instead I want to put it together with this email input field so we go inside the form control of application email we rep this into another diff like this next to it we put a span with MX2 which is horizontal margin this will say or and on this sff we just created we add flex and item center now the text is in the middle of the tool and also each of them needs an error message right so below the form control we put the form message and the same down here and also I want the placeholder to say website and not URL and then remember we have a little bit of dynamic Behavior here because the email is only required if we don't provide a website we only need one of the two which means that when this shows an error message that we need to provide an email but I type something into the website field then I want to revalidate the email field and remove the error message if we have a valid website which means that in the unchange event of the website input we should trigger validation of the email input for this we can overwrite the onchange event here and handle this ourselves again so again this is the kind of logic you have to write yourself if you want really Dynamic validation as usual we get past the onchange event here we want to execute a function we want to fields. un Change as usual so we get the same behavior as before but we also want to trigger validation and again we have a special function for that which we got out of the form here we can trigger the validation of a specific field we want to trigger validation of the email field we will see how this works later when we have our submit button okay and Below we want to put the rich text editor for the description for which we will use this react draft what you see is what you get component that we installed earlier it's a rich text editor and it's very customizable I liked this one again we should put this into a separate component for organization and reusability so let's create a component which I'm going to call Rich Text Editor this will also be a client component because this editor only works on the client and again we need a ref in here so let's write export default forward ref again we have to Define the types here the type of the ref is object this time because this is the type we get provided by this what you see is what you get editor this is not under my control and the type of the props is editor props which is an import from this package those are the props that this editor accepts and we want to be able to pass the same props to this component right we call forward ref pass a function that we give the same name Rich Text Editor parentheses and then here we get our props and the ref as usual then here between these two parentheses we add the function block so there's one parenthesis still at the outside like this okay but this one will not be as complicated as our location input here we return an editor which is an import from react draft we spread the props into it to apply them and then we want to customize this a little bit but before we customize it let's just save it for now and put it into our UI so now we have to be careful here because we have this repper for the application Fields right so above the form closing tag we put another form field and don't worry soon our form here is done it's the most complex part of this whole app again we pass the control the name is description and again re render our form field and close this down here again in here we put a form item again we use a normal label because the form label actually doesn't work with this editor because it doesn't have an ID attribute instead we have to handle the label click ourselves later for now we just put a label in here that says description below that we put the form control control and in here we put our Rich Text Editor which is the component we just created okay it looks a bit weird we will fix this in a moment but before we forget it below the form control we also add the form message then let's go back to the r text editor the reason it looks like this is because we haven't added the CSS this is the CSS that comes with this library that you have to add to the Rich Text Editor file so type this out by hand save it and now we have proper styling here and now we can customize this editor as usual this is described in the documentation of this library but I prepared all of this first of all we can style the editor with editor class name here we want to call our CN function so that we can apply default styling but still pass styling from the outside so here we pass a string comma and the second argument is props do editor class name this is the editor class name that we pass from the outside via the editor props but here in this first string we put the default styling I want to style this similar to a normal input field right now this is completely unstyled doesn't even have a border so let's take care of this border rounded MD and I think we don't get autoc completion here which makes this a bit tricky probably because of the name of this prop I'm not sure so we have to spell this out by hand PX minus 3 for some petting so now it already looks better I want to give this editor A Min height which I hardcoded to 150 pixels is it can still get bigger but not smaller than that which I think looks better now when we hover over this we can see that we have this cursor icon only up here but down here we have a normal cursor which looks a bit weird because when we click this part we still select the input field we can fix this by adding the cursor text class now no matter where we hover over this input field we have this text curve then I want to apply the same ring that we also get when we focus any of these other fields for this we need a bunch of classes which are ring minus offset minus background then Focus minus within column outline minus none then again Focus within colum ring two and again Focus within column ring minus ring and unfortunately we have to add all of this manually but only once and the last one is focus within colum ring minus offset minus 2 so those are all the classes we have to add again you can also open the repository in the video description and copy them from there if you don't want to type them out by and but now we have the same ring that we also have on the other input Fields this is not necessary but it looks better then we can also customize what we show in the toolbar I actually don't want to show most of this stuff because we don't need images or emojis or headings or stuff like that we can customize this by passing the toolbar prop to which we pass another pair of curly braces and then here we can configure this we can for example set the options which expects an array here we pass s strings in line list link and history when we save this some of these options will be removed yeah and those are the ones we defined in this array and I also want to get rid of these mathematic options here which we do by passing this inline prop curly braces again this expects options again this is a string array we want to allow bolt italic and underline and remove all the other options so this is much cleaner and this is all we need bold lists links and these undo and redo buttons and that's basically it we also need to assign the ref remember we have it up here but we don't do anything with it usually we assign the ref like this but this editor doesn't have this ref prop instead it has this editor ref prop but this works a bit differently we can't just pass the ref to it instead this is basically a function that gets pass a ref so we create an aror function like this and this ref is of type object this is why I made the type of our ref also an object so they are compatible and then we need to assign this ref of the additor to the ref rep pass here now this forwarded ref that we get passed we this forward ref function can actually have one of two types it can either be an object ref or a function ref you can read more about these two different types in Google but what we have to do is we have to check if type type of our ref the ref that we pass to this component is of type function then we have to call our ref function and pass this ref to it and else we want to check if ref has a value so if it is defined and if it's not a function then it has this current field because now the type is mutable ref object if it's not a function it's this one and then we assign R like this so again this ref we pass to the component can be either a function or this mutable ref object and we use them both differently the function we have to call and the object we have to assign via the current field and this is what we do here so this was a bit complicated but now we have our Rich Text Editor we can add styling here it even supports keyboard shortcuts can underline make bold and so on isn't this cool we also have this history here so this is fully functional only one little thing we have to fix here when we look into our console we see this error message window is not defined because it tries to render this editor on the server in nextjs but this only works on the client to fix this we have to import this in a different Ray so here we imported it directly up here we remove this and instead we have to use a dynamic import because a dynamic import will only be executed on the client and then we get rid of this error message this looks like this we create a const editor which replaces our import we call Dynamic which is coming from nextjs right here this is a function to this function we pass an arrow function here we call import the name of the package react draft what you see is what you get and since the editor is not the default export we also have to append dot then after this has resolved we get this mod variable and mod is just short for module I think but in here we will find the editor again this part here is necessary because the editor is not the default export and this way we import the editor dynamically only on the client and when we refresh the page we still get an error message because we also have to add a configuration to Dynamic we have to set SSR to fults to avoid this editor being rendered on the server so now when we refresh this we don't get our error message anymore let's clear the terminal refresh the page and the error message is gone you can also see that the editor loads with a little bit of a delay we can't avoid this because again this only runs on the client now but this is necessary but now this editor is finished and we can finish putting this into our form again we handle the unchange Callback ourselves because this is not completely compatible we get let's pass a draft object from this editor in onchange there's this onchange prop in here but here we receive this raw draft content State this is not text and also not markdown but we need to turn this into markdown because this is what we want to store in our database and for this we installed a seate dependency markdown draft JS this can turn this draft object into markdown format by calling a function here draft to a markdown to which we pass the draft and then we give this markdown string to react hook form so we can later submit it and besides on change we also need the ref right which we get from field. ref and again the ref is necessary so that react hook form can automatically focus our input field now as I explained earlier this editor we installed doesn't support the ID attribute so we can't really connect a label to it at least not with the HTML 4 prop but I still want the editor to be selected when we click the label so the last workaround we can do is handling the onclick event on the label ourselves this is what we will do here because there is no other way in fact this editor is actually not even a text area it's just a normal diff and a normal diff doesn't support the same accessibility attribute as an input field but we can work around this here we call that Focus which again is a value we extracted from the form object yeah and as the name implies we can focus a specific field and this is handled via this ref so let's try this when we click description we focus the input field if we remove the Rev refresh the page and try this again then the focus doesn't work anymore because react hook form needs the ref in order to focus this input field and then we can use the set Focus function but the way we set it up here it works and it's a good replacement for the missing idea on this input field okay the last input field we need is the salary which will be much simpler again again I want to copy one of the existing text inputs here paste it below change the name to salary the label as well we remove the placeholder we spread the field values and then we also set the type to number this way we cannot type in normal characters in here only numbers lastly we need a submit button for this form but I actually don't want to use our form submit button because when we use react hook form we don't handle this via this use form status instead react hook form has its own loading state which is inside the form state is submitting so we need a separate loading button and what I want to do is I want to extract this layout into a separate loading button and then we can use the same loading button button in here so let's do this let's create a separate component in the components folder let's call it loading button and then I want to split the screen put the form submit button over here and the loading button here okay the loading button needs props loading button props we want to extend the same type as over here so we get the default button props so loading button props xtends react button HTML attributes blah blah blah and we add one more prop which is the loading Boolean and then we pass these props to our button load loing button props in here we destructure the children the loading State and the remaining props and here we want to return this button the form submit button because this contains the layout with the loading State inside it we import the button and the loader icon we still spread our props here we don't want to set the type to submit and we want to replace the pending State for the loading state that we pass to this button The Styling stays the same and here we say just children instead of props children and then over here we don't want to duplicate this layout instead we just return our new loading button to which again we pass the prop props that we pass to this component here we set the type to submit again and for loading we pass the pending state so now we have a separate form submit button and a more generic loading button okay let's close these files and then put this button into our new job form at the bottom so right before the form closing tag right here we put our new loading button we set the type to submit here again but again I didn't hardcode this type into this component because we might want to use it outside of a form this is why we set the type from the outside now via a prop and for the loading State as I already explained earlier we use the forms is submitting state yeah and the button will say submit and that's basically it okay there's one mistake I just noticed here I called draft to markdown but we also have to call field do unchange because this only Returns the mark down right so here we call field do onchange and to dysfunctional we pass this Mark on string this is what we actually want okay and in our onsubmit function right now we just show an alert message right let's try this out first of all let's try to submit this form without any input we see all our error messages let's type something in the error messages disappear in real time company let's skip the logo for now location onsite and now when onsite is selected and I try to submit this yeah it requires us to select a location Berlin for example now we either have to provide an email address or a website if I type in gibberish it says invalid email address if I type in floran coding andf flow.com this is valid but instead I can also insert a URL coding andf flow.com and then the email is not required anymore and here we have our markdown editor let's try this out with some formatting salary 1 to3 now we can submit the form and there are all the values here you can see the markdown the Bold part has to STS and there are all our values so this worked now we can send these values to our back end into a Z action and create a new database entry and that's basically it one more thing I want to to change if we don't select the city we get this error message right if I change this back to remote I want to remove this error message immediately because now this is not required anymore we can submit this but it doesn't automatically remove the error message we have to handle this ourselves we can handle this in the same way that we handled the application email earlier with this trigger function so let's go to the location type input field we can overwrite the onchange Handler in the select field where again we get past the event first of all we call field. unchange and pass the event to it and then we want to check if the value is remote then we want to trigger validation again so if e do current target. value this is where the input is in is equal to remote then we want to trigger validation of the location field let's try this out I try to submit this while onsite is selected it requires a location when I select remote this error message disappears okay this was the toughest part of this tutorial this form is quite complex but now comes the payoff now we are cre create a new server action we call it when we submit this form and then we store the new entry in our database okay we can't declare a server action inside a client component because server action runs on the server and a client component runs on the client however we can still call server actions from client components we just have to put them into a separate file so let's create a new file in the same folder as our new job form and the naming convention for this is often just actions here at the top we put the use server directive remember we added the same string in our job filter Side Bar up here to declare a server action instead of adding this to a single function we can also add this to a whole file and now every function inside this file will be a server action and here we export an async function which we call create job posting now there's actually no rule that says that server actions have to receive form data they are normal functions they can also receive any other kind of value the value has to be ziz because it's sent from the front end to the back end so it can only contain your primitive types basically but it doesn't have to be form data now when we use a forms action prop then the argument has to be form data because this action prop can only send form data but when we call it from a client component we can pass anything so we could pass our separate create job values for example however we still want to use form data here why because we send an image file to the back end and an image file has to be wrapped into form data when we send it to a server action otherwise it won't work but in general you can also pass any other object here okay and then again we want to turn this form data into a JavaScript object which we can then pass to our job schema we have done this before we create a const values we call object from entries we have done this with our page filters before and to this we pass form data. entries which is a function and now the cool thing is we can reuse our create job validation schema that we already used on the front end we use the same schema on our back end to validate the input and to automatically get the correct types out of the form data and you need to validate your server actions because they are technically just normal post end points a user doesn't have to call a server action through our front end they can also send a request to it directly at least in theory or they could modify the front end to send invalid data so we have to verify the incoming values on the back end as well just like you would do it in a normal server endpoint so we create another constant that we destructure here we take our create job schema andent recall pass again to pass these values if any of the values is invalid this will throw an error otherwise we get our values in here that all have the correct typee the title for example which is the required string and I want to destructure all of them company name company logo which is optional location type location the optional application email or URL description and salary those are all values we sent through our form next I want to create a slug before we store this entry in our database because remember each entry needs a slug and that's just a relative URL where we want to store it we could also use the ID for that but the slck is more readable and what I want to do is I want to take the title turn it on lower case and add a random number at the end to avoid duplicates for this we will create a little utility function let's go into the utils file export another function that we call two Slug and to this we pass a string what name you give this function or this argument is up to you here we return the string but we turn it into all lower case then we want to replace all empty spaces for dashes which we do with a Rec X which looks like this SLS space SLG comma and we want to replace this with a dash so that we don't have spaces in the URL and one more replace with this r x this makes sure that we don't have multiple spaces or dashes in a row this takes multiple spaces and it replaces them for a single space because this just looks better so type this out save this this file and then we use this in our server action so we create a con Slug and re assign this to a backtick string this backtick string starts with the to slack call to which we pass the title of this job entry then a minus and then a random idea and for this we use Nano ID which we installed earlier but we have to import this import from Nano ID and in here is this Nano ID function which I want to call here and rep pass the number 10 to it so this Nano ID has 10 characters but this closing curly brace here is misplaced should be over here so this creates this slug next we want to upload the company logo file to our blob storage so we create a lead this time not a con a let company logo URL which is of type string or undefined and we initialize this with undefined only if we actually send a logo to the back end can we upload it and get a URL back so below we check if company logo is defined then we create a const blop we call await put and again put is an import from theel Blob should be in here from how it's called yeah at verel blop with put we can put a file into the blob storage this is described in the documentation but it's pretty simple to use so the first argument here is the path name this is the file name and the folder name if we want to create a folder we create a back Tex string we uh create a folder called company logos slash and then the file name and for the file name I want to use the slug that we generated and then I want to append the file extension so PNG or jpeg or whatever so after slug we add another expression without a space and here we call Path which is this import here do XT name which is short for extension name we call this and pass company logo. name to it so again this creates a folder called company logos and it puts this image file in there the name of the file is the slug and a file extension okay that's the path the second argument is the company logo file itself and the third argument is some configuration we set the access to public so every everyone can view this image and by default the zel blop adds a random suffix to the name we don't want this because we already have a unique name with our Slug and then at the end of this if block here but still inside it we assign the company logo URL let variable to blob. URL this is the uploaded image so as you can see the zel blob is pretty easy to use now it's important to note that you can only send files up to 4.5 megabytes into a server action that's the limit but we also have a 2 megabyte file size limit on our front end if you want to upload larger files this is also possible but then you have to upload them from the front end the way this works is that the user basically uploads the file from the browser directly to theel blob without this detour over our own server this is a bit more complicated because then it's more difficult to modify the file name and validate the file so for small files I prefer sending it over our server but just in case you want to upload something bigger then this is also possible this is described in the Basel blob documentation okay and now outside of this if block we can create the actual job in the database so we need our Prisma client right import Prisma from at SL lip SL Prisma and then below we can call await Prisma do job. create parenthesis cly Braes and here we have to pass the data which are all the fields that our table in the database expects so let's pass them here we pass this slck we pass the title but I also want to trim the title to get rid of any unnecessary White spaces then the type which we don't need to trim because it's coming from our own list of dropdown options the company name can be trimmed as well company logo URL which is only defined if we actually uploaded a logo otherwise this value will be ignored location type doesn't need trimming location doesn't need trimming either but let's trim the application email because again we could type white spaces into the input field the same for the URL and I miss GitHub co-pilot right now I don't have it installed on my recording machine here but it's very useful in day-to-day life anyway we trim the description as well and it automatically adds the save call operators because these values can be undefined and the salary has to be a number but we receive a string through our form data so we have to pass this number into an INT like this and this should be all the values if we removed one of the non-optional values then Prisma will also show an error because Prisma is type save then we go below at this point our new entry is created and then I want to redirect the user to a confirmation page so we import this from next SL navigation and I want to navigate to slash job submitted which is a page that we will set up in a moment yeah and that's our whole server action now we go into our new job form and into the onsubmit function here let's remove this alert call here we get the data not as form data but as this create job values object but we have to turn it into form data again because again this is necessary in order to send a file so we create a const form data and we initialize new form data like this and then instead of assigning each field one by one we use a loop like a real programmer for this we call object. entries we pass values which turns this object into an array then we call for each parentheses parentheses scrap brackets scrap brackets this time because this way we can destructure this value that we get in for each which is a tuple basically so here we have a key comma and a value make sure that you write the correct Sy sytax this is how this looks and in here we check if the value is defined so if value then we want to take our form data and append this value under this key simple as that so these values are either strings or a file in case of our company logo we append them all to the form data and then we can call our server action we wrap this into a tri catch block in the catch block I just want to show an alert message you could also show a toast message or render an error message in the UI but I want to keep it simple here and we don't really expect any errors so I just use this alert dialogue something R wrong please try again with correct spelling now when we call a server action from a server compon component like we did earlier then we can't wrap this into TR catch because here we just use this action prop and that's it but for our filters this is fine if there is an error which should pretty much never happen because this is just a redirect then we would get to the error page the error TSX file if we don't catch this error here then we also get to this error page but if this happens the user would also lose the form input which would be annoying because they might have typed in a lot of stuff already already this is why just to be sure we catch any potential errors here even we don't expect any maybe if the database is down then we could get an error but by catching it here we don't lose our input and the user can just click the submit button again and in the tri block we call theer Action and we await it create job posting to which we pass the form data before we submit an actual job let's actually try out the error message here so just for a moment I'm going to throw an error here and comment out this other stuff then we try to submit the form yeah and we see this alert dialogue but we still have our input now here's something interesting to note the way we set this up here we can actually not forward an error message from our server action to the front end we can only show a generic error message even if we try to get the error message out of this error in production nextjs actually removes the error message because otherwise it could happen that we accidentally leak sensitive data from our back end to the front end so whatever message we put into this error here like the Bazinga text will not Beed on the front end if we want to show an actual readable error message from the back end to the the front end then we have to return it as a string this is not necessary in this form because there are no errors we expect that we want to show to the user but later we will Implement such an error message so stay tuned and watch this tutorial to the end for this form here a generic error message is fine and before we create a job I also want to create this job submitted page here the confirmation page let's do this in the app folder job minus submitted SL page. TSX export default function let's just call it page it only contains a simple layout it needs a main tag with some styling M Auto mixed with 5xl my10 the same as the other pages basically space y5 PX3 and text Center that should be it in here I want to put an uppercase H1 which says job submitted and a paragraph your job posting has been submitted and is pending approval because remember we have to approve a job first before it is shown so let's try this out but we don't have our admin back end yet so just for now let's set a pro to True by default but we will remove this afterwards I just want to try out this form yeah I'm just going to add some dummy data basically description maybe add some mark down here one to 3 one to three and I also want to upload a logo but I'm yeah I'm going to use my own logo here okay and now is the big moment this should create a new entry in our database and also upload the image to the zel blob takes a moment but it's actually faster in production in development mode everything is a bit slower there's our confirmation page which means that we didn't get an error and since we said approv to true it should be shown on the front page oops yeah this is another step we have to do we have to allow this URL of our blob storage to load images from so let's go into our next config file and the reason we have to do this is because we use a next image which automatically resizes images which uses compute power and to not allow anyone to abuse our compute power we have to allow a specific URLs so inside next config we have this images field here we pass an object in here we have remote patterns to which we pass an array of objects so curly braces and then we set the host name to a string which includes everything behind the https I can actually not select this here so let's paste this over here we need everything after the https SL SL all the way up to.com but not this part here so this is what we want to put in here and then we actually have to restart the development server with npm runev for these changes to come into effect refresh the page and then it should work and voila there's our job with all the metad data with the logo with the rounded Corners isn't this cool and we can also take a look into to our blob storage back end here we actually have a file browser here we can see the company logos this is all on ver.com here's our image which contains the slck and then this random Nano IDE we can view this image here we can delete it but we will also later be able to delete it from our front end really cool stuff and this is how you call a server action from the client you call it like any other normal function and pass arguments to it but we are not done here we have to still Implement a few other pages and we also haven't learned about used form state yet which is another important hook that's related to server actions but before we go ahead let's remove this approved true because by default new jobs should be unapproved okay next I want to set up a job Details page where we also render the description in markdown format for this let's create a a new page let's put it into the jobs folder so here we have jobs new we want to create a new path here so we create a new file we want the URL to be at slash jobs slash then the slug which is a variable so we put it into square brackets and in here we put a page TSX and then we export default function page and this Dynamic slug will be passed to our page as a prop for this we declare the page props interface in here we will find the params again I explained this in my next JS beginner tutorial this is how these relative URL parameters work and in here will be this slug because that's the name we gave this Dynamic variable and the slug will be a string and then we can destructure this param down here of type page props and in here will be this slck and this slck decides which job we fetch from our database and then show on the screen just like any other page this is a server component so we can make this an async function and we can fetch the job in here but instead of executing our Prisma col directly in here we do it above by creating a new variable which we call get job then we call this cach function which is an import from react we call this and to Cache we pass an async arrow function like this async parentheses slug of type string right arrow curly braces I will explain why we rra it into this cache function in a moment but first let's finish this here we create a con job and we call await Prisma again we have to import this import Prisma from at lip Prisma and then we want to find a zingle job so Prisma do job. find unique parentheses curly braces where and then we want to compare this slug because this slug is what we pass to this page as a URL and since the slug is unique we can use it to find a unique entry in our database but we might type a slug that doesn't exist into our URL we have to take care of this case as well in this case the job returned here will be null so here we check if exclamation mark job then we call this not found function which is an import from next navigation this function will redirect us to the 404 not found page which makes sense because this job doesn't exist and we will set up this 404 page in a moment lastly from this function we want to return the job and then we can call it in our page cons job equals await get job to which we pass the slug that we receive through the params before we take care of the layout we also need to export another function here export a in function this time without the default keyword generate metadata make sure the spelling is exactly the same as I have it here because with this function we can generate the page metadata dynamically we have set up page metadata statically before like this by exporting this metadata object the difference now is that the metadata of this Details page depends on the data we get from our database because I want to use the title of it single job as the page title so we first have to get it from the database and this is why we need this function this function will return a promise because it's async of type metadata the same type as we used to export our static metadata and this function receives the same parameters as our page which contains this Slug and to get the job data we have to fetch the job in here again again this is how it works in the nextjs app router you can't directly share data between the page and this generate metadata function you have to fetch this data in both places this is why I wrapped this call into this cache function because this way we will only make one request for this page even though we call this function two times cach will cach the return value and this is how it's supposed to be done this is also described in the nextjs documentation the only the exception is if you use fetch fetch is automatically D duplicated for you but all these other ways of fetching data like using an OM or using AOS for example are not automatically duplicated you have to do this yourself with this cache function okay and now that we have the job in here we can generate metadata from this information so we return a metadata object and for the title I want to use the job tytle maybe for now let's return an empty main tag to see if the metadata works I want each of these items to be a link to the details page so let's go into the job results component and let's wrap each item into a link a next link of course the reason I RP it here and not inside the joob list item is because on the admin page I want to have a different link later so we do this on the outside so first of all we have to move the key up to the outer component here then we set an h r to a back tck string to this URL we set up here slash jobs SL slug so inside the string slash jobs slash and the slug is contained in the job object so we interpolate the variable job. slug again we could have also used the unique idea of each job as the URL param but the slug is more readable the slug contains the actual title and not just a number let's save this yeah and to maintain the spacing between these elements we also have to style this link and set it to display block with this block class and now each list entry is a link to the details page when we click it it loads a bit slowly but in production later these pages will be statically cached and load instantly what I care about right now is that this Title Here is correct full stack developer at stripe so this is how we generate Dynamic metadata okay let's finish our layout here first of all we style the main tag with Max width 5 XL like on the other Pages M Auto M10 I want to keep these Pages somewhat consistent we make it a flex box and a flex column by default but on the MD breakpoint we make it a flex row because on this Details page we will have the job information on the left side and a little side bar on the right side where we have our application link on small screens I want them to be a below each other and on MD screens and above I want them to be aligned horizontally then we add a few more classes items Center for alignment a gap of five and MD items start those are all okay and then in here we want to put the job Details page but I actually want to put this into a separate component because we also want to reuse this later on the admin page the admin page has a different sidebar but the same job description and everything so let's put this into a separate component component into the components folder because we use it on different pages let's call it job page or job Details page export default function job page but those are still server components because we don't need any client side features okay to this job page component we will pass a single job as prop so that we can extract the information out of it job page props and again we use this job type from the Prisma client and then pass this to this component I also want to D structure this job variable so that we don't have to repeat job dot job dot all the time so all the data we want to show on this page the title description company name application URL we don't need the application email here because we will create a link to this on the page itself in here I just want the URL because I want to generate a link to the company website next we need the type the location type the location salary and Company logo URL then let's return the layout itself serve I'm going to use a section for the outer tag again because this is its own section on the page but you could also use a div we style this with W4 and grow so that it takes up the available space in a flex box and we add some vertical spacing with space y5 in here we put another div which we Style with flex item Center and a gap of three and another div below the div at the top will contain the meta information like the type and the location and the diff down here will contain the description rendered as markdown so up here first of all we want to render the company logo but only if it exists because we don't have to upload a logo so we check if company logo URL is defined then we want to render a next image we don't need a placeholder as fallback if this entry doesn't have a logo we just don't show the image at all the srz is of course the company logo URL for the alt text we insert the string company logo we set the width and the height to 100 pixels and lastly we make this rounded again with rounded XL let's select the job that already has a logo so that we can see it on the screen let's go over to the page TSX file and put the job page in here and pass the job that we fetched to it and when we save this we already see the logo okay and then directly below this company logo URL expression we put another diff and since this is a flex Row the logo will be shown on the left side and this diff below will be shown on the right side in here we put another diff and in here we put an H1 this time with a lowercase H because I don't want to use our default styling to render the title I want this to be a bit smaller and not have our default H1 styling we seted text to XL and font bold and this is enough for the edge one for this page next I want to render the company name and if we have an application URL provided I want this company name to also be a link to the root URL of the company website so if the application link is apple.com applier I want this link to point to just apple.com So Below we put a paragraph tag I also want want to make this zi bolt and in here we put an expression with a turn operator we check if application URL is provided question mark then we want to render something and if not we want to render something else if not we want to render a normal span that just contains the company name if the application URL exists I want to render a link here which also shows the company name but also points to the URL of the website so we add an h and I want the root URL of the application URL as I described before we can get this by creating a new URL object URL all upper we pass the application URL to it and then we access this origin field and this will contain only the root URL without relative paths and without search params so again if the application URL is apple.com application this will point to only apple.com and I also want to style this link a little bit because by default in Twin CSS links are completely unstyled I want to set the color to text green 500 and when we hover over the link I want it to be underlined and there it is since we have an application URL here this is now a link that points to coding andf flow.com and if the job entry doesn't have an application URL then this will just be a normal text and then below this link I want to show the same meta information that we also show in the list the type the location salary and so on So Below this inner diff here that reps our H1 and the company name we put another div and on this diff we set text muted foreground to make the color of the text this light gray again and we can actually copy this stuff from the job list item because it looks very similar in fact we actually have the same text muted foreground diff in here let's copy the contents of the stff paste it in here we need to import all these icons and our utility functions but I actually don't want to show the created ad Tim stamp here on this Details page and I also don't want this item here to get removed on small screens so we delete the SM hidden and there it is and then Below in this empty div that we created earlier I want to render the description now of course by default we just see this raw mark down here we need to format this properly but I also want we have some padding here so we go to the page again and we are missing PX3 here to have some horizontal padding now we want to render this markdown for this I like to use this react markdown package to which you can pass a markdown string and it will render it we already installed this in the beginning let's create a new component for it so that we can reuse it in different places if we need to I'm just going to call it markdown TSX export default function markdown of course we need to pass the markdown string itself to it as the children of this tag so we create markdown props which will contain children but instead of react node we make this a string this time because this component only makes sense when we pass a string children to it so then we pass these props and then we return this react marked down again I have to import this manually react markdown from react minus markdown so we can use this component here and then we forward the children to it let's see how this looks by default so we save this and then down here we actually want to check if the description is defined because remember it's optional then we want to render our markdown component which we just created and in here we want to pass the description as children and voila now our text is bold let's take a look at the page with a larger description not this one this one here now it looks better than before at least we have bold text now but the spacing is not great and also we don't render lists some of these here are actually lists we can customize this react markdown component with our own styling first of all we add the class name space Y3 so that we get vertical spacing between the different markdown elements like this bold text and the paragraphs then we can customize each HTML tag generated from the markdown we do this again we are a prop to react markdown which is called components and to this repac Javas script object so two pairs of curly braces and in here we have a key for each possible HTML tag so for example we can tell it how to render a list we write UL column for unordered list and here we return a function that gets past the props and here we can tell it what element we want to return for it we want to return a UL element in here we want to spread these props which also include the children so whatever we put inside this list and the point of returning this is that we can now style this element by adding class names so right now each list is unstyled which again is the default interin CSS to style these lists we add two classes list inside which will change the spacing and list disk which adds these little dots in front of them and now we have our UL elements in the markdown styled and you can do this for every HTML tag here inside this components configuration let's also style links which are anchor tags so a colum again we get past the props again we want to render a link but not a next link this time because these links usually point to external URLs and next link only makes sense for internal URLs we also have to add a comma here then again we want to spread all the props which contains the children and the AG ref and so on and then we want to style this again with text screen 500 but this is up to you underline and I also want these links to open a new tab which we can do with Target uncore blank like this let's see if we find an entry that has a link inside it yeah here is one this is part of the markdown but it's rendered as an actual link and smart diary is by the way a startup I'm building so you're welcome to check this out smart diary. not. com but. zo let's see how this looked without rendering markdown so just for a moment I remove this markdown parer here just render the raw description and as you can see this is a markdown link this is the markdown link syntax but we want to render actual markdown this is enough styling for now again you can add more components to this react markdown tag to customize them but I'm fine with just links and lists for now next I want to create this not found page because again we redirect to it if this job with this slug doesn't EX exist let's try this out and by the way let's also take a look at how these slugs look this one just has a single digit because this was part of the placeholder data but the one we created through our front end has this URL so the title in lowercase and then this 10 digigit random string when we try to open a job that doesn't exist we get to this default not found page this is provided by default in nextjs but we can customize this and replace it for our own page for this we go into our app folder and right inside the app folder we create a new file with this exact name not minus. TSX which is another special file type in the nextjs app rouer just like error. TSX we export it default function cult not found the name of the component is up to you doesn't matter here we return a main tag which again we Style just like the other Pages Max with 5xl M Auto m y10 space y5 PX3 and text Center in here we put an H1 uppercase that say is not found and a little text below and this should be Max with 5 XL and not five the text will say sorry the page you are looking for does not exist just some information for the user and again they will get there if they try to open yeah either a job that doesn't exist but also any other URL on our website that doesn't exist this will automatically bring them to our not found page so our job Details page layout is basically done but again I also want to have a site bar on the right side with the application link so let's go back to the job page and we want to generate this link if the application email is provided I want to use that one if the URL is provided I want to use the URL instead but this is just the logic I came up with you can handle this however you want we do this down here in our page after fetching the job I want to destructure the email and the URL out of this job object application email application URL then I create a con called application link and here we check if application email exists question mark then the link should look like this back teex string mail ma tool column and in here we put the application email with mail tool we automatically open this email address in the email client of the device you might have seen this before and if application email is not defined then we want to render the application URL instead and in our validation schema we defined that one of these values has to be passed when we create a new job entry nevertheless typescript doesn't really know that one of these has to be defined so the application link can still be null but this is unexpected because again we validate this when we submit a new job so what we do is we check if application link is undefined then first of all we want to log an error to the console because something somewhere went wrong job has no application link or email we will see this in our server locks in production and then we want to redirect to not found again which again will show the 404 page because it doesn't make sense to show a job if there is no application link and again this is a Sit duration that should never happen and then next to the job page I want to show a Zite bar so we add in aite here in here I just want to show a button from our components folder now actually this is a link not a button because it will use our application link but it should look like a button so again we use the button component with the S child prop in here we put an external link to yeah application link which is either the application URL or the mail to address then I want to style this a little bit we set the width to a for here which is 10 Ram and on MD break points we set the width to fit you will see how this looks in a moment and this will just say apply now and there you can see it on small screens this is a flex call so they are below each other and when it's below each other I made the button a bit larger with this fixed width when the screen is bigger it moves to the right side because then it's a flex row MD Flex row and then the width is z to just being big enough to fit the text inside it okay and when we hover over this we can see that this points to the application URL let's take a look at another one chat gbt and the URL is open.com slapper let's see if we also have one with an email address yeah this one is mail to apply at stri r.com and when you click this link this will automatically open your email client on your device okay our layout is finished but we still have to take care of caching by default nextjs caches Pages statically whenever possible this means the data is fetched at compile time and not when the page is opened then nextjs renders the whole layout and then at runtime when the user opens the page this finished layout is served to them which loads the page much faster the problem is when our page has Dynamic params like our slack here then we actually disable the static caching because nextjs can't know in advance which value we passed for the slug so it doesn't know which data it has to fetch which also means it can't fetch it at compile time it has to fetch it at runtime when we actually open the page we can see this when we compile this project with npm run build after this has finished we see these icons here and and a circle means statically cached this is good and fast it automatically does this whenever possible for example on the job submitted confirmation page because we don't do anything special there but again this luck page is dynamically rendered because again we don't know this luck in advance we can also start this in production mode with npm start then you also need to refresh the page and now when we click one of these entries it takes a moment to load it's not slower but it's also not instant because again this data has to be fetched from the database when we open the page to enable static caching for these Dynamic Pages we have to tell nextjs what slugs we expect in advance and we can do this because we know what entries we have in our database right so we know all the Slugs already and to tell nextjs about them we have to export another function from this file we can put it above or below a generate metadata doesn't matter it's an export async function which again has a very exact name so make sure the spelling is correct generate static params in caml case this one doesn't need any arguments here we need all the Slugs that we want to render so let's fetch them from the database const jobs equals array Prisma job. find men where approved is true and we only need the slug nothing else so let's add the select slack column true and then we simply have to return an array that contains each slack as a string so it will look like this slack one blah blah blah slack two blah blah blah blah and so on and this jobs object is an JavaScript object that contains this slck right so we have to map it into this shape so we return jobs. map parenthesis parenthesis curly braces to destructure the slug out of here and the slug string is what we want to return from this map call to turn this into an array of strings and this is all we have to do and now when we compile the project again with npm run build we can see that this Lux page is now statically cached pre-rendered as static HTML here it says uses get static props this was the old way to do it in the Pates router in the app router it's handled via generate static params now this page is statically cached so let's start it again and we will notice a big difference and loading time now when I click a link boom it opens instantly because nextjs also prefetches links and together with this statically cached page there's virtually zero loading time again the way this works is that nextjs fetches all the job data at compile time and not when we open the page then it renders this HTML and it Returns the cached HTML to the user when they open the page and of course fast loading times are very good for SEO now another nice side effect of generates param is that after we implemented this if there is a new page with a new slug that we didn't cach yet which happens after we added a new job via our form and approved it because we might not compile the project again to generate these new pages here but every slack that's not included here will be rendered the first time a user opens it and then it will be cached for all successive users so it will only have a slow loading time one time for one single user and then it gets cached but this only happens if we Implement generate static params in the first place otherwise we always load it dynamically okay so that's very cool one more thing since this is a server component the markdown is also rendered on the server so this markdown Library here never reaches the client we execute this on the server and the client only sees the finished HTML this is the benefit of server components because we have a smaller Java JavaScript bundle size because we don't send all these packages to the client the react markdown package is only used on the server and again all of this makes our page load much faster which is great for SEO and SEO is important on a website like this because you want these entries to show up in Google Search right but also if you're building your own blog or a startup then it's really important to know this stuff okay so our website already looks very cool we can filter jobs we can look at jobs we can post new jobs but we still need our admin back end right and we also need pagination here so let's take care of this okay make sure to run your project in development mode again otherwise you won't see the changes that you're making so npm run Def and then refresh the page for authentication we will use clerk which is an authentication provider and they make it very easy and quick to implement yeah very powerful authentication features and this is perfect for our jobboard application because I don't want to spend an hour setting up off instead we will use clerk here we have to create an account I will put a sign up link into the video description below creating an account is free and they also have a very generous free tier so you don't have to pay anything go ahead and create an account here and then you should get to a dashboard and here we create a new application which we have to give a name nextjs jobboard for example how will your users sign in email address when we click on show more we can also see social logins which is of course very useful for a user facing app but since this is an admin dashboard I actually want to disable this and I only want email login to be available of course this is up to you you can enable any social provider but I'm going to stick with this and then we create an application then we get to a z up guide they have one for nextjs the first step is to uh yeah copy these API Keys into our n file paste them here as usual this is sensitive data so you shouldn't share this with anyone I will delete these keys after recording this tutorial the remaining setup steps are described in in the documentation but of course I've already prepared them we have to wrap the pages that we want to protect in a clock provider now I actually don't want to protect the whole website so we don't put it into our root layout because then we would have authentication activated for all pages which is unnecessary we only want authentication on the admin Pages for this we create a new folder called admin and in here we put another layout. TSX to create a layout that's only active for/ admin pages so in here we create a layout export default function we call it layout and as props it will take the children similar to the root layout and the type of this is children colon react do react node and then we return a clock provider looks like we have to import this manually again from at clerk nextjs in here is the clock provider we use this tag here and Rew wrap the children this way clock will be active on all child pages of the admin layout which are all pages under SL admin I also want to change the title of these admin Pages export const metadata of type metadata I want to set the title to just admin but it will still have our template from the root layout active so it will actually say at admin vertical bar flow drops and that's it for our admin layout next we need to set up a middleware this middleware will take care that clerk checks for authentication before we open an admin page you put this middlewares directly into the srz folder they have to be put here and they have to be called middleware dots this is another special file type in the nextjs app rouer okay here we export default AR middleware which is an import from Clerk nextjs and we call this with an empty configuration again this is described in the clar documentation then we export const config which defines where this middleware runs and we Define this via this meure value in form of an array of strings and into to the string we put a reg X we type slash parentheses admin another pair of parentheses in here do asterisk and that's it this is not dark magic it's just a r x that will protect all/ admin RS and execute the clock authentication middleware on them we save this and next I want to set up a very simple admin page where we can see our unapproved jobs we put it into the admin folder here page. TSX export default function page or admin page again this name doesn't matter for now let's just return a main tag that says admin page to see if authentication works so we save this and then when we try to open slash admin we should see the clock login and it works by default we get this account portal on this special URL here this is provided by clerk you can replace this for your own page I did this in some other tutorials but in this tutorial I didn't bother because it's just an admin login and the URL doesn't matter so we can stick with this generated page so here we can create an account for our admin back end let's say floran at coding andf flow.com again you can also enable social off if you want and I set the password to flow jobs admin it also provides password protection out of the box recreate an account don't want to save this and I get sent a verification code and this is cool about these authentication providers they take care of this stuff for you otherwise you have to implement this yourself and they also send these transactional emails for you okay let me confirm this real quick and then after we logged in we get to the/ admin page and now we are authenticated the page is still empty right now uh we didn't return this main tag I always forget this and there's the text admin page yeah and now let's build this admin page first of all we make this an async function this is a server component and here I want to fetch all unapproved jobs so const unapproved jobs equals arate Prisma as usual we have to import this job. find menu this time where approved is false we only want to show unapproved jobs and then we want to render them in the UI down here first of all I want to add some styling here so we add class names to the main tag as usual M Auto my10 Max width 5xl space y10 and PX3 then inside this main tag I put an uppercase H1 with class name text zenter which will say admin dashboard and below I put a section where we showed the unapproved jobs section with flex Flex call and a gap of three in here I want to put a sub headline in age2 that will say unapproved jobs colum so the idea is that your admin dashboard maybe has different sections and this is one section the under approv jobs I want to make the text a bit bigger with text LG and font bold and below the H2 inside the section I want to render these unapproved jobs with unapproved jobs. match then we take each job and we show a job list item right which again we wrap into a link just like we did it on the job results page here so let's copy this over here paste it import next link and job list item the difference is that the link should not point to SL jobs but to slash adminjobs SL slug which is a new page we will set up later where we can then approve these jobs yeah and when we save this we won't see anything because we don't have any unapproved jobs so let's create one real quick I'm just quickly going to fill this out with some gibberish upload an image here I don't know if this of this dude location remote we can still pick a location even for remote jobs um blah bl.com markdown salary just so that we have an unapproved job job submitted we go to/ admin and I think it makes sense to not have a link to the admin page on our front end because the admin page is only meant for admins right there should not be a public link there anyway now we see our unapproved job in our admin back end and next we create a separate page where we can then approve these jobs and also look at the description one more thing we also want to have an empty text here below the map call if unapproved jobs length is equal to zero then we want to render a paragraph tag with text muted foreground that says no unapproved jobs before we take care of this I also want to customize our clock login page a little bit now we don't have a log out button yet so we can log out by deleting the cookies of this page this will also delete the authentication cookie so click up here on I cookies and side data manage on device side data and delete the cookies if you can't find this it's also not a big deal because we will add a proper lockout button later then we have to reload the page and we get to the sign and screen again in the clock dashboard we see our one user and we can customize how authentication looks and behaves first of all of course you can enable different email providers including social providers one cool thing we can do is we can go to the restrictions and enable in allow list now this is a pro feature but in development you can use it for free you also don't really need this but what this does is we can only allow the email address that we actually expect as the admin so Florian at coding and flow.com and then no one else can sign up here so if we try to create a new account on/ admin blah bl.com this should now not work anymore only this one email address is allowed continue you are not allowed to access this application which is perfect for an admin dashboard because we don't want random people to sign up here but even if we don't enable this our application is secure because we will later give give our user an admin role and only they can actually execute admin operations so even if random users sign up here it's not a problem it just adds new users to your database we can also customize how this account portal looks here on this account portal option here we have the customization field again you can also replace this for your own login page but it's very convenient to use these generated pages I want to set the primary color to this slate color just so it looks a bit better and we can also set the font to for example inter which is what we use in our app as well but this is not really necessary just looks a bit more consistent and under customization and branding we can even upload our app logo so in this project in the assets folder we have our logo right we can upload this here also as the icon which is the faff icon let's do this as well refresh the page now we see the faff icon here and this logo which again just looks a little bit better you can explore this dashboard if you want again you can enable social logins you can add more data to user accounts you can also enable multifactor authentication which might be useful for an admin dashboard so it doesn't get hacked and again by using an authentication provider like clock we avoid a lot of hassle because this is taken care for us I'm going to close this for now and log it back into my account floran at coding and flow flow jobs admin and there we are okay when we approve or delete a job as an admin we will execute a server action of course so let's create a new actions file directly in the ad folder actions. TS and here we export an async function called approve submission which will take form data as input because we will execute this via a form action now remember on our new job form we have this Tri catch block and I explained that we always see this generic error message if something goes wrong because by default nextjs doesn't send the error message from the server action to the front end to avoid leaking sensitive data if we want a more specific error message we have to return it as a string from our server action and it makes sense to do this on our admin dashboard because as admins we always want to know exactly what is going on right we don't want a generic error message we want a specific error message and also this is a good opportunity to see how we can handle specific errors in server actions so we create a return type for our admin functions type let's call it form State equals this will be a JavaScript object that contains this optional error string field you can also add more values to the return State like a success message but I only care about error message but this can also be undefined so in the success case we don't have to return anything and then we want to set the return type of our function here to this form state but we have to wrap this into a promise because this is an async function and async functions always return promises then in here we can put the tri catch block and in the catch block we can get the error message out of the error and then return it via our form state so let's create a let message which will say unexpected error that's theault back because the type of error is unknown this can be anything below we check if error instance of the error class then we find an actual error message in here in this case we assign our let variable to error. message and then in the catch block we return our form State re return this error message and then on our front end we receive this return value and we can handle the error message there and one more time if we didn't catch this error in here and instead catch this on the front end then this error message will be removed for security reasons we have to return it explicitly if we want to receive it on the front end okay and in this form data we will send the ID of the job that we want to approve and since there is no other data in there a z schema would be a bit Overkill so instead we handle this manually cons job ID equals pass int here we pass form data. getet job ID we will later put this into our form and we also have to cast this to a string as I explained earlier then we have to validate that the user that tries to call This Server action is actually an admin because again front end protection loan is not enough anyone can call this endpoint here if they wanted to so we want to get the current user and for this we have this current user function from clerk that we can just call then we can check if a user is logged in but I also want to check if the user has the admin role so that we avoid that anyone can sign up create an account and then call our admin actions here only admins should be allowed to do this so what we do is we go into our clerk dashboard again we go to users and open this user here and down here we have this public metadata we added this and in here we put one key that says roll and we set this to admin we can then read this value on our front end and this way we know that this user is actually an admin back into our project let's go into to our utils folder where we create a new function called is admin this will take the current user as input which can be one of two types and we have to import it manually so we import from clock but not slash nextjs but slash types and here is the user resource and then we also need another one the next one is called just user from clerk nextjs SLS server when we fetch the current user on the back end we get this type when we fetch it on the front end we get this type but both of them contain the public metadata which we can use to check if this user is an admin so down here the user we have to pass is either a user resource or a user so we create this intersection type with this vertical bar and then we return user dopu metadata do roll which is the field we just put in there is equal to admin if this is the case we know that this user is an admin we save this and go back into our actions file below current user we check if exclamation mark us user or exclamation mark is admin to which we pass the user then we want to throw a new Arrow which says not authorized again this way we avoid that any random user calls this server action only admins should be allowed to execute this and when we get through this authentication check we want update this job in our database again we need the Prisma client import Prisma from at lip Prisma await Prisma do job. update where the idea is this job ID that repairs and the data is what we actually want to update we want to set approved to True nice and then lastly we want to tell nextjs to rerender the front page to show this updated data we do this with revalidate path which is an import from next cache to which we pass a string with a slash inside it we want to revalidate the front page now when you call revalidate path in a server action it also clears the client size router cache so what we see on the screen will also be refreshed no matter what page we are currently on so this refreshes the page as well and I want to put one more server action in here to delete a job so another export async function called delete job again rep pass form data to it which will contain the job idea so actually this part here in the beginning including the authentication is the same we wrap this into TR catch paste this here and the catch block is also the same then let's go inside the tri block this time we want to delete a job but we also want to delete the corresponding logo file from our blob storage right so the first step is to fetch the job cons job equal await Prisma do job. find unique where ID is the job idea then below we check if job. company logo URL if this company logo exists then we want to delete it which we do with await de d is another import from the zel blob it's the delete function and to this we simply have to pass the URL of this image job. compan logo URL so this is really easy to use if this image exists it will be deleted and then we also want to delete the job entry from the database so again await Prisma job. delete where colon idea is again the job idea again we want to revalidate the front page to rerender it but this time I also want to redirect back to the admin dashboard because after we deleted this it will be gun right and I don't want to land on a 404 page for this we have a redirect function but we have to be careful because we must not put this redirect function into the tri block we have to put it below the the try catch here we call redirect from next navigation and we want to redirect to/ admin the reason why we have to put this outside of the try catch is because internally this throws an error this is how nextjs handles redirects v a special error that this function throws an error boundary automatically catches this and redirects us but this will not work if we put this into the dry catch block because then we catch this error so you always have to put this outside okay and I almost forgot since those are server actions we have to put the use server directive at the top right we did this before and the only difference to our other actions file is that this time we handle specific errors here we didn't have a TR catch we just caught the error client side with a generic error message but now we have this more specific error message when we click any of these cards this points us to/ b/ jobs slendis luck we haven't created this page yet so let's do that now and this page will look very similar to our existing Details page but this time we don't care about SEO or caching so we can leave out all of this stuff here so we create a new path in our admin folder right click on admin new file jobs slash slack in scrap records SL page. TSX and again here we need to read this slck param from the URL so we create an interface page props which contains the params which contains the slug then we export default function called page in here we find our slug we need to fetch the job for this page const job equals the weight we have to import Prisma make this an async function Prisma doj job. find unique this time we don't care if this job is approved because we are under admin page we just want to fetch the job for this slug again if the job doesn't exist we redirect to not found this makes sense on the admin page as well and by the way when we car not found this returns never which means that we will never reach the code below we will never render this page we only render this page if the job is actually available and then we put a main tag here which we style as usual M Auto my10 Max width 5xl Flex call because we have a sidebar here as well item Center Gap five PX3 and MD Flex Row for responsiveness and one more MD item start okay we will keep this layout here very simple we render our job page which contains the job details and the markdown description we pass the job to it and then I want to have a little admin z bar here on the right side or below just like on our public job Details page just that here we don't have the application link we have the admin actions so let's create this admin sitar and it makes sense to pull it into the same folder because we don't use it anywhere else admin sitebar TSX export default function admin zbar and it needs the job as a prop admin sidebar props job of type job as usual we pass this prop here and from this component I want to return an a side tag to create a side bar we have to style this a little bit want to make this a flex box I want to give this a fixed width of 200 pixels with flex none we avoid that this sidebar shrinks when there's not enough room on the screen we make this a flex row by default so on small screens the buttons in here are aligned horizontally but on larger screens we make this Flex call to have them below each other we also add item Center and a gap of two and the last one is MD items stretch so that on larger screens the buttons we put in here will take up the full 200 pixel width just looks better in my opinion for now let's just put a text in here that says admin sidebar and put it into our admin job Details page so we render it next to the job details admin sidebar which also takes the job because we need a job ID to know which job to approve or delete there is one class missing here this also needs to be a flex boox okay and now we should already see our side bar here or on the right side depending on the screen size now we put two buttons in here that call our two admin actions to approve or delete a job again we don't care about caching or SEO on this page because only admins see this page this doesn't show up in Google and normal users also don't see this page let's go back into our admin Side Bar and finish setting this up now your first idea might be to just put two buttons in here here and then call our server actions in the onclick Handler and this would work but again the problem with this approach is that it requires JavaScript so it doesn't support Progressive enhancement and Progressive enhancement is always a good goal to have so instead we will handle this via form actions again because form actions work without JavaScript and actually I read somewhere that the react best practices now that we have server components move towards using more form actions and less onclick handlers so let's see how this works for organization I want to put them into separate components So Below we create a function called approve submission button to it we will pass the job idea let's create an interface for this we call it admin button props we use the same props for our delete button later we will have to pass the job idea which is actually a number that's the ID in our database we could also pass the slck but the ID is sufficient here this is of type admin button props and in here we want to render a lowercase form which will then execute our server action but this form should only contain a button not an actual input field but we still need our job idea and the way to add data data to a form without having to insert It Is by adding a hidden input so we add the lowercase input we set it to Hidden we give it the name job idea this name has to be the same as we used here in our actions file because this is the value we try to get out of the form data and then we set the value of this input field to the job idea that we pass to this function so this input field will not actually be visible in the UI but it will contain the job ID and put it into our form data and then below we put a form submit button which then sends this form it executes the server action and it sends the job idea so we style this a little bit with W full I want to set it to a green background color and when we hover we set it to a slightly darker green and the text of the button will say approved let's put it into the admin sidebar layout we delete this text and here I actually first want to check if the job is already approved then I don't want to show the button instead I want to show a text that says approved so here I put a span with class name text Center font zi Bol and text green 500 this will say approved but if the job is not approved yet we want to render our approved submission button to which we pass the job idea let's save this and there it is this job isn't approved yet so we see this button okay so far this looks very similar to our job filter sidebar right we have a server component and a a form and this form executes a server action but the difference this time is that we want to be able to read this error message if there is one but we still want to allow Progressive enhancement we still want this page to work without JavaScript and this is where react's new use form State hook comes into play not to confuse with use form statas which we used earlier this one works a bit differently we basically pass our server action to it and then we get back the form State and the form state is whatever we return from our server action in our case that's the optional error message the cool thing about use form state is that we can still use Progressive enhancement because it still uses the form's action prop so let's see how this works first of all since this is a hook this page has to be turned into a client component there is no other way because we can't call hooks inside server components but Progressive and enhancement will still work and that's the most important part then we go into our approv submission button component again Rec create a const scrap records the first value will be the form State and the second value will be the form action how you call these two variables is up to you entry assign this to this use form State hook which is an import from react Dom not the react hook form one we want to use the react Dom import to this we pass the server action we want to call which up here is approve submission which we import from our actions folder and then the second argument is the default State and this now has the same type as you can see here as the return type of our Z action which is this form State type and since we Z this can also be undefined we can initialize this with undefined alternatively we could also put a default state in here but undefined is sufficient because when we open this page we don't have any errors yet this still shows an error because when you call a server action from use form State we get one additional argument passed to it which is the breath state which is also of type form State this is the previous St as the name implies but usually you don't use this for anything we will completely ignore this we also have to add this to our second server action and this has to be the first argument and the second argument is the form data okay now that we edit this the error here disappeared we get this new action here which we now pass to our form as the action prop form action and I also want to add some styling here space y1 space y1 because below the button I want to render the error of the form state if we get one back so here we write form state. error if it exists then we want to render an error message and again this value is whatever we return here on this aror field but it can also be undefined this is why we have the save call operator if we get an error we want to render a text a small text texts SM we make it red and this will contain form state. error the form state is coming from the use form State hook now to recap instead of use form State we could have also used a normal use State then execute our server action via an onclick Handler read The Returned error message if there S1 and put it into this state the benefit that use form State brings is that this still enables Progressive enhancement because it uses the native form action prop which doesn't require JavaScript this is what we gain by using this hook and you could return more form state from the server action could also contain a success message or other values now let's try this out let's save this file and I want to return an error from our approve function here so let's throw an error which obviously we remove later let's comment out this stuff and then try to call this approve we get a loading State because we use our form submit button and after a short moment we see our error message okay this is how you use use form State let's also try approving a job approve again in development this is a bit slower and now we see this approved text and now let's add a delete button here as well in our admin sidebar we create another component below called delete job button and again this takes the job idea and the layout looks very similar so I'm going to copy this just that of of course we want to call delete job here we want to change the color to Red 500 and red 600 the text should say delete and I think this should be it let's put it into our Zar layout below this block here delete job button which also needs the job idea and there it is let's try deleting a job as well but let's see if this works without JavaScript so one more time first of all I want to throw an error here as well so I comment out the content of delete jop I throw a new error let's use another error message this time we already saw that this works with JavaScript but what is without JavaScript let's disable JavaScript script refresh the page click delete no loading spinner but the cool thing is we still see our error message and this is really special about use form State because normally you need state to show an error message dynamically and state requires JavaScript but with use form State this works even without JavaScript and now let's remove the error and try actually deleting this job so JavaScript is still disabled build we will not see a loading indicator but the delete operation worked let's enable JavaScript again there are no unapproved jobs anymore the entry is scun from the database and when we look into the blob storage the image will also not be there anymore just two little corrections I want to make I misspelled texts here there's a dash missing and down here as well and also I want to put the actions file into the same folder as the admin sidebar I put it one folder above accidentally doesn't really matter it's only for organization but I want to move it in here and this should automatically update the import statements if it didn't you have to update it here on the admin zbar file and save the changes okay the last thing I want to do for this admin part is add a little admin knar to the top with a lockout button and also a link that brings us to the admin dashboard so let's do this again we can put this directly into the admin folder and I call it admin navbar TSX export default function atmin navbar this function doesn't take any props but this has to be a client component because we need an onclick Handler to lock the user out which requires JavaScript okay first of all at the top we create a constant that we destructure and we call use clerk from clerk nextjs in here we get the current user and the sign out function we also need the router because we want to redirect the user after logging out client s and for this we can call the use router hook here you have to be careful to import the run from next SL navigation not next SL router the SL router one only works in the Pages directory in the app directory we need this one okay and then we are return a relatively simple component here so this is a div with a px of three let's put it into the layout for now we do this in our admin layout TSX because we only want to show this admin nfar on admin pages right and we can easily achieve this with these nested layouts this is a cool feature about nextjs so above the children in the clock provider we render the admin nafar and when we save this and yeah there it is here in the top left but on the front page or all other page that are not behind SL admin we don't have the SN bar only here okay let's finish setting it up in here we put another div which we Style with M Auto Flex h10 Max withth 5 XL item Center justify between and the gap of two then as the first element in here I want to put a next link which will bring us back to/ admin this is useful for example if we are on a job Details page and we want to get back to the admin page we also want to style this with font zi bolt and underline to indicate that this is a link and this will a admin dashboard and it looks like this this is a link that always brings us back here then after this link we put another div with space X2 here we put a span that will show the email address of the currently logged in user user. primary email address and this is managed by clerk the email address is saved when we create an account and in here we have to access this email address field and I also want to make this span zi bolt so here is my email address just so that we see who is logged in currently and then below the span we put an unstyled button where we can log out via an onclick Handler the onclick function is async because the sign out function is async and we want to await it so here we call await sign out and then we want to redirect to the front page we do this client site via the router I also want to make this button underlined so it looks like a link and it will a Lo out and there it is it's not the most beautiful enough bar but I didn't want to spend too much time styling this I think this is sufficient you're welcome to improve this if you think you're better than me how dare you offending my design skills okay let's see if this works log out and we get redirected to the front page and the next time we go to slash admin we have to log in again and if you want to delete an existing job then you can open it you can change the url go before jobs enter SL admin open this page and then you have the admin sidebar with the delete button by the way let's also take a look into our blob storage there should only be one image in there right because the other ones were deleted yeah and indeed in the company logos is only our one image we deleted the other one when we deleted the entry there are still two steps left for one I want to implement pagination here which is important because if you have 100 jobs or a thousand you don't want to load them all at once and then we also want to deploy this project which requires a few additional steps so make sure to watch this video all the way to the end and please leave a like if you haven't yet and it would be even more amazing if you shared this video with someone because this really helps this channel grow even if you just post it I don't know on Twitter or some random Facebook page it really helps me a lot so please consider doing this because the more views this video gets the more incentive I have to create more of these free tutorials okay the page will become another URL param for one because of sharability but also because then it will work without State and without JavaScript so we go into the root page. TSX at the top we already read the search param now we want to read one more an optional page it's a string because again our search parameters are strings and we want to read this value here down in the page component and we will forward it to the job results component in there we will put a new page prop in a moment here we check if page is defined in this case we want to pass the page number from the string otherwise we pass undefined then we go into the job results component and handle this new prop here we add it to the drob results props next to the filter values we now also expect this optional page which we also D structure down here and what we can do is since this is optional we can set the default value to one so if we don't pass a page then it will automatically be set to one okay next I don't want to structure the filter values here anymore because we will also need to pass this filter values to the pagination component and if we D structure it here we can't pass this object directly instead we cut this out but we destructure them again below so here we create a const but without this column and we destructure it from the filter values the difference is that we can now also use this filter values variable directly next we create a const called jobs per page which defines how many jobs we want to show per page I set this to a small value because we don't have that many jobs in our database a more reasonable value would probably be a 10 or even higher depending of how many jobs you want to show we store this value in a variable to avoid magic numbers this makes our code more readable instead of putting just six everywhere okay and pagination works by skipping a certain number of results when we read from our database how many results do we want to skip well on the first page page one we don't want to skip any but any page afterwards we want to skip the page number times these results so what we do is we create a con skip which we then pass to our Prisma query we assign this tool page minus one in parentheses times jobs per page so on the first page page this will be zero on page two it will be six and so on always the page size okay then we go down to our Prisma gr down here find menu after all buy we add take how many results we want to return six results jobs per page and then we also pass skip which contains the skipping logic how many results we want to skip before we get to the page okay and I also want a account of the total aailable Pages for this Prisma has a special function that we can call Prisma do job. count and here we can add the same filters and we just pass our wear filter which already contains all our filtering Logic the same one we pass to find many but now we count these results of course we have to store this value in a variable and this returns a promise as well well but when we write it like this a wait find many a wait count then these two promises will be executed one after another which is a bit wasteful because there's no reason to wait for the first result before we fetch the second one instead we should fetch them concurrently so what we do is we remove this AIT up here we call this jobs promise down here in front of count we create a const count promise again we don't call await yet because we do this below with promise.all which looks like this this returns an array with the two values of these two operations the jobs and the total results or the account you can name this whatever you want and we assign this to await promise. R to which we pass an array with our two promises the jobs promise and the count promise and again now these two will be executed at the same time this is how you avoid these so-called waterfalls in react server components where we have to wait for one request to finish before we execute the next one okay and for organization I want to put the page nation into a separate component which we put below called ption it will also take props interface page Nation props this will contain the current page in form of a number the total pages so the count and also the filter values of type job filter values because we need them to construct a new link because when we go to the next page we change the page param in the the URL but we also want to keep the filter params in the URL right so we have to forward them here we pass them to our component and destructure them and we can also destructure the filter values so the navigation is handled via links and as I explained we want to maintain these search parameters in this link so we create a reusable function in here called generate page link to which we pass the newer page number to which we want to go and here we need to construct a search param so we create a con search param we assign it to a new URL search params parentheses curly bracers and in here we want to put all the search param if they are defined so again we use this spread syntax that we used before where we can conditionally put a value in here so only if Q is defined we put it in here we do the same for the type the location and remote but again remote is a Boolean so we have to turn this into a string value remote column true and lastly we put a new page in here which is this page number that we pass but we also have to turn this into a string because again URL search params are always strings so now we have these new search params and we can return the string as a backt string to a slash question mark variable placeholder search params to string we have used this before and now we can construct our pagination down here in the return block inside the div before we write the layout let's put it into our UI so here below our results we put another expression which first checks if jobs. length is greater than zero we only want to show pagination if there are actually results then we want to render pagination to which we pass current page which is the page that we pass from the search params up here we pass the total Pages for this we fetched the total number of results but we still have to divide this by the page size and then round the value up with meth. seal we round it up so if it's 5 and a half pages then we have six pages of course and not 5 and a half and to it repairs total results divided by jobs per page the page size and lastly we need to pass the filter values and then close this okay then let's go down again into our div we want to style this div a little bit flex and justify between because I want to have a back button on the left side and the next page button on the right side I don't actually show every single page number because setting up these layouts is always a bit tedious and I want to keep it simple here but you are free to modify this if you want okay and in here we basically put two links so in next link for the AG ref we use our generate page link function to which we pass current page minus one and then we want to style this link and I actually want to use the ZN function here because I want to have some logic in here and this is easier to write with the CN function so the first class name String is the default styling Flex item Center Gap two and font zib Bol and this link will say previous page okay down here is our previous page button but we are on the first page so we want to hide this this is why I added this CN function here because as the second value we pass a little conditional we check if current page is less than or equal to one then we want to add the class invisible to hide this now why do I use invisible instead of rendering this component conditionally because I want to keep this justify between active I want to have the next page button on the right side and by making this hidden the element is still there and still takes up this position which makes it easier to create the layout I want if we didn't render this link at all then the next page button would move to the left side and I don't want this so now we will only see this when we navigate to Page tour so we type in question Mark page equals 2 into the URL here's the button again and I actually want to put an error IC can left to it Arrow left from lit react and reset the size to 16 let's see there it is when we click it we get back to the first page now let's also add a next page button below the first link we put another one maybe let's copy the existing one here and just modify it generate page link is of course current page + one and we want to hide this element if current page is greater than or equal to the total number of pages this should say next page and it should be an error right and voila there it is now the error should actually be on the right right side of the text this is better let's see if this works yeah and we can navigate isn't this cool one more thing in the middle I also want to render the total number of pages so we go between these two links add a span with font zib bolt and here I want to render a text that says page current page of total pages so page one of tour next page page two of two and just like that we implemented pagination okay I consider this website finished the last step is to deploy it to production okay party people time to deploy this thing now when we deploy a nextjs project that uses Prisma to Vil we have to add this post install script otherwise we will get an error on r z back into our project into the package.json up here to the scripts we add a new one comma post install and this should execute Prisma generate to make sure that every time this project is compiled a new Prisma client is generated we save this then to deploy this project on Vel you have to push it to GitHub you can click here on Source control in v code and this allows you to create a GitHub repository it can be private doesn't have to be public then we go into our Vel dashboard where we earlier also set up our databases and here we can add a new project if you Vel account is connected to your GitHub account then you will see your repositories here otherwise you can go to the settings and add the lockin connection here under this lockin connections menu option you need to connect your GitHub account then we select this repository nextjs jobboard in this case it automatically detects that this is nextjs then we have to insert our environment variables because we need them in production as well so we copy them over one by one post address URL and the value is what we have here between quotation marks you have to add each one I'm going to skip this part in this video to not waste your time just put them all in here okay I have added all environment variables then we click on depoy and wait until this is done and while it's building it also generates all our static pages so it has to fetch the data which might take a minute or two and then it's done and we get some confid way okay before we open our amazing website we have to make a little change let's open our project here and go into the settings and here under General we have to set the node.js version to 20 remember we installed node.js 20 on our machine because I said that this file type in our server action doesn't work on earlier versions so so we have to set it to 20 here as well otherwise we get an error when we execute our server action so we select 20 we save this then we go to the deployments tab and we have to redeploy this otherwise the node.js change will not come into effect so we click on redeploy and then again we have to wait 2 minutes or so until this is finished and after this is done we can finally open our page and there it is and this is a real URL here you can actually share this with someone it's not the most pretty URL but it works let's see if our filters here still work there there and there it is and the pages still open very fast the cached ones here super cool pagination amazing Post in your job validation and everything should still work the same the cool thing about Vel is that when you now push new changes to the main branch of your repository then nextjs will automatically redeploy your project and 2 minutes later they will be applied to production so you don't have to deploy manually all the time you just push your changes to the main branch you can also buy a domain directly on Brazil somewhere here is this domains option and here you can directly buy a domain you don't have to you can also buy it by another provider and connect it here but it's possible one more thing in the clock dashboard you can also switch to the production mode you can read more about this in the documentation this requires just a few changes and if you actually want to deploy your project to production then you should do this but the clock authentication here on our website also works without setting this to production mode so we can still log into our admin back end where we see our unapproved jobs okay congratulations for building this amazing project I hope this tutorial was helpful if it was please leave a like on this video subscribe to the channel if you haven't yet and even more amazing would be if you shared this video with someone because this really helps the channel grow and I have more incentives to create more tutorials like this in the future all right thank you very much Happy coding take care
Info
Channel: Coding in Flow
Views: 26,742
Rating: undefined out of 5
Keywords: next js progressive enhancement, progressive enhancement, progressive enhancement react, next.js tutorial, next.js 14, nextjs 14, next.js 14 server actions, next.js server actions, next js server actions error handling, next js server actions example, next js server actions form, useformstate, useformstate nextjs, useformstate react, useformstate typescript, useformstatus, useformstatus next js, next js useformstatus, react hook form, shadcn ui, zod, next js prisma, vercel blob
Id: XD5FpbVpWzk
Channel Id: undefined
Length: 379min 22sec (22762 seconds)
Published: Fri Jan 05 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.