NextJS + Postgres and Error Monitoring - Full Stack Support Ticketing System
Chapters52
Overview of the tech stack (Next.js, Neon Postgres, Prisma) and the role of Sentry for error and performance monitoring in a production-ready app, plus a plan to build a custom auth flow with JSON Web Tokens.
Traversy Media builds a full-stack Next.js 13 app with Neon PostgreSQL, Prisma, and Sentry to demonstrate a production-ready support-ticket system plus a custom auth flow using JWTs via Jose.
Summary
Brad Traversy guides you through building a four-phase Next.js + Postgres project focused on a production-ready support-ticket system. He emphasizes not just the UI, but the tooling: a Neon cloud Postgres database, Prisma ORM with a Neon adapter, and the Sentry error and performance monitoring workflow. The video walks you step-by-step through creating the Next app, configuring Sentry with the wizard, and wiring Prisma migrations to a cloud database. Brad then pares the project into UI phases with Tailwind styling, server actions, and use of useActionState for modern form handling in Next.js. Authentication is implemented from scratch using JSON Web Tokens (JWT) via the Jose library, with tokens stored in HTTP-only cookies and a custom login/register/logout flow. The tutorial shows how to tie tickets to users, enforce per-user views, and add UX touches like toast notifications and a simple close-ticket flow. Throughout, Brad demonstrates Sentry dashboards, breadcrumbs, session replays, traces, and real-time error logging to illustrate debugging in a real production context. The end result is a functional, authenticated ticketing system that demonstrates best practices for deployment, data modeling, and observability—perfect as a learning blueprint for modern full-stack development with Next.js and Postgres.
Key Takeaways
- Use Next.js 13 app router with server actions to submit forms directly to server-side logic (no traditional API routes).
- Prisma migrations with Neon: create a Ticket model, run prisma migrate dev to sync your schema with a Neon cloud database, and then generate the Prisma client for use in server/routes.
- Set up Sentry with the wizard for a quick integration, then explore breadcrumbs, stack traces, and session replays to debug production issues.
- Implement a custom authentication flow using Jose for JWTs, storing tokens in HTTP-only cookies and validating them on each request to protect routes and data access.
Who Is This For?
Essential viewing for developers who want to implement a full-stack stack with Next.js, Neon Postgres, Prisma, and bespoke authentication. It’s especially helpful for those moving from basic tutorials to building production-ready apps with observability and user-scoped data access.
Notable Quotes
"Brad: 'We’re using Nex.js, which is an SSR framework that uses React.'"
—Intro to the stack components: Next.js, Neon, Prisma, and Sentry.
"Brad: 'Sentry... gives you this main dashboard, this centralized location where you have all the events that happen in your website.'"
—Explains why Sentry is valuable for monitoring and debugging.
"Brad: 'We’ll be using a package called Jose that will just generate JSON web tokens for us.'"
—Describes the custom authentication approach using Jose for JWTs.
"Brad: 'Neon offers serverless Postgres databases.'"
—Notes on the Neon cloud database and its serverless nature.
"Brad: 'We’ll be using server actions, which you can use in place of API routes from older versions of Next.js.'"
—Highlights modern Next.js data handling with server actions.
Questions This Video Answers
- How do I set up a Next.js 13 app with a Neon Postgres database and Prisma?
- What is Sentry and how do I integrate it into a Next.js project for error monitoring and performance tracing?
- How can I implement a custom JWT-based authentication flow with Jose in a Next.js app?
- How do server actions differ from API routes in Next.js 13?
- How can I associate tickets with users and enforce per-user data access in a Prisma/Next.js app?
Full Transcript
Hey, what's going on guys? So, I got a cool project for you today and I'm excited about this one. It's not so much about the project itself, which is a support ticket system, which I'll talk about in a few minutes, but more so the tools that we're going to use and how to use them together. So, as far as the tools, we're using Nex.js, which is an SSR framework that uses React. We'll be using Neon, so we'll have a a cloud Postgres database. Prisma, which is an OM. So, we'll use that to interact with our database and create models and migrations and stuff.
And then I'd say the main focus of this video is going to be a tool called Sentry, which is an error monitoring and tracking tool as well as performance monitoring. So, the reason you use something like Sentry is because normally, you know, you have your your website, your application, your users are going to it, and it's just it's just inevitable that you're going to have bugs. you know, you're going to have issues. Your users are going to run into issues and they may or may not tell you about them and it could go days, weeks, months without you even knowing that these problems are happening and and turning away your users.
So, what Sentry does is it gives you this this main dashboard, this centralized location where you have all all the events that happen in your website. You know, you can log as as many events as you want. Uh, and of course your errors. You know, you're going to see all your your errors that that take priority as well as your just custom logs. And this will allow you to to, you know, pinpoint issues. So, especially if you can't replicate them yourself. Um, you can get email notifications when your users run into issues. And you're going to have all the information that's available as far as the stack trace goes, as far as your user information.
And then you also have a screen recording of what the user was doing, what happened uh on screen when that error happened, which can help you, you know, debug. So it's it's going to help you not only find errors and know about errors, but also debug them. Um, and this is something that you would use in a production project. It's something, you know, often you'll use when you're working for a large company. So it's really valuable to to know how to use this stuff. So, I'll show you how to use that. We're going to get set up with the Sentry Wizard and the SDK for Nex.js.
And then we're also going to be using a or building a custom authentication system rather than using something like Next Off, which is great, you know, but I wanted to do something a little lower level for this project. So, we'll be using a package called Jose that will just generate JSON web tokens for us. and uh and we'll be hooking up, you know, the login, the the register, the log out, managing sessions, etc. And then as far as the the project itself, we'll be able to, you know, log in and create tickets. So, support tickets where we can describe an issue.
Um, and then you'll be able to, you know, obviously see a list of the tickets, see the details view, you'll be able to close the ticket, delete the ticket. So, it's pretty simple as far as functionality, but again, this isn't about the project itself. It's about learning how to use these tools so that you can build your own projects with them. All right, so grab a coffee, grab a tea. I really suggest that you follow along and let's get into it. All right, guys. So, I'm really excited for this project. I think you're really going to like it.
And again, I would suggest that you follow along with me, write the code with me, and I think that that's just the best learning experience you can get. So, I'm going to just go through the phases so you have a highle view of the tasks and how we'll be doing this, the order of of um functionality that we'll be creating. So, there's four phases. The first one is going to be setting up Sentry and our database. Okay, we're using a Neon Postgres database. So number one, we'll get our next app up and going. So create next app.
Very simple. You guys know that most of you. Uh number two, we'll set up Sentry, which is really simple. There's something called the Sentry wizard that we can run with NPX, and it gives us a sample error that we can fire off. So we can go into the Sentry dashboard and kind of see how it works, see what it gives us, and then from there, we can integrate it into all the different functions throughout our application. Then we're going to set up our Neon database, which is very simple, just a couple clicks. Uh add the database URL to our environment variables.
And then for Prisma and Neon adapter setup. So Prisma is the OM that we're using. We're going to interact with the database using that. And the Neon adapter, we're going to use that um to ena basically enable us to use um Prisma and Neon together. Neon offers serverless Postgres databases. So there's just a couple extra steps we need to take, especially if you're going to deploy to Verscell. And then number five, database ticket schema and migration. So we're going to start off with no authentication, no users. I just want to get the ticketing system working, which is, you know, submitting the ticket and getting the tickets and so on.
And we what we do with Prisma is we create a schema which is all the different fields that we want like the ticket uh the priority or the title things like that the date. So we set up a schema and then we run a migration with Prisma and that will create our database tables in our Neon database. Okay so that's phase one. Phase two we have some basic UI stuff and I also want to handle creating new tickets. Okay, so we'll set up Tailwind. Really, really easy with version 4 uh and Nex.js. And then we'll create the welcome screen, which will just be a landing page with a couple buttons to submit a ticket or view your tickets.
And then from there, we'll create the ticket form. Okay. Um we're going to be using Nex.js or uh actions, so server actions, which you can use in place of API routes from, you know, older versions of Nex.js. So basically our form will submit to a server action and then from there we can do all of our database tasks using Prisma. All right. And we're going to be using a hook in the form called use action state which allows us to manage that form state with what comes back from the action. So it's the the most modern way of doing this with Nex.js.
And of course we're going to have our Sentry integration in the ticket flow. Um, so we'll create a custom log event function we can use that that fires off a couple different sentry methods. Um, we want to test it and check the sentry reporting, look at any playbacks, etc. So that's phase two, just getting being able to submit new tickets. And then phase three will be the ticket display. So we'll have an action once again a server action that will fetch the tickets from the database using Prisma. We'll have the tickets index page which will list them all.
the details page which will list a single ticket and then we'll have our sentry reporting and tracking and just some UI element styling like if it's a serious priority we'll make it red or whatever just little things like that uh we also have like toast notifications and then the final phase phase four is users and authentication because up to this point we can just create tickets and see them but we want to be able to log in right and we want to be able to see only that user's tickets. So, we'll start off with just creating a simple navbar component with a login and register link.
Um, we want to create a new user schema because obviously when you have authentication, you need somewhere to store your users. Um, we'll have a custom authentication flow with JSON web tokens. We're going to be using a package called Jose, which is kind of a more modern version of the JSON web token package. We're going to create some low-level functions like encrypt and decrypt to deal with our token and deal with cookies. You know, saving our token and a cookie and HTTP only cookie. And then highle functions like register user, login user. These will be called from our actual components.
And then the authorization of course you want to have a relationship between the tickets and your users so that the user can only see their own tickets and not everybody else's. So that's kind of the the entire flow of this project just so you can have an idea. So let's go ahead and jump into it and let's start to create our next app. Okay. So we went over the kind of the the workflow, the order that we're going to do things. So I'm assuming that most of you have at least a little experience with Nex.js.
If not, that's okay. But what I would really suggest is you at least know the basics of React. If you don't know anything about React, then you could watch my React crash course or just any kind of beginner React content just to get familiar with state props, components, things like that. All right, so let's jump into it. I'm going to just open my terminal, navigate to where I want to create this project, and then we're going to run npx and we want to do create-next app at latest. And then I'm going to call this project quick-t.
Now, as far as using TypeScript, I'm going to use it, but if you don't want to, you can say no. There's not a ton of TypeScript we're going to write, so you'll be fine if even if you've never used it before, and you click yes. Uh, ESLint, we'll click yes. Tailwind, I'm going to use it. Um, using the source directory. App router, yes. Turboac for nextdev, I'm going to say no. and a customize the import alias. I'll say no to that as well. All right, so that's going to go ahead and install all these dependencies and just scaffold up an Nex.js application for us.
So now that that's done, let's CD into QuickT. And then from here, there's a couple other um dependencies I want to install. So Prisma, we want the Prisma client, which is at Prisma/Client. We're also using React-icons. There's some other things later, but this is all I want to install at the moment. Um, and then I want to once we get this the dev server up and running, we'll start to integrate Sentry as well. Okay, so let's go ahead and open this folder in Visual Studio Code or whatever text editor you want to use. I'm going to be using the integrated terminal, so I'll close that up and then open the integrated terminal.
And we can go ahead and run npm rundev. And let's see. I want to open that here. So, let's go. It's going to be localhost 3000. And we should just see the the default Nex.js page. And we can do a little bit of cleanup here. So, in the source folder, we have our app folder. And you may have chosen not to use a source folder. That's fine. Your app folder will just be in your root. And from here, the page.tsx is the homepage. And if you're new to Nex.js, it uses filebased routing. So the app folder is like your routing folder where the page .tsx that's that's directly in it is your homepage.
If you wanted, let's say, slashabout, you would create an about folder with a page tsx in that about folder and that would that would just show up at slashabout and all the pages are are react components. In fact, we're going to wipe away this one, the page tsx. I'm going to generate a component with the react simple snippets extension that I have. So, sfc enter or tab and then it just scaffles out a component. I like to use the arrow function syntax with the export at the bottom, but there's other, you know, formats as well.
And then for now, let's just do an H1. And I'm going to give it a Tailwind class just to show you that that works. And we'll just say welcome. Okay. So, you can see that the heading, this class is working. Tailwind just works right out of the box because we, you know, we selected it when we initialized the project. Okay. So, now what I want to do is set Sentry up, which is pretty simple. So, we're going to go to um Sentry.io. IO. And as far as pricing goes, absolutely free for a single developer, one user, error monitoring, tracing, you get uh session replays, alerts, all kinds of stuff.
So, we're just going to sign in. You can use Google or GitHub. I'm going to use GitHub. All right. And then from here, so for me, I have all these issues because I already have a project, but you won't see that. What you want to do is create an a project. You can also create a team as well. And then here you want to select your platform. We're going to select Nex.js. And then you can set your alert frequency because it's really cool. You can get emails and alerts when you know a user runs into an issue.
Um, and it just notifies you right away so you can get to the problem and fix it as soon as possible. But for me, I'm just going to choose I'll create my own alerts later. And the project name, I'm going to change that to quick-t use my Traversy Media team, but you can create a new one if you want. And then it's going to give you this npx sentry wizard command. So basically, this will install all the dependencies that we need and it will create our config files and anything else that Sentry needs to to, you know, be integrated into our project.
So just copy that. I'm going to stop the server and then just paste that in and run it. And it has our project name and team name as well. And then it's going to ask some questions. So I have unttracked files. Do you want to continue? We'll say yes. And it's just going to validate in the browser and then send you back to the terminal. And we're going to just pretty much select the defaults for the rest of the questions. So do you want to route sentry requests in the browser through your Nex.js server? We'll say yes.
enable tracing. Yes. Enable session replay. And then it's asking if we want to create an example page, which you don't have to, but I'm going to just to kind of show you how Sentry works. So, I'll choose yes for that. It's going to ask if you're using a CI/CD tool. So, if you plan on deploying to to like Versell or or GitHub, uh if you want to use GitHub actions or whatever, we'll say yes to that. And it's going to generate this Sentry O token which you don't have to copy because what happened is it created this.
Eenv uh Sentry build plug-in file and it has the token in there. In fact, what we can do is just rename that to um Whoops. We want to rename that to env. And that will be our environment variable file. Get rid of the comments. Uh I also like to just add quotes here. And now we have that O token in there if we need it. So we'll just click enter again and then we should be all set. All right. Now I want to run the server. So npm rundev. And I just want to quickly show you before we set up our Neon database and stuff.
I want to show you how to integrate how to how Sentry works. Um so let's open our project back up. And you'll notice that in your project, if you chose to to show the example page, you'll have this Sentry example page folder in your app folder, which means that that's an accessible route. So, if I were to go to localhost 3000/entry example page, that should load. And it has a button that says throw sample error, but I'm not going to click that just yet because I want to show you that page. So, Sentry example page page tsx and this we're going to delete this after.
So, you don't really have to worry about this. I just want to show you where the button clicks. So, basically it first calls this sentry.start span and this is if you want to test performance of the page and I'll show you this in the sentry dashboard. Now, when we click it, it runs a function and it makes a request to slash API/entry example API. And if we look in our API folder, we can see that route right here. All right. So, it makes a request to that route. Let's take a look at it. This is the route.
And all it does is it throws an error, just a kind of a sample error, and passes in a message sentry example API route error. And then back in the page where it makes the request, it checks to see if the response is okay or if it's not okay. And if it's not, then it throws an error here. Okay. So, two errors are being thrown. one in the back end or API and one here in the front end. Now let's jump over to the page. Let's click the button that makes the request throws the error and you can see sample error was sent to Sentry.
Now if we go over to Sentry and go to issues make sure that the right project is selected. So for me quick ticket and we can see the example front-end error and the API error. Okay, so it tracks our errors and if we click on the front-end error, it's going to show you basically any information that is possible to see. It shows the server information, the IP address, which I don't care if you guys see. I'm on a VPN. Um, and then if you scroll down, you can see what are called the breadcrumbs, which is just kind of a trail of what happened to get to that.
From the UI click to making the request to the exception. Um, you have the trace, you have the stack trace, you have uh your HTTP information, so you'll be able to see the the user's um device and and operating system, things like that, the browser they're using. So again, just all the information that is possible on a single request. And then one really cool addition is this session replay where you can see exactly what happened in the browser prior to that error being thrown. So in my case, obviously I just clicked a button and that's not very helpful in terms of debugging.
But if it was something different like let's say a form that a user was filling out, maybe we could see that they missed a field or they filled out something wrong or in the wrong order. So anything any mistake in the UI we would be able to see here, which is is really cool. And then if we go back to issues, you also have the API route error. Um, there's no replay for this because it's it's an API error. It's a backend error. But we do have the breadcrumbs. We have the stack trace, HTTP info, and so on.
All right. And then if we go to traces, remember that start span. So this will show you any spans that were created and shows you the performance. So you can see the page loads, um, you know, the performance of that page. So, and I I don't use this too much, so I don't know too much about it, but um but you can just test performance of different routes and see if you need to, you know, make any adjustments. Okay, so that's kind of the gist of Sentry. And of course, you can make all types of logs and stuff.
We'll be doing that throughout our project so you can see how it works. Okay, if we go back to issues, you can, you know, change the the priority to low, medium, high. We can let's say that these are resolved. We can just mark resolved and then they'll go away. But it's just nice to have one place where all of your issues, all of your logs, warnings, errors, everything is shown here. All right. So, back to our project. We can now delete both of those pages. So, the Sentry example page, we can delete that. Sentry example API folder, we can delete that as well.
and we'll just go back to uh yeah back to the homepage here. Okay, so now we want to set up our database which is very very simple using Neon. So let's jump over to neon.te and we're going to log in. You can log in with GitHub or Google and it's literally just like a a couple fields. So new project, add a project name. I'm going to call it quick dash ticket database name let's say quick dash ticket db going to keep AWS as my provider create and now we have a cloud database now to connect to it we can click connect and then it gives us this string where we want to say show password and then copy the snippet and we want to put that in thev file that we already created.
So here, let's say database URL. And I'm going to set that to that database string. Okay. And Prisma when we set that up is going to use this database string. And we'll be able to run migrations, create some schemas, and it'll create our database tables. Okay. And you can see your tables here, which right now, obviously, we don't have any, but we will after we run our migration. Now to initialize Prisma, let's uh let's go ahead and I'm just going to open up a new tab and I'm going to run Prisma or sorry npx because we don't have Prisma installed globally.
So npx Prisma init. All right. And what that does is it creates a Prisma folder with a schema.prisma Prisma file and it has our client generator, our data source which in our case is Postgres and this is where it uses that database URL that we just created. Now I would suggest that you install the Prisma VS code extension if you are using VS Code. So this right here because this will give you you know syntax highlighting, linting, code completion. So definitely you know get that installed and then let's open up our schema file and from here we can create what are called models and any resource in your project like users tickets in our case uh blog posts products anything like that you're going to create a model here and then run the migration which will then create the table.
So there's no there's no logging in in to your database and writing raw SQL queries or anything like that. This makes it really easy. So let's create a model called ticket. And in our ticket model, we want to have uh an ID and the type for that will be int. So these are Prisma specific types. And then we can add special options. In this case, I'm going to use at id, which defines a custom primary key. Okay, so in our database, this will be uh you know a variable character and it'll be marked as a primary key.
And then we can add defaults. So at default and then I want to add an auto increment parenthesis. So that will mark it as auto increment. So that when we create our first ticket, it'll have an ID of one. Next will be two and so on. Okay. After ID, let's do a subject, which will be a string. And then let's do description, which will also be a string. Then we'll do a priority that will also be a string. And then we're going to have a status. Status will be a string. And I want to give this a default.
So the default for status is going to be a string of open and then we want created at that type is going to be a date time. And let's do a default for that which is going to be now with parenthesis. So that'll just be the current date and time. And then we'll do an updated at and that will be a date time. And we can do at updated at which will just add whatever the current date and time is for for that update. So later on we'll have a user model as well when we implement authentication.
But for now I just want to be able to create and read tickets through our project. So uh yeah we can close this up. And there's two things we have to do whenever we edit that file. Whether we create a new model or we edit fields in a current one, we have to run the migration and we have to generate the client. So let's come down to the terminal and let's do our migration with npx prisma. So whenever we run a prisma command, we're going to do npx unless it's you know installed globally. And then we want to run migrate dev-name and I'm going to call it init since it's our first one.
And what this is going to do is is look at that file. It's going to look at our ticket model and it's going to create a table in our database based on that. So now it says your database is now in sync with your schema which is good. So now let's go to Neon and I'm going to reload the tables and you'll see a migrations table as well because whenever you create a migration it puts it in here. But we can see ticket with an ID, subject, description. So all the fields that we put in our model.
So it's as easy as that to create tables. Now the second thing we have to do is run npx prisma generate. So that will generate the client. So then when we, you know, we're in our code and we use the client, we can now work with tickets. Now one other thing that we need to do is in the package.json, JSON, we want to add a post install script because when you deploy this, whether it's to Verscell or wherever, you want to generate the client on the server. So the way you do that is add a post install script and from here just simply add Prisma generate so that that runs when you install or when you deploy.
Okay. So yeah, so we should be good as far as the database goes. Now in this tab, so here I have my my npm rundev. In this one, I want to run Prisma Studio, which is going to be NPX Prisma Studio. And this is a really cool tool that will show you all your all your tables, all your data. You can see I have my ticket table, ID, subject. So obviously I have no tickets, but if I did, they would show here. So I'm going to keep this open so that we can always refer to our data if we need to.
So now that we've done that, I'm going to start working on the the actual UI. But I just want to initialize a git repository. So I'm going to open up a new tab here and let's just do git and um you also want to make sure I believe it's by default, but make sure your git ignore has the env. Yeah. So it does. All right. And then let's do get. We'll say get add all. And I'm g say get commit. Obviously, you can do this whenever you want. Uh whoops. Shoot. Didn't mean to do that.
Uh so get commit. And let's see. We'll say initial initial um I guess initial Prisma setup. Yeah, we'll just do that. All right. And I'll be making commits off camera, but like I said, you can, you know, obviously make whatever uh commits you want. Okay. So, now we're going to work on the welcome page, which is app and then page.tsx. And for this, it's just it's going to be a welcome page with two buttons. One is going to be to create a new ticket. One is going to be to to to see your tickets. Okay.
So, we're going to bring in a couple things here. Let's import link from nextlink. And then for I want to use an icon here from React icon. So, fa ticket alt. All right. And then for the JSX, it's just a bunch of tags and classes. So, I'm going to paste it in. So, for the return in parentheses, we'll go ahead and paste that. Um, oh, now one thing I did forget to do is in the global CSS, I want to change the background to EFF 6 FF, which is a light gray. Now, the reason this is dark is because of this block here.
And since I'm not using any kind of dark mode, I'm going to just get rid of that block. Okay, which will make the background light. And I haven't used the ticket yet. So, let's add that. I'm going to go right above the H1. And again, you can copy this if you want. You can type it out or you can get it from the the repo. But let's add FA ticket alt and then just a couple classes. So MX auto uh what else did I want to do? We'll do margin bottom four. And I'm going to make it red.
So text red 600. And then we can also add a size for the icons. I'm going to do 60. So, that will just add a little red icon. Now, one other thing I wanted to do is just something really simple, but I figured that it's just a nice add-on is to have these buttons fade in and up when we come to the page. So, what I'm going to do is the wrapper that is around the two links. I'm going to add to that uh a new class called animate. We'll say animate dash slide. And then I'm also going to add a Tailwind class of opacity zero, which is going to make them disappear at the moment, but then we'll fade them back in.
So we're going to do that through our global CSS file. So let's see, we'll go down here. We're going to create a new key frame. So let's say key frames, and we're going to call this slide up. And from here I want to say at 0%. So at the start of the animation I want the opacity to be zero which is just you know not there invisible. And then we're going to add a transform and then translate on the yaxis because we want it to fade up. So I want it to start 20 pixels below.
So we'll say 20 pixels. All right. Which means it's going to be pushed that the buttons will be pushed down 20 pixels. Then on 100% which is when the animation ends is where we want to add opacity one which is you know full view um no no fading at all and then transform we want to do translate y and then put it back to its original position which is zero. Okay. And then all we have to do is take our animate dash slide class that we added onto the wrapper and add the key frame into the animation property.
We want to use the slide up animation. The duration will be 0.6 seconds. We'll do an ease in and forwards. Okay, we don't want it to to repeat. We just want it to do it once. And then animation delay. I'm just going to put a very slight delay of 0.2 2 seconds. So now when I save that, you can see that the buttons now fade in and up. If I reload. Okay, so it's just a nice little addition. That's, you know, not a lot of code at all. So that's that. Now I want to work on the submit ticket page.
If I click that, it takes me to the route of, as you can see here, slashtick slashnew. Now since Nex.js JS is filebased routing. In the app folder, we're going to have a folder called tickets. And then in that, we'll have a folder called new. And then in that, we'll have a file called page.tsx. And let's do sfc. Oops. Sometimes this doesn't work. Like now, sfc tab. Okay, there we go. And let's call this new new ticket page. Whenever it's a page component, I always like to end it with page just to just preference. And just to make sure this is working, let's just say new.
And then this should show this. There we go. So we just see new on the page. And we're going to add our form here. Now to begin with, I'm just I'm just going to add the JSX just tags and classes. So I'm going to paste it in. So the return let's add some parenthesis and then paste this in. So basically we just have a div flexbox. Uh we have another div which is kind of like the the wrapper the card or whatever. Then h1 then we have a form with with an input for the subject.
Okay. Okay. So, it has name, subject, uh text area, which is going to be the description, and then a select box, which is going to be the priority of low priority, medium, and high, and then the submit button. So, let's save that. And that's what the form is going to look like. Okay. So, now we get to the fun part, which is submitting the form and making it work. Now, we're going to be doing this the most modern way in Nex.js, which is using server actions. Okay. So server actions are just files where obviously they run on the server and you can submit directly to them.
Like we can have an action attribute and submit it directly to the action just like you would with like PHP which is really cool. U and we're also going to be using a new hook called use action state. So let's start off by creating the action file for tickets which is going to be in source. Create a folder in source called actions. And then let's create a file called ticket.actions.ts. Okay. So any actions that have to do with tickets, whether it's creating one or fetching them, whatever, we're going to put that in here. So this is the backend for tickets.
Now, when you create a server action, the first thing you want to do at the very top is say use server. Okay? So that will mark it as a server action. And all a server action is is an asynchronous function. So, we're going to say async function create ticket. And this you could do an arrow function if you want, but it's it's up to you. All right. Now, this is actually going to take in the form data. And we'll give it a type of uppercase F form data because when you use an action directly, that form data is available.
So what we'll do is get let's say con subject and set that to that form data and then there's going to be a get method on that and that's how we get our data. So we want to get the data or the input that has the name of subject. Okay. So this whatever we put here has to match the name. So this so get form subject and just to please TypeScript we'll say uh let's say as string and then I'm going to do the same with the other fields. So we have the description description and then what do we got the priority so I'll just select this and this.
So, priority and then just to test it out, let's do a console log. Again, this is not There we go. Console log of the subject of the description and the priority. And then I'm going to return from this an object that has a success boolean. In this case, I'm just going to say true. So, true. And then we'll do a message and let's say ticket created successfully. I know it wasn't, but we'll just respond with that because I just want to show you how this works if you've never used actions before. And now in the new ticket page, let's bring in so import.
Let's bring in create uh create new ticket. Why isn't it showing here? Did I export it or just ticket? Import. There it is. All right. So, create ticket and then come down to the form and we should be able to just add action set that to the uh create ticket. And don't worry about this TypeScript error right now. So, let's go ahead and try this. I'm just going to show the server uh console here because when we submit it should show it down here. And I have a form filler. I'm just going to use that and then submit.
And there we go. So, we can see the subject, the um description, and then the priority, which was medium. So, that's that's just how a basic action works. Now, we're not handling the the response in any way. So, we're going to get to that, but need to use a hook that's relatively new called use action state. So, let's import that. So, use action state. And that's from React. And this is similar to use state, but instead of dealing with component state, we're dealing with the state that we get back from the action. Okay. So, the way that this is going to work and and we have to make this a client component because we're using a hook and I don't like to particularly use client components for pages.
So later on, we'll move the form itself to a client component and keep this server rendered. But just for now, we'll say use client. And then to use the use action state, we're going to come up here above the return. Let's say const. We want to pass in state and form action. Okay, so we're destructuring from use action state. And then what we pass into use action state is going to be one the the action we want to use which in our case is create ticket and then the initial state which is going to be success false and message just an empty string because remember that's what we're what we're getting back from here from the create ticket.
So we're just setting that that initially before we actually submit the form. Success obviously will be false because it hasn't been submitted and message will just be empty. Now when we use an action in use action state we have to edit the the function signature. So basically what gets passed in here. So in addition to form data we need the previous state. Okay. So first we'll be we'll say pre state then we get the form data and the previous state as far as typing is just going to be success which is a boolean and the message which is a string and then let's add the return type here for this function.
So the return is going to be a promise that will have an object with success which will be boolean and a message which will be a string. And again you don't have to if you're not using TypeScript then you don't have to uh have to add that. All right. So back over here now instead of passing create ticket in directly we want to pass this in form action because create ticket is being used through use action state. So let's change that to form action and that should clear up that error as well. Okay. And we should still be able to submit and console log.
So let's try that real quick. I'm just going to add some stuff in here with the form filler. Submit it. And we're seeing that in the console. All right. Now, I want to check I want to do some form validation. I know we used the HTML required here, but let's actually get rid of that for the text u for the description and the subject because I want to validate here. And I also want to use sentry to log um if you know if it's not valid or not which might not be something you do in real life because you probably don't want you know for everybody that misses a field or whatever you probably don't want that logged but just to show you how we can capture messages with sentry.
So let's do a check and let's say if not subject or not description or not priority. Then I want to first of all we're going to do our return and this all of our actions are always going to return a success and a message. Okay. And that's just by design. That's that's not like you don't have to do that. That's just what I'm choosing to do uh with my with my actions and how I'm handling things. So message let's say all fields are required. Okay. Now in addition to that to returning right above it I want to use sentry.
So I need to import and the way we do this is we say import all as sentry from and then at sentry/next.js JS and then I'm going to do sentry and I'm going to use a method called capture message and for that message let's say validation error colon we'll say missing uh missing ticket fields. Okay. And then when when we get our um when we get back here with the state, we can use that, right? So if there's an error, if success is not true, we probably want to show it in the form, right? So why don't we add this uh let's see, we'll go right below the H1 and open up some curly braces and let's say state message.
So if there's a message and if not state.success, okay, then we know it's an error. Then we want to show let's open up some parentheses. Then we want to show a paragraph. I'm just going to give it a class of text- red-500. And let's do margin bottom four and text uh text dash center. And then in the paragraph we can just add our state message. Okay. So whatever we're passing back here or here is going to be available in this state object and I'm using that down here. So now let's fill this in, but let's leave off the subject.
And we get all fields are required. Now this should also have gotten logged to sentry. So I'm going to go to my sentry issues. And since it's just a regular message, it's not going to show under prioritize. It's going to be under for review. And you can see our validation error missing ticket fields. And it shows the the request. So post to tickets new and it just shows the uh the breadcrumbs that lead up to it stack trace. Um now when we capture a message if we don't add uh a level manually then it's going to be info which is just that's ex just what it is.
It's just like a log. It's not looked at as an error. It's not an exception. Uh it's just a simple log. Now, we can change that. So, let's say we want to log it as a warning, which is probably what I do because it's a little more than just information. Something did go wrong, but it's not, you know, detrimental. So, let's go back to where we have our capture message. And what we can do is just add u a second argument here. And this will be these are the different levels. So, debug error fatal info, which is what it is by default, log and warning.
So, I'm going to set it to warning. So, now I'll go ahead and save that. And then I'm going to go back to my project here. Let's refresh that. And this time, let's leave off the description. Okay. So, we get all fields are required. And let's go back to Sentry and I'm just going to re refresh this. So now if I go over to my validation error now you can see that the warning the level is now warning and you can attach data to this as well which I'll get into later but I just wanted to show you that how to capture a message.
Now let's do uh u we're going to use capture exception. So if something really if a there's a problem with the server. So what we'll do is wrap everything here. We're going to do this anyway is wrap this in a try catch. So let's open a try catch and just take everything that's in the function. And we'll just cut that and then put this in the try. And then what I want to do in the catch is first off our return which I'll just copy this. So we're going to return false. Okay. And then for the message, let's just say we'll just say an error an error [Music] occurred while we'll say while creating creating the ticket.
Okay. But before we do that, before the return, we're going to do sentry. And now we're going to use capture exception. And in the capture exception, we want to pass in the error as an error. And then we can also pass data to this. So what I'm going to do is pass an object with extra. And I want the form data to be attached to this. So let's add an object with form data. And we can get that with let's say object dot and then we'll use from entries. and then we'll pass in our form data form data entries parenthesis.
Okay, so that should pass the actual form data. Now to fire this off, what we'll do is just temporarily throw an exception. So in the try at the very top, let's say throw new error. Okay. And then I'll just say simulated um simulated Prisma error for testing. And we'll save that. And then we're going to go ahead and submit our form. Let's just uh let's just refresh this. And and we don't it doesn't matter. You don't have to leave anything off or whatever. Just submit. And we get back an error occurred while creating the ticket because when we submitted it threw this error which then called the code and the catch which returns this and that this message was put in the the output.
Now this also ran so it captured the exception with sentry. So let's try let's take a look at that and let's go to issues. So now we have this in prioritize right? It's not in the for review. It's in prioritize because it's an actual error. It's an actual exception. So, let's go ahead and click on that. And we see our breadcrumbs. And then we should see, let's see, down at the bottom right here. So, additional data. We have our form data. And you can see here the subject, the priority, the description, and then we have these action keys, action ref.
So this was attached to to the exception and you can attach anything you want which will obviously help you in your you know your debugging process. Now before we do anything else I want to actually be able to submit a ticket and add it through Prisma. Now there's there's a step that maybe I should have did it earlier but we need to at least do it now and that is create uh a a special file for our Prisma client. Because if we use it as is the Prisma client asis, we could run into some issues because of the way that server side rendering works and the way that um platforms like Burcell work with Neon, which is serverless, uh meaning there's not like a continuous connection.
So we don't want a case where multiple Prisma instances are being created. Basically we want to create a global instance and then reuse that unless it's not created. Right? So we wanted to check to see if there's a a global Prisma instance. If there is, use it. If there's not, then create it. So what we're going to do is create a a new file in the source folder or new folder rather called DB. So, source db and then a file in that called prisma.ts. And then I'm going to paste this in. And you can just you can either copy it or you can grab it from the from the repo, whatever.
And it's doing what I said. It's bringing in the client. And it's bringing it in from the generated file rather than just at Prismaclient. So, this should match whatever you have in your schema. So right here let's see for this output right so source generated Prisma so that's what you want to bring the client in from here and then we're using the global this object with it which is a global JavaScript object like window for the browser or global for NodeJS and we're just assigning that Prisma client here we're checking to see if it exists so if it does set it to Prisma if it doesn't create a new Prisma instance and we're exporting it.
So from now on when we want to when we want to use the Prisma client, we want to use this. Okay, we want to bring it in from this file db/prisma, not from Prisma client and not from the generated. So let's save that. And now go back to our ticket actions where we're going to use Prisma and we're going to import let's say Prisma and that's going to be from and then at slashdb/prisma. Okay. So again whenever you're going to use the client bring it in from here and then I'm also going to import one more thing and that's going to be revalidate ca uh not cache revalidate path from next slashcache.
So that will just make sure everything's up to date. When we submit a ticket, we want to make sure our list of tickets includes that new ticket. All right. Uh for certain paths. So now let's uh let's get rid of this simulated error. So get rid of that. And then we're getting the data. We're checking it. We're sending the sentry if needed. Now what I'm going to do is go right above the return in the try block and let's say create ticket. So we'll say const ticket and set that equal to await prisma. dot and then we're going to use the create method and that's going to take in an object with data and for the data we want to send the data from the form.
So, subject, description, and priority. So, that'll create the ticket. Now, I want to capture this in sentry when we create a new ticket. And I want to create a breadcrumb, which is uh, you know, a trail of how that happens. So, we're going to say sentry dot and then add breadcrumb. And we can pass in a few things here. So, one is a category. So this has to do with tickets. So we'll have a ticket category. Let's add a message. So I'm actually going to use back ticks here because I want to include the ID.
So we'll say that the ticket was created. And then we'll just add in the ticket. ID. All right. And then we can add a level as well. So remember the different levels I showed you. This is not a warning or an error. It's just a log. So, we're going to use info. And then I want to capture a message. So, let's say sentry dot capture message. And again, we'll just use back ticks and say ticket was created su um yeah, we'll say ticket was created successfully. And then we'll have the ticket ID. And then the last thing we want to do before we return success is revalidate the path.
So we want to revalidate the slash tickets path, which doesn't exist yet, but it will. And it'll list all of our tickets. So we want to make sure that it's up to date with this latest ticket. All right. So let's try this out. I'm going to come over to new, and I'm not going to use my form filler. I'm going to type in here. Let's say ticket one. This is a sample ticket. and let's make it medium priority. Let's submit. Now, I have nothing happening when we submit. So, don't worry about that. We'll we'll redirect or show a message or something.
But let's go to um Prisma Studio. You could also use just you could check Neon directly. But if we go to Prisma Studio and reload, there it is. Ticket one. This is a sample ticket. medium for the priority and the status is open by default. And we can go to neon and reload our table and you can see that our ticket is there in the database. And we can go to uh sentry and we should have under for review ticket was created successfully. And we have our breadcrumbs here as well. And of course, this is optional, but again, it's nice to just know exactly what's happening um throughout your application.
All right. So, one thing I'd like to do though before we move on is create a helper function for this sentry stuff because I want to just have one function called log event. And that will add a breadcrumb and it will add either a message or an exception depending on if there's an error or not. If there's an error, it'll be an exception. If not, it'll be a message. So, what we can do is go into source and let's see, we'll create a folder here called utills. And in utils, let's create a file called sentry.ts.
And we want to import all as sentry from at century. Whoops. At sentry next.js. And then uh I'm going to add a type of log level. Actually, let's make that an uppercase L. So type log level. Again, if you're not using TypeScript, you don't have to do this, but it has to be either fatal or error. Oops. Or error or what's the other ones? Um, warning or info or debug. Okay, so it has to be one of these. And then let's export our function. And function. And I'm going to call this log event. Okay. Now, this is going to take in a few things.
It's going to take in the message, which will be a string. It's going to take in the category, which will be a string, and we'll give it a default of general. Okay. Then we'll do optional data. So, the question mark just means it's optional. And for the type we're going to do we're going to use record. Um so record and then in angle brackets we're going to add string and any. Now this this record means that it can be an object with any number of properties and values of any type. So this is useful for logging additional information about the event.
Um for example we can log the ticket ID or the user ID. Then we're going to take a log level. The type for that is going to be the type we just created above, which is log level. And I'm going to set the uh the default to info. And then we'll have the error, which will be optional because it could be just a message. There might not be an error. And I'm going to give that a type of unknown. All right. So now in the actual function, we're first going to create our breadcrumb. So sentry.add add breadcrumb and then we're going to pass in an object with the category.
So this stuff is all going to come from the the arguments. So category, the message, the data, and the level. Okay, it may or may not have an error. And then let's see. Um oh, you know what? This should just be level like that. So we got our breadcrumb. Then we want whether it's going to be capture message or capture exception depends on if there's an error. So let's say if there's an error then we want to call sentry capture exception and pass in the error and then any extra data. So remember we can do this extra and then pass in our data and let's say else if it's if there's not an error then we want to just capture message.
So capture message pass in message and pass in the level and that's it. Okay. So make sure that uh that this function is being exported so that we can use it and we'll close that up. And now we can use this in place of a lot of the stuff we did here. Just kind of clean it up a little bit. So in this right where we check for the fields instead of doing the capture message, let's get rid of that. And let's say actually we have to bring in log event. So just make sure you bring that in right here.
Import from utils sentry. Then we're going to log event and let's pass in the message which is going to be validation error colon and uh we'll say missing missing ticket data or not yeah we'll say missing data fields and then the second thing we're going to want to pass in is the category which is going to be ticket. Okay. Then we want to pass in the data which I'm going to pass an object with the subject, the description and the priority. And then after that we want the level which is going to be in this case warning.
Okay, so hopefully that makes sense. So passing in the message, the category, the data and the level and that's going to you know it's going to call our log event which we just created. So now let's come down here where we do the breadcrumb and the message and we can replace both of those. So we'll get rid of that and then we can just do our log event and we'll pass in a message. I'm going to use back tick and say successfully and let's add the ticket ID. So ticket ID and then after that we want the category which is ticket.
After that we want the data which I'm going to do ticket ID. set that to ticket id and then the level which we'll do info. Okay, so that'll log that. Then let's come down to the uh where we do the capture exception and the way that we for we created it we can do log event for exceptions as well. So let's say an error occurred while creating the ticket. Okay. So that's the that's that. Then we want the category. So ticket. Then we want the data which again I'm going to do the form entries or form data.
So an object and let's do from entries. And then we can pass in the form data dot entries. Okay. And then we want to pass let's see after that object. So, comma here we want to pass in the level which is going to be an error. And then we want to pass in the error itself. Okay, which comes from here. So now we can use just this one function we can use to add the breadcrumbs, capture the message, um capture the exception, add categories and all that stuff. All that good stuff. Okay. So now let's just try this one more time.
I'm going to first off try to do it without a field. So that that should throw an error or a warning rather. And then let's create another ticket. Let's say ticket ticket two. This is a sample ticket. And then I'll do low priority submit. All right. So now if we go back to Sentry should see should see created and sometimes it takes I've seen it take up to like a minute or so but we should see the next ticket. Let's just make sure it was actually submitted. So let's go to Prisma Studio and reload. Okay, so ticket two was created.
And there it is. So ticket created successfully and we can see it has an ID of two. So it took about 30 seconds to to be able to show up. So now I want to make it so that it redirects when we submit a ticket. So what we can do is we're going to do this in the in the page or the component, not the action because the action is always going to return an object with true um not true but success and message. So what we'll do is come back to the the page. So this is the new page and I want to bring in use effect and basically we we want to check that success option right and if that changes then we want to redirect because when we get a a response back that it'll send a success message or not a success message but a value.
So uh let's see we also want to bring in the use router hook. So let's import say use router and that's going to be from next navigation not from next router and then we'll go right above the return. Let's add our use effect and I'm assuming that you know how use effect works. Um we're going to pass in our dependency array and we want to look at state.success or the router. So if either of those changes and I didn't initialize the router which we have to do. So let's say const router set that to use router.
Okay. So if either of those changes then whatever I put in this use effect is going to run. And what I want to put is first of all just check for if state.success and then we want to redirect. So router.push push and we want to redirect to slash tickets. Okay, so I'm just going to refresh this tickets new. And now if I submit a new ticket, so ticket three, this is a sample ticket. And then if I submit that, I get redirected to slash tickets, which of course doesn't exist yet. So let's create that. Now, we already have in our app folder, we already have a tickets folder with the new folder, but we want to create a page.tsx directly in the app tickets folder.
So let's create page.tsx. Let's do sfc. And let's call this tickets page. And for now, we'll just say tickets. So that should render. All right. Now, before I add anything to the tickets page, I want to add a uh a toast notification when we submit a new ticket. And I'm going to use a package called Sauner. Sauner. Soner. It's s o n ne. And I've been using this lately. um rather than React Toastify, but you could use that if you want to. I mean, they all do kind of the same thing. But let's go to a terminal and I'm going to npm install s ner.
Okay. And in order to use this, we're going to add in our layout. So, if you go to your layout tsx file, we're going to import the toaster. So toaster from sauner and let's see we're going to go right under children. So right here and we're going to add toaster like that. And then I'm going to just add a position. And you can go to the documentation to see like the different attributes and positions and stuff. I'm going to do top center. Okay. So now we have an output for our toaster. Now let's go back into the new page.
Right? So this is the new ticket page and we're going to import toast. So import toast sauner. Okay. And then all I'm going to do is go to the use effect. So, right before we redirect, we're going to say toast dot success and let's say um we'll say submitted successfully. And yeah, that should do it. So, I'm going to save that. And let's change this title, this create next app. So that's going to be in the layout. So where is it? Yeah, right here. Let's change that to say quick ticket support and let's say get support for your product or whatever you want to put.
Anything's better than create next app. Okay, so let's try this out. I'm just going to put some dummy data and submit. And there we go. Tickets submitted successfully. So it shows the toast and then it goes away after a few seconds. Now before we actually work on this page, the tickets page, I want to move, like I said earlier, I want to move the form from the the new page here because I don't want this to be client uh client rendered. I want this to be server rendered and I think it's just better organization. So why don't we create a new file?
And you you could put this in a components folder, but the way I like to do this is if the form is is, you know, so attached to the page and we're not using this form anywhere else, I'm going to put it in the the new folder next to the page. So I'll create a new file here and let's call this ticket. Uh we'll say ticket-form.tsx. All right. And then I'm basically going to just copy everything from the page. paste this in here and then I'm going to change the name. So, new ticket page here and here at the bottom we're going to change to new ticket form.
Okay, so change the name of that and let's see, we're going to basically keep all this. Um, so we have our use effect, and let's see what I don't want to keep is this this div here. Let's get rid of this top div in the ticket form. So, right here, I'm going to delete that. And then, of course, we got to delete the ending div. Okay. So, we'll get rid of that. And I think that's really all we need to do. everything else is is uh you know encapsulated in this this file. So let's save that and go back to our page.
And now uh I'm just going to I'm just going to get rid of this whole thing. Let's regenerate this. So new new ticket page. And then what we can do is bring in is bring in the new ticket form, right? And let's see. I'm gonna I want that wrapping div. So, let's do some classes here. We're going to do min-h screen. And what else do we have? BG dash blue 50 flex uh items dash center justify dash center and px-4. Okay. Okay. So, we have that wrapping div. And then in there, we can put the new ticket form.
All right. So, let's save that. Let's try to go to slash ticket slash new and then we should see our form. So, the page itself is server rendered, but then we're putting in a new ticket form which is client side. So, now we want to work on getting the tickets and we're going to have an action for that. So, let's go into our ticket actions and we'll go down to the bottom here and let's say export or export async function and we'll call this get tickets. Okay, we're going to add a try catch and we're going to use Prisma here.
So let's say con tickets and set that to await and we want prisma dot ticket dot and we're going to use the find many method and I want to order by the date. So we're going to pass in here order by and we want to set let's say created at that's the field we want to use and we want it to be descending. So that will get our tickets and then let's do a log event for sentry. So just log event pass in fetched fetch ticket list. And this is totally optional. And if you want to pick and choose some of this stuff to log, you can.
Um I'm going to pass in here the count of tickets. So we can get that with tickets. length. So that'll be the extra data. And then let's make it an info level. Okay. And then we just want to return the tickets. And then if there's an error, let's log event. And we'll say error fetching tickets. And let's do the category of ticket. And I'm not going to send any data. So I'll just put an empty object. The level is error. And we want to send the actual error. And then what I want to return is just an empty array.
And that's it. So that should get our tickets. Now we want to use that in our ticket page. So let's go to our app folder tickets and then page.tsx. And then from here we're going to bring in let's say import. We want to bring in that action. So get tickets. And then we're going to also bring in our log event utility. And then let's bring in link. Okay. And then we're going to come down into our function here. And we want to use that action. So we can just simply say get uh const and then tickets set that to await get tickets and we do have to make asynchronous.
Okay, so that'll get our tickets and we can do a quick console log. I don't know why that's not working. So, console log of the tickets just to make sure that that we're getting them. So, I'm going to go to slash tickets. Oops. All right. And we should see that down here. Yep. So, there we go. We know that we're getting them from our action, which is good. So, let's get rid of that console log. And then down here, um, we're going to make this a div. And let's give it some classes. We're going to do a min h screen bg dash blue 50.
And let's do a padding padding of eight all around. Okay, let's get rid of that. And then I want to have an H1. So we'll do text 3XL. Let's do font dashbold text blue 600 margin bottom eight and let's do text dash center. And then in the H1 we'll just say support tickets. Okay, let's see if that shows up. There we go. Support tickets. I just want to make this smaller. Okay. Now under that H1, I want to check to see if there are any tickets first. So what we can do is say if the tickets.length.
So if that is equal to zero, then we'll show something else. We'll show something else. So if it's equal to zero, then I'm going to have a paragraph with let's say text dash center. Let's do text uh text dash gray 600 and we'll just say no tickets yet. Okay, so that's if there's no tickets. And then here in in these parentheses, if there are tickets, then we're going to have a class of let's do space dash on the yaxis 4. We're going to do a max width. So, max W3XL and let's do margin on the X axis auto and we'll close that sidebar up.
So, in here we want to take our tickets and we want to create a list. So, we want to map through those. So, tickets.map pass in our function and we'll say for each ticket then we want to render some JSX. So open some parenthesis. We'll have uh a div. Let's do a class of flex. We'll do justify dash between. Let's do items items dash center. Background is going to be white. And I want some rounded corners. So rounded large. We'll do a shadow. We'll do a border. And let's do a border dash gray dash two uh 200 and padding six.
Okay. So in this div um actually you know what we got to give this a key because it's a list. So for the key I'm going to use the ticket id. All right. And then just going to put a comment here. So we'll have the left side and we'll have the right side. Okay. So, for the left side, uh, we're just going to have a div. And then in that, we'll have our H2. Let's say text-XL for the size. And then font dash semibold. And let's do a text blue 600. And this is going to be the ticket subject.
So, we're going to say subject. See if that renders. There we go. Good. So, we see all the subjects and then and that's all I want on the left side. So, the right side, let's do text dash write class. And let's add some space. So, space on the yaxis too. And then let's have a div with text dash small and text dash gray- 500. And I want the priority. So we'll say priority. And then here I want to have a span. So we'll do a span with um actually we'll do the class after because I actually want it to be a dynamic class where it's a different color depending on the priority.
But for now we'll just add ticket priority. Okay. If I save that, there we go. We see the the different priorities. And then we want also want a link to go to the single individual ticket page. So that's going to go under this div, the div that wraps the priority. And let's add a link. And this is going to be let's say href use some back ticks here. Uh well back ticks in curly braces. And that's going to be slash tickets. So slash tickets slash. And then we want the ID. So, ID. And I also want to just add some classes on the this link.
Let's uh let's close it. And it'll just say view ticket. Okay. Now, as far as the classes go, I'm just going to grab them because there's quite a bit. Okay. So, those are the classes. Let's take a look. Okay, there we go. Good. Now, again, I want to just make the priority a certain color. So, I'm going to have a little helper function. I'll just put it in this file. So, I'm going to put it right here. We'll say const and let's call this get priority class, which will take in priority, which is going to be a string.
Okay. And uh Whoops. Get a set. Okay. And then I'm just going to use a switch here. So for the switch, we want to test priority. And let's say if there's a case of it being high, then uh whoops, what am I doing? high. Then we want to return a class of text- red-600. And I'm also going to just add font dashbold. And then let's do a case where it's medium. Then we'll return let's say text dash yellow. Yeah, we'll do yellow 600 font bold. And then we want a case of low. And if it's low, we're going to return text dash green-600 and font bold.
All right. So, we have our get priority class. Now, we want to use this down here. So let's see. We want to use it on the span. So right here on the span, I'm going to give it a class name. And that class name is going to be set to it's going to be dynamic. And it's going to be set to get priority class. And then we'll pass into that the priority. So if we save that now, you can see the different colors. Okay. Okay. So, we have medium, low, right? There's no high. I guess we can we can go to slash new.
Create a new ticket. Uh, I'll just say new ticket. This is a test. And we'll choose high. Submit. And there we go. So, yeah, this is starting to come together. Now, the next thing I want to work on is the single ticket page. If I click on the view ticket, it takes me to the route ticket slash and then the ID. So that's what we'll work on next. Now I I usually like to start with the action first. So let's create an action that will get an individual ticket. So let's export a function down here.
So export async function and we're going to call this get ticket by ID. And it's going to take in an ID which is going to be a string. And then first thing we'll do let's add a try catch. And then we want to get the ticket using Prisma. So we'll say ticket set that to await on Prisma. And we're going to use the find unique method. And in find unique we're going to pass in an object. We want to find where the ID is equal to the ID. Now I want to uh cast this as a number.
So I'm going to wrap this in number like that. All right. And then we'll check for the ticket. So we'll or check if not ticket. So if not ticket then I want to log a sentry event. So we'll call log event. And let's pass in a message. which will say that the ticket so ticket not found we'll give it a category of ticket and let's give it let's attach some we'll attach the ID so ticket ID set that to the ticket do ID and then we'll give it a warning type all right uh let's see oh I'm sorry this should not Ticket ID is just ID because it's getting passed in here and then we just want to return the ticket.
Now in the catch I want to log event and I'm going to just say error fetching ticket details and we'll give it the category of ticket. We'll give it for data ticket ID which is going to be ID and the level is going to be error and we want to pass the actual error and then we want to return null like that. So for the page right so we need to create a page for an individual ticket. Now this is a dynamic route. So what we can do is go into tickets right because we want it to be for instance slashtick slash one which would be the ID of one.
So we create a folder in tickets and we use brackets and then call it ID right so that's the name of the folder brackets ID the brackets just signifies that it's dynamic and this could be anything and then in that we have our page.tsx tsx. Okay. And then we're going to call this uh let's do sfc. Let's call this we'll call it ticket details page. And then for now we'll just say details. And then this should load if you go to ticket slash and then an ID like five or whatever. Okay. If I go to slash one that should still work.
So we want to bring our action in. So up at the top here, let's import get ticket by ID. Let's import our log event utility. And let's import link from next link. And then I want to also import not found. Um, so let's say not found and that's going to be from next navigation. Okay. So it'll just load the 404 if we call that. And then I'm going to um we're going to have the same colors for the priority. So we can actually copy from let's see from this page the tickets page this get priority class.
We could put this in I guess we could put this in a separate file rather than repeat ourselves. Yeah, I guess we could do that. Why don't we just let's cut it and we'll we can put it in our utils. So, let's say utils. We'll create a new file here. And I'll just call it uh we'll call it UI.ts just for little UI things like this. Okay. And then I'm going to paste that in. Let's export it. Okay. And then we'll bring that in here. This is the tickets page. So, import what is it? Get priority class.
And then we want to bring it in to this as well, our ID. Okay. Now, we can use that when needed. So, let's see this um the ticket details page. we have to get the ID from the URL, right? So, if it's one, we have to be able to get that. And the way we get that is by passing in uh first of all, let's make this async and then we're going to pass in let's say props and it's going to be params. The type will be a promise and it'll be in brackets. It's going to be an object with ID which will be a string.
Okay. And then we can dstructure from that. Let's say const from the params. So props params. We can dstructure the ID. So we're going to get that from and then await props params. That should give us the ID. In fact, we can test that out by coming down here. Let's say details. And then I'll just add ID like that. And we should see details one. If I go to details /2, then we see the two. Okay. So that's how we can get uh the ID that's in the URL. Now we want to get the ticket by using this action which takes in the ID.
So right under that let's say const set that to await and then get ticket by ID and pass in that ID. And if it's not found, so let's say if ticket, then we're going to call not found. Okay, so it'll just it load the the default not found page. And then I also just want to log event and let's say uh viewing ticket details. Again, you don't have to do this stuff. I'm just trying to get you comfortable with logging stuff to Sentry. just so you have it you know all the events that are happening.
So ticket um let's add the ID as well. So ticket ID set that to and this time it is ID and then we'll have info as the level. So now we want to obviously output the data. So, I'm going to copy the JSX because it's mostly just uh just static, you know, tags and classes. So, let's see. We'll return in parenthesis. I'll paste that in. Save it. See what that looks like. Okay. So, pretty simple. Uh we just have a couple divs. An H1. The H1 has the subject. And then we have the this div with an H2 for the description uh description label and then a paragraph with the actual description.
We have our priority using the get priority class to color it. Then we have the date the created at um and then we have a link to go back to tickets. Okay, so that's ticket two. If I go to ticket three, we see ticket three. So pretty simple. So, next thing is the navbar because right now there's not there's no way for me to get back to like the the submit page or the create page without typing it in the URL. So, we want to have some kind of navbar. Also, when we start to implement authentication, we need a login link, a register, uh a logout link.
That stuff's all going to go in the navbar. So, what we'll do is create in source. I don't believe we have a components folder yet, do we? No. So in source, I'm going to create a folder called components. And this is for UI components that you know are are common and don't have anything to do with specific pages. So let's create a file here. We'll call it navbar.tsx and sfc navbar. Okay. And just for now, I'm just going to just say nav. And where we're going to want to put this is in the layout because I want it on every single page.
So, let's go into our app folder and then the layout file. And then from here, I'm going to import the navbar. And I'm going to put this just above the children line. So, right here within the body. Let's say navbar. Save. Excuse me. Save that. And we should now see nav no matter what page we're on. So if I go to ticket slash new, we still see that nav. And the nav stuff for now I'm just going to paste in because it's it's just links. So let's go into the return here. Put some parenthesis and I'm going to paste this in.
Okay, we need to bring in link. So up at the top, let's import link from next link. And if we save that, we should see that render at the top. Okay. Now this is all squished together on small screens. But later on when we have authentication, all of this will not show at the same time. Right? If we're not logged in, then log out's not going to show. These two links aren't going to show. If we are logged in, then register and login are not going to show. So, even on small screens, everything will fit.
If you wanted to to create like a hamburger menu, you could do that, but I don't want to really waste time on that because that's kind of not really what what this is about. Um, this is kind of beyond that. So, I don't want to deal with hamburger menus. It's going to look good when after we implement authentication, which is actually what I want to do next. So, with authentication, you have a lot of different options. You can use something like next off which is very popular. Um you could use a library like Ozero um clerk.
So these are are really great libraries but I wanted to do this kind of in just a custom authentication flow using JSON web tokens rather than using a library. U I think that that stuff is is fun to do. Uh and it's not this isn't going to be very difficult. We're going to use a package called Jose, which allows us to, you know, create and sign JSON web tokens. It's very similar to the JSON web token library or package. So, we're going to use that. We're also going to use brypt to encrypt our passwords and we'll be dealing with cookies as well.
Um, because we'll have the the JSON web token stored in an HTTP only cookie. So, let's get started with that. All right. And first thing before we do anything to do with authentication, we…
Transcript truncated. Watch the full video for the complete content.
More from Traversy Media
Get daily recaps from
Traversy Media
AI-powered summaries delivered to your inbox. Save hours every week while staying fully informed.









