0:00 Hey, what's going on guys? So, I got a 0:02 cool project for you today and I'm 0:04 excited about this one. It's not so much 0:05 about the project itself, which is a 0:07 support ticket system, which I'll talk 0:09 about in a few minutes, but more so the 0:11 tools that we're going to use and how to 0:13 use them together. So, as far as the 0:16 tools, we're using Nex.js, which is an 0:18 SSR framework that uses React. We'll be 0:20 using Neon, so we'll have a a cloud 0:23 Postgres database. Prisma, which is an 0:26 OM. So, we'll use that to interact with 0:28 our database and create models and 0:30 migrations and stuff. And then I'd say 0:32 the main focus of this video is going to 0:34 be a tool called Sentry, which is an 0:36 error monitoring and tracking tool as 0:38 well as performance monitoring. So, the 0:40 reason you use something like Sentry is 0:42 because normally, you know, you have 0:44 your your website, your application, 0:46 your users are going to it, and it's 0:48 just it's just inevitable that you're 0:51 going to have bugs. you know, you're 0:52 going to have issues. Your users are 0:53 going to run into issues and they may or 0:55 may not tell you about them and it could 0:58 go days, weeks, months without you even 1:00 knowing that these problems are 1:02 happening and and turning away your 1:04 users. So, what Sentry does is it gives 1:06 you this this main dashboard, this 1:09 centralized location where you have all 1:12 all the events that happen in your 1:14 website. You know, you can log as as 1:16 many events as you want. Uh, and of 1:18 course your errors. You know, you're 1:20 going to see all your your errors that 1:22 that take priority as well as your just 1:25 custom logs. And this will allow you to 1:29 to, you know, pinpoint issues. So, 1:32 especially if you can't replicate them 1:33 yourself. Um, you can get email 1:35 notifications when your users run into 1:38 issues. And you're going to have all the 1:41 information that's available as far as 1:43 the stack trace goes, as far as your 1:45 user information. And then you also have 1:47 a screen recording of what the user was 1:51 doing, what happened uh on screen when 1:53 that error happened, which can help you, 1:56 you know, debug. So it's it's going to 1:58 help you not only find errors and know 2:01 about errors, but also debug them. Um, 2:04 and this is something that you would use 2:05 in a production project. It's something, 2:08 you know, often you'll use when you're 2:10 working for a large company. So it's 2:12 really valuable to to know how to use 2:14 this stuff. So, I'll show you how to use 2:16 that. We're going to get set up with the 2:18 Sentry Wizard and the SDK for Nex.js. 2:21 And then we're also going to be using a 2:23 or building a custom authentication 2:25 system rather than using something like 2:27 Next Off, which is great, you know, but 2:30 I wanted to do something a little lower 2:32 level for this project. So, we'll be 2:34 using a package called Jose that will 2:36 just generate JSON web tokens for us. 2:39 and uh and we'll be hooking up, you 2:40 know, the login, the the register, the 2:43 log out, managing sessions, etc. And 2:46 then as far as the the project itself, 2:49 we'll be able to, you know, log in and 2:51 create tickets. So, support tickets 2:53 where we can describe an issue. Um, and 2:56 then you'll be able to, you know, 2:58 obviously see a list of the tickets, see 3:00 the details view, you'll be able to 3:01 close the ticket, delete the ticket. So, 3:04 it's pretty simple as far as 3:05 functionality, but again, this isn't 3:07 about the project itself. It's about 3:09 learning how to use these tools so that 3:11 you can build your own projects with 3:13 them. All right, so grab a coffee, grab 3:15 a tea. I really suggest that you follow 3:17 along and let's get into 3:23 it. All right, guys. So, I'm really 3:26 excited for this project. I think you're 3:28 really going to like it. And again, I 3:29 would suggest that you follow along with 3:31 me, write the code with me, and I think 3:33 that that's just the best learning 3:35 experience you can get. So, I'm going to 3:37 just go through the phases so you have a 3:39 highle view of the tasks and how we'll 3:43 be doing this, the order of of um 3:46 functionality that we'll be creating. 3:48 So, there's four phases. The first one 3:50 is going to be setting up Sentry and our 3:53 database. Okay, we're using a Neon 3:55 Postgres database. So number one, we'll 3:57 get our next app up and going. So create 4:00 next app. Very simple. You guys know 4:02 that most of you. Uh number two, we'll 4:04 set up Sentry, which is really simple. 4:06 There's something called the Sentry 4:07 wizard that we can run with NPX, and it 4:11 gives us a sample error that we can fire 4:13 off. So we can go into the Sentry 4:15 dashboard and kind of see how it works, 4:17 see what it gives us, and then from 4:18 there, we can integrate it into all the 4:21 different functions throughout our 4:22 application. Then we're going to set up 4:24 our Neon database, which is very simple, 4:27 just a couple clicks. Uh add the 4:29 database URL to our environment 4:31 variables. And then for Prisma and Neon 4:34 adapter setup. So Prisma is the OM that 4:37 we're using. We're going to interact 4:38 with the database using that. And the 4:41 Neon adapter, we're going to use that um 4:45 to ena basically enable us to use um 4:49 Prisma and Neon together. Neon offers 4:52 serverless Postgres databases. So 4:54 there's just a couple extra steps we 4:56 need to take, especially if you're going 4:57 to deploy to Verscell. And then number 5:00 five, database ticket schema and 5:02 migration. So we're going to start off 5:04 with no authentication, no users. I just 5:07 want to get the ticketing system 5:09 working, which is, you know, submitting 5:11 the ticket and getting the tickets and 5:13 so on. And we what we do with Prisma is 5:16 we create a schema which is all the 5:19 different fields that we want like the 5:21 ticket uh the priority or the title 5:24 things like that the date. So we set up 5:26 a schema and then we run a migration 5:28 with Prisma and that will create our 5:30 database tables in our Neon database. 5:33 Okay so that's phase one. Phase two we 5:36 have some basic UI stuff and I also want 5:38 to handle creating new tickets. Okay, so 5:41 we'll set up Tailwind. Really, really 5:43 easy with version 4 uh and Nex.js. And 5:46 then we'll create the welcome screen, 5:48 which will just be a landing page with a 5:50 couple buttons to submit a ticket or 5:52 view your tickets. And then from there, 5:54 we'll create the ticket form. Okay. Um 5:57 we're going to be using Nex.js or uh 6:00 actions, so server actions, which you 6:04 can use in place of API routes from, you 6:07 know, older versions of Nex.js. So 6:09 basically our form will submit to a 6:11 server action and then from there we can 6:13 do all of our database tasks using 6:15 Prisma. All right. And we're going to be 6:17 using a hook in the form called use 6:20 action state which allows us to manage 6:22 that form state with what comes back 6:25 from the action. So it's the the most 6:27 modern way of doing this with 6:29 Nex.js. And of course we're going to 6:31 have our Sentry integration in the 6:33 ticket flow. Um, so we'll create a 6:35 custom log event function we can use 6:37 that that fires off a couple different 6:40 sentry methods. Um, we want to test it 6:42 and check the sentry reporting, look at 6:44 any playbacks, etc. So that's phase two, 6:47 just getting being able to submit new 6:49 tickets. And then phase three will be 6:52 the ticket display. So we'll have an 6:54 action once again a server action that 6:56 will fetch the tickets from the database 6:58 using Prisma. We'll have the tickets 7:01 index page which will list them all. the 7:03 details page which will list a single 7:05 ticket and then we'll have our sentry 7:08 reporting and tracking and just some UI 7:11 element styling like if it's a serious 7:13 priority we'll make it red or whatever 7:15 just little things like that uh we also 7:18 have like toast 7:19 notifications and then the final phase 7:21 phase four is users and authentication 7:24 because up to this point we can just 7:25 create tickets and see them but we want 7:28 to be able to log in right and we want 7:31 to be able to see only that user's 7:33 tickets. So, we'll start off with just 7:35 creating a simple navbar component with 7:37 a login and register link. Um, we want 7:40 to create a new user schema because 7:42 obviously when you have authentication, 7:44 you need somewhere to store your users. 7:46 Um, we'll have a custom authentication 7:48 flow with JSON web tokens. We're going 7:51 to be using a package called Jose, which 7:53 is kind of a more modern version of the 7:55 JSON web token package. We're going to 7:57 create some low-level functions like 7:59 encrypt and decrypt to deal with our 8:02 token and deal with cookies. You know, 8:04 saving our token and a cookie and HTTP 8:06 only cookie. And then highle functions 8:08 like register user, login user. These 8:11 will be called from our actual 8:13 components. And then the authorization 8:16 of course you want to have a 8:18 relationship between the tickets and 8:19 your users so that the user can only see 8:22 their own tickets and not everybody 8:24 else's. So that's kind of the the entire 8:27 flow of this project just so you can 8:29 have an idea. So let's go ahead and jump 8:32 into it and let's start to create our 8:33 next app. Okay. So we went over the kind 8:36 of the the workflow, the order that 8:39 we're going to do things. So I'm 8:41 assuming that most of you have at least 8:43 a little experience with Nex.js. If not, 8:46 that's okay. But what I would really 8:48 suggest is you at least know the basics 8:50 of React. If you don't know anything 8:52 about React, then you could watch my 8:55 React crash course or just any kind of 8:57 beginner React content just to get 8:59 familiar with state props, components, 9:02 things like that. All right, so let's 9:04 jump into it. I'm going to just open my 9:06 terminal, navigate to where I want to 9:08 create this project, and then we're 9:10 going to run npx and we want to do 9:13 create-next app at latest. And then I'm 9:16 going to call this project 9:21 quick-t. Now, as far as using 9:23 TypeScript, I'm going to use it, but if 9:25 you don't want to, you can say no. 9:27 There's not a ton of TypeScript we're 9:28 going to write, so you'll be fine if 9:30 even if you've never used it before, and 9:32 you click yes. Uh, ESLint, we'll click 9:35 yes. Tailwind, I'm going to use it. Um, 9:38 using the source directory. App router, 9:40 yes. Turboac for nextdev, I'm going to 9:42 say no. and a customize the import 9:45 alias. I'll say no to that as well. All 9:48 right, so that's going to go ahead and 9:49 install all these dependencies and just 9:51 scaffold up an Nex.js application for 9:54 us. So now that that's done, let's CD 9:56 into QuickT. And then from here, there's 9:59 a couple other um dependencies I want to 10:02 install. So Prisma, we want the Prisma 10:05 client, which is at 10:06 Prisma/Client. We're also using 10:09 React-icons. There's some other things 10:12 later, but this is all I want to install 10:14 at the moment. Um, and then I want to 10:17 once we get this the dev server up and 10:19 running, we'll start to integrate Sentry 10:21 as 10:22 well. Okay, so let's go ahead and open 10:25 this folder in Visual Studio Code or 10:27 whatever text editor you want to use. 10:30 I'm going to be using the integrated 10:32 terminal, so I'll close that up and then 10:34 open the integrated terminal. And we can 10:37 go ahead and run npm 10:41 rundev. And let's see. I want to open 10:45 that 10:48 here. So, let's go. It's going to be 10:50 localhost 10:54 3000. And we should just see the the 10:56 default Nex.js page. And we can do a 10:59 little bit of cleanup here. So, in the 11:01 source folder, we have our app folder. 11:03 And you may have chosen not to use a 11:05 source folder. That's fine. Your app 11:07 folder will just be in your root. And 11:09 from here, the page.tsx is the homepage. 11:13 And if you're new to Nex.js, it uses 11:16 filebased routing. So the app folder is 11:18 like your routing folder where the page 11:21 .tsx that's that's directly in it is 11:24 your homepage. If you wanted, let's say, 11:26 slashabout, you would create an about 11:29 folder with a page tsx in that about 11:31 folder and that would that would just 11:34 show up at slashabout and all the pages 11:36 are are react components. In fact, we're 11:38 going to wipe away this one, the page 11:40 tsx. I'm going to generate a component 11:43 with the react simple snippets extension 11:45 that I have. So, sfc enter or tab and 11:49 then it just scaffles out a component. I 11:53 like to use the arrow function syntax 11:54 with the export at the bottom, but 11:56 there's other, you know, formats as 11:58 well. And then for now, let's just do an 12:01 H1. And I'm going to give it a Tailwind 12:03 class just to show you that that works. 12:06 And we'll just say 12:07 welcome. Okay. So, you can see that the 12:10 heading, this class is working. Tailwind 12:12 just works right out of the box because 12:14 we, you know, we selected it when we 12:16 initialized the project. Okay. So, now 12:20 what I want to do is set Sentry up, 12:23 which is pretty simple. So, we're going 12:24 to go to um Sentry.io. IO. And as far as 12:30 pricing goes, absolutely free for a 12:32 single developer, one user, error 12:35 monitoring, tracing, you get uh session 12:38 replays, alerts, all kinds of stuff. So, 12:42 we're just going to sign in. You can use 12:43 Google or GitHub. I'm going to use 12:46 GitHub. All right. And then from here, 12:48 so for me, I have all these issues 12:51 because I already have a project, but 12:53 you won't see that. What you want to do 12:54 is create an a project. You can also 12:57 create a team as well. And then here you 13:01 want to select your platform. We're 13:02 going to select Nex.js. And then you can 13:05 set your alert frequency because it's 13:07 really cool. You can get emails and 13:09 alerts when you know a user runs into an 13:12 issue. Um, and it just notifies you 13:15 right away so you can get to the problem 13:17 and fix it as soon as possible. But for 13:20 me, I'm just going to choose I'll create 13:21 my own alerts later. And the project 13:24 name, I'm going to change that to 13:27 quick-t use my Traversy Media team, but 13:30 you can create a new one if you 13:32 want. And then it's going to give you 13:34 this npx sentry wizard command. So 13:37 basically, this will install all the 13:39 dependencies that we need and it will 13:41 create our config files and anything 13:43 else that Sentry needs to to, you know, 13:46 be integrated into our project. So just 13:49 copy that. I'm going to stop the server 13:51 and then just paste that in and run it. 13:54 And it has our project name and team 13:56 name as well. And then it's going to ask 13:58 some questions. So I have unttracked 14:00 files. Do you want to continue? We'll 14:02 say yes. And it's just going to validate 14:04 in the browser and then send you back to 14:07 the terminal. And we're going to just 14:09 pretty much select the defaults for the 14:11 rest of the 14:12 questions. So do you want to route 14:14 sentry requests in the browser through 14:16 your Nex.js server? We'll say yes. 14:19 enable tracing. Yes. Enable session 14:21 replay. And then it's asking if we want 14:24 to create an example page, which you 14:26 don't have to, but I'm going to just to 14:28 kind of show you how Sentry works. So, 14:30 I'll choose yes for that. It's going to 14:32 ask if you're using a CI/CD tool. So, if 14:35 you plan on deploying to to like Versell 14:37 or or GitHub, uh if you want to use 14:39 GitHub actions or whatever, we'll say 14:42 yes to that. And it's going to generate 14:44 this Sentry O token which you don't have 14:46 to copy because what happened is it 14:48 created this. Eenv 14:51 uh Sentry build plug-in file and it has 14:54 the token in there. In fact, what we can 14:56 do is just rename that to um Whoops. We 15:00 want to rename that to 15:03 env. And that will be our environment 15:06 variable file. Get rid of the comments. 15:08 Uh I also like to just add quotes 15:11 here. And now we have that O token in 15:14 there if we need it. So we'll just click 15:16 enter again and then we should be all 15:19 set. All right. Now I want to run the 15:23 server. So npm rundev. And I just want 15:26 to quickly show you before we set up our 15:28 Neon database and stuff. I want to show 15:30 you how to integrate how to how Sentry 15:34 works. Um so let's open our project back 15:37 up. And you'll notice that in your 15:40 project, if you chose to to show the 15:42 example page, you'll have this Sentry 15:45 example page folder in your app folder, 15:48 which means that that's an accessible 15:49 route. So, if I were to go to localhost 15:54 3000/entry example page, that should 15:57 load. And it has a button that says 16:00 throw sample error, but I'm not going to 16:01 click that just yet because I want to 16:03 show you that page. So, Sentry example 16:06 page page tsx and this we're going to 16:08 delete this after. So, you don't really 16:10 have to worry about this. I just want to 16:11 show you where the button clicks. So, 16:14 basically it first calls this 16:16 sentry.start span and this is if you 16:19 want to test performance of the page and 16:21 I'll show you this in the sentry 16:23 dashboard. Now, when we click it, it 16:25 runs a function and it makes a request 16:28 to slash 16:29 API/entry example API. And if we look in 16:33 our API folder, we can see that route 16:35 right here. All right. So, it makes a 16:38 request to that route. Let's take a look 16:40 at it. This is the route. And all it 16:42 does is it throws an error, just a kind 16:44 of a sample error, and passes in a 16:47 message sentry example API route error. 16:50 And then back in the page where it makes 16:51 the request, it checks to see if the 16:54 response is okay or if it's not okay. 16:56 And if it's not, then it throws an error 16:58 here. Okay. So, two errors are being 17:01 thrown. one in the back end or API and 17:04 one here in the front end. Now let's 17:06 jump over to the page. Let's click the 17:09 button that makes the request throws the 17:12 error and you can see sample error was 17:14 sent to Sentry. Now if we go over to 17:17 Sentry and go to issues make sure that 17:21 the right project is selected. So for me 17:23 quick ticket and we can see the example 17:26 front-end error and the API error. Okay, 17:30 so it tracks our errors and if we click 17:32 on the front-end error, it's going to 17:34 show you basically any information that 17:37 is possible to see. It shows the server 17:39 information, the IP address, which I 17:42 don't care if you guys see. I'm on a 17:43 VPN. Um, and then if you scroll down, 17:46 you can see what are called the 17:48 breadcrumbs, which is just kind of a 17:50 trail of what happened to get to that. 17:52 From the UI click to making the request 17:55 to the exception. Um, you have the 17:58 trace, you have the stack trace, you 18:00 have uh your HTTP information, so you'll 18:04 be able to see the the user's um device 18:07 and and operating system, things like 18:09 that, the browser they're using. So 18:12 again, just all the information that is 18:14 possible on a single request. And then 18:17 one really cool addition is this session 18:20 replay where you can see exactly what 18:22 happened in the browser prior to that 18:25 error being thrown. So in my case, 18:27 obviously I just clicked a button and 18:29 that's not very helpful in terms of 18:31 debugging. But if it was something 18:33 different like let's say a form that a 18:35 user was filling out, maybe we could see 18:37 that they missed a field or they filled 18:39 out something wrong or in the wrong 18:41 order. So anything any mistake in the UI 18:44 we would be able to see here, which is 18:47 is really cool. And then if we go back 18:50 to issues, you also have the API route 18:52 error. Um, there's no replay for this 18:54 because it's it's an API error. It's a 18:57 backend error. But we do have the 18:59 breadcrumbs. We have the stack trace, 19:01 HTTP info, and so on. All right. And 19:04 then if we go to traces, remember that 19:07 start span. So this will show you any 19:10 spans that were created and shows you 19:12 the performance. So you can see the page 19:14 loads, um, you know, the performance of 19:17 that page. So, and I I don't use this 19:20 too much, so I don't know too much about 19:22 it, but um but you can just test 19:25 performance of different routes and see 19:27 if you need to, you know, make any 19:29 adjustments. Okay, so that's kind of the 19:31 gist of Sentry. And of course, you can 19:34 make all types of logs and stuff. We'll 19:36 be doing that throughout our project so 19:39 you can see how it works. Okay, if we go 19:41 back to issues, you can, you know, 19:43 change the the priority to low, medium, 19:46 high. We can let's say that these are 19:48 resolved. We can just mark resolved and 19:50 then they'll go away. But it's just nice 19:53 to have one place where all of your 19:55 issues, all of your logs, warnings, 19:58 errors, everything is shown here. All 20:01 right. So, back to our project. We can 20:03 now 20:04 delete both of those pages. So, the 20:07 Sentry example page, we can delete that. 20:11 Sentry example API folder, we can delete 20:13 that as well. 20:16 and we'll just go back to uh yeah back 20:18 to the homepage here. Okay, so now we 20:22 want to set up our database which is 20:24 very very simple using Neon. So let's 20:27 jump over to neon.te and we're going to 20:30 log in. You can log in with GitHub or 20:34 Google and it's literally just like a a 20:38 couple fields. So new project, add a 20:41 project name. I'm going to call it quick 20:43 dash ticket database name let's say 20:47 quick dash ticket db going to keep AWS 20:52 as my provider create and now we have a 20:56 cloud database now to connect to it we 21:00 can click connect and then it gives us 21:02 this string where we want to say show 21:03 password and then copy the snippet and 21:06 we want to put that in thev file that we 21:09 already created. So here, let's say 21:12 database URL. And I'm going to set that 21:15 to that database string. Okay. And 21:18 Prisma when we set that up is going to 21:21 use this database string. And we'll be 21:24 able to run migrations, create some 21:27 schemas, and it'll create our database 21:29 tables. Okay. And you can see your 21:32 tables here, which right now, obviously, 21:34 we don't have any, but we will after we 21:36 run our migration. 21:38 Now to initialize 21:40 Prisma, let's uh let's go ahead and I'm 21:43 just going to open up a new 21:45 tab and I'm going to run Prisma or sorry 21:49 npx because we don't have Prisma 21:51 installed globally. So npx Prisma 21:55 init. All right. And what that does is 21:58 it creates a Prisma folder with a 22:00 schema.prisma Prisma file and it has our 22:03 client generator, our data source which 22:06 in our case is Postgres and this is 22:08 where it uses that database URL that we 22:11 just created. Now I would suggest that 22:13 you install the Prisma VS code extension 22:17 if you are using VS Code. So this right 22:19 here because this will give you you know 22:22 syntax highlighting, linting, code 22:24 completion. So definitely you know get 22:27 that installed and then let's open up 22:29 our schema file and from here we can 22:33 create what are called models and any 22:36 resource in your project like users 22:39 tickets in our case uh blog posts 22:43 products anything like that you're going 22:44 to create a model here and then run the 22:47 migration which will then create the 22:49 table. So there's no there's no logging 22:52 in in to your database and writing raw 22:56 SQL queries or anything like that. This 22:59 makes it really easy. So let's create a 23:01 model called 23:02 ticket. And in our ticket model, we want 23:06 to have uh an ID and the type for that 23:09 will be int. So these are Prisma 23:11 specific types. And then we can add 23:14 special options. In this case, I'm going 23:15 to use at id, which defines a custom 23:19 primary key. Okay, so in our database, 23:22 this will be uh you know a variable 23:24 character and it'll be marked as a 23:26 primary key. And then we can add 23:29 defaults. So at default and then I want 23:32 to add an auto increment parenthesis. So 23:34 that will mark it as auto increment. So 23:37 that when we create our first ticket, 23:39 it'll have an ID of one. Next will be 23:41 two and so on. Okay. After ID, let's do 23:45 a subject, which will be a 23:48 string. And then let's do description, 23:52 which will also be a 23:55 string. Then we'll do a 23:58 priority that will also be a string. And 24:02 then we're going to have a status. 24:04 Status will be a string. And I want to 24:06 give this a default. 24:10 So the default for status is going to be 24:12 a string of 24:13 open and then we want created at that 24:18 type is going to be a date 24:22 time. And let's do a 24:26 default for that which is going to be 24:28 now with parenthesis. So that'll just be 24:30 the current date and time. And then 24:32 we'll do an updated at and that will be 24:36 a date time. And we can do at updated at 24:40 which will just add whatever the current 24:42 date and time is for for that 24:45 update. So later on we'll have a user 24:48 model as well when we implement 24:50 authentication. But for now I just want 24:52 to be able to create and read tickets 24:54 through our project. So uh yeah we can 24:58 close this up. And there's two things we 25:00 have to do whenever we edit that file. 25:02 Whether we create a new model or we edit 25:04 fields in a current one, we have to run 25:07 the migration and we have to generate 25:09 the client. So let's come down to the 25:11 terminal and let's do our migration with 25:14 npx prisma. So whenever we run a prisma 25:17 command, we're going to do npx unless 25:19 it's you know installed globally. And 25:22 then we want to run migrate 25:26 dev-name and I'm going to call it init 25:28 since it's our first one. 25:31 And what this is going to do is is look 25:33 at that file. It's going to look at our 25:35 ticket model and it's going to create a 25:37 table in our database based on 25:40 that. So now it says your database is 25:42 now in sync with your schema which is 25:45 good. So now let's go to Neon and I'm 25:49 going to reload the 25:51 tables and you'll see a migrations table 25:54 as well because whenever you create a 25:56 migration it puts it in here. But we can 25:58 see ticket with an ID, subject, 26:01 description. So all the fields that we 26:03 put in our model. So it's as easy as 26:06 that to create tables. Now the second 26:08 thing we have to do is run npx prisma 26:12 generate. So that will generate the 26:15 client. So then when we, you know, we're 26:17 in our code and we use the client, we 26:19 can now work with 26:20 tickets. Now one other thing that we 26:23 need to do is in the package.json, JSON, 26:26 we want to add a post install script 26:28 because when you deploy this, whether 26:30 it's to Verscell or wherever, you want 26:32 to generate the client on the server. So 26:35 the way you do that is add a post 26:37 install script and from here just simply 26:40 add Prisma generate so that that runs 26:43 when you 26:44 install or when you 26:47 deploy. Okay. So yeah, so we should be 26:50 good as far as the database goes. Now in 26:52 this tab, so here I have my my npm 26:55 rundev. In this one, I want to run 26:58 Prisma Studio, which is going to be 27:01 NPX Prisma Studio. And this is a really 27:05 cool tool that will show you all your 27:09 all your tables, all your data. You can 27:11 see I have my ticket table, ID, subject. 27:14 So obviously I have no tickets, but if I 27:16 did, they would show here. So I'm going 27:18 to keep this open so that we can always 27:20 refer to our data if we need 27:23 to. So now that we've done that, I'm 27:26 going to start working on the the actual 27:28 UI. But I just want to initialize a git 27:31 repository. So I'm going to open up a 27:33 new tab here and let's just do git 27:37 and um you also want to make sure I 27:40 believe it's by default, but make sure 27:41 your git ignore has the 27:44 env. Yeah. So it does. All right. And 27:47 then let's do get. We'll say get add 27:51 all. And I'm g say get commit. 27:53 Obviously, you can do this whenever you 27:55 want. Uh 27:57 whoops. Shoot. Didn't mean to do that. 28:00 Uh so get commit. And let's see. We'll 28:03 say 28:04 initial initial 28:08 um I 28:09 guess initial 28:13 Prisma setup. 28:16 Yeah, we'll just do 28:17 that. All right. And I'll be making 28:20 commits off camera, but like I said, you 28:23 can, you know, obviously make whatever 28:26 uh commits you want. Okay. So, now we're 28:29 going to work on the welcome page, which 28:32 is app and then 28:34 page.tsx. And for this, it's just it's 28:37 going to be a welcome page with two 28:38 buttons. One is going to be to create a 28:40 new ticket. One is going to be to to to 28:42 see your tickets. Okay. So, we're going 28:45 to bring in a couple things here. Let's 28:48 import link from 28:51 nextlink. And then for I want to use an 28:53 icon here from React icon. So, fa ticket 28:59 alt. All right. And then for the JSX, 29:01 it's just a bunch of tags and classes. 29:03 So, I'm going to paste it 29:05 in. So, for the return in 29:09 parentheses, we'll go ahead and paste 29:12 that. Um, oh, now one thing I did forget 29:15 to do is in the global CSS, I want to 29:17 change the background to 29:19 EFF 6 FF, which is a light gray. Now, 29:22 the reason this is dark is because of 29:24 this block here. And since I'm not using 29:26 any kind of dark mode, I'm going to just 29:29 get rid of that block. Okay, which will 29:31 make the background light. And I haven't 29:34 used the ticket yet. So, let's add that. 29:36 I'm going to go right above the H1. And 29:38 again, you can copy this if you want. 29:41 You can type it out or you can get it 29:43 from the the 29:44 repo. But let's add 29:47 FA ticket 29:50 alt and then just a couple classes. So 29:54 MX auto 29:57 uh what else did I want to do? We'll do 29:58 margin bottom 30:00 four. And I'm going to make it red. So 30:03 text red 600. And then we can also add a 30:07 size for the icons. I'm going to do 60. 30:11 So, that will just add a little red 30:13 icon. Now, one other thing I wanted to 30:15 do is just something really simple, but 30:17 I figured that it's just a nice add-on 30:19 is to have these buttons fade in and up 30:21 when we come to the page. So, what I'm 30:23 going to do is the wrapper that is 30:25 around the two links. I'm going to add 30:27 to that uh a new class called animate. 30:31 We'll say animate dash slide. And then 30:34 I'm also going to add a Tailwind class 30:36 of opacity zero, which is going to make 30:39 them disappear at the moment, but then 30:41 we'll fade them back in. So we're going 30:43 to do that through our global CSS 30:46 file. So let's see, we'll go down here. 30:49 We're going to create a new key frame. 30:51 So let's say key frames, and we're going 30:54 to call this slide up. 30:57 And from here I want to say at 0%. So at 31:00 the start of the animation I want the 31:02 opacity to be zero which is just you 31:05 know not there invisible. And then we're 31:08 going to add a transform and then 31:11 translate on the yaxis because we want 31:14 it to fade up. So I want it to start 20 31:17 pixels below. So we'll say 20 pixels. 31:21 All right. Which means it's going to be 31:22 pushed that the buttons will be pushed 31:24 down 20 pixels. Then on 100% which is 31:28 when the animation 31:30 ends is where we want to add opacity one 31:34 which is you know full view um no no 31:38 fading at all and then transform we want 31:42 to do translate y and then put it back 31:45 to its original position which is zero. 31:48 Okay. And then all we have to do is take 31:49 our animate dash slide class that we 31:53 added onto the wrapper and add the key 31:56 frame into the animation property. We 31:59 want to use the slide up animation. The 32:02 duration will be 0.6 seconds. We'll do 32:05 an ease in and 32:08 forwards. Okay, we don't want it to to 32:10 repeat. We just want it to do it once. 32:13 And then animation delay. I'm just going 32:15 to put a very slight delay of 0.2 2 32:18 seconds. So now when I save that, you 32:20 can see that the buttons now fade in and 32:23 up. If I reload. Okay, so it's just a 32:26 nice little addition. That's, you know, 32:28 not a lot of code at all. So that's 32:31 that. Now I want to work on the submit 32:34 ticket page. If I click that, it takes 32:36 me to the route of, as you can see here, 32:40 slashtick slashnew. Now since Nex.js JS 32:43 is filebased routing. In the app folder, 32:46 we're going to have a folder called 32:48 tickets. And then in that, we'll have a 32:50 folder called new. And then in that, 32:53 we'll have a file called 32:56 page.tsx. And let's do sfc. 33:00 Oops. Sometimes this doesn't work. Like 33:03 now, sfc tab. Okay, there we go. And 33:07 let's call this new new ticket page. 33:11 Whenever it's a page component, I always 33:13 like to end it with page just to just 33:16 preference. And just to make sure this 33:18 is working, let's just say new. And then 33:21 this 33:23 should show this. There we go. So we 33:25 just see new on the page. And we're 33:28 going to add our form 33:29 here. Now to begin with, I'm just I'm 33:32 just going to add the JSX just tags and 33:35 classes. So I'm going to paste it in. So 33:38 the return let's add some parenthesis 33:41 and then paste this in. So basically we 33:44 just have a div flexbox. Uh we have 33:48 another div which is kind of like the 33:50 the wrapper the card or whatever. Then 33:53 h1 then we have a form with with an 33:57 input for the subject. Okay. Okay. So, 34:00 it has name, subject, uh text area, 34:03 which is going to be the description, 34:05 and then a select box, which is going to 34:07 be the priority of low priority, medium, 34:09 and high, and then the submit button. 34:11 So, let's save that. And that's what the 34:14 form is going to look like. Okay. So, 34:17 now we get to the fun part, which is 34:18 submitting the form and making it work. 34:20 Now, we're going to be doing this the 34:22 most modern way in Nex.js, which is 34:24 using server actions. Okay. So server 34:27 actions are just files where obviously 34:29 they run on the server and you can 34:32 submit directly to them. Like we can 34:34 have an action attribute and submit it 34:37 directly to the action just like you 34:39 would with like PHP which is really 34:42 cool. U and we're also going to be using 34:44 a new hook called use action state. So 34:48 let's start off by creating the action 34:50 file for tickets which is going to be in 34:52 source. Create a folder in source called 34:55 actions. And then let's create a file 34:58 called 35:01 ticket.actions.ts. Okay. So any actions 35:03 that have to do with tickets, whether 35:05 it's creating one or fetching them, 35:07 whatever, we're going to put that in 35:09 here. So this is the backend for 35:11 tickets. Now, when you create a server 35:14 action, the first thing you want to do 35:15 at the very top is say use server. Okay? 35:18 So that will mark it as a server action. 35:21 And all a server action is is an 35:23 asynchronous function. So, we're going 35:25 to say async 35:27 function create 35:30 ticket. And this you could do an arrow 35:32 function if you want, but it's it's up 35:34 to you. All right. Now, this is actually 35:37 going to take in the form data. And 35:40 we'll give it a type of uppercase F form 35:43 data because when you use an action 35:46 directly, that form data is available. 35:49 So what we'll do is get let's say con 35:53 subject and set that to that form data 35:56 and then there's going to be a get 35:58 method on that and that's how we get our 36:00 data. So we want to get the data or the 36:03 input that has the name of subject. 36:05 Okay. So this whatever we put here has 36:08 to match the name. So 36:11 this so get form subject and just to 36:14 please TypeScript we'll say uh let's say 36:17 as string and then I'm going to do the 36:20 same with the other fields. So we have 36:23 the 36:27 description description and then what do 36:31 we got the priority so I'll just select 36:34 this and 36:36 this. So, priority and then just to test 36:40 it out, let's do a console log. Again, 36:42 this is not There we go. Console log of 36:46 the subject of the 36:49 description and the priority. And then 36:53 I'm going to return from this an object 36:56 that has a success boolean. In this 37:00 case, I'm just going to say 37:02 true. So, true. And then we'll do a 37:05 message and let's say 37:08 ticket created successfully. I know it 37:12 wasn't, but we'll just respond with that 37:16 because I just want to show you how this 37:17 works if you've never used actions 37:19 before. And now in the new ticket page, 37:22 let's bring in so import. Let's bring in 37:26 create 37:29 uh create new ticket. Why isn't it 37:32 showing here? Did I export it or just 37:36 create 37:39 ticket? 37:43 Import. There it is. All right. So, 37:46 create ticket and then come down to the 37:50 form and we should be able to just 37:52 add action set that to the 37:58 uh create 38:00 ticket. And don't worry about this 38:02 TypeScript error right now. So, let's go 38:06 ahead and try this. I'm just going to 38:08 show the server uh console here because 38:10 when we submit it should show it down 38:12 here. And I have a form filler. I'm just 38:14 going to use that and then submit. And 38:16 there we go. So, we can see the subject, 38:19 the um description, and then the 38:22 priority, which was 38:24 medium. So, that's that's just how a 38:27 basic action works. Now, we're not 38:29 handling the the response in any way. 38:32 So, we're going to get to that, but need 38:34 to use a hook that's relatively new 38:37 called use action state. So, let's 38:40 import that. So, use action state. And 38:43 that's from React. And this is similar 38:45 to use state, but instead of dealing 38:47 with component state, we're dealing with 38:50 the state that we get back from the 38:52 action. Okay. So, the way that this is 38:55 going to work and and we have to make 38:56 this a client component because we're 38:59 using a hook and I don't like to 39:01 particularly use client components for 39:04 pages. So later on, we'll move the form 39:07 itself to a client component and keep 39:09 this server rendered. But just for now, 39:12 we'll say use client. And then to use 39:15 the use action state, we're going to 39:17 come up here above the return. Let's say 39:19 const. We want to pass in state and form 39:24 action. Okay, so we're destructuring 39:27 from use action 39:29 state. And then what we pass into use 39:32 action state is going to be one the the 39:36 action we want to use which in our case 39:37 is create ticket and then the initial 39:40 state which is going to be success false 39:44 and message just an empty string because 39:47 remember that's what we're what we're 39:48 getting back from here from the create 39:51 ticket. So we're just setting that that 39:54 initially before we actually submit the 39:56 form. Success obviously will be false 39:58 because it hasn't been submitted and 40:00 message will just be empty. Now when we 40:03 use an action in use action state we 40:08 have to edit the the function signature. 40:11 So basically what gets passed in here. 40:13 So in addition to form data we need the 40:16 previous state. Okay. So first we'll be 40:19 we'll say pre state then we get the form 40:23 data and the previous state as far as 40:27 typing is just going to be success which 40:30 is a 40:31 boolean and the message which is a 40:35 string and then let's add the return 40:38 type here for this function. So the 40:40 return is going to be a 40:42 promise that will have an object with 40:47 success which will be 40:49 boolean and a message which will be a 40:54 string. And again you don't have to if 40:56 you're not using TypeScript then you 40:58 don't have to uh have to add that. All 41:01 right. So back over here now instead of 41:05 passing create ticket in directly we 41:07 want to pass this in form action because 41:10 create ticket is being used through use 41:12 action state. So let's change that to 41:16 form action and that should clear up 41:17 that error as well. Okay. And we should 41:21 still be able to submit and console log. 41:23 So let's try that real quick. I'm just 41:25 going to add some stuff in here with the 41:27 form filler. Submit it. And we're seeing 41:29 that in the 41:31 console. All right. Now, I want to check 41:35 I want to do some form validation. I 41:37 know we used the HTML required here, but 41:41 let's actually get rid of that for the 41:42 text u for the description and the 41:45 subject because I want to validate here. 41:48 And I also want to use sentry to log um 41:52 if you know if it's not valid or not 41:55 which might not be something you do in 41:57 real life because you probably don't 41:59 want you know for everybody that misses 42:01 a field or whatever you probably don't 42:03 want that logged but just to show you 42:05 how we can capture messages with sentry. 42:08 So let's do a check and let's say 42:12 if not subject or 42:16 not 42:18 description or not 42:23 priority. Then I want to first of all 42:26 we're going to do our return and this 42:29 all of our actions are always going to 42:30 return a success and a message. Okay. 42:34 And that's just by design. That's that's 42:36 not like you don't have to do that. 42:38 That's just what I'm choosing to do uh 42:41 with my with my actions and how I'm 42:43 handling things. So message let's 42:48 say all fields are required. Okay. Now 42:52 in addition to that to returning right 42:54 above it I want to use sentry. So I need 42:57 to 42:57 import and the way we do this is we say 43:00 import 43:01 all as sentry from and then at 43:07 sentry/next.js 43:09 JS and then I'm going to do sentry and 43:12 I'm going to use a method called capture 43:17 message and for that message let's 43:21 say validation error colon we'll say 43:25 missing 43:27 uh missing ticket 43:31 fields. Okay. 43:33 And then when when we get our 43:38 um when we get back here with the state, 43:42 we can use that, right? So if there's an 43:44 error, if success is not true, we 43:46 probably want to show it in the form, 43:48 right? So why don't we add this 43:51 uh let's see, we'll go right below the 43:54 H1 and open up some curly braces and 43:57 let's say state message. So if there's a 44:00 message and if not 44:04 state.success, okay, then we know it's 44:06 an error. Then we want to show let's 44:08 open up some parentheses. Then we want 44:11 to show a paragraph. I'm just going to 44:15 give it a class of text- 44:19 red-500. And let's do margin bottom four 44:24 and text 44:26 uh text 44:27 dash center. And then in the paragraph 44:31 we can just add our state 44:35 message. Okay. So whatever we're passing 44:37 back here or here is going to be 44:40 available in this state object and I'm 44:43 using that down here. So now let's fill 44:46 this in, but let's leave off the 44:49 subject. And we get all fields are 44:51 required. Now this should also have 44:53 gotten logged to sentry. So I'm going to 44:56 go to my sentry issues. And since it's 45:00 just a regular message, it's not going 45:02 to show under prioritize. It's going to 45:04 be under for review. And you can see our 45:07 validation error missing ticket fields. 45:09 And it shows the the request. So post to 45:13 tickets 45:14 new and it just shows the uh the 45:18 breadcrumbs that lead up to it stack 45:21 trace. Um now when we capture a message 45:25 if we don't add uh a level manually then 45:30 it's going to be info which is just 45:32 that's ex just what it is. It's just 45:34 like a log. It's not looked at as an 45:36 error. It's not an exception. Uh it's 45:38 just a simple log. Now, we can change 45:41 that. So, let's say we want to log it as 45:44 a warning, which is probably what I do 45:46 because it's a little more than just 45:48 information. Something did go wrong, but 45:50 it's not, you know, detrimental. So, 45:54 let's go back to where we have our 45:56 capture message. And what we can do is 45:58 just add u a second argument here. And 46:01 this will be these are the different 46:03 levels. So, debug error fatal info, 46:06 which is what it is by default, log and 46:09 warning. So, I'm going to set it to 46:12 warning. So, now I'll go ahead and save 46:14 that. And then I'm going to go back 46:17 to my project here. Let's refresh that. 46:21 And this time, let's leave off the 46:25 description. Okay. So, we get all fields 46:27 are required. 46:29 And let's go back to 46:32 Sentry and I'm just going to re refresh 46:35 this. So now if I go over to my 46:38 validation error now you can see that 46:41 the warning the level is now 46:43 warning and you can attach data to this 46:46 as well which I'll get into later but I 46:49 just wanted to show you that how to 46:51 capture a message. Now let's do uh u 46:53 we're going to use capture exception. So 46:55 if something really if a there's a 46:57 problem with the server. So what we'll 46:59 do is wrap everything here. We're going 47:01 to do this anyway is wrap this in a try 47:03 catch. So let's open a try catch and 47:07 just take everything that's in the 47:10 function. And we'll just cut that and 47:12 then put this in the 47:15 try. And then what I want to do in the 47:18 catch is first off our return which I'll 47:21 just copy 47:23 this. So we're going to return 47:25 false. Okay. And then for the message, 47:29 let's just 47:30 say we'll just say an 47:33 error an error 47:35 [Music] 47:36 occurred while we'll say while 47:40 creating creating the 47:42 ticket. Okay. But before we do that, 47:45 before the return, we're going to do 47:47 sentry. And now we're going to use 47:48 capture exception. 47:52 And in the capture exception, we want to 47:56 pass in the error as an error. And then 47:59 we can also pass data to this. So what 48:02 I'm going to do is pass an object with 48:06 extra. And I want the form data to be 48:10 attached to this. So let's add an object 48:12 with form data. And we can get that with 48:16 let's say object dot and then we'll use 48:19 from 48:20 entries. and then we'll pass in our form 48:24 data form data 48:27 entries parenthesis. Okay, so that 48:30 should pass the actual form data. Now to 48:33 fire this off, what we'll do is just 48:36 temporarily throw an exception. So in 48:39 the try at the very top, let's say throw 48:43 new 48:45 error. Okay. And then I'll just say 48:47 simulated 48:49 um simulated Prisma 48:52 error for 48:55 testing. And we'll save that. And then 48:58 we're going to go ahead and submit our 49:02 form. Let's just uh let's just refresh 49:05 this. And and we don't it doesn't 49:07 matter. You don't have to leave anything 49:08 off or whatever. Just submit. And we get 49:10 back an error occurred while creating 49:12 the ticket because when we submitted it 49:14 threw this error which then called the 49:18 code and the catch which returns this 49:21 and that this message was put in the the 49:24 output. Now this also ran so it captured 49:28 the exception with sentry. So let's try 49:30 let's take a look at that and let's go 49:33 to issues. So now we have this in 49:36 prioritize right? It's not in the for 49:38 review. It's in prioritize because it's 49:40 an actual error. It's an actual 49:42 exception. So, let's go ahead and click 49:43 on that. And we see our breadcrumbs. And 49:47 then we should 49:48 see, let's see, down at the 49:52 bottom right here. So, additional data. 49:55 We have our form data. And you can see 49:58 here the subject, the priority, the 50:02 description, and then we have these 50:04 action keys, action ref. So this was 50:08 attached to to the exception and you can 50:11 attach anything you want which will 50:12 obviously help you in your you know your 50:14 debugging process. Now before we do 50:18 anything else I want to actually be able 50:20 to submit a ticket and add it through 50:22 Prisma. Now there's there's a step that 50:25 maybe I should have did it earlier but 50:28 we need to at least do it now and that 50:30 is create 50:32 uh a a special file for our Prisma 50:35 client. Because if we use it as is the 50:39 Prisma client asis, we could run into 50:41 some issues because of the way that 50:43 server side rendering works and the way 50:45 that um platforms like Burcell work with 50:48 Neon, which is serverless, uh meaning 50:51 there's not like a continuous 50:52 connection. So we don't want a case 50:55 where multiple Prisma instances are 50:58 being created. Basically we want to 51:00 create a global instance and then reuse 51:04 that unless it's not created. Right? So 51:07 we wanted to check to see if there's a a 51:09 global Prisma instance. If there is, use 51:11 it. If there's not, then create it. So 51:13 what we're going to do is create a a new 51:15 file in the source folder or new folder 51:18 rather called DB. So, source db and then 51:22 a file in that called 51:25 prisma.ts. And then I'm going to paste 51:27 this in. And you can just you can either 51:30 copy it or you can grab it from the from 51:32 the repo, whatever. And it's doing what 51:34 I said. It's bringing in the client. And 51:36 it's bringing it in from the generated 51:38 file rather than just at 51:41 Prismaclient. So, this should match 51:43 whatever you have in your schema. So 51:46 right here let's see for this output 51:50 right so source generated Prisma so 51:52 that's what you want to bring the client 51:53 in from here and then we're using the 51:56 global this object with it which is a 52:00 global JavaScript object like window for 52:03 the browser or global for NodeJS and 52:06 we're just assigning that Prisma client 52:08 here we're checking to see if it exists 52:11 so if it does set it to Prisma if it 52:14 doesn't create a new Prisma instance and 52:17 we're exporting it. So from now on when 52:19 we want to when we want to use the 52:20 Prisma client, we want to use this. 52:23 Okay, we want to bring it in from this 52:25 file 52:26 db/prisma, not from Prisma client and 52:29 not from the generated. So let's save 52:32 that. And now go back to our ticket 52:33 actions where we're going to use Prisma 52:36 and we're going to import let's say 52:39 Prisma and that's going to be from and 52:41 then at 52:44 slashdb/prisma. Okay. So again whenever 52:47 you're going to use the client bring it 52:48 in from here and then I'm also going to 52:51 import one more thing and that's going 52:53 to be revalidate ca uh not cache 52:56 revalidate path from next slashcache. So 53:00 that will just make sure everything's up 53:01 to date. When we submit a ticket, we 53:03 want to make sure our list of tickets 53:05 includes that new ticket. All right. Uh 53:08 for certain paths. So now let's uh let's 53:12 get rid of this simulated 53:15 error. So get rid of that. And then 53:19 we're getting the data. We're checking 53:21 it. We're sending the sentry if needed. 53:23 Now what I'm going to do is go right 53:25 above the return in the try block and 53:28 let's 53:30 say create ticket. So we'll say const 53:34 ticket and set that equal to 53:38 await 53:40 prisma. dot and then we're going to use 53:42 the create method and that's going to 53:45 take in an object with data and for the 53:48 data we want to send the data from the 53:50 form. So, 53:52 subject, description, and 53:56 priority. So, that'll create the ticket. 53:59 Now, I want to capture this in sentry 54:03 when we create a new ticket. And I want 54:06 to create a breadcrumb, which is uh, you 54:08 know, a trail of how that happens. So, 54:10 we're going to say sentry dot and then 54:14 add 54:15 breadcrumb. And we can pass in a few 54:18 things here. So, one is a category. So 54:20 this has to do with tickets. So we'll 54:22 have a ticket category. Let's add a 54:25 message. So I'm actually going to use 54:27 back ticks here because I want to 54:28 include the ID. So we'll say that the 54:31 ticket was created. And then we'll just 54:35 add in the 54:37 ticket. 54:39 ID. All right. And then we can add a 54:41 level as well. So remember the different 54:43 levels I showed you. This is not a 54:45 warning or an error. It's just a log. 54:48 So, we're going to use 54:49 info. And then I want to capture a 54:52 message. So, let's say sentry dot 54:55 capture message. And again, we'll just 54:58 use back ticks and say ticket 55:01 was 55:03 created su um yeah, we'll say ticket was 55:06 created 55:07 successfully. And then we'll have the 55:10 ticket 55:13 ID. And then the last thing we want to 55:16 do before we return success is 55:18 revalidate the path. So we want to 55:21 revalidate the slash tickets path, which 55:23 doesn't exist yet, but it will. And 55:25 it'll list all of our tickets. So we 55:27 want to make sure that it's up to date 55:29 with this latest ticket. All right. So 55:32 let's try this out. I'm going to come 55:33 over to new, and I'm not going to use my 55:36 form filler. I'm going to type in here. 55:38 Let's say ticket 55:39 one. This is a sample ticket. 55:44 and let's make it medium priority. Let's 55:48 submit. Now, I have nothing happening 55:51 when we submit. So, don't worry about 55:54 that. We'll we'll redirect or show a 55:56 message or something. But let's go to um 56:01 Prisma Studio. You could also use just 56:04 you could check Neon directly. But if we 56:07 go to Prisma Studio and reload, there it 56:09 is. Ticket one. This is a sample ticket. 56:12 medium for the priority and the status 56:14 is open by default. And we can go to 56:17 neon and reload our table and you can 56:21 see that our ticket is there in the 56:23 database. And we can go to 56:26 uh sentry and we should have under for 56:29 review ticket was created successfully. 56:33 And we have our breadcrumbs here as 56:35 well. 56:37 And of course, this is optional, but 56:39 again, it's nice to just know exactly 56:42 what's happening um throughout your 56:46 application. All right. So, one thing 56:49 I'd like to do 56:50 though before we move on is create a 56:53 helper function for this sentry stuff 56:56 because I want to just have one function 56:58 called log event. And that will add a 57:01 breadcrumb and it will add either a 57:03 message or an exception depending on if 57:06 there's an error or not. If there's an 57:08 error, it'll be an exception. If not, 57:09 it'll be a message. So, what we can do 57:12 is go into source and let's see, we'll 57:15 create a folder here called 57:18 utills. And in utils, let's create a 57:21 file called sentry.ts. 57:25 And we want to import all 57:30 as sentry from at century. 57:35 Whoops. At sentry 57:37 next.js. And then uh I'm going to add a 57:41 type of log 57:44 level. Actually, let's make that an 57:46 uppercase L. So type log level. Again, 57:49 if you're not using TypeScript, you 57:51 don't have to do this, but it has to be 57:52 either fatal or error. Oops. Or 57:58 error or what's the other ones? Um, 58:03 warning or 58:06 info or 58:08 debug. Okay, so it has to be one of 58:11 these. And then let's export our 58:14 function. 58:16 And function. And I'm going to call this 58:18 log 58:20 event. Okay. Now, this is going to take 58:22 in a few things. It's going to take in 58:25 the message, which will be a string. 58:29 It's going to take in the 58:31 category, which will be a string, and 58:34 we'll give it a default of 58:37 general. Okay. Then we'll do optional 58:40 data. So, the question mark just means 58:43 it's optional. And for the type we're 58:46 going to do we're going to use record. 58:49 Um so record and then in angle brackets 58:52 we're going to add 58:54 string and any. Now this this record 58:58 means that it can be an object with any 59:00 number of properties and values of any 59:03 type. So this is useful for logging 59:06 additional information about the event. 59:08 Um for example we can log the ticket ID 59:10 or the user ID. Then we're going to take 59:13 a log level. The type for that is going 59:16 to be the type we just created above, 59:18 which is log level. And I'm going to set 59:20 the uh the default to 59:24 info. And then we'll have the error, 59:27 which will be optional because it could 59:29 be just a message. There might not be an 59:31 error. And I'm going to give that a type 59:32 of 59:33 unknown. All right. So now in the actual 59:37 function, we're first going to create 59:39 our breadcrumb. So sentry.add add 59:41 breadcrumb and then we're going to pass 59:43 in an object with the category. So this 59:46 stuff is all going to come from the the 59:49 arguments. So category, the message, the 59:54 data, and the 59:57 level. Okay, it may or may not have an 60:00 error. And then let's see. Um oh, you 60:04 know what? This should just be level 60:06 like that. So we got our breadcrumb. 60:09 Then we want whether it's going to be 60:11 capture message or capture exception 60:13 depends on if there's an error. So let's 60:15 say if there's an 60:17 error then we want to call 60:21 sentry capture 60:24 exception and pass in the error and then 60:28 any extra data. So remember we can do 60:30 this extra and then pass in our 60:33 data and let's say else if it's if 60:37 there's not an error then we want to 60:38 just capture message. So capture message 60:43 pass in message and pass in the level 60:47 and that's it. Okay. So make sure that 60:50 uh that this function is being exported 60:52 so that we can use it and we'll close 60:55 that up. And now we can use this in 60:57 place of a lot of the stuff we did here. 60:59 Just kind of clean it up a little bit. 61:01 So in this right where we check for the 61:04 fields instead of doing the capture 61:06 message, let's get rid of that. And 61:08 let's say actually we have to bring in 61:10 log event. So just make sure you bring 61:13 that in right here. Import from utils 61:17 sentry. Then we're going to log event 61:21 and let's pass in the message which is 61:23 going to be validation 61:26 error colon and uh we'll say 61:31 missing missing ticket 61:34 data or not yeah we'll say missing data 61:38 fields and then the second thing we're 61:40 going to want to pass in is the category 61:42 which is going to be 61:44 ticket. Okay. Then we want to pass in 61:48 the data which I'm going to pass an 61:51 object with the subject, the 61:56 description and the 61:59 priority. And then after that we want 62:01 the level which is going to be in this 62:04 case 62:07 warning. Okay, so hopefully that makes 62:10 sense. So passing in the message, the 62:13 category, the data and the level and 62:16 that's going to you know it's going to 62:18 call our log event which we just 62:20 created. So now let's come down here 62:23 where we do the breadcrumb and the 62:24 message and we can replace both of 62:27 those. So we'll get rid of that and then 62:29 we can just do our log 62:32 event and we'll pass in a message. I'm 62:35 going to use back tick and say 62:37 ticket 62:39 created 62:41 successfully and let's add the ticket 62:45 ID. So ticket ID and then after that we 62:50 want the category which is ticket. After 62:54 that we want the data which I'm going to 62:56 do ticket ID. set that to ticket id and 63:02 then the level which we'll do 63:05 info. Okay, so that'll log that. Then 63:08 let's come down to the 63:11 uh where we do the capture 63:13 exception and the way that we for we 63:16 created it we can do log event for 63:18 exceptions as well. So let's say an 63:23 error 63:24 occurred while 63:27 creating the 63:29 ticket. Okay. So that's the that's that. 63:32 Then we want the category. So ticket. 63:35 Then we want the data which again I'm 63:38 going to do the form entries or form 63:40 data. So an 63:43 object and let's do from entries. And 63:46 then we can pass in the form data dot 63:52 entries. Okay. And then we want to pass 63:55 let's see after that object. So, comma 63:59 here we want to pass in the level which 64:02 is going to be an error. And then we 64:04 want to pass in the error 64:06 itself. Okay, which comes from here. 64:10 So now we can use just this one function 64:12 we can use to add the breadcrumbs, 64:16 capture the message, um capture the 64:19 exception, add categories and all that 64:22 stuff. All that good 64:24 stuff. Okay. So now let's just try this 64:27 one more time. I'm going 64:29 to first 64:32 off try to do it without a field. So 64:35 that that should throw an error or a 64:38 warning rather. And then let's create 64:40 another ticket. Let's say 64:42 ticket ticket 64:44 two. This is a 64:48 sample ticket. And then I'll do low 64:50 priority 64:52 submit. All right. So now if we go 64:56 back to 65:01 Sentry should 65:06 see should see created and sometimes it 65:09 takes I've seen it take up to like a 65:10 minute or 65:12 so but we should see the next ticket. 65:16 Let's just make sure it was actually 65:17 submitted. So let's go to Prisma Studio 65:20 and reload. Okay, so ticket two was 65:23 created. 65:27 And there it is. So ticket created 65:28 successfully and we can see it has an ID 65:30 of two. So it took about 30 seconds to 65:33 to be able to show up. So now I want to 65:36 make it so that it redirects when we 65:38 submit a ticket. So what we can do is 65:42 we're going to do this in the in the 65:43 page or the component, not the action 65:45 because the action is always going to 65:47 return an object with true um not true 65:50 but success and message. So what we'll 65:53 do is come back to the the page. So this 65:56 is the new page and I want to bring in 65:59 use 66:00 effect and basically we we want to check 66:03 that success option right and if that 66:06 changes then we want to redirect because 66:10 when we get a a response back that it'll 66:12 send a success message or not a success 66:15 message but a 66:16 value. So uh let's see we also want to 66:19 bring in the use router hook. So let's 66:22 import say use router and that's going 66:25 to be from next navigation not from next 66:28 router and then we'll go right above the 66:31 return. Let's add our use effect and I'm 66:34 assuming that you know how use effect 66:36 works. Um we're going to pass in our 66:39 dependency array and we want to look at 66:41 the 66:44 state.success or the router. So if 66:46 either of those changes and I didn't 66:48 initialize the router which we have to 66:50 do. So let's say const router set that 66:54 to use router. Okay. So if either of 66:57 those changes then whatever I put in 66:59 this use effect is going to run. And 67:01 what I want to put is first of all just 67:03 check for if 67:08 state.success and then we want to 67:10 redirect. So router.push 67:13 push and we want to redirect to slash 67:20 tickets. Okay, so I'm just going to 67:22 refresh this tickets new. And now if I 67:25 submit a new ticket, so ticket 67:31 three, this is a sample ticket. And then 67:35 if I submit 67:38 that, I get redirected to slash tickets, 67:41 which of course doesn't exist yet. So 67:43 let's create that. Now, we already have 67:46 in our app folder, we already have a 67:48 tickets folder with the new folder, but 67:51 we want to create a page.tsx directly in 67:54 the app tickets folder. So let's create 67:58 page.tsx. Let's do sfc. 68:04 And let's call 68:06 this tickets 68:08 page. And for now, we'll just say 68:12 tickets. So that should 68:15 render. All right. Now, before I add 68:17 anything to the tickets page, I want to 68:19 add a uh a toast notification when we 68:23 submit a new ticket. And I'm going to 68:25 use a package called Sauner. Sauner. 68:28 Soner. It's s o n ne. And I've been 68:30 using this lately. um rather than React 68:33 Toastify, but you could use that if you 68:36 want to. I mean, they all do kind of the 68:38 same thing. But let's go to a terminal 68:41 and I'm going to npm install s 68:47 ner. Okay. And in order to use this, 68:50 we're going to add in our layout. So, if 68:54 you go to your layout tsx file, we're 68:57 going to import the 68:59 toaster. So toaster from 69:03 sauner and let's see we're going to go 69:05 right 69:07 under children. So right here and we're 69:10 going to add 69:13 toaster like that. And then I'm going to 69:16 just add a position. And you can go to 69:18 the documentation to see like the 69:20 different attributes and positions and 69:23 stuff. I'm going to do top center. Okay. 69:25 So now we have an output for our 69:27 toaster. Now let's go back into the new 69:30 page. Right? So this is the new ticket 69:32 page and we're going to import 69:36 toast. So import toast 69:41 from 69:43 from 69:46 sauner. Okay. And then all I'm going to 69:48 do is go to the use effect. So, right 69:52 before we redirect, we're going to say 69:54 toast 69:56 dot 69:57 success and let's say um we'll say 70:03 ticket 70:06 submitted 70:10 successfully. And yeah, that should do 70:13 it. So, I'm going to save that. And 70:14 let's change this title, this create 70:16 next app. So that's going to be in the 70:20 layout. 70:23 So where is it? Yeah, right here. Let's 70:26 change that 70:28 to say 70:31 quick ticket 70:36 support and let's say get support for 70:41 your product or whatever you want to 70:43 put. Anything's better than create next 70:47 app. Okay, so let's try this out. I'm 70:49 just going to put some dummy data and 70:51 submit. And there we go. Tickets 70:53 submitted successfully. So it shows the 70:55 toast and then it goes away after a few 70:58 seconds. Now before we actually work on 71:02 this page, the tickets page, I want to 71:05 move, like I said 71:06 earlier, I want to move the form from 71:10 the the new page here because I don't 71:13 want this to be client uh client 71:16 rendered. I want this to be server 71:17 rendered and I think it's just better 71:20 organization. So why don't we create a 71:23 new file? And you you could put this in 71:25 a components folder, but the way I like 71:28 to do this is if the form is is, you 71:30 know, so attached to the page and we're 71:32 not using this form anywhere else, I'm 71:34 going to put it in the the new folder 71:36 next to the page. So I'll create a new 71:39 file here and let's call this ticket. Uh 71:43 we'll say 71:47 ticket-form.tsx. All right. And then I'm 71:49 basically going to just copy everything 71:51 from the page. 71:53 paste this in here and then I'm going to 71:55 change the name. So, new ticket page 71:58 here and here at the bottom we're going 72:00 to change to new ticket 72:04 form. Okay, so change the name of that 72:08 and let's see, we're going to basically 72:11 keep all 72:12 this. 72:15 Um, so we have our use 72:17 [Music] 72:19 effect, and let's see what I don't want 72:22 to keep is this this div here. Let's get 72:25 rid of this top div in the ticket form. 72:28 So, right here, I'm going to delete 72:29 that. And then, of course, we got to 72:30 delete the ending 72:32 div. Okay. So, we'll get rid of that. 72:35 And I think that's really all we need to 72:37 do. everything else is is uh you know 72:40 encapsulated in this this file. So let's 72:44 save that and go back to our page. And 72:46 now uh I'm just going to I'm just going 72:49 to get rid of this whole thing. Let's 72:53 regenerate this. So new new ticket page. 72:58 And then what we can do is bring 73:01 in is bring in the new ticket 73:06 form, right? And let's see. I'm 73:09 gonna I want that wrapping div. So, 73:13 let's do some classes here. We're going 73:15 to do 73:16 min-h 73:18 screen. And what else do we have? BG 73:21 dash blue 73:24 50 flex 73:27 uh items dash 73:29 center justify dash center and 73:35 px-4. Okay. Okay. So, we have that 73:37 wrapping div. And then in there, we can 73:39 put the new ticket 73:43 form. All right. So, let's save that. 73:46 Let's try to go to slash ticket slash 73:48 new and then we should see our form. So, 73:52 the page itself is server rendered, but 73:55 then we're putting in a new ticket form 73:58 which is client side. So, now we want to 74:00 work on getting the tickets and we're 74:02 going to have an action for that. So, 74:05 let's go into our ticket 74:07 actions and we'll go down to the bottom 74:10 here and let's say 74:14 export or export async function and 74:18 we'll call this get 74:23 tickets. Okay, we're going to add a try 74:26 catch and we're going to use Prisma 74:29 here. So let's say con 74:31 tickets and set that to 74:34 await and we want prisma dot ticket dot 74:39 and we're going to use the find many 74:42 method and I want to order by the date. 74:44 So we're going to pass in here order 74:47 by and we want to set let's say created 74:51 at that's the field we want to use and 74:53 we want it to be descending. 74:56 So that will get our tickets and then 74:58 let's do a log 75:00 event for sentry. So just log event pass 75:05 in fetched fetch ticket 75:09 list. And this is totally optional. And 75:12 if you want to pick and choose some of 75:14 this stuff to log, you can. Um I'm going 75:17 to pass in here the 75:19 count of tickets. So we can get that 75:22 with tickets. 75:25 length. So that'll be the extra data. 75:28 And then let's make it an info 75:31 level. Okay. And then we just want to 75:33 return the 75:36 tickets. And then if there's an error, 75:39 let's log 75:41 event. And we'll say 75:44 error fetching 75:47 tickets. And let's do the category of 75:52 ticket. And I'm not going to send any 75:54 data. So I'll just put an empty object. 75:56 The level is error. And we want to send 75:59 the actual error. And then what I want 76:02 to return is just an empty 76:06 array. And that's it. So that should get 76:09 our tickets. Now we want to use that in 76:11 our ticket page. So let's go to our app 76:14 folder tickets and then 76:16 page.tsx. And then from here we're going 76:19 to bring 76:20 in let's say import. We want to bring in 76:24 that action. So get 76:26 tickets. And then we're going to also 76:29 bring in our log event 76:32 utility. And then let's bring in 76:37 link. Okay. And then we're going to come 76:40 down into our function here. And we want 76:43 to use that action. So we can just 76:46 simply say get uh const and 76:49 then tickets set that to 76:54 await get tickets and we do have to make 76:58 this 77:01 asynchronous. Okay, so that'll get our 77:04 tickets and we can do a quick console 77:06 log. I don't know why that's not 77:08 working. 77:10 So, console log of the tickets just to 77:13 make sure that that we're getting them. 77:16 So, I'm going to go to 77:19 slash 77:21 tickets. 77:26 Oops. All right. And we should see that 77:29 down 77:30 here. 77:31 Yep. So, there we go. We know that we're 77:34 getting them from our action, which is 77:35 good. So, let's get rid of that console 77:38 log. And then down here, 77:41 um, we're going to make this a div. And 77:44 let's give it some classes. We're going 77:46 to do a min h screen bg 77:51 dash blue 50. And let's do a padding 77:56 padding of eight all around. Okay, let's 77:59 get rid of that. And then I want to have 78:02 an H1. So we'll do text 3XL. Let's do 78:07 font dashbold text 78:10 blue 78:12 600 margin bottom eight and let's do 78:15 text dash center. And then in the H1 78:19 we'll just say 78:21 support 78:22 tickets. Okay, let's see if that shows 78:26 up. There we go. Support tickets. I just 78:29 want to make this smaller. Okay. Now 78:32 under that H1, I want to 78:34 check to see if there are any tickets 78:38 first. So what we can do is say if the 78:42 tickets.length. So if that is equal to 78:46 zero, then we'll show something else. 78:49 We'll show something else. So if it's 78:52 equal to zero, then I'm going to have a 78:53 paragraph with let's say text dash 78:57 center. Let's do text uh text dash gray 79:04 600 and we'll just say no tickets 79:09 yet. Okay, so that's if there's no 79:11 tickets. And then here in in these 79:14 parentheses, if there are tickets, then 79:17 we're going to have a class of let's do 79:19 space dash on the yaxis 4. We're going 79:22 to do a max width. So, max 79:26 W3XL and let's do margin on the X axis 79:31 auto and we'll close that sidebar up. 79:35 So, in here we want to take our tickets 79:37 and we want to create a list. So, we 79:39 want to map through those. So, 79:42 tickets.map pass in our function and 79:45 we'll say for each ticket then we want 79:49 to render some JSX. So open some 79:52 parenthesis. We'll have uh a div. Let's 79:56 do a class of flex. We'll do justify 80:00 dash between. Let's do items items dash 80:06 center. Background is going to be white. 80:09 And I want some rounded corners. So 80:11 rounded large. We'll do a shadow. We'll 80:14 do a border. And let's do a border 80:18 dash gray dash two uh 200 and padding 80:25 six. Okay. So in this div um actually 80:29 you know what we got to give this a key 80:30 because it's a list. So for the key I'm 80:33 going to use the ticket 80:36 id. All right. And then just going to 80:39 put a comment here. So we'll have the 80:41 left 80:42 side and we'll have the right side. 80:47 Okay. So, for the left side, uh, we're 80:50 just going to have a div. And then in 80:52 that, we'll have our 80:54 H2. Let's say 80:56 text-XL for the size. And then font 81:00 dash 81:01 semibold. And let's do a text blue 81:06 600. And this is going to be the ticket 81:09 subject. So, we're going to say 81:12 ticket 81:13 subject. See if that 81:16 renders. There we go. Good. So, we see 81:18 all the 81:19 subjects and then and that's all I want 81:22 on the left side. So, the right side, 81:24 let's do text dash write class. And 81:28 let's add some space. So, space on the 81:30 yaxis too. And then let's have a div 81:34 with text dash small and text dash gray- 81:40 500. 81:41 And I want the 81:43 priority. So we'll say priority. And 81:46 then here I want to have a span. So 81:49 we'll do a span with 81:51 um actually we'll do the class after 81:54 because I actually want it to be a 81:55 dynamic class where it's a different 81:57 color depending on the priority. But for 81:59 now we'll just 82:01 add ticket 82:03 priority. Okay. If I save that, there we 82:06 go. We see the the different priorities. 82:08 And then we want also want a link to go 82:11 to the single individual ticket page. So 82:14 that's going to go under this div, the 82:17 div that wraps the priority. And let's 82:20 add a link. And this is going to be 82:23 let's say href use some back ticks here. 82:26 Uh well back ticks in curly 82:28 braces. And that's going to be slash 82:32 tickets. So slash tickets slash. And 82:35 then we want the ID. So, 82:38 ticket. 82:40 ID. And I also want to just add some 82:43 classes on the this link. Let's uh let's 82:47 close 82:50 it. And it'll just say view 82:53 ticket. Okay. Now, as far as the classes 82:56 go, I'm just going to grab them because 82:57 there's quite a 83:01 bit. Okay. So, those are the classes. 83:05 Let's take a look. 83:09 Okay, there we 83:11 go. Good. Now, again, I want to just 83:14 make the priority a certain color. So, 83:17 I'm going to have a little helper 83:19 function. I'll just put it in this file. 83:22 So, I'm going to put it right here. 83:23 We'll say const and let's call this get 83:27 priority class, which will take in 83:30 priority, which is going to be a 83:34 string. Okay. And uh Whoops. Get a 83:44 set. Okay. And then I'm just going to 83:46 use a switch 83:48 here. So for the switch, we want to test 83:51 the 83:53 priority. And let's say if there's a 83:55 case of it being 83:59 high, then 84:02 uh whoops, what am I doing? 84:04 case 84:05 high. Then we want to return a class of 84:09 text- 84:11 red-600. And I'm also going to just add 84:13 font 84:16 dashbold. And then let's do a case where 84:19 it's 84:22 medium. Then we'll 84:24 return let's say text dash yellow. Yeah, 84:28 we'll do yellow 84:30 600 font 84:33 bold. And then we want a case of 84:38 low. And if it's low, we're going to 84:41 return text dash 84:45 green-600 and font bold. All right. So, 84:49 we have our get priority class. Now, we 84:51 want to use 84:52 this down 84:55 here. So let's see. We want to use it on 85:00 the span. So right here on the span, I'm 85:02 going to give it a class name. And that 85:04 class name is going to be set to it's 85:07 going to be dynamic. And it's going to 85:08 be set to get priority class. And then 85:13 we'll pass into that the 85:16 ticket. 85:19 priority. So if we save that now, you 85:22 can see the different colors. Okay. 85:24 Okay. So, we have medium, low, right? 85:27 There's no high. I guess we can we can 85:31 go to slash 85:33 new. Create a new ticket. Uh, I'll just 85:36 say new 85:39 ticket. This is a test. And we'll choose 85:43 high. Submit. And there we 85:47 go. So, yeah, this is starting to come 85:49 together. Now, the next thing I want to 85:51 work on is the single ticket page. If I 85:53 click on the view ticket, it takes me to 85:56 the route ticket slash and then the ID. 86:00 So that's what we'll work on next. Now I 86:03 I usually like to start with the action 86:05 first. So let's create an action that 86:08 will get an individual ticket. So let's 86:10 export a function down here. So 86:13 export async function and we're going to 86:16 call this get ticket by ID. 86:21 And it's going to take in an ID which is 86:23 going to be a 86:26 string. And then first thing we'll do 86:28 let's add a try catch. And then we want 86:32 to get the ticket using Prisma. So we'll 86:34 say ticket set that to await on 86:39 Prisma. And we're going to use the find 86:42 unique 86:44 method. And in find unique we're going 86:46 to pass in an object. We want to find 86:50 where the ID is equal to the ID. Now I 86:56 want to uh cast this as a number. So I'm 86:58 going to wrap 87:00 this in number like that. All right. And 87:04 then we'll check for the ticket. So 87:06 we'll or check if not 87:09 ticket. So if not ticket then I want to 87:11 log a sentry event. So we'll call log 87:15 event. And let's pass in a message. 87:17 which will say that the 87:19 ticket so ticket not 87:22 found we'll give it a category of 87:26 ticket and let's give it let's attach 87:29 some we'll attach the ID so 87:32 ticket ID set that to the ticket do ID 87:37 and then we'll give it a warning 87:41 type all right uh let's 87:45 see oh I'm sorry this should not Ticket 87:48 ID is just ID because it's getting 87:49 passed in 87:51 here and then we just want to return the 87:56 ticket. Now in the catch I want to log 88:02 event and I'm going to just say error 88:07 fetching ticket 88:10 details and we'll give it the category 88:13 of ticket. We'll give 88:15 it for 88:17 data ticket ID which is going to be 88:21 ID and the level is going to be error 88:24 and we want to pass the actual error and 88:28 then we want to return 88:30 null like 88:33 that. So for the page right so we need 88:36 to create a page for an individual 88:38 ticket. Now this is a dynamic route. So 88:41 what we can do is go into tickets right 88:43 because we want it to be for instance 88:45 slashtick slash one which would be the 88:48 ID of one. So we create a folder in 88:51 tickets and we use brackets and then 88:54 call it ID right so that's the name of 88:57 the folder brackets ID the brackets just 89:00 signifies that it's dynamic and this 89:02 could be anything and then in that we 89:05 have our page.tsx 89:09 tsx. Okay. And then we're going to call 89:11 this uh let's do 89:13 sfc. Let's call 89:17 this we'll call it ticket details 89:22 page. And then for now we'll just say 89:25 details. And then this should load if 89:27 you go to ticket slash and then an ID 89:30 like five or whatever. Okay. If I go to 89:33 slash one that should still work. 89:37 So we want to bring our action 89:41 in. So up at the top here, let's import 89:45 get ticket by ID. Let's import our log 89:49 event 89:52 utility. And let's import link from next 89:56 link. And then I want to also import not 90:01 found. Um, so let's say not found and 90:05 that's going to be from next navigation. 90:07 Okay. So it'll just load the 404 if we 90:09 call that. And then I'm going to 90:13 um we're going to have the same colors 90:15 for the priority. So we can actually 90:17 copy 90:18 from let's see from this page the 90:22 tickets page this get priority class. We 90:25 could put this in I guess we could put 90:27 this in a separate file rather than 90:31 repeat ourselves. Yeah, I guess we could 90:32 do that. Why don't we just let's cut it 90:35 and we'll we can put it in our utils. 90:38 So, let's say utils. We'll create a new 90:40 file here. And I'll just call it uh 90:44 we'll call it 90:45 UI.ts just for little UI things like 90:49 this. Okay. And then I'm going to paste 90:51 that in. Let's export 90:56 it. Okay. And then we'll bring that in 91:00 here. This is the tickets page. So, 91:05 import what is it? Get priority 91:11 class. And then we want to bring it in 91:14 to this as well, our 91:18 ID. Okay. Now, we can use that when 91:21 needed. So, let's see this 91:25 um the ticket details page. we have to 91:28 get the ID from the URL, right? So, if 91:30 it's one, we have to be able to get 91:32 that. And the way we get that is by 91:34 passing in uh first of all, let's make 91:37 this async and then we're going to pass 91:39 in let's say 91:41 props and it's going to be 91:45 params. The type will be a promise and 91:49 it'll be in brackets. It's going to be 91:52 an object with ID which will be a 91:55 string. Okay. And then we can dstructure 91:58 from that. Let's say const from the 92:00 params. So props params. We can 92:03 dstructure the ID. So we're going to get 92:06 that from and then await 92:09 props params. That should give us the 92:12 ID. In fact, we can test that out by 92:15 coming down here. Let's say details. And 92:17 then I'll just 92:19 add ID like that. And we should 92:22 see details one. If I go to details /2, 92:27 then we see the two. Okay. So that's how 92:29 we can get uh the ID that's in the URL. 92:33 Now we want to get the ticket by using 92:35 this action which takes in the ID. So 92:38 right under that let's say 92:41 const set that to await and then 92:46 get ticket by ID and pass in that ID. 92:52 And if it's not found, so let's say if 92:55 not 92:56 ticket, then we're going to call not 92:59 found. Okay, so it'll just it load the 93:01 the default not found page. And then I 93:05 also just want to 93:06 log event and let's say 93:11 uh viewing ticket 93:14 details. Again, you don't have to do 93:17 this stuff. I'm just trying to get you 93:18 comfortable with logging stuff to 93:20 Sentry. just so you have it you know all 93:23 the events that are happening. So ticket 93:27 um let's add the ID as well. So ticket 93:30 ID set that to and this time it is 93:33 ticket 93:34 ID and then we'll have info as the 93:42 level. So now we want to obviously 93:46 output the data. So, I'm going to copy 93:49 the JSX because it's mostly just uh just 93:53 static, you know, tags and classes. So, 93:57 let's see. We'll 93:58 return in parenthesis. I'll paste that 94:02 in. Save it. See what that looks 94:07 like. Okay. So, pretty simple. Uh we 94:11 just have a couple divs. An H1. The H1 94:14 has the 94:15 subject. And then we have the this div 94:18 with an H2 for the description uh 94:21 description label and then a paragraph 94:23 with the actual description. We have our 94:26 priority using the get priority class to 94:28 color it. Then we have the date the 94:31 created at 94:33 um and then we have a link to go back to 94:36 tickets. Okay, so that's ticket two. If 94:39 I go to ticket three, we see ticket 94:41 three. So pretty simple. 94:44 So, next thing is the navbar because 94:47 right now there's not there's no way for 94:49 me to get back to like the the submit 94:52 page or the create page without typing 94:55 it in the URL. So, we want to have some 94:57 kind of navbar. Also, when we start to 95:00 implement authentication, we need a 95:02 login link, a register, uh a logout 95:05 link. That stuff's all going to go in 95:06 the navbar. So, what we'll do is create 95:09 in source. I don't believe we have a 95:10 components folder yet, do we? No. So in 95:13 source, I'm going to create a folder 95:15 called components. And this is for UI 95:18 components that you know are are common 95:20 and don't have anything to do with 95:22 specific pages. So let's create a file 95:25 here. We'll call it 95:31 navbar.tsx and 95:33 sfc 95:35 navbar. Okay. And just for now, I'm just 95:38 going to just say nav. And where we're 95:41 going to want to put this is in the 95:43 layout because I want it on every single 95:45 page. So, let's go into our app folder 95:48 and then the layout 95:51 file. And then from here, I'm going to 95:55 import the 95:57 navbar. And I'm going to put this just 96:00 above the children line. So, right here 96:02 within the body. Let's say 96:05 navbar. Save. Excuse me. Save that. And 96:08 we should now see nav no matter what 96:12 page we're on. So if I go to ticket 96:14 slash new, we still see that nav. And 96:18 the nav stuff for now I'm just going to 96:20 paste in because it's it's just links. 96:23 So let's go into the return 96:27 here. Put some parenthesis and I'm going 96:29 to paste this in. Okay, we need to bring 96:31 in 96:33 link. So up at the top, let's import 96:35 link from next link. And if we save 96:39 that, we should see that render at the 96:41 top. Okay. Now this is all squished 96:43 together on small screens. But later on 96:47 when we have authentication, all of this 96:49 will not show at the same time. Right? 96:51 If we're not logged in, then log out's 96:54 not going to show. These two links 96:56 aren't going to show. If we are logged 96:58 in, then register and login are not 97:01 going to show. So, even on small 97:03 screens, everything will fit. If you 97:05 wanted to to create like a hamburger 97:07 menu, you could do that, but I don't 97:09 want to really waste time on that 97:10 because that's kind of not really what 97:12 what this is about. Um, this is kind of 97:15 beyond that. So, I don't want to deal 97:17 with hamburger menus. It's going to look 97:19 good when after we implement 97:21 authentication, which is actually what I 97:24 want to do next. So, with 97:27 authentication, you have a lot of 97:29 different options. You can use something 97:31 like next off which is very popular. Um 97:34 you could use a library like Ozero 97:38 um clerk. So these are are really great 97:41 libraries but I wanted to do this kind 97:44 of in just a custom authentication flow 97:47 using JSON web tokens rather than using 97:51 a library. U I think that that stuff is 97:53 is fun to do. Uh and it's not this isn't 97:57 going to be very difficult. We're going 97:58 to use a package called Jose, which 98:00 allows us to, you know, create and sign 98:03 JSON web tokens. It's very similar to 98:06 the JSON web token library or package. 98:09 So, we're going to use that. We're also 98:10 going to use brypt to encrypt our 98:13 passwords and we'll be dealing with 98:15 cookies as well. Um, because we'll have 98:17 the the JSON web token stored in an HTTP 98:20 only cookie. So, let's get started with 98:23 that. All right. And first thing before 98:26 we do anything to do with 98:28 authentication, we need a user table. So 98:31 we're going to go back to our Prisma 98:34 schema. All right. So Prisma schema. And 98:37 if if you have these comments, you can 98:38 get rid of 98:39 these. So we have our ticket model. Now 98:42 we need to create a user model. But we 98:45 also need to create a relationship 98:47 between users and tickets. So let's go 98:50 above ticket and let's create a model 98:52 called user. 98:54 All right. And then we're going to have 98:56 an ID, which is going to be a 98:59 string. I want this to be the primary 99:02 key. So I'm going to say at ID. And then 99:04 for the default, I'm not going to pass 99:06 in auto increment. I actually want to 99:08 pass in CUID, which is a what does this 99:12 stand for? It's collision. I think it's 99:15 collision resistant unique identifier. 99:18 So it'll generate uh a long random 99:21 unique string uh that'll have numbers 99:24 and letters and it's it's more secure. 99:28 So that's why I'm using it for users. 99:30 And I'm just using regular auto 99:31 increment for tickets. And you could do 99:34 auto increment for users as well, but I 99:36 think that this is a better idea. So 99:38 we're going to do that for for users. 99:40 And then let's add an email, which will 99:43 be a string. And I want emails to be 99:45 unique. So we're going to add on to that 99:48 unique. Then we have the name which will 99:52 be string and names are going to be 99:55 optional. So I'm going to put a question 99:56 mark here. And then we get the password 99:59 which will be 100:02 string. Then we have the created at 100:07 which will be a date time and let's do 100:09 the default for 100:13 now. All right. Then we have our updated 100:17 at which is going to be date time and at 100:22 updated at. Okay. Now we need to create 100:25 a relationship between users and 100:27 tickets. So the way we do this is down 100:31 here in the ticket model we're going to 100:33 add two things. One is going to be a 100:35 user ID field which is going to be a 100:38 string. And then we want to add user. 100:41 And this is where we're adding the 100:42 relationship. So here for the type we're 100:44 going to say user uppercase u and we 100:47 want to add onto this at relation and 100:52 then pass in fields and for this fields 100:55 we want to add in the user ID and then 100:59 the reference to to to the field in the 101:02 other table which is going to be the ID. 101:05 So references ID. Okay. Okay. So, what 101:08 we're saying is that this user ID field 101:10 in this table is going to reference the 101:13 ID in the user table. Now, there's a red 101:16 line here because one other thing we 101:18 need to do is in the user, we now need 101:20 to reference tickets and set that to 101:24 ticket with brackets because it's it's 101:26 going to be an array. Okay, so that's 101:28 how we create a relationship between 101:31 users and tickets. 101:33 Uh, one thing I want to do is just 101:35 delete any tickets that we have in there 101:37 right now. So, I'm going to go to Prisma 101:40 Studio. And I'm going to just reload 101:42 that and just delete all 101:46 these. Okay. So, if we were to go 101:49 back to 101:51 tickets, we see no tickets 101:54 yet. All right. Now, in order for any of 101:57 this to to matter to to be, you know, 102:00 updated in the database, we have to run 102:02 the migration and generate the client 102:05 again. So, I'm going to stop Prisma 102:07 Studio because sometimes that can that 102:09 can interfere. And then I'm going to 102:11 stop the the server and then we're going 102:14 to do 102:15 npx and let's say mig uh Prisma. So, npx 102:21 prisma 102:23 migrate dev and then 102:25 d-name and let's say add user model. So, 102:29 we're just giving a description of what 102:31 we're doing. And let me just double 102:32 check this before I actually run 102:36 it. 102:39 So, yeah, I think that that should be 102:42 good. So, let's run that. 102:46 And what that should do is update the 102:48 database where it'll create the user 102:50 table and uh update the ticket table as 102:54 well and add the user ID. And we can 102:57 check that out. So let's go to 103:02 uh we can go to Prisma Studio. Oh, let's 103:04 oh, you know what? We'll check it after. 103:07 We can check it in neon real quick. So 103:09 if I reload the neon 103:11 tables, there we go. So we got our 103:13 ticket and we should have a user ID 103:16 field. Yep. Or user and user ID. And 103:20 then we have the user table as well. All 103:23 right. So now we want to generate. So 103:26 npx prisma 103:32 generate. All right. So that looks good. 103:34 Now I'm going to run npm 103:36 rundev here. And then I'll run prisma 103:39 studio in this one. 103:45 So now you can see here all models we 103:48 have both ticket and 103:51 user. All right. So we can see both of 103:54 those. Good. Now we can continue on with 103:57 our authentication. So there's a few 104:00 dependencies that we need to install. So 104:02 let's say npm install. We want 104:05 bryptjs. So that's to to encrypt 104:08 passwords. Jose for our JSON web tokens 104:12 and 104:16 cookie. All right. Now, we need to have 104:20 uh some kind of secret to sign our 104:23 token. So, that's going to go in thev 104:26 file. So, let's open that 104:28 up. 104:33 Env secret. Now, I mean, you can set 104:37 this to whatever you want, but we can 104:39 generate a random key that's 32 bytes 104:42 long with open 104:46 SSL 104:48 rand-base 104:52 6432. Okay. And then I'm just going to 104:54 grab that and I'm going to use that as 104:56 my O secret. But, uh, again, that could 105:00 be anything. Now, I just want to go over 105:02 a couple things real quick in some 105:04 slides. So, we're using JSON web tokens. 105:07 And if you're not familiar with them, 105:09 they're a compact URL safe means of 105:12 representing claims to be transferred 105:14 between parties. And it's formatted as a 105:17 long string with three different parts. 105:19 And those three parts are a header, 105:22 which says it's a JWT and also specifies 105:25 the algorithm it uses to sign it. The 105:28 payload, which is your your user info or 105:30 any data, you can encrypt any data into 105:32 a JSON web token. and then the signature 105:35 which is a special hash to make sure 105:37 that it hasn't been changed or tampered 105:39 with. Okay. And if to give you an 105:41 example, if we go to the 105:45 jwt.io website, there's an example here. 105:48 So this is what a token looks like and 105:50 it highlights in different colors the 105:52 different parts. So again, the first 105:54 part is the header has the algorithm and 105:57 the second is the payload. So in this 105:59 case like the name is encrypted in 106:01 there. um the issued at date and then 106:03 the signature. So that verifies it 106:06 hasn't been tampered with. So that's 106:07 what what the token actually looks like. 106:10 Now to give you an idea of the 106:11 authentication flow that we're using, 106:14 we're going to log in with our 106:15 credentials, right? So we have a form 106:17 where it's going to match our our 106:19 database uh email and password, 106:22 encrypted password of course, and we'll 106:26 have a register page as well. And then 106:28 the server checks those credentials. it 106:30 matches them. Um, if it fails, obviously 106:33 we set have an error message and they 106:36 can't go any further. Then if it does 106:38 pass the credentials, then it stores the 106:41 token. It'll sign and create the token, 106:43 encrypt the data, and store it in a 106:46 cookie. And you could also store it in 106:47 local storage. Then you're going to have 106:50 that cookie on the client, and you send 106:52 it with future requests. And then when 106:55 you send it, the server verifies the 106:57 token to make sure that it's not altered 106:59 or anything. and it can see the 107:01 encrypted data which in most cases is 107:03 going to be uh at least your user ID so 107:06 it knows who you are through that token. 107:08 So that's our authentication flow. Now 107:11 in order to do this we're going to have 107:13 some low-level functions that we create 107:15 and some higher level functions and 107:17 actions. So our low-level functions are 107:20 going to be sign off token which is 107:22 going to generate encrypt and sign the 107:25 token with that secret that we just 107:26 created. verify O token which is going 107:29 to decrypt and verify the token. So when 107:32 you send it, it'll get verified. And 107:34 then set O cookie is going to store that 107:37 token in the cookie. Get OC cookie will 107:39 get the token from the cookie. Remove 107:42 off cookie will remove the token from 107:44 the cookie. So pretty simple, but these 107:46 are the low-level functions. Now the the 107:49 higher level functions and most of these 107:51 are going to be actions are going to be 107:53 register user which is you know these 107:55 are pretty self-explanatory. It's going 107:57 to create a user login user which is 107:59 going to authenticate the user with the 108:02 credentials. Uh log out user we'll call 108:05 that remove o token actually I think 108:07 it's remove off cookie but we'll we'll 108:09 fix that. Uh that clears the user and 108:12 then get current user is going to get 108:14 the logged in user. So if you want to 108:18 obviously view your tickets, it needs to 108:20 know what user is logged in and that 108:23 happens because you lo you authenticate 108:25 the user that ID gets put into the token 108:30 and then that cookie has that has the 108:32 token gets sent to the server on every 108:34 request and it validates the user and 108:36 then you can return it in this function 108:38 and do what you want with that user. you 108:41 know, show their tickets or submit a 108:43 ticket from them or or whatever it might 108:45 be. All right, so let's start off with 108:48 the low-level functions. And they're 108:51 they're not huge functions, so we should 108:53 probably just create them all now, and 108:55 then we can just use them. We don't have 108:56 to deal with going back into the file. 108:59 So, I'm going to put this in, let's see, 109:03 I mean, you could put this in a number 109:04 of places. I want to have a lib folder 109:08 because this is kind of like like an 109:10 authentication library almost. So in 109:14 source, let's create a new folder called 109:17 lib. And then in that I'm going to 109:19 create a file called 109:21 o.ts. Now there's a few things we need 109:23 to import here. So from the Jose library 109:26 we're going to want sign 109:28 JWT and we also want JWT verify which 109:33 they do just that. that signs the token 109:35 and verifies the token. And then we're 109:38 going to import cookies. Now, I think I 109:41 installed the cookies package, but I 109:43 don't think we're going to need that 109:45 unless I installed it for another 109:47 reason. I forget. Well, if if I did did, 109:49 we'll get to it. But, um, we can bring 109:52 in cookies from next headers. Okay, 109:54 because we're on the server side. And 109:55 then let's import the log event from our 110:00 Sentry utility. And then for the secret 110:03 that's in the thev file, I'm going to 110:05 put that in a a variable. But in order 110:08 for Jose to work with that, we need to 110:12 encode it in uh a certain format in uh 110:16 unit 8 array format. So we're basically 110:19 converting it to bytes. And we can do 110:21 that with the text encoder function. 110:26 So we're going to say new text encoder 110:28 and then dot encode and then we can pass 110:32 in our off secret which we can get from 110:35 process.env because it's in thev file 110:38 and then oc secret. Okay, so we're going 110:43 to need that in order to sign it. And 110:45 then I'm also going to put the cookie 110:47 name here because we're going to use it 110:49 in a few different places. So cookie 110:51 name I'm just going to call O- token. So 110:54 when the when the cookie gets put on 110:56 your browser, that's what it'll be 110:58 called. And now we can start to create 111:00 our low-level functions. So let's uh and 111:04 we're going to export them all. So we'll 111:05 say export, and this is going to be 111:08 async function, and we're going to call 111:11 this 111:12 sign sign off 111:14 token. Now, this is going to take in a 111:17 payload, which is the data that we want 111:19 to encrypt. And for now, I'm just going 111:21 to use any as the type. And then let's 111:23 do a try 111:25 catch. And I'm going to close that up. 111:28 So in the try, we want to create and 111:31 sign our token. We can do that with the 111:34 sign JWT function that we brought in, 111:37 which is asynchronous. So we want to uh 111:40 let's see, we want to equals await. And 111:42 then we're going to say 111:45 new s uh sign JWT. Whoops, got that. 111:49 Sign JWT. 111:51 and that that takes in the payload. 111:53 Okay, because we're encrypting that 111:55 payload into the 111:56 cookie. Then we're going to call set 112:00 protected header and the algorithm that 112:04 we want to use. So we're just going to 112:06 use the H 112:08 uh 112:09 HS 112:11 256 112:13 algorithm. And then we want to also set 112:16 the issued at. So let's say set issued 112:19 at. That'll give it uh a field called I 112:22 IAT. So issued at which will be the just 112:24 a time stamp of when it was issued. And 112:27 then we also want the expiration. So 112:29 that'll be set expiration time. And you 112:32 can set it to whatever you want. I'm 112:33 going to say 7D, which would be 7 days. 112:36 And then we want to the last thing we 112:38 want to do is sign it. So dot sign and 112:40 we sign it with the 112:42 secret. Okay. So that will create the 112:45 token. And then we simply want to return 112:48 the token from this function. And then 112:50 if there's some something goes wrong, 112:52 then we're going to log that 112:54 event. And let's say 112:57 token token signing 113:00 failed. And for the category, we're 113:03 going to say off. Okay, we're no longer 113:05 dealing with tickets. And then I'm going 113:07 to actually add the payload as the extra 113:10 data. It's going to be of the log level 113:14 error. And we're going to pass in the 113:16 error. And then what we want to do here 113:19 is just throw let's say throw new 113:22 error and just say 113:25 token uh 113:27 signing 113:29 failed. Okay. So that's our sign off 113:32 token. So it's it's pretty simple. We're 113:34 just creating a token um setting the 113:37 issued the ex expiration and we're 113:39 signing it with the secret that's in our 113:42 env. And this has nothing to do with the 113:44 cookie yet. It's just it's creating the 113:46 actual token. Now, we need a way to 113:48 verify it when that token gets sent in 113:51 the cookie. So, uh, and let's just put a 113:54 we'll put some comments here, I guess. 113:56 Let's just say this will 113:58 encrypt and 114:00 sign 114:02 token. And then this is going to 114:06 decrypt and 114:09 verify token. So let's say export async 114:14 function and we'll call this 114:17 verify o 114:20 token and let's see it's going to take 114:22 in a 114:24 token which is going to be a 114:27 string and return a 114:31 promise and we're going to use we're 114:34 going to use um a generic type here. So 114:37 in brackets we'll put this uppercase t. 114:40 Same thing over here after the function 114:43 name. So uppercase T. And this allows us 114:47 to specify the type of the payload when 114:49 we call this function. So for example, 114:51 if we want to return the user ID in the 114:53 email, we can specify that type at the 114:56 time of calling the function. And then 114:59 in the function body, this is where 115:00 we're going to get the payload. So let's 115:02 just do try catch here. And we can 115:05 dstructure the 115:07 payload from the verify or 115:12 JWT verify function. And that's 115:15 asynchronous. So we do have to add a 115:18 wait. And then this JW verify takes in 115:22 the token and takes in the 115:26 secret. Okay. And that token is going to 115:28 get passed into this function. And then 115:31 we want to just return the payload. And 115:34 we're going to say as 115:36 t. And then in the catch, let's do a log 115:40 event. And let's say 115:43 token decryption 115:46 failed. And category is going to be off. 115:51 And for the 115:52 data, we can send the let's send the the 115:55 actual token, but we'll just send the 115:57 first 10 characters. So we can do that. 116:00 Uh, we're going to send an object with 116:02 token snippet and we can then take the 116:05 token and just do dot slice and pass in 116:10 from zero to 10. So that'll give us the 116:12 first 10 characters and then we'll just 116:15 add the error as the log level and the 116:18 actual error 116:20 message. Okay. And then I'm just going 116:22 to 116:23 throw new 116:26 error. So throw a new error and let's 116:28 say 116:30 token decryption 116:33 failed. So that's our verify O token. 116:37 All right. And the next thing we want is 116:40 the set O cookie. So this is where we 116:44 have the token and then we want to put 116:46 it we want to put it into a cookie so 116:49 that we can then you know send it back 116:51 to send it with every request to to get 116:54 validated or decrypted. 116:57 So let's say set 117:01 the off cookie and we're going to export 117:05 function or 117:08 async function and we'll call this set 117:14 off set off cookie or you could call it 117:17 set off token whatever you want. So 117:20 that's going to take in the 117:23 token and we're going to wrap 117:27 that. Okay, let's say const and let's 117:31 say cookie store and then 117:34 await cookies which we brought in from 117:37 from 117:38 uh from next.js from this next 117:42 headers. Okay, so we got our cookie 117:44 store and then we want to set the 117:47 cookie. So we take our cookie store and 117:51 then call set and then we pass the 117:54 cookie name in which I described above. 117:57 Right? So up above it's it's going to be 118:00 O right here. O token is the cookie 118:03 name. So I'm going to pass that in as 118:05 the first argument. Then the token and 118:09 then some options. So we're going to set 118:12 the HTTP only. We want to set that to 118:15 true. So this prevents JavaScript from 118:17 accessing the cookie. So we want this is 118:19 this makes it more secure. And then I'm 118:22 going to set same site to lax. So string 118:26 of lax which means that it can it's it's 118:29 not strict to our domain but it it can 118:31 only get set from top level get requests 118:34 from other sites. Um there's no 118:36 cross-sight post requests or anything 118:38 like that. and for secure which means 118:42 that it's only going to be used with 118:45 HTTPS. I do want this to be true in 118:47 production. So what we can do is set the 118:52 process.vnode environment. We're going 118:54 to look at that and say if it's equal to 118:57 production then this will be true. And 119:00 then the path which is going to be just 119:02 slash. Then we have the max age. So when 119:05 do we want this to expire? I want it to 119:07 be 7 days. So, I'm going to do 60 * 60. 119:12 So, 60 minutes time 60 seconds. And then 119:16 we want to do times 119:19 uh 119:20 24, which would be 1 day. And then we 119:22 want to time seven. So, that'll be 7 119:25 days. And I'll just put a comment here 119:27 saying that's seven 119:29 days. And then in the catch, we're going 119:31 to do a log event. and we'll say failed 119:35 to set 119:38 cookie and that's going to be in the off 119:41 category and we'll send the 119:45 token and it's an error log level and 119:49 send the 119:51 error. All right, so that's our set o 119:54 cookie. Add the get o cookie. So this 119:57 will let's say 119:58 get O token from cookie. So export 120:06 async function and let's call this 120:10 get yeah we'll say get off 120:15 cookie and first thing we'll do is get 120:19 the cookie 120:22 store and we want to await on 120:26 cookies and then we'll create a variable 120:29 called token and we'll get that from 120:32 cookie 120:34 door.get and we want to pass in the 120:37 cookie 120:38 name which is O token. We put that up 120:41 above and then we just want to return 120:44 the token. Now it's going to be in a 120:47 value. So I'm going to say token do 120:50 value and we're going to use the 120:52 optional chaining here just so we don't 120:54 get any weird error messages. 120:57 Um that should give us the 121:00 token. Okay. Okay. Then the last thing 121:02 we want here is to 121:04 remove O 121:07 token 121:11 cookie. So let's call 121:13 this remove O 121:19 cookie. Okay. And we'll go ahead 121:23 and actually let's put this 121:26 in try 121:28 catch. So I want to uh get the cookie 121:34 store. So 121:38 await and then we're going to take the 121:40 cookie 121:44 store and we're going to call 121:47 delete and pass in the cookie 121:50 name. Okay, so that'll delete the 121:52 cookie. And then in the catch, I'm just 121:54 going to log 121:58 event. We'll say that it failed to 122:01 remove the 122:02 cookie, the O 122:05 cookie. Set the category to O. And just 122:09 send an empty object for data 122:13 error and 122:15 error. That's it. So that's our 122:18 authentication core. And if I mess 122:21 something up in here, we'll we'll, you 122:23 know, we'll fix it later on when we use 122:26 it. But we have that. So, we can close 122:28 close that up now. And we want to work 122:32 on the actions as well as obviously the 122:35 UI. So, let's start off with the let's 122:39 start off with registering users. And 122:42 I'm going to do the action first. So, in 122:45 the actions folder, we want to have a 122:47 new file here called let's call it 122:49 o.action. 122:51 actions.ts. I mean, you could call it 122:53 user actions or or whatever you want. 122:56 Uh, so in this o actions, let's make 123:00 this a server 123:02 uh uh server action. So, userver and 123:05 then we're going to 123:06 import prisma. And remember, we want to 123:09 bring that in from our file. So, it's 123:12 going to be at 123:15 slashdb/prisma. And then we're also 123:17 going to be encrypting the password 123:19 here. So let's import brypt from 123:22 bryptjs and then we'll also use our log 123:26 event and then we want to bring in 123:31 um the sign off 123:33 token from our lib and also the 123:37 set set o cookie. We're going to bring 123:40 that in as 123:41 well. And then let's create our 123:43 function. And this is going to be to 123:45 register a new 123:47 user. And we're going to 123:50 export a sync function. We'll call it 123:53 register 123:56 user. All right. Now, when we have our 123:59 form, we're going to be using the the 124:01 use action state hook like we did with 124:04 the token when we, you know, not the 124:06 token, the uh the ticket. When we submit 124:08 a ticket, remember, we use that that 124:10 hook. So that means that this is going 124:14 to be an action that takes in two things 124:18 prestate. Okay. And also 124:23 um the form data. So form data with the 124:25 type is going to be form data. Now for 124:28 the type for the state it's going to 124:31 it's going to be a success and message. 124:34 Uh so I'm actually going to create the 124:36 type here. So we'll say type response. 124:39 We can do this in the other file the 124:41 tickets action as well. Um we'll just 124:43 say response result which is going to be 124:45 an object with the success which is a 124:50 boolean and the message which is a 124:54 string. Okay. So now we can just say for 124:57 the previous state that type is going to 125:00 be response result and also um what this 125:05 function will return is a promise that 125:08 will be 125:09 the response 125:11 result. Okay, which is going to be again 125:13 success and 125:15 message. So let's get the form data 125:18 first. So we'll say coname set that to 125:22 form data.get get and we want to get by 125:27 the name which is name and we'll say as 125:30 string and then we also want to get the 125:34 email. So let's take this and this 125:37 change that to email. Then we want to 125:39 get password. So this and this 125:44 password. Okay, that'll get us the form 125:47 data. Then let's make sure there 125:51 is all those fields. So if not name, if 125:55 not 125:55 email and if not 126:00 password then let's log 126:04 event and we'll 126:07 say validation 126:09 error and missing registration. Our 126:13 register 126:15 fields type is going to be 126:19 off and we'll send the name and 126:24 email and log level is going to be 126:29 warning. And that should do it for the 126:32 for the log event. Uh actually I wanted 126:34 to put this in a try catch. That's 126:36 right. So let's go right here. Say try 126:40 catch. And then I'm just going to move 126:41 this stuff 126:43 So, we'll just cut that and put that 126:45 into the 126:46 try. All right. And then after that, we 126:49 just want to 126:50 return the result, which is going to be 126:54 success. 126:56 Um, wait a minute. I want to be in here. 127:00 So, this is going to be 127:02 return success 127:05 false. And let's Oops. And let's do 127:08 message. 127:11 all fields are 127:13 required. Okay. So now outside of that 127:16 if statement I want to check if the user 127:21 exists. So let's say conexisting user. 127:25 We're going to set that to await. And 127:27 then we're going to use our Prisma 127:28 client and say Prisma user dot and then 127:32 let's do find 127:34 unique and pass in a 127:37 wear and just match the email. Okay. 127:40 Okay, so we're checking for that user 127:42 and then we want to 127:45 um say 127:46 if if the user exists. So if existing 127:51 user then let's do a log. So log event 127:55 and we'll say registration 128:01 failed. Registration 128:04 failed. User already 128:08 exists. And I want to put the email 128:11 actually. So I'll use back tick 128:14 here. So back tick. And then we'll put 128:18 the 128:22 email. Okay. And then we're going to 128:25 pass the 128:26 category. And I mean I guess we don't 128:29 have to put the email there. We could 128:31 put it here, but I'll just put it in 128:32 both places. And then we'll make that a 128:35 warning. 128:38 So if the user exists that then we want 128:40 to return. So I'll just grab 128:43 this here and we're going to return 128:46 false and just change the message up to 128:50 user already 128:53 exists. Right? And then we want to go 128:55 outside of that. So if those pass, 128:57 right? If the fields are are filled in 129:00 and the user email matches, now we want 129:03 to 129:04 hash the 129:06 password. So we'll say const hashed 129:10 password. Set that to 129:13 uh await and then we're going to use 129:19 brypt.hash. Pass in the plain text 129:21 password and the number of rounds. So 129:23 we'll do uh 129:25 10 and 129:29 uh let's see we got the hash password. 129:30 Now we want to create the 129:35 user. So we can do that. Let's say con 129:38 user set that to await and then we'll 129:40 take the 129:41 Prisma client and user and then the 129:44 create 129:45 method and we want to pass into that 129:48 takes in data which is going to be the 129:51 name 129:53 email password but we want to make sure 129:55 that we set the password to the hash 130:00 password. All right. So, we'll do that. 130:03 And the way I want this to work is after 130:07 the user is registered, I want them to 130:09 to be logged in. So, that means we're 130:11 going to set and sign the token and set 130:13 set the cookie. So, uh let's actually 130:16 put a comment here. We'll say sign and 130:20 set off token. So, we'll say con token 130:25 set that to a wait. And this is where we 130:27 want to call our sign off token function 130:30 that we created in our lib off file. And 130:33 then we're going to pass the payload in 130:36 which is going to be the user ID. And we 130:39 set that here to the user ID. Okay? 130:42 Because we got we're getting the user 130:43 here. We're creating it. The user gets 130:46 put here. And then we're setting the ID 130:48 to the user ID in the payload of the 130:52 token. All right. And then we want to 130:56 await and then set off cookie. Okay, so 131:00 the first this one signs creates 131:03 encrypts and the payload and signs the 131:05 token. This one is going to set it to 131:07 the cookie. So we want to pass in the 131:10 token and then we'll just go ahead and 131:12 log 131:13 it. So let's put back. We'll say 131:16 user 131:19 registered successfully. 131:24 user registered successfully and I'll 131:26 put 131:27 the 131:32 email of um let's pass in the user 131:36 ID. So it's going to be user ID and 131:40 let's pass in 131:42 email and let's make it 131:47 info. Okay. And then for the return, we 131:50 just want the success. Success is going 131:52 to be true. And the message 131:56 uh we'll say 131:59 registration 132:02 registration 132:04 successful. And then in the error, let's 132:08 return success is going to be false. and 132:14 message. We'll just say something went 132:19 wrong. Please try 132:21 again. And we'll do a log event as 132:26 well. So log event and let's do 132:29 unexpected 132:32 uh unexpected error during 132:38 registration. Okay. Okay. And that's 132:40 going to have a 132:41 category of off. And then I don't want 132:45 there's no data that I'm going to pass. 132:48 And then error level and the 132:52 error. Okay. So we have our register 132:55 action. And just to kind of go through 132:57 it again, we get the form data. We check 133:00 the form data. We check if the user 133:03 exists. If it does, we log it. And then 133:07 we send a message saying they already 133:09 exist. If they don't exist, we go and we 133:12 hash the password. We create the user 133:14 with that new hash password, the name 133:16 and email. We then sign the token, 133:20 create and sign the token, set it in the 133:22 cookie. So we're essentially logging in, 133:24 log the event, and then we return 133:27 success true. So now we have to create 133:31 the the front end of this, the the 133:33 register form. So let's go 133:37 to the register page. Actually we have 133:40 to create it. So in app I'm going to 133:44 create a 133:45 folder. I'm going to create a folder 133:47 with parentheses called off. So this is 133:50 just a group meaning that it's not 133:52 actually going to be part of the URL. 133:54 It's not going to be slash 133:56 offregister. It'll still just be 133:58 slashregister if I put a folder in here 134:00 called register. And again, this is for 134:03 people that aren't really familiar with 134:04 Nex.js. Um, but it won't be part of it. 134:07 If you do want it to be part of the 134:08 route and you want it to be slash 134:10 offregister, then you could remove the 134:12 parenthesis. But the parenthesis just 134:14 makes it a group for organization. So, 134:17 I'm going to create a file in here 134:18 called 134:20 page.tsx. And 134:22 let's write this out. So, this is going 134:24 to be the register page. 134:27 And just for now, we'll just say 134:31 register. And then if I go and I click 134:34 on the register button, now we see our 134:37 register page. Okay. So, we're going to 134:40 bring some packages into this uh and 134:43 some hooks. I want to use the use action 134:46 state hook like we did in the other 134:48 form. And we're also going to bring in 134:50 use effect. And since we're using hooks, 134:53 we're going to make this a client 134:54 component at least at the 134:57 moment. And then let's also import toast 135:01 because I do want to show a toast 135:03 notification. So that's going to come in 135:04 from sauner. And then also the use 135:08 router 135:10 hook because we're going to want to 135:13 redirect. And we want to bring in of 135:15 course our register user action that we 135:18 just created. And that's going to be 135:20 from off 135:21 actions. Okay. 135:24 Now, since we're using uh since we're 135:27 using use action 135:29 state, we need to pass in our initial 135:32 state. And I'm going to put that into a 135:33 variable into an object called initial 135:36 state. And just like with the when we 135:39 create a ticket, the response we're 135:41 going to get is going to have a success 135:42 boolean and a message string. So the 135:46 initial state is going to be 135:48 success false and then message will just 135:52 be an empty string. Okay. And then we 135:56 want to go ahead and use the use state 135:58 hook. So that's going to we're going to 136:00 pass in here state and then the form 136:04 action and use action state. That's 136:08 going to take in the action we want to 136:10 call which is going to be register user 136:13 and then also our initial 136:17 state. All right. And then let's 136:20 um let's just let's just start to output 136:23 the the form before we do anything else. 136:27 So for that I think I'm just going to 136:29 paste in. So let me just grab that real 136:33 quick and let's return parentheses. And 136:36 then I'm going to paste that 136:39 in. Save it. And that should There we 136:42 go. So we now have our register form. 136:45 And just to kind of go over it real 136:46 quick, we just have a div. We're using 136:48 flexbox. Same as the other form. We have 136:50 an H1. We have our form, which is uh I 136:54 have form action here, which obviously 136:57 is coming from here. And then let's see, 137:00 we have our our input for the name. We 137:04 have an input for the email and for the 137:06 password and then a button. So you can 137:09 either copy this or you can grab it from 137:11 the the final repo. Now I want to 137:15 redirect once they log in I want them to 137:18 redirect to slash tickets. And if 137:20 there's an error I want to show a toast. 137:22 So what we'll do is we'll use use effect 137:24 for that. Um actually before we do that 137:27 we have to initialize our router. So 137:29 let's say con 137:31 router set that to use router and then 137:35 the use effect I'm going to put right 137:36 above the return down here. So let's say 137:40 use effect and let's pass in our 137:43 function and then as far as the 137:46 dependencies go. So we want this to fire 137:49 off when the state or router 137:53 changes. Okay. And then in the use state 137:56 let's check for success. So if the state 138:01 success then that means it's successful. 138:04 So I'm going to show a 138:07 toast.uccess and say uh 138:10 registration. So 138:12 registration 138:15 successful and then let's redirect. So, 138:19 we're going to take our router and we're 138:20 going to call 138:22 push and we want to push to slash 138:28 tickets. And then I just want to do a 138:30 refresh. So, 138:33 router.refresh. Yeah, 138:37 refresh. Did I bring router in from the 138:40 right thing? 138:43 Yeah, I think. Right. Next. No, I'm 138:46 sorry. This is next navigation. 138:51 There we go. All right. So, we want to 138:54 do an else. So, if it's not successful, 138:57 then let's just show a an error in the 138:59 toast. And that's going to be toast 139:03 error. And we want to show the message. 139:05 So, state 139:07 dossage. In fact, let's do else 139:10 if 139:14 state. All right. So, we can try that 139:16 out. Let's see. We got an error going on 139:18 here. Oh yeah, this is a last pass 139:21 error. So I have the the last pass 139:23 extension which puts these uh you know 139:26 these blocks in here for your past you 139:28 know your uh your fields or whatever. So 139:32 that's going to it's not matching what 139:34 Nex.js is giving us. So it's 139:36 complaining. So if you want to get rid 139:39 of that you can just 139:41 disable LastPass if you have that 139:43 installed. 139:45 So, I'm just going to find that here and 139:49 just shut that off. If I reload, then 139:51 that error should go 139:53 away. So, now we'll try it out. Let's 139:56 register. I'll just put my own name 139:58 here. And let's say 140:00 [Music] 140:03 Bradgmail. And let's Whoops. 140:08 and let's put a 140:13 password. Okay, so I get registration 140:16 successful and then it brings us to 140:18 tickets. Now to check that we can go to 140:21 Prisma Studio. I'm just going to refresh 140:23 this and we should see under user. So it 140:27 sees one user and there it is. Brad at 140:30 Gmail. It generates a CU ID or C ID, 140:35 whatever it's called. hashed password. 140:38 Good. So, we're now able to register 140:40 user. So, now what I'd like to do is 140:42 show the correct links in the navbar, 140:44 right? Because I'm logged in, I 140:46 shouldn't be seeing login and register. 140:49 So, I want to create uh I guess a helper 140:52 to get the current user. And we can do 140:55 that through the O token. So, let's 140:59 let's jump into our file structure here. 141:02 And I'm going to put this in the let's 141:04 put it in the lib folder. I'm going to 141:06 create a new file and we're going to 141:08 call it 141:12 current-user.ts. And then from here 141:14 we're going to import a couple things. 141:16 We want to bring in the uh let's bring 141:20 in the 141:21 verify what did I call it? Uh verif I 141:24 think it's yeah verify o token because 141:27 that's what'll decrypt it. So verify O 141:31 token and then we want to be able to get 141:33 it from the cookie. So get O cookie. 141:35 We're bringing that in from our O file 141:38 which is in the same folder. It's in the 141:39 lib folder. And then let's also bring in 141:42 the Prisma client. So I'm going to 141:44 import Prisma. And remember that's going 141:47 to be from the 141:49 uh at 141:53 slashdbrisma. And then let's create a 141:56 type for the payload because remember 141:58 the the the JSON web token has uh a user 142:01 ID as getting sent as the payload. So 142:04 I'm just going to put that here. We'll 142:06 say type and let's call this 142:10 off 142:13 payload and we're just going to have a 142:16 user ID which is going to be a 142:19 string. Okay. Now we're going to export 142:22 an async function and let's call it get 142:27 current 142:31 user. And the way we can do this first 142:33 off let's do a try catch. The way we can 142:36 do this is get the token from the um 142:40 from the cookie. So let's say const 142:44 token set that to await and then our get 142:47 off 142:49 cookie and let's just say if there's no 142:53 token. So if there's no token I just 142:55 want to return 142:58 null. Then we want to get the payload 143:00 from that token and we can do that with 143:03 the verify o token function. So let's 143:06 say payload and let's set that to I'm 143:10 going to put this in parenthesis and 143:11 we're going to say await and then verify 143:14 O token pass in the 143:17 token and I just want to add to this as 143:21 the O payload. So we're using that 143:24 type. Then let's say if not payload and 143:30 I want to check for the user ID. I'm and 143:32 I'm going to use optional chaining here. 143:34 So if not payload user ID, we also want 143:37 to return null. So if there's no token 143:40 or there's no payload or I'm sorry, no 143:42 user ID in the payload, then we're going 143:44 to return null. And then let's go under 143:46 that. And then we want to get the user. 143:48 So we'll say con user set that to await 143:52 Prisma. So we're using the Prisma 143:54 client, the user 143:56 model. I can't see user model. And then 143:59 let's do find unique. And we'll pass in 144:03 our 144:04 where. And let's say where the ID is 144:08 equal to the 144:10 payload user ID. Okay. So we're just 144:13 matching the payload that's sent in the 144:15 token with the ID of the user in the 144:18 database. And then I don't want to get 144:20 all the fields like I don't want the 144:22 password hash obviously. So what we can 144:25 do is pass in a select and I can say I 144:30 only want to select the ID. So we do ID 144:33 true and I want the email. So email true 144:38 and then the 144:41 name and that should get the user. Then 144:43 we just want to return that user. So 144:45 right under here let's just return the 144:48 user. Okay. Now in the catch I'm just 144:51 going to do uh console log 144:56 And we'll just we'll just say that 144:58 there's an error getting the current 145:02 user. And let's also just pass the 145:05 error. And then we want to just return 145:09 null. So that's our our get current user 145:12 function. Now let's use that in the 145:14 navbar. So I'm going to close that up 145:16 and then open the let's see 145:19 components 145:21 navbar. All right. Okay. And then we'll 145:23 bring that in. So let's import get 145:26 current user from our current user 145:30 file. And let's see, remember that 145:33 returns the user which is either going 145:34 to be the user or null based on if the 145:37 token is valid or not. So let's say con 145:39 user set that to await get current 145:44 user. And then uh oh and this has to be 145:48 asynchronous. So let's say 145:51 async. And then let's see. We're going 145:54 to we're going to go in this first in 145:56 this div right 145:58 here. And what I'm going to do is add in 146:01 some curly braces and we're going to say 146:03 if user then show something else show 146:08 something else. And what I want to show 146:11 if the user is there is going to be 146:13 first of all these two links, right? The 146:15 new ticket and the my tickets. Okay. So 146:18 I'm just going to cut those and let's go 146:22 into this first one. So this is if there 146:24 is a user. So I'm going to put a 146:26 fragment and then in that I'll paste the 146:29 two 146:30 links. Okay. And then let's grab the 146:33 other ones. So we get the login, we get 146:35 the register, and we get the log out, 146:38 which is a form with a button. So we're 146:40 going to cut those and that's going to 146:42 go in the else. 146:44 So, we'll put a 146:46 fragment and then paste those in. And 146:48 then I'll 146:51 save. And now we see new ticket and my 146:55 tickets. We should Oh, the logout should 146:57 should go in the other one. So, this log 147:00 out form right here, let's just cut 147:01 that. And then we're going to put that 147:05 under the link for my tickets so that 147:08 that 147:09 shows. Okay. And now if it's on smaller 147:12 screens, it fits 147:14 right. So the next thing I want to do is 147:17 is a log out. We need to be able to log 147:19 the user out, which will consist of 147:22 removing the O cookie, which we already 147:24 have a function for. So let's create our 147:26 logout user. That's going to be an 147:28 action. So we can go into our O actions 147:33 file. And let's go down at the bottom 147:36 here. We'll just say log user out. and 147:41 remove 147:42 uh remove off 147:45 cookie. So let's say export function or 147:50 async 147:53 function and we'll call this logout 147:57 user. And as far as what it's going to 148:00 return, let's say promise and it's going 148:04 to be that that object with success and 148:06 message. So we'll pass in here um going 148:09 to go on the next line here. Let's say 148:11 success which is going to be a 148:14 boolean and then we have message which 148:17 will be a 148:20 string. All right. So that's what we 148:22 return. Now let's go into the function 148:24 body and we'll open up a try 148:28 catch and we want to call the the remove 148:32 o cookie. So let's await on that. So 148:36 remove o cookie which should be autoimp 148:39 imported. Let's just make sure. Yep. So 148:42 that was brought in from our lib off 148:45 file. Okay. So we're removing that. And 148:48 then I'm going to log the event to 148:51 Sentry. So user 148:56 uh user logged out 148:58 successfully. And again it's up to you 149:01 if you want to do logs for stuff like 149:03 this. If you have a huge site and a ton 149:06 of users, you probably don't. But I the 149:09 point is just to show you, you know, how 149:11 this works. It's up to you on what you 149:14 want to actually 149:15 log. And I missed the data. So after the 149:20 what is it? After the category is going 149:23 to be the data, which in this case is 149:25 just going to be an empty object. 149:28 So after the log event we just want to 149:30 return our object with 149:33 success is going to be true and 149:37 message we'll say 149:39 logout 149:44 successful. Now in the catch I'm going 149:47 to log 149:49 event for the message we'll say 149:52 unexpected 149:54 error. uh we'll say unexpected error 149:57 during 149:59 logout. So that's going to be an off 150:02 category and then for the data I'm just 150:05 going to attach just an empty object 150:08 error for the log level and then pass 150:10 the actual error. Okay. And then what 150:12 we're going to return is going to be our 150:14 object with success which in this case 150:16 is going to be false. And then we have 150:19 our message which I'll say log out 150:24 failed and we'll say please please try 150:29 again. Now we want to use this in the 150:32 the navbar but we're going to be using 150:35 some hooks um such as get uh such as use 150:39 action state and use effect. So I don't 150:42 want to make the navbar a client 150:44 component in order to use those hooks. 150:46 So what we'll do is create a component 150:48 for the logout button and then bring it 150:50 in. So let's go to components and let's 150:53 create a new file here called logout 150:59 button.tsx. And let's create a component 151:03 called logout 151:06 button. And let's see, I'm going to 151:09 grab from the navbar. I'm going to grab 151:12 the 151:14 form. So, right here, this logout form, 151:17 I'm going to just cut that. And then 151:19 we're going to paste that in 151:22 here. And let's just save that as is for 151:25 now and bring it into the 151:27 navbar. So, we'll say import logout 151:32 button. And then we're going to just go 151:34 to where that form just was, and we're 151:37 going to add that here. Log out button. 151:41 And it should still just look the same. 151:45 Okay. Now, in the logout button, we're 151:47 going to make this a client 151:49 component. So, let's say use 151:53 client. And a few things I want to bring 151:56 in. We want the use action state because 151:58 we're treating the logout form just like 152:00 any other form where we have it's going 152:02 to hit an action that returns a success 152:05 and a message. I also want to bring the 152:07 use effect so we can do the redirect. 152:11 And if we're going to redirect, we need 152:12 to also bring in use router. And it's 152:15 going to be from next navigation, not 152:17 next router. And let's import the action 152:20 that we just created, the logout user. 152:23 And also the toast from 152:26 sonner. Toast 152:31 from just toast. Okay. Now we're going 152:34 to initialize the router. So let's say 152:38 const 152:42 router and then let's create our initial 152:46 state. You don't have to put this in a 152:48 variable but just I think it makes it a 152:50 little cleaner. So let's say success 152:53 initially is going to be false and 152:55 message initially is going to be an 152:57 empty 152:58 string. Then we want to use the use 153:01 state hook. So that's going to be state 153:02 and form 153:05 action from use action state and that's 153:08 going to take in our action that we're 153:10 going to call which is logout user and 153:12 it's going to take in our initial state. 153:14 So you should see the pattern that that 153:16 we're doing here for for any form that 153:18 deals with an action. And then on the 153:21 form tag we want to add the action 153:23 attribute and set that to the form 153:27 action. And then to take care of the the 153:30 redirect, let's create our use effect. 153:33 We'll go right above the 153:35 return. So use effect. 153:39 And just like with the others, we want 153:42 to look at the state and the 153:48 router. Okay. Then we're going to check 153:50 for success. So if the state 153:53 success so if that's true then let's 153:58 show a success message. So toasts 154:00 success 154:02 uh and then we're going to say log 154:06 out log out 154:10 successful and then we want to redirect. 154:13 So let's take the router and call push 154:16 and I'm going to redirect to slash 154:20 login and then let's do an else or an 154:23 else if and check for the state 154:28 statessage. Okay, so if if state success 154:31 is false and there's a message, we know 154:33 that message is the error. So we'll say 154:36 toast. And pass in the state 154:41 dossage. And that's it. 154:44 So, I'm just going to refresh this. And 154:46 we're logged in right now because I 154:48 registered, which also logs you in. 154:50 Click log out. And now we're logged out. 154:53 It killed the cookie. And now we see 154:55 login and register in the 154:58 navbar. Okay, cool. So, the next thing 155:01 we're going to do is uh the login, 155:04 right? So, we have this login page, 155:05 which right now doesn't exist. If I go 155:07 to it, we get a 404. So let's start off 155:10 with um let's just get the form showing. 155:14 So we'll go to our app and then our o 155:16 group and create a new folder called 155:19 login. And in that we'll create a file 155:21 called 155:23 page.tsx. And we'll create our 155:25 component. Let's call this we'll call it 155:27 login. Login 155:31 page. And for now I'm just going to say 155:33 login just to show 155:36 something. All right. Okay. And before 155:38 we do this the the login page, I want to 155:41 add the action. So let's go into our O 155:44 actions and go down to the bottom. We'll 155:48 say log user 155:52 in. So export 155:56 async call it login 156:00 user. Okay. Now, this is going to take 156:04 in the previous 156:06 state. Notice on the log out, so log 156:10 out, we didn't pass in like pre state 156:12 and form data because we didn't need it, 156:13 right? We don't there's no form data. 156:16 There's no fields that they're filling 156:17 out. They just click the button. But 156:19 here, login user, we're taking in data. 156:22 So, we need the pre state. And I'm going 156:24 to set that to our response result that 156:26 we have up above that we did earlier. 156:28 And then form data will be set to 156:32 uppercase F form data. And then what 156:35 this will return is going to be a 156:38 promise with the 156:41 response response 156:44 result. And let's open up a try catch. 156:47 And then in there we're going to get the 156:49 the fields. So we have an email that we 156:51 need to get which we can use form 156:54 data.get 156:56 get the name of it will be email and 156:59 let's just say as 157:01 string and then we also want the 157:04 password. So let's take this and this 157:08 change it to 157:10 password. So that'll get our our fields 157:13 and then I'm just going to do a quick 157:15 check. So if not email or not 157:21 password then let's log event 157:26 And we'll just say 157:28 validation validation error and 157:32 missing missing login 157:35 fields. And it's going to be the off 157:39 category. And for data, we'll send the 157:44 email and we'll make the log level a 157:47 warning. 157:49 And then as far as what we want to 157:51 return if if we have that error is going 157:53 to be success 157:55 false and 157:58 message will be email and 158:02 password are 158:05 required. Okay. So that's if they they 158:08 miss the email password. Now if it 158:11 passes or if they fill in the fields 158:14 then we want to get the user right or we 158:17 want to match the user with the email 158:21 that's passed in. Okay, user in the 158:23 database to email that's passed in. So 158:24 let's await prisma user and we'll use 158:29 find find 158:31 unique and we want to find where the 158:35 email right so where email equals email 158:38 which this is the same as doing 158:41 this and then underneath that we want to 158:44 check to see if the user was found. So 158:47 we'll say or if it wasn't found and if 158:49 it wasn't then we'll log the event. 158:53 say uh actually I'm going to use back 158:55 ticks here and say 158:57 login 158:58 failed. We'll say user not 159:03 found and uh I'm just going to put the 159:05 email in as well. So we'll just put a 159:07 dash and then the 159:10 email. Okay. And it's the O 159:13 category. I'll also pass the email in 159:16 here as 159:17 data. And we'll set it to warning. 159:22 And then let's return we want uh success 159:27 false. And for the 159:31 message we'll just say 159:34 invalid email. Actually, you know what? 159:37 Let's check for the password as well. So 159:39 if not user 159:41 or not user 159:45 dot 159:48 password then we'll say invalid email 159:53 or 159:56 password. All right. 159:59 Now we need to compare the the the plain 160:03 text password to the hash password in 160:06 the database and that's where brypt 160:08 comes in. There's a um there's a method 160:12 called compare that we can do to you 160:14 that we can use to do this. So let's 160:17 create a variable called is 160:19 match and we'll set that to await and 160:22 then let's use brypt.compare 160:25 compare. And then you want to pass in 160:28 two things. You want the the plain text 160:32 password, which is just password, right? 160:34 Because that's the up here that's coming 160:37 in from the form. And then you want to 160:40 pass in the hashed password, which we 160:42 can get from the user from 160:44 user. So that's going to be what we 160:46 passed in second is 160:49 user. Okay. Okay. And that will give us 160:51 a a true or false based on if it 160:54 matches. So if it doesn't match, so if 160:57 not if not is 161:00 match then they have the wrong password. 161:03 So let's just do a log event. I'll just 161:05 copy this 161:07 one and let's say we'll do login failed 161:11 and we'll just change this 161:13 to 161:15 incorrect password. And we can make this 161:18 quotes or leave it back tick. It's up to 161:22 you. And then off. And then let's see. 161:25 We'll send the email warning. So that's 161:27 all good. And then let's do our return, 161:29 which I'm going to do the same return I 161:31 did 161:32 here. Okay. So after the log event, I'm 161:36 going to say invalid email or password. 161:38 Now, this is kind of a a security thing. 161:41 You don't want to let the user know that 161:44 the email exists, but the password was 161:47 wrong because that tells the user if 161:50 that email is an actual user of that 161:52 website of that that application. 161:56 So, if the email matches and the 161:59 password is wrong or if there's no email 162:02 and the user isn't found, you still want 162:04 to say the same thing. You still want to 162:06 say invalid email or password. Okay? 162:09 That's why I put it in both places here. 162:12 So, uh I think that that's just it's 162:14 just a security issue. You don't want 162:16 people seeing like who's registered and 162:18 who's not. So, now after we pass, right? 162:23 So, we'll go under the is if is not ma 162:26 or not is match. Then we want to get the 162:29 token or I should say create the token 162:32 which we're going to use the 162:35 sign sign off 162:38 token which I think was brought 162:41 in. Yeah. So we brought that in from our 162:44 O library. So sign off token and that's 162:47 going to take in the payload which is 162:49 going to be the user. We're going to set 162:51 user ID to user 162:55 ID. Okay. So that will pass the payload 162:58 in and encrypt it. And then we want to 163:01 set the cookie. So let's say await and 163:03 then set off cookie and pass in the 163:09 token. All right. And then let's return 163:12 from 163:13 that success 163:16 true and 163:18 message we'll say 163:21 login 163:23 successful. And then if there's an 163:25 error, let's log 163:28 event and we'll just say unexpected 163:32 oops unexpected 163:35 error during 163:40 login for the data just be it just be 163:43 empty and the error log level and then 163:46 pass the actual error. Okay. And then 163:48 we're going to return from this 163:51 success 163:53 false message 163:56 uh login. We'll just say 163:59 uh what do I want to say? 164:02 Error during 164:07 login. All right. And yeah, I think that 164:11 should do it. Save it. So now we have 164:15 the the login action. And now we need to 164:17 create the the actual login page. So 164:19 let's do that next. Now I don't I don't 164:23 want to make the login 164:25 page client rendered and the form has to 164:29 be because it has hooks. So I think what 164:31 we'll do is just create a form a form 164:35 component for the login as well as the 164:37 register because right now if we look at 164:39 the register page it's client rendered. 164:42 So why don't we do this? Why don't we 164:44 copy everything that's in the register 164:46 page and then in addition to the page 164:49 I'm going to create 164:51 form.tsx or we could call 164:54 it 164:56 register-form 164:58 tsx. And then I'm going to paste in the 165:02 whole page and change this and the 165:06 bottom where it exports. Let's change it 165:08 to register 165:11 form. Okay. And then what we'll do is 165:13 just bring that into the page. So the 165:16 register page, we can just get rid of 165:18 all this, generate a new 165:21 uh new component and call it register 165:25 page like it was. And now we just want 165:28 to bring 165:29 in the form that I just created. So 165:32 register form. And then we're just going 165:34 to render 165:35 that. So register form. So that way the 165:40 page is server rendered but the form is 165:42 client rendered and we should still see 165:44 it no problem if I go to register. So 165:46 now we'll do the same thing with the 165:49 login. So let's see close that up. So 165:53 for login page we have that but let's 165:56 create an addition. Let's say login 166:02 form.tsx and then we'll create our 166:06 component. Uh yeah, login 166:11 form and for now I'll just say 166:15 form and then we're going to bring that 166:18 into the login page like we did the 166:20 register. So import the login form and 166:25 then we'll just render that 166:31 here. Okay. And then we should still see 166:35 Yep, we should see form. All right. So, 166:38 we can close up the page. Let's go back 166:39 to the login form. And now we want to 166:43 want to do a lot of the same stuff we 166:45 did on the register page. So, why don't 166:46 we just copy the the or I should say the 166:50 register form. So, why don't we just 166:51 copy that the whole thing and in the 166:54 login form I'll just paste that in. And 166:57 then we'll change register form here and 167:00 at the bottom to login form. And then 167:03 just go through and change a few things 167:05 like the button. We'll say 167:07 login. Um let's go to up to the top 167:11 here. So all this will stay the same. 167:13 We're still getting the same initial 167:15 state. Um we're going to bring in login 167:17 user action instead of 167:20 register. And then we're going to pass 167:23 in login user right 167:25 here. Instead of 167:27 register and then let's see here we'll 167:30 say login successful. And then when we 167:33 redirect, it'll be still be to 167:36 tickets. Uh let's see. This is all going 167:38 to stay the same. The header here, 167:41 register, we'll change that to login. 167:44 And then we don't need the name field. 167:47 We just want the email and password. So 167:49 we'll get rid of that name 167:51 field. And the form should go to form 167:53 action. So yeah, I think the rest of 167:55 this can stay the same. Let's go ahead 167:57 and save that. And now we have our login 168:01 form. All right. and it should connect. 168:03 It's going to the action is set to form 168:06 action which we have the login user 168:09 action pasted into or put into. So let's 168:14 try it out. I'm going to try with 168:15 something that doesn't exist first. Like 168:17 I'll do 168:23 brad1gmail.com. Okay, so invalid email 168:25 or password. Now let's try 168:27 bradgmail.com. 168:32 log in. And now we get login successful. 168:36 We can see the correct links in the 168:38 navbar and we get redirected to our 168:41 tickets page. Now before we move on, I 168:44 just want to take a second and look at 168:48 Sentry. So I'm just going to refresh 168:51 this. So these are Let's see. I'm on the 168:54 issues 168:57 page and let's go to prioritize. So this 169:00 is any error that was thrown. So fet 169:03 fetch ticket list 169:05 um login failed 169:09 error the default export is not a react 169:11 component. So anything in you know in 169:14 the process of development is going to 169:17 show here. So a lot of these like the 169:20 default export that's not that's fixed. 169:22 So we could mark that resolved. This 169:25 other stuff these are just logs. fetch 169:27 ticket 169:30 list and we can just see what happened 169:33 before which is nothing really because 169:36 it's just fetching the the 169:41 tickets. Yeah. So it's just showing me 169:43 on the page um hydration error. So that 169:47 was when I had to disable last pass. So 169:51 we know that that's 169:53 resolved. All these hydration this one 169:56 here and then next router was not 169:59 mounted. So that's not exist. So all of 170:01 these are taken care of. Scheduled 170:03 Prisma error for testing that was just a 170:06 simulation. Yeah. So we could mark all 170:08 of these as solved. And then for review 170:12 this is going to be just like regular 170:13 logs. So user registered successfully 170:16 viewing ticket details. 170:19 So yeah, I 170:21 mean again, you don't have to do this 170:25 stuff, but it it can be helpful just to 170:28 see everything that happened on your 170:30 site and who did 170:32 it. But yeah, you can mark all these 170:36 resolved. So I just wanted to just take 170:38 a look at that. And then you can check 170:40 out any performance. So if you want to 170:43 look at you know durations and page 170:45 loads and the different routes how they 170:48 perform and so on you can do that. All 170:51 right. Now the next thing I want to do 170:54 now that we can log in I want to make it 170:58 so that when we submit a ticket right if 171:00 we go to new tickets we need to attach 171:03 the user to the ticket because right now 171:07 it's just going to submit a ticket and 171:08 and we don't know who who did it right 171:11 who submitted it. So we have to attach 171:13 the user to it. So let's open up the 171:16 actions for the ticket. So action uh 171:19 ticket.actions 171:20 actions and let's go to the create 171:22 ticket which I believe is at the top and 171:24 and you'll see this um we're getting a 171:27 TypeScript error here because now the 171:29 ticket is expecting a user because of 171:32 the Prisma schema right if we look at 171:34 our Prisma schema you should have the 171:37 user ID and user in here so it's 171:40 expecting that um so first thing I'm 171:43 going to do is bring in at the 171:45 top I'm going to bring in the the get 171:48 current user because we to know which 171:51 user is is being is using the form. So 171:55 we'll bring that in and then let's go to 171:57 the create ticket and at the top of the 171:59 try block let's check for the user. So 172:03 we'll say if well actually let's 172:08 uh yeah we'll just get the user first. 172:10 So const user set that equal to a 172:14 wait get current user and then we'll 172:18 check the 172:20 user. So if there's not a user, I'm 172:23 going to first of all log the 172:25 event and I'll say 172:28 unauthorized unauthorized 172:32 ticket creation 172:37 attempt. And this is going to be of the 172:39 type ticket. I guess it could be off 172:42 too. Depends on how you look at it. And 172:45 we'll make that a 172:46 warning. Okay. And then what we're going 172:49 to do is 172:51 return success. Return that as false. 172:56 And then the message will say you must 173:00 be logged in to create a 173:05 ticket. Okay. So that's first thing we 173:08 want to do. Then when you create the 173:10 ticket, we want to add the user ID. So 173:12 let's go down to where we see that 173:13 TypeScript error. So we have subject, 173:15 description, priority. Now we're also 173:18 going to add in let's say user and we're 173:21 going to set that to an object and since 173:23 it's a relationship we're going to add 173:25 here connect and we want to set in the 173:28 in the connect we want to set the ID to 173:31 the user 173:32 id. Okay so that will connect the user 173:35 to this 173:36 ticket and that should be all we have to 173:39 do to connect a user to a ticket. Now 173:42 before we test that out there's one 173:43 thing I I want to do. So in the log out 173:46 button, I have it right now where I'm 173:49 bringing in the router and if success 173:53 then we're pushing to the login page. I 173:56 don't want to do that here. What I want 173:58 to do is make it so that the tickets 174:00 page, right? So my tickets, if you're 174:04 not logged in, it should redirect you to 174:06 the to the login page. So let's not do 174:09 this here. Let's get rid of the 174:11 router.push. And we can get rid of 174:13 anything to do with the router in the 174:15 login button. So we don't need to pass 174:18 it in here. And then we don't need to 174:20 initialize it. And then we don't need to 174:23 bring it in the use router. Okay. What I 174:26 do want to do though is go to the ticket 174:29 page which is source app 174:32 uh tickets and then page.tsx. And I want 174:35 to get the user. So I'm going to import 174:37 get current 174:39 user. And then we're going to go right 174:41 above where we get the tickets and we're 174:43 going to get the user with await and 174:46 then get current 174:48 user. And then we're just going to do a 174:50 check here on the tickets page. If not 174:54 user, then we're going to use redirect 174:57 from next navigation and redirect to 175:02 /lo. Okay. So if I'm not logged in, 175:04 which I am right now, but if I'm not, I 175:06 shouldn't. This should redirect me. So, 175:09 let's test out adding the ticket first. 175:12 So, I'm going to call this Brad's 175:15 ticket. I'll just say this is a sample 175:19 ticket from 175:21 Brad. And then let's submit 175:24 that. Okay. So, tickets successfully 175:27 submitted. And I see Brad's ticket. And 175:29 if I go to Prisma Studio and I look at 175:31 the tickets, now that user is connected. 175:35 You can see the user ID as well as the 175:38 just the entire user. If I click on it, 175:40 we have the Brad at 175:42 Gmail. All right. So 175:44 now the problem that we still have 175:47 though is anyone can see this Brad's 175:49 ticket. And I'm going to log out, which 175:52 you can see redirects me to the login 175:54 because I can't be on that tickets page 175:56 if I'm not logged in. But let's actually 175:58 register a new user. So, John Doe and 176:03 John 176:06 atgmail. And this this not only 176:09 registers but also logs me in. And I'm 176:11 logged in as John, but I see Brad's 176:13 ticket. So, obviously, we don't want 176:14 that. We want to limit it to that user's 176:17 tickets. So, we're going to do that in 176:19 the actions. So, the ticket actions and 176:22 go down to our get 176:24 tickets. And from here, we want to get 176:27 the user. So let's go right in the try 176:30 here and say const 176:33 user and we want to await get current 176:38 uh get current user and then if there's 176:42 not a 176:44 user so if there's not a user 176:48 then I want to first of all log 176:52 that so we'll log an event and let's say 176:56 unauthorized unauthorized is access 176:59 to ticket 177:02 list. And for the for the category, you 177:05 could say t o or ticket. I'm going to 177:07 say ticket just because we're in the 177:08 ticket actions. And just an empty object 177:11 for the data. And let's do a 177:15 warning. Not a warning, but 177:17 warning. And then we'll just return an 177:21 an empty array if there's no user 177:23 because we don't want them seeing any 177:25 tickets even though they can't get to 177:27 this page anyway from the front end. 177:30 Now to limit the tickets to the user 177:33 tickets, we need to go right here where 177:36 we call this Prisma ticket find many. 177:39 And we're going to limit it by saying 177:41 where the user ID because now the 177:45 tickets have a user ID field match it to 177:48 or it has to match the user ID and that 177:52 user is coming from the the get current 177:54 user which ultimately that user ID comes 177:57 from the token. So hopefully you can see 178:00 the whole flow of this application. And 178:03 now you'll see that it disappears 178:04 because I'm logged in as John. So I 178:07 can't see Brad's ticket. Now I'm going 178:09 to create a new ticket as John. Let's 178:11 say John's 178:13 ticket. This is a sample ticket from 178:19 John. And we'll do medium and submit. 178:24 Now I get that submitted successfully 178:26 and I can see it on the listing page. So 178:29 I'm going to go ahead and log out. 178:31 Redirects me to login. Now I'm going to 178:33 log in as 178:35 Brad. And as you probably guessed, I see 178:39 Brad's ticket and I don't see John's 178:41 ticket. So now now it's user 178:44 specific. So our project is pretty much 178:46 done. We can log in and register. So we 178:50 have authentication. We can create new 178:51 tickets. So we can see our our tickets 178:54 and only our tickets. So that's kind of 178:56 the gist of what I wanted to do, but 178:58 we'll do a couple more things. So I want 179:00 to make this its own component, the 179:03 ticket item, and we can also do 179:06 something with the status. Like if it's 179:08 closed, we can fade it out, something 179:10 like that. Um, but there's a lot of 179:12 things you could add on to this project. 179:14 So I would suggest that you do that. I 179:17 think that that's the best way to learn 179:18 is to just jump in and start doing 179:20 things yourself and getting your hands 179:22 dirty. So, this just gives you a good 179:25 start. So, let's close off or close up 179:29 all this stuff. And I want to create a 179:32 new component in the components folder. 179:35 And we'll call this, let's call it 179:38 ticket 179:40 item. and let's say 179:45 SFC ticket item. And then it's going to 179:48 take in a prop 179:50 of of 179:53 ticket and then for the type let's set 179:57 that to we'll say ticket 180:01 uh what do we want to call this ticket 180:02 item props and then I'll just create 180:05 that right here. So type will be ticket 180:08 item 180:09 props and that's just going to be an 180:12 object with a ticket and that's going to 180:15 have a type of ticket which I can 180:16 actually bring in from generated Prisma 180:20 and when I bring in type usually like to 180:23 add import type so we know what it is. 180:26 All right. 180:27 Now as far as uh what I want to put in 180:30 the return let's go to the tickets page. 180:33 So app tickets page 180:35 tsx and what we have inside of this 180:38 tickets.m mapap is what I want. So this 180:41 div which ends right here. I'm going to 180:43 just go ahead and cut that and then I'm 180:46 going to paste that in to the return 180:49 here. Now this uses the get priority 180:52 class and a link. So those are two 180:54 things we need to bring in as well. So 180:56 up at the top here, let's say import 180:59 link and let's 181:02 import get priority 181:06 class. All right. Now, this ticket item 181:08 we're going to bring into the ticket 181:11 page. So, we no longer need this 181:15 link and or the get priority 181:18 class. And it looks like I'm not using 181:21 log event here either. So, we can get 181:23 rid of that. And then we're going to 181:25 bring in the ticket 181:29 item. And I'm going to just add that 181:31 into the map here. 181:35 So ticket item. And we want to add on a 181:39 key to that which will be the ticket ID 181:43 and then the ticket 181:47 itself. There we go. Let's save it. And 181:49 it should just show us the same 181:52 thing except now it's a little cleaner 181:54 and it's in its own component. All right 181:57 guys, so now I want to do the close 181:59 ticket functionality. So if we view it, 182:01 we should have a close button down here. 182:03 So that means we have to create an 182:05 action and then we'll add a a close 182:07 ticket button component to uh to this 182:11 page here. So let's go ahead and jump 182:13 into our ticket actions. And then down 182:16 at the very bottom, we'll say this is 182:18 going to close ticket. And we want to 182:21 export an async function called close 182:27 ticket. Okay. Now, we're going to be 182:30 using the use action state. So, just 182:33 like with all of our other actions, it's 182:35 this is going to take in actually it's 182:37 not going to be an object. It's going to 182:38 take in pre state. And that's going to 182:42 be as far as the type it'll be an object 182:44 with success which will be a 182:47 boolean and also a message which will be 182:51 a 182:52 string. So that's pre state. Then we 182:55 want uh form data which will have a type 182:59 of form 183:01 data. And then as far as what it's going 183:05 to return, so the function will return a 183:08 promise and that promise is going to be 183:12 again success which will be a 183:15 boolean and 183:17 message which will be a 183:20 string. All right. And then I'm going to 183:23 get the ticket ID. So let's say ticket 183:26 ID set that to 183:29 uh we're going to get it from the form 183:30 data. So form 183:33 data.get and ticket ID, but I'm going to 183:38 cast that as a number. So we're just 183:40 going to wrap that in 183:43 number. Once we do that, we're going to 183:46 check if 183:47 not ticket ID. Okay. So if there's no 183:50 ticket ID then let's just do a return 183:53 success 183:55 um return an object with 183:59 success false and 184:03 message will be 184:05 ticket ID is 184:10 required and if there is a ticket ID 184:13 then we want to get the user so that'll 184:17 be await wait get current 184:23 user and we'll check for the user. So if 184:26 not 184:29 user then let's 184:32 return success 184:35 false and 184:37 message we'll just say 184:41 unauthorized. Okay. And then for both of 184:44 these we should probably do a sentry log 184:46 event. So before the return, 184:50 oops, let's see. So right here, before 184:53 the return, let's say log 184:56 event. And for that, we'll say 185:01 ticket or missing ticket ID. I guess 185:04 we'll say 185:05 missing ticket ID. And it'll be 185:11 ticket just an object for the data. And 185:14 the type will be a 185:17 warning. All right. And then I'll just 185:20 copy that. We'll do the same thing here 185:22 for the user. We'll say missing user 185:28 ID. 185:30 Yeah. So if there is a ticket and there 185:33 is a user, we now we want to get the 185:35 ticket and make sure it it belongs to 185:37 that user. So we get the ticket by 185:43 using prisma.t and we'll use the find 185:47 let's use find 185:49 unique and we want to find 185:52 where where the ID of the ticket equals 185:56 the ticket 185:59 ID. Yeah. And then we're going to 186:04 check. So if not the ticket 186:07 or if the ticket um dot user id. So this 186:13 is where we check for the user. If that 186:15 is not equal to the user id. Okay. And 186:20 this user is coming from get current 186:22 user which is the logged in user. So it 186:24 needs to match. So if it doesn't then 186:28 let's do uh log 186:30 event and we'll say 186:35 unauthorized ticket close 186:39 attempt. Okay. And then 186:42 uh it's going to 186:44 be the ticket category. And for the data 186:47 that we send, let's send the ticket 186:50 ID. And let's send the user ID, which we 186:54 would set to user 186:57 ID. And then we'll do 187:02 warning. All right. Okay. And then as 187:04 far as what we want to 187:06 return, so that'll be 187:09 success will be 187:11 false. Message 187:14 um again we'll just we'll just say this 187:17 or this is to the user. So we'll say 187:20 you are not 187:22 authorized to close this 187:27 ticket. Okay. So now that we've done 187:31 that, let's do the actual update. So 187:33 we'll say await prisma dot 187:39 ticket.update. And we want to update 187:41 where the ID of the ticket is equal to 187:45 ticket ID. And then the data that we 187:48 want to send. Did I do that right? Uh 187:51 this should have curly 187:54 braces. And then the data that we want 187:58 to send is going to be the status and 188:02 that's going to be set to 188:07 closed. I think that's 188:09 right. So data set the step. Oh, it 188:13 needs to be set to a string of closed 188:16 with uppercase C. Okay. So that'll do 188:19 the actual update. Then I just want to 188:21 revalidate path for two paths. One is 188:25 going to be for the tickets page itself 188:28 and then one is going to be for the 188:31 specific ticket. So let's do back ticks 188:34 here and then we'll do slash and 188:37 then ticket ID. So just to make sure 188:40 that those now have the you know the up 188:43 the most recent ticket which would be 188:46 this one setting the status and then 188:49 we'll 188:52 return success 188:54 true and 188:56 message will say that the ticket has 188:58 closed 189:00 uh ticket has closed 189:05 successfully. All right so that's our 189:07 action. 189:09 Now, we want to use this in obviously in 189:12 our front end. So, I'm going to create a 189:14 new component for the close button. So, 189:18 under components, let's create a new 189:21 file. We'll call this 189:22 close close ticket 189:28 button.tsx. And this will be close 189:31 ticket 189:33 button. And then I just want to have a 189:36 couple imports here. So, we're going to 189:38 be using use action 189:40 state. So, that's from React. And we're 189:43 going to be using use 189:46 effect. And then we want our action of 189:49 course that we just created. So, close 189:52 ticket from the actions file. And then 189:54 I'm also using the sauner toast. So, 189:57 let's bring in uh toast. That's going to 190:00 be from 190:03 sauner. All right. And then let's see. 190:05 I'm just going to put the initial state 190:07 in a variable. Actually, we can put it 190:11 right here. Const initial 190:15 state success false by default and 190:20 message 190:22 empty. Okay. 190:25 Now, let's get let's say state and form 190:30 action. So, state and form action. Um, 190:34 we're going to get 190:36 that from use action state and that's 190:40 going to get passed in the actual action 190:42 which is close ticket and then it's 190:45 going to be the initial 190:48 state. Um, now this close ticket button, 190:51 it is going to take some props. So, we 190:53 should probably add those in here. So, 190:55 for props, it'll have the ticket ID and 190:59 also is closed. And as far as the type 191:02 for that, it's going to be an object 191:04 with ticket 191:05 ID, which is going to be a 191:08 number. And the is 191:12 closed, of course, is going to be a 191:15 boolean. All right. Now, let's create 191:18 down. Well, actually, before the return, 191:20 let's just check to see if it's closed. 191:22 So, we'll say if it is closed, then 191:26 we're just going to return null because 191:28 I don't want to show this close ticket 191:30 button. if it's already 191:33 closed. And then in the return, since 191:35 we're using uh this form action here, 191:39 we're going to be we're going to have an 191:41 actual form with a button, not just a 191:43 link. So, let's create our form and add 191:46 the action and set that to the form 191:51 action. Okay. And then in that form, 191:54 we'll have an input. Let's give it a 191:57 Let's give it a type of hidden because 192:00 this is going to be the ticket ID. And 192:03 we need a way to send that. It's not 192:05 going to be obviously an input that that 192:07 the user can see. So, it's going to be 192:09 hidden. And let's call this ticket ID. 192:12 And then the value will be the ticket 192:16 ID. All right. Then we're going to have 192:18 our button. And I just want to give this 192:21 button some classes. So class name, 192:25 let's do background. We'll do background 192:28 red 192:29 500. And let's do text dash 192:33 white and padding x3. Padding 192:39 y3. And what else? We're going to make 192:41 this full. So width or 192:44 wful. We'll make rounded corners. On 192:48 hover, I want to make the background a 192:50 little darker. So let's say hover BG red 192:54 uh do 192:55 600 and also 193:00 transition and then inside the button 193:03 we'll just have close 193:06 ticket. Okay. And also make sure that 193:08 you add a type on this and set that to 193:12 submit. 193:15 So now the last thing we have to do is 193:18 once we submit 193:20 it, we want to run the use effect and 193:23 check for the success. If it's 193:25 successful, we'll show the the toast 193:27 success message. If not, we'll show the 193:29 error. So let's put that um we'll go 193:32 right 193:34 above where do I want to put this? Yeah, 193:36 we'll go right above this if is closed 193:39 and let's say use 193:41 effect and we pass 193:44 in a function. And then as far as the 193:47 dependencies, we just want to look at 193:48 the state. So when state changes, this 193:51 will run. And we're going to then check 193:53 for 193:57 state.uccess. And then let's do 194:01 toast.uccess. Uh and then for the 194:04 message that's going to be whatever is 194:06 coming from the action. So state 194:09 message and then let's say else 194:13 uh else 194:15 if else if the state message. So if it 194:19 has a message but it's not successful. 194:23 So 194:24 state.uccuess then obviously that means 194:27 there's an error. So we're going to 194:28 toast. 194:30 and still show the state message which 194:33 should be an error 194:36 message. Okay. So now we want to use 194:39 this close button and it's going to be 194:42 on the single page which is going to be 194:44 in the app folder tickets and then the 194:46 brackets ID page. So from here let's 194:49 import the close ticket button and then 194:53 we're going to put this down at the 194:55 bottom. So underneath the link and I 194:58 only want to show if the ticket is is 195:01 open. So let's say if the ticket status 195:05 if it's not 195:07 equal to closed 195:10 then we're going to show 195:14 uh let's say 195:16 close close ticket button and then 195:19 that's just going to take in the ticket 195:21 ID. 195:22 So set that to the ticket. ID and then 195:26 we also want to set it or set pass in is 195:30 closed which will set that to an 195:32 expression of 195:35 ticket.status 195:36 equals closed. Okay. So if it's closed 195:40 that'll be true. If not it'll be false. 195:42 So now let's save that. Um you're 195:45 importing these action state. Oh, so I 195:47 forgot to make the client ticket button 195:49 a client component. Our close ticket 195:52 button a client component. So we just 195:54 want to do use client at the top because 195:56 we are using 196:02 hooks. All right. So now we should see 196:05 the button. There it is. Close ticket. 196:08 So I'm going to go ahead and click that. 196:09 And now the button goes away. Okay. So 196:14 we're able to close it. And just to 196:15 double check, I'm going to go to Prisma 196:17 Studio and reload. And let's check out 196:19 the status field. And you can see Brad's 196:22 ticket, the status is 196:25 closed. Now, you can handle this how you 196:28 want. What I'm going to do is when we 196:30 list out the tickets, if it's closed, I 196:32 want it to be like faded out. In fact, 196:35 I'll add a new ticket just so we can see 196:37 the difference. So, let's say Brad's 196:40 Brad's ticket two. 196:44 This is a sample ticket and I'll make 196:46 this medium submit. Okay. So now this 196:50 one's closed, 196:51 right? So what I'll do is go to where do 196:55 we want to handle this? It's going to be 196:57 in the ticket 196:58 item component, right? So ticket item 197:02 because that's what each one of these is 197:03 a ticket item. And what we can do is go 197:06 right above the return and let's create 197:09 a variable called is uh is closed. And 197:14 we'll set that to let's say ticket dot 197:18 oops 197:20 ticket.status and set that to close. So 197:25 if it's closed then this is closed will 197:27 be true. Now we can use this to add some 197:30 dynamic styles. So for instance uh the 197:34 the div that wraps it like I said I want 197:36 to set the opacity to uh to 197:40 50. So in order to do that instead of 197:43 for the class name instead of for using 197:45 quotes let's do a curly brace and then 197:47 brackets end it with um no not curly 197:52 brace curly brace and back tick and then 197:55 end it the same way. And then what we'll 197:58 do is add let's see I'll just go on to 198:00 the end here and use our template 198:03 literal syntax and say if is 198:06 closed then I want to have a class of 198:10 opacity- 50 else then just 198:14 nothing. So now if I save that and we 198:17 take a 198:20 look now this is faded out right because 198:23 it's closed. However I can still click 198:25 the view ticket button. and I don't even 198:26 want to be able to do that. So, let's uh 198:29 and it's up to you on how you want to 198:31 handle this. Right now, we're getting 198:32 into UX stuff. So, it's really up to 198:35 you, but I'm choosing to to make this 198:37 button gray and 198:40 disabled. So, we'll come down to where 198:44 we have the 198:45 link and let's see. I'm 198:48 gonna for the class name, same thing. 198:52 I'm going to use curly brace back tick 198:55 and then where it ends instead of a 198:58 quote we'll do back tick curly brace and 199:01 I want to take this bg blue and the text 199:04 white I'm going to cut 199:07 that and then at the the end here again 199:11 we'll open up our this syntax here and 199:13 say if is closed then show something 199:17 show a class else show something else 199:20 now in the something else if it's not 199:22 closed is where I want to have that BG 199:24 blue and text white and also this hover 199:27 BG blue which I'm going to cut and put 199:30 into 199:31 that. Okay. Now, if it is closed, then 199:36 I'm going to add a BG- 199:41 gray-400 and 199:44 text-g 199:47 gray- a to click it. And I don't want 199:51 the pointer the you know the cursor to 199:53 be a pointer. So we can use the class 199:56 cursor 199:57 dashnot dash allowed and also the 200:01 pointer dashvents 200:04 dashnone. So these tailwind classes will 200:08 make it so that we can't click on it and 200:10 it doesn't turn to you know the hand to 200:12 the pointer. So now I can't I can't open 200:16 this. I mean, you could still 200:18 technically go to type in the URL to go 200:21 to the page, but it's not a big deal. 200:23 Uh, I just want something to show that 200:25 the ticket is closed. Now, the last 200:28 thing I want to do is this new ticket 200:30 page. If I log out, I can still go to 200:33 the page, which obviously we don't want 200:35 that. So, to fix that, we're going to go 200:38 to the app folder, tickets, and then the 200:41 ID folder and page tsx. And from here we 200:46 want to bring in no not this. This is 200:48 the details page. Sorry. We want to go 200:50 to new page 200:52 tsx. And from here we're going to import 200:55 get current user. And then we're just 200:59 going to go right above the return con 201:02 user set that to await get current 201:06 user. And then we're just going to check 201:08 if not user. Okay. Okay. So if the user 201:11 is not logged in then I'm going to call 201:13 redirect from next navigation and I want 201:17 to redirect to the slash 201:20 login. So if I save 201:23 that and come back over here now you can 201:27 see I get 201:28 redirected. So now I can't visit that 201:30 page if I'm logged in. I mean if I'm not 201:32 logged in. And if I log in we'll log in 201:35 as John this time. 201:41 Now I go to new ticket and it works. And 201:45 that's it. So hopefully you guys learned 201:48 a lot from this. And just to show you 201:51 what our Sentry dashboard looks like. So 201:54 it just gives us a full history of 201:56 everything that's happened in our 201:58 project, whether it's an error, whether 202:00 it's a log that we added. So um you can 202:03 see here we got this element type is 202:06 invalid expected string. So, I mean, we 202:09 we were able to fix this stuff as we 202:11 went along, but if it's something that 202:13 you really get stuck with, then this can 202:16 really help. Uh, and again, it just 202:18 shows it just shows everything that 202:21 happened. So, unauthorized ticket 202:24 access, viewing ticket details. I mean, 202:27 it's up to you on what you want to log. 202:29 You probably wouldn't log as much as we 202:31 did because for every user, you're 202:33 probably not going to want to just like 202:35 show if they viewed the ticket details, 202:39 but uh but you can, you know, and 202:41 there's a whole bunch of other stuff 202:42 that we didn't get to. I just basically 202:44 showed you the basics. So, yeah, I mean, 202:47 I would suggest taking this project and 202:49 and just run with it. Add some more 202:51 features. You could even create a whole 202:52 admin area where, you know, you could 202:54 respond to the tickets and maybe add 202:57 file uploading. There's just so many 202:59 different features you could add and and 203:01 just have a really good learning 203:03 experience. But I appreciate you guys 203:05 sticking around, watching it, following 203:07 along with me, and I'll see you in the 203:09 next