In this chapter, we're going to develop the UI for our authentication. This will include the auth pages, auth layout, auth module, and finally the auth views or components. Let's go ahead and let's do one thing first. So right now, if you take a look at our package.json, in here you can see the dash dash TurboPack option. And when you run your app, you will see the TurboPack here and you will see TurboPack right here.
What I want you to do is to remove TurboPack. That is because I found an issue with the layouts which is something that we will be learning in this chapter and that issue simply makes it a bit harder to demonstrate what I want to teach you in this chapter. So for now go ahead and remove that and save package.json and then go ahead and do npm run dev again. You will know that you've done it correctly when you no longer see TurboPack as opposed to seeing it right here. So just go ahead and do that.
That is the first thing we have to do. The next thing we are going to do is create our first route. So let's go inside of source app folder and let's do sign-in and inside a page.dsx file. Let's go ahead and do an export default and let's simply return sign in page. Two things you have to know here.
Page is a reserved file name. If you've named it something other than page, Next.js will not recognize this as a route. The second important thing is to always do a default export in that reserved page file name. Otherwise, Next.js will not be able to find the component it needs for routing. Now if you want to find this, what you have to do is simply go to localhost 3000 slash sign dash in.
So let's go ahead and visit that page right here. So you can see I still have my old form here, but if I go to localhost 3000 slash sign dash in, we can find the sign in page. So now let's go ahead and copy and paste this and let's rename this one to sign up. Let's go inside of the page and change this to sign up page. This one is visible at the same one just dash sign up.
There we go. Now let's say that I want to add some design here for example for this sign-in page I will add a class name background muted flex minimum height of svh flex column items center, justify center, padding of 6, medium padding of 10. And then I'm going to add an inner div here, which will be used to limit the width. Width will be full by default, but on the mobile devices, the maximum it will be able to expand to is 24 rem or 384 pixels. On larger devices, we are going to increase this to 3xl or 768 pixels.
Now, let's go ahead and check this out. So sign up page is all the way here but if I go into sign in you can see how it is pushed down here and this is actually centered and it will be easier for you to understand that if you for example wrap it with a card component. You have the card component available since we have added all components from ShatCN UI. So it is right here in source components UI card. And that's where you can import it from.
But now it makes more sense. This is definitely a centered component. What I want you to do now is go inside of sign up page here and simply add the same card without anything else. So just the card. So now you can see a clear difference between sign in and sign up pages.
They have different backgrounds and they have different positions. But since we know that we want the exact same style for both of these routes, there should be a way to reuse this layout. And there is. Let's learn that by creating a new folder called auth. Let's go ahead and select both sign in and sign up and let's drag and drop them inside.
If you are asked to update the imports you can select yes. Now, first thing you will notice is that our page has turned into a 404. That's because it is no longer available at slash sign in. Now it needs auth sign in. That's how you create nested routes.
There we go. And same thing is true for sign up. But what we've unlocked now is a parent folder. Parent folder can now have a layout file. Layout is a reserved file name, just like page, and it behaves pretty similar.
It needs to have a default export. The name of the component does not matter. But what does matter is that you always return the children from here. Children are a type of React node. Let's go ahead and restructure them from here and let's render them inside of this empty div.
So right now not much will change. Sign up still looks like this and sign in still looks like this. But what I want to do is I want to go inside of sign in here and I want to copy these two divs and I will instead put it here in the layout And I will add an additional closing div here. And I will indent the children. And now I'm going to go inside of the sign-in page and I will remove everything besides the card here.
So my sign-in page and my sign-up page now look identical besides the text inside of them. And in my layout, I have now added the class names. And what happens now? The sign-in looks exactly the same and the sign-up also looks the same. Sign-up has now gotten those styles that it didn't previously have.
So that's the power of layout files. Layout files are reusable, well, layouts that you can use, and this will apply to every route that I create inside of the out folder. That's why we needed a parent folder. But you are probably wondering what if you just want to have an organizational folder, right? Because while this is cool it also extended our URL, right?
We don't have to visit using slash auth here. What if I want to go back to this? Do I have to write the layout file individually in each page as I did before? No, you don't. What you can do is use something called route groups.
Go ahead and rename this into auth in parentheses. If it asks you to import to update the imports, you can select yes. What this will do is it will mark this as an auth as a route group, which means that your routes are now officially accessible back through its original URL. You can see that the current one is 404, but if I go back to my old ones, there we go. Now I have a much cleaner URL, but I also preserved the ability to create a unique layout file for that group of routes.
So this basically tells the Next.js router do not include this in the URL. That doesn't mean don't include this folder in routing. It simply means this specific folder is just a grouping folder. This will not be a part of the URL. This will be a part of the URL.
And you can nest these kinds of things as deep as you want. They won't stop working, right? Whenever you have something in parentheses inside of the app folder, it will be excluded from the URL. But that does not mean excluded from routing. That's important to understand.
Great, so now that we have this set up and we have learned about the layout and how we can use it how about we go and create the actual module. So let's go inside of the source make sure you are in the source and create modules and inside of here create the alph module and in here I'm going to keep everything alph related let's start with our UI in here we are going to have views so modules alph ui views go ahead and create sign in view.tsx just like this This is a component so it does not need to have a default export. We can just do signinview and return signinview like this. Or let's be a bit more specific let's return a card from components UI card And now if you go inside of your sign-in page, so right here in the auth route group, you can just return that sign-in view. So now our page here is used purely for routing and it just renders this.
And we are going to develop the actual code inside of the module where it belongs. So now sign in looks identical. So let's go ahead and do the same thing. Sign in view. Let's copy it.
Sign up view. Let's rename this to sign up view and change this to sign up view and just to clarify this is a my code structure This is not something you have to do. Modules is not a reserved folder in Next.js. This is simply how I like to separate my modules. Instead of writing the actual code in pages, I mostly use page for redirects, for prefetching, And then I just render the actual component I need maybe for some suspense or something like that.
But I keep them as server components so that I can leverage everything server components has to offer. And then I simply do the client stuff inside of these views right here. So now let's go ahead and go inside of sign up here and let's do the same thing here. So sign in view, my apologies, sign up view. Remove the unused imports and you can remove these comments.
We now know how to access these routes right. And now before we move on I just want to go quickly back inside of these views and into both of them go ahead and add use client at the top. So sign up view and sign in view. Both of them should have use client at the top. And what I want you to do is add a console log, sign up view, and go inside of sign in and add sign in view.
And then go inside of these pages here. So sign in page, add a console log here, sign in page. And sign up, Sign up page. So all of these four should have both the views and the pages. The only differences that sign up views have these use client thingies.
I'm purposely doing this to show you something. So you can see that when I'm on my sign-in view, the sign-in page console logs from page.tsx, but it has a server prefix. This means that it's actually logging this on the server and not on the page, right? So I just wanted to show you that difference. And if you go inside of the signup, you will see the same thing.
You can see we have signup page on the server, where a sign in view is the actual client component. So you can now remove these console logs and I'm going to talk more about server and client components moving on, but I think this will give you the most basic difference between the two. So our views need to have use client because we are going to do client stuff in them. So let's start with the sign in view. What I'm going to do is I'm going to wrap this inside of a div.
Whoops, let's just move that here. I'm going to wrap the card inside of a div and I'm going to give the class name flex flex column and gap of six. Now for the card I'm going to give it a class name overflow hidden and padding of zero. Then I'm going to import card content from the same at slash components UI card like this in the card content let's give it a class name here grid padding of zero and on desktop grid columns too so right now if you add a paragraph for example column one and then another one column two, and head into sign in, you will see that these columns are next to each other. But when you zoom in, they collapse, because that's exactly what we told them to do.
Only on desktop, this has to become two columns next to each other. Otherwise, it's going to be beneath one another. That's exactly how we want them to behave. Great. So now what I'm going to do is I'm going to create a form element here like this and in here I will create a div element simply so we can begin separating these two.
Now, let's go ahead and do the following. Let's do the second column first, because it's easier. So this column right here. We're going to do a class name here, background radial, ROM, and then in here, let's go ahead and say green 500 to green 800. Something like this.
We can experiment a bit. Let's try 700 to 800, or maybe make this a bit darker something like this for now it's okay Let's keep it relative. Hidden. MD. Flex.
Flex column. Gap y of 4. Items. Center and justify. Center.
So now you should see column two here in the center like that. And now what we're going to do is we're going to render a paragraph here with a class name, text to Excel on semi bold and text white. And this will have meet AI text inside or whatever you are going to call your app. And then above this, we should have a normal image element. So yes, it should have this warning that's okay we should go to slash logo.svg and give it an alt here of image and give it a class name here of height 92 pixels and the width of 92 pixels Now you will get this because we don't have a logo yet and I'm gonna show you where you can get one.
So I find my placeholder logos on LogoIpsum. I think it's an amazing website with a bunch of placeholder logos. So you should only use this for your demo applications. And the one I found is this one. So logoipsum.com You can of course find the link in the description.
All you can do is just click on this logos and that will copy the SVG. You can of course use the normal download or you can just click copy and then go inside of public logo SVG and paste it inside. Let me just... Yes. If it asks you to open that, just click yes to open the editor and then paste it inside.
And then this will have the logo and when you refresh, there we go, we have the logo. Now let's go ahead and just change the colors a little bit here. So we have this color E22935. Go ahead and use the find and replace option here and change it to the following color 16A34A and click this one to replace all instances. There's two of them.
There we go. So already when you refresh, you will see a change of color. But now we have four more instances of the red color. So use the find method again. You can use Control F or Command F if you haven't been able to find it.
And replace this one with 6cc58d. And you can use this one to replace all four instances and refresh and there we go. Perfect. So that is it for this column right here and now we can focus on actually developing the form. So what I'm going to do is I'm going to go back in my sign in view here.
And since we installed all components from chat-cn, we also got some new packages. And one of them is Zod. So we can import Z from Zod. And you can go inside of package.json and search for Zod to confirm that you have it. Besides this, we're also going to need ZodResolver from at hookform resolvers slash Zod.
And we are also going to need the input component from components UI input. We are also going to need the button from components UI button. And we're also going to need all the components from components UI form. So let's add this as well. You have all of this installed, right?
All of this already exists in your project. Don't confuse this at sign with this at sign. So this is the name of the npm package and this is the name of our alias. That's where I add space between imports to separate my npm imports versus my local imports. Besides this, let's also import the alert component, So alert and alert title from components UI alert.
And let's also prepare octagon alert icon from Lucid React. I think that's enough for us to start with the development of the form. So let's define the form schema here to be z.object, email, a type of string, and email, and password, also a type of string. And the only thing I'm going to pass here is the minimum length with the message password is required. I will not change this to be any other length because this is the sign in view.
So it really does not matter what length I put here, right? The length matters for the registration, but I don't think you should ever enforce the length on the sign-in view, because what if you change your requirement later on and you have a bunch of users who no longer meet the requirement, you will prevent them from logging in. So it only makes sense to enforce that on registration. So now that we have the form schema, Let's go ahead and let's get useform, my apologies, const form from useform and we did not import this but we should from react-hook-form. This is another package that we have installed.
React-hook-form, you can find it here. You can find hook form resolvers, you can find lucid react. All of those are installed when we added all components from chat cn ui great so now that we have this inside of this let's define z infer and let's add type of form schema. Inside of this let's add a resolver to be Zod resolver and pass in the form schema and let's finally add the default values. This should be an email and a password just like this.
Now let's go ahead and let's go back inside of our card content here and I'm going to separate these with a space just so I can find them more easily and let's actually add our form with a capital F to wrap our native form element so this form with a capital F is the import from Shadcny and it needs the entire form props. So this is like a provider for all the form hooks and components which we are going to use going forward. Let's give this form a class name of padding 6 and on large devices padding 8. Then let's finally remove this text and inside let's open a div and another div inside. With the first outer div give it a flex flex column and gap 6.
And in the inner div give it a flex flex column again items center and text center inside of this inner div you're going to render an h1 element and below it you're going to render a paragraph element. The h1 element will render out welcome back with a class name of text to excel and font bold and let's quickly see how that looks like. There we go. And in the paragraph here, add a class name, text muted, foreground and text balance. And in here you're going to render login to your account.
Now let's go ahead and outside of this inner div, but still inside of this outer div, you're going to render a div with a class name, GridAndGap3. And inside, you're going to render the form field, which is a self-closing tag, pass the control prop, which is form.control, name, which is email, render, which will destructure our field, and go ahead and then inside the render form item. Inside a form item add a form label and write in your label for this field which is email. Then add a form control and finally inside the component you want to use, which in our case is a normal input component. Type is email placeholder can be m at example dot com and go ahead and spread field.
So now this input becomes a controlled component. So that's how we are going to build our forms modularly. And you can also add a self-closing form message, which will be used to render errors if there are any. Excellent. So now you have your email right here.
Perfect. And now what you can do is you can copy this div including the form field, paste it below, and change the name of this to be password. And if you haven't noticed already, this is strictly typed. So I can only choose between email and password because it reads from our default values here. It reads from our form schema.
So now in here where we added the name password, change the form label to be password and change the type to be password. And for the placeholder, well, you can just add some asterisks here. 1, 2, 3, 4, 5, 6, 7, 8. There we go. So now, let's go ahead below this.
And outside of this div, we are going to do the following let's do true so literally hardcode the word true and render the alert component and inside render the octagon alert icon and close it and then add an alert title and render error. In the alert, give it a class name of BackgroundDestructive divided by 10, which basically means 10% opacity. Then, let's go ahead and add border-none. And for the octagonalert icon, give it a class name, height-4, width-4, and add an exclamation point text destructive. This marks as dash important.
I mean exclamation point important. So now you have an error here. So this is where we are going to display our server errors because we don't know what field exactly caused an error, but maybe our connection went out. Maybe the database is down. This is where we are going to display server errors.
That's why I've hard-coded it to true. Later, this will be an actual Boolean value. Below this, add a button and sign in text. Go ahead and give it a type of submit and the class name with pool. Like this.
There we go. And now let's go ahead and do the following here. Below this button add a div with a class name. After border-border, a relative, text center, text small, after absolute, after inset zero, after top one and a half, after z index of zero, after flex, after items center, and after border top. Add a span element here or continue with and give this a class name, background color of card, text muted foreground, relative, z index of 10 and px of 2.
So now you have this cool looking line crossing text. And in here we are going to add the buttons to log in with socials. So outside of this div, go ahead and add another div which will serve as our grid, specifically handling two columns here. If you have three social logins, you would change this to three, right? In here, add a button.
Go ahead and give it a variant of outline, a type of button, and a class name with pool. Type button is quite important, otherwise this button would submit the form. And inside of here what we are going to do is render the Google icon. So for now let's just add the text Google. And in here, GitHub.
So now you have Google and GitHub texts right here. Great. And then what I want to do is go outside of this div right after we close this button and add one more div here with a class name, text center and text small. And in here you're going to add don't have an account. And you can use, you can't exactly put an apostrophe here.
You have to use at sign apos, like this. So don't have an account, import link from next slash link. And in here, you're simply going to redirect to slash sign up if you don't have it. And inside, just sign up. You can also do, if it's easier for your eyes, you can add an empty space like this, and then collapse this.
There we go. And give this link a class name, underline and underline offset four. There we go. Don't have an account? Sign up.
Like this. Great. So now, what I want to do is just one more thing here. Outside of this card here, add a div with a class name, text muted foreground, asterisk, column, square brackets, href element, column, hover, column, text primary. Text center, text extra small, text balance.
Again, this type of sign, but this time for underline and then just copy it and add underline offset 4. And inside of here, you can add a text by clicking Continue. You agree to our, and you would probably use an href or a link here for terms of service and privacy policy and give this also an href. So just some template for you to have. There we go.
Perfect. So now let's go ahead and actually make this form work. So in order to do that we have to add our auth utils. So let's go ahead and import the auth client, which we can do here. There we go.
Auth client from lib auth client, like that. And Let's do the following. Let's add const router here, useRouter. You can import that from next navigation. Like that.
And then let's also add error, setError to be useState from React. By default, set it to null and give it a type of string or null. You can import useState, of course, from React. And now let's create our const onSubmit method, which is an asynchronous method which accepts data, which will be z.infer type of form schema. So we know exactly what kind of data we are getting here.
First of all, we are going to reset our error here. And then we're going to extract the error by using a wait out client sign in dot email. And in the first argument for this option, we will pass the email to be data dot email and password to be data dot password. And in the second argument, we are going to pass in the onSuccess here, which will be a router.push to the front page. And then in here, if we have an error, actually we can do this better.
We don't need this to be asynchronous at all. We can just, I think, do this. And then in here, onError, setError, error, which we have to get from this error. And let's see, dot error dot message like this. Or, yeah, I think this will always be available.
Maybe we can destructure this. Yeah. And then like this. I think this should work. And now what we have to do is go inside of this form here, so native HTML element, and add on submit here to be form handle submit on submit.
Let's see any errors. We do have an error here, unused variable. So let's go and find our alert button. Instead of true, it will be if we have an error and you can add double exclamation points here. Make sure there's double.
So this turns it into a boolean and in here just render the error so let's go ahead and try it out so just by clicking sign in you should have the normal for validation errors but if I try using something that doesn't exist So I'm pretty sure I don't have this user and click Sign In, I should be getting an error back. There we go, invalid email or password. So this came from the server, right? If I If I try this again, you can see that this error message is coming from the server and we are rendering it here. So the user knows something went wrong.
We can improve this a little bit by also adding the... Let's see what we can do. Disabled if we are submitting. But I'm trying to find a good way of doing this. So we could, of course, just add a new field, setIsLoading.
Maybe I should do that because, yes, we can also use the form state is submitting. But since we will be using social login buttons, I think it's more reliable for us to actually introduce loading. And actually, this will be pending. Set pending. Use state.
False by default. So set pending here. False as well. And let's go ahead. Actually, this will be set pending to true.
And then in here, we're going to add on, let's see that we have on. We can just do it after this, I suppose then. Set pending false. Maybe this isn't, No, this will not work like that. We can just do this.
It's easier. And then find the button, and if it's pending, disable it. So now you have a nicer experience. You can see the button is disabled until it either returns with an error or success. So you can copy this and also add it to these buttons right here.
There we go. Perfect. So This form should now work. Now let's go ahead and copy it. So we can copy everything in here and go inside of signup view and paste everything in here.
I'm first going to rename this back to signup view and go inside of my sign up page to ensure no errors are being thrown. And when I control or command click here, I will be redirected to this newly copied component. Make sure you are working in the new copied components so you don't override anything. And let's start by introducing the new form schema. So besides the password here, we are also, I'm also just going to say password is required.
I won't introduce any validation here because we have back-end validation from BetterOut. But what I will do is ensure that user has to confirm password. And I will also add a name, which will be zString and min1 message, name is required. The reason we don't need that for email is because this takes care of that. But the confirm password will be a little bit different.
So this can stay the same. And we're then going to add .refine, get the data, and check if data.password matches data.confirmPassword. But if it doesn't, the message we are going to pass is passwords don't match. And the path, this basically means what field should show this error. That should be the confirm password.
Like this, there we go. So now inside of here, everything can stay the same, but we now also have name, And we have confirm password. And now what we have to do here is we have to change the onSubmit method to call the sign up method. And we also need the name here. So data.name.
That's it. No errors at all. And now in here, we're going to change instead of Welcome Back, this will be Let, and let's use At Appos. So let's get started. And this will be Create your account.
The first field here will be name so you can copy the entire form field with the grid gap and paste it above. Change this name to be name and this to be name. And the type can be text and this can be John Doe, for example. So you can click sign up here and you should be seeing that new form, which now has name here. Now below this we have email, below that we have password and then copy the div grid gap and the form field, paste it below and change just the name here to be confirm password and well we should also change this to be confirm password I guess there is so let's just zoom out a bit there we go so let's just try something here.
Let's try 1, 2, 3 and 1, 2, 3, 4. There we go. Passwords don't match. But if I try 1, 2, 3, you can see that it works. So let's try adding a new user for example and I will also prepare my database here.
So what we can do of course is run npm run database studio here which should get us you can click this link and it should open the studio then. And in here, I think I have one user, Antonio Antonio Demo. So let's go ahead now and create a john, johndemo.com and let's add one, two, three, four, five, six. And let's do one, two, three, four, five, six. I'm purposely doing something wrong to see if this will throw an error.
There we go. Password too short. So let's add 7, 8, 7, 8. And let's see what's going to happen now. There we go!
Logged in as John. And if I go ahead and check my Drizzle Studio here and refresh this and refresh this. There we go! JohnDemo.com. So if I sign out now and if I go back to sign in here, I should be able to log in as johndemo.com.
Let's try an invalid password first. There we go. Let's try the correct password. And there we go. I'm logged in.
Perfect. So we have completely modified the form from the previous one. Now it looks much better, of course. But there are some things we still have to fix, including this one. Let's wrap up the chapter with this.
So inside your sign up view, go down here and change this to sign in and this to sign in. And instead of don't have an account, it will be already have an account. So now you can switch between sign up and sign in here. So what we have to do is we have to improve this gradient a bit. I don't like how it looks.
And we have to add proper icons for this. And of course we have to actually enable Google and GitHub social sign ins, But that will be for another chapter. And also this button might need to have a different primary color. We'll see about that later. But yes, you can play around with the form.
I'm pretty satisfied with how it turned out. Great, let's go ahead now and wrap up the chapter. We created the out pages, out layout, and the out module. We created the out views, we got the register login, and the app logo. And now let's create, review, and merge this pull request.
So what I'm going to do is you can change your branch either from using the three dots here and then clicking checkout to and but I think you can also do clicking here and then create a new branch from here maybe that's easier and let's call this 04 authentication UI let me just double check it is 04 authentication UI there we go I'm on my new branch now I will go inside of the source control and I will click plus here. Now all of these are staged changes. I will add 04 authentication UI here. Confirm one more time I'm on the new branch and I will click commit and publish branch. Now let's head to GitHub.
Let's go ahead and open a pull request here and create a pull request. And let's wait a second for our AI reviewer to start. And here we have our code summary. So let's for a change read the walkthrough instead. This is a bit more detailed.
So this update introduces a new authentication related React components for sign in and sign up flows, including form validation and UI layouts. We also add a layout wrapper for authentication pages, and we updated the development script to remove the TurboPack flag. So that's precisely what we did. In here, as always, we have a file by file change and in here we have an interesting sequence diagram. This is what I was talking about.
This will make it easier for us to understand what we just developed. So the user navigates to sign in and that renders the sign in view component from the sign in page. The user submits email and password in the sign in view, which is then sent through the out client. Depending on success or failure, we either redirect to home or we display the error alert. And the same functionality is for sign up view and sign up page.
In here we have some actionable comments here and this is an actual issue I forgot to do. So it caught that in my sign up view I forgot to change the submit button label it still says sign in so this is something we are going to fix in the next chapter great catch by CodeRabbit AI And then of course it gave us actionable advice here to enable the social logins but since we don't yet have them configured we will not be doing that. Nevertheless, let's go ahead and merge this pull request. Once the pull request is merged you can go back to your IDE here, go ahead and click here and find the main branch. Whether you will use this main or origin main it does not matter Just make sure it's main or if you use anything else for your default branch.
And once you are there, click on this button to pull and push commits from origin main. This will make it synchronized with everything else. So there we go. Perfect. Amazing, amazing job.
Now let's go ahead and mark this as completed. And see you in the next chapter.