YouTube Deep SummaryYouTube Deep Summary

Star Extract content that makes a tangible impact on your life

Video thumbnail

Build an AI Voice Dental Assistant with Next.js & Postgres - Full SaaS Tutorial

Codesistency β€’ 2025-10-02 β€’ 417:44 minutes β€’ YouTube

πŸ€– AI-Generated Summary:

Building Dentwise: A Modern Dental Platform with AI Voice Assistant and Full-Stack Functionality

In today’s post, we’ll explore the journey of building Dentwise, a comprehensive dental platform featuring a sleek landing page, appointment booking system, admin dashboard, and an AI-powered voice assistant. This project leverages cutting-edge technologies including Next.js, Tailwind CSS, Prisma, Clerk, and VPY AI, wrapped in a seamless full-stack development experience.


Overview of Dentwise Features

Dentwise is designed to offer users a unified platform for dental care management:

  • Modern Landing Page: Eye-catching UI with gradients, animations, and responsive design.
  • Authentication: Supports sign-up/sign-in via Google, GitHub, and email/password with six-digit verification.
  • Appointment Booking: A guided three-step processβ€”select dentist, choose service/date/time, and confirm booking with email notifications.
  • Admin Dashboard: Manage doctors, appointments, and monitor analytics with real-time updates.
  • AI Voice Agent: An interactive voice assistant providing dental info, tips, and support, available on paid subscription plans.
  • Subscription Plans & Payments: Free and two paid plans with billing optimized for upgrades and prorated payments.
  • Email Notifications: Automatic appointment confirmations sent using Resend.com.
  • Version Control & Code Quality: GitHub workflows with branch management, pull requests, and AI-powered code review using CodeRabbit.
  • Deployment: Hosted on Savala, leveraging developer-friendly features and free credits.

Technology Stack

  • Frontend: Next.js (app router, server components), Tailwind CSS, ShadCN UI components, Lucide React icons.
  • Backend: Prisma ORM with PostgreSQL (Neon provider), Clerk for authentication and payments.
  • AI Integration: VPY AI for voice assistant capabilities.
  • Email: Resend.com for transactional emails.
  • Developer Tools: Git & GitHub, CodeRabbit for code reviews.
  • Hosting: Savala platform.

Development Highlights

1. Setting Up Next.js and UI Components

  • Initialized a Next.js app with TypeScript, Tailwind CSS, and ShadCN UI for ready-to-use components.
  • Customized a dark purple theme using TweakCN.
  • Created reusable components for the landing page (header, hero, pricing, footer) with responsive design and animations.

2. Authentication with Clerk

  • Integrated Clerk to handle user authentication effortlessly with pre-built UI components.
  • Supported multiple sign-in methods and email verification.
  • Synced authenticated users from Clerk to the local database using a server action, ensuring data consistency.

3. Database Design with Prisma

  • Defined models for User, Doctor, and Appointment with relations and enums (e.g., gender, appointment status).
  • Leveraged Prisma to generate type-safe database queries.
  • Implemented cascading deletes for appointments when users or doctors are removed.

4. Admin Dashboard

  • Built a secure admin page with environment variable-controlled access.
  • Features include doctor management (add/edit profiles with avatars), viewing and updating appointment statuses.
  • Used React Query (TanStack Query) for efficient data fetching and cache invalidation.
  • Added UI components for stats, welcome messages, doctor lists, and recent appointments.

5. Subscription Plans and Payments

  • Created plans (Free, AI Basic, AI Pro) in Clerk dashboard with features and pricing.
  • Integrated Clerk’s billing UI for managing subscriptions.
  • Implemented logic to check user plan status for feature access control.
  • Handled plan upgrades with prorated payments.

6. AI Voice Assistant Integration

  • Used VPY AI to develop an interactive voice assistant named Riley.
  • Configured assistant with system prompts and first messages to guide conversations about dental services.
  • Implemented client-side widget handling call lifecycle events, messages, and speech animation.
  • Enabled voice agent access exclusively for paid subscribers.

7. Appointment Booking Workflow

  • Developed a step-by-step appointment booking UI:
  • Step 1: Dentist selection from active doctors.
  • Step 2: Appointment type, date, and available time selection with real-time slot availability.
  • Step 3: Booking confirmation with summary and modification options.
  • Server actions validate and create appointments linked to users and doctors.
  • Implemented optimistic UI updates and error handling with toasts.
  • Displayed user’s upcoming and past appointments with detailed info.

8. Email Notifications with Resend.com

  • Configured transactional email sending via API route.
  • Built React email templates for appointment confirmations.
  • Ensured email delivery with domain management and API keys.
  • Sent detailed appointment info including doctor, date/time, service, and pricing.

9. Deployment with Savala

  • Prepared the app for production including Prisma generate in build scripts.
  • Deployed on Savala with environment variables configured for Clerk, database, VPY, and Resend.
  • Used Next.js’ unoptimized image setting to resolve image loading issues.
  • Verified full live functionality including authentication, booking, AI voice, admin controls, and emails.

Developer Workflow & Best Practices

  • Git Branching: Created feature branches for landing page, Prisma schema, admin page, pro page, voice page, dashboard, appointments, email sending, and deployment.
  • Pull Requests & Code Review: Leveraged CodeRabbit AI to identify improvements, errors, and best practices before merging.
  • Server Actions & Client Hooks: Followed a clean separation of server-side logic and client-side React hooks using Next.js app router conventions.
  • Environment Variables: Managed sensitive keys securely and adapted URLs for deployment.
  • UI Reusability: Utilized ShadCN components and custom components for consistency and efficiency.
  • Error Handling & Loading States: Provided user feedback during data fetching and mutations.

Conclusion

Building Dentwise has been an exciting journey integrating modern web development with AI capabilities to create a user-friendly, functional dental platform. From setting up authentication and databases to crafting admin tools and voice interactions, this project demonstrates full-stack best practices with scalable technologies.

If you’re interested, the full source code is freely available, and you can leverage this project as a foundational template for similar SaaS platforms.


Ready to Build Your Own?

Whether you’re a developer looking to expand your skills or a dental professional exploring tech solutions, building a platform like Dentwise is within your reach. Follow along with the detailed tutorials, use the recommended tools, and bring your ideas to life!


Happy coding and here’s to better dental care powered by technology!


πŸ“ Transcript (9072 entries):

In this course, we are going to build a dental platform which has an appointment booking system, a complete admin dashboard, a modern looking landing page, and a voice agent that can talk to your users and provide information in real time. On top of all these, we have features like monthly subscriptions and sending emails. Just before we jump into coding, let me show you the end result so that you know what we are building in this course. So this is a platform called Dentwise which allows users to book appointments and get access to a voice AI agent in one place. We will start by building a modern looking landing page that has beautiful backgrounds, gradients and really nice looking images. Then we will implement authentication where users can sign up with Google, GitHub or email and password combination. If they use email and password, we will send them a six-digit verification code and they will be authenticated. Once they are authenticated, they will be able to book appointments under this page. And there are three steps to complete this process. First, you would choose your dentist. Second, you would select the service with a date and time. And you're going to see if a time slot is already booked or not. And finally, you can confirm the booking. We also have the option to go back and modify our booking. So once you confirm, the appointment will be saved to the database and you're going to get an email in your inbox with the details. And under the dashboard page, you will be able to see your booking under this upcoming appointments section. Now, the most exciting feature is talking with the AI voice agent. But to be able to unlock it, you have to be using one of our paid plans which can be found under the pro page. We have one free plan and two paid plans. Once you subscribe to one of the paid plans, you will get the invoice in your inbox and you will access to the voice agent which can provide information about the services we have and you can talk with this assistant about anything related to dental health. Let's go ahead and start a call just to see how it sounds. I'll start the call. Let's give access to the microphone. >> Hi there, this is Riley, your dental assistant from Dentwise. I'm here to help you with all your dental needs. I can provide information about our service prices, give you immediate tips for dental pain or concerns, help you understand different treatment options, and share oral health prevention advice. What can I help you with today? Oh, yeah. Can you please provide me the services and fees that you have? >> Of course. I'd be happy to explain our service pricing. Here's what we offer. Regular dental checkup, $120. >> So, as you can tell, it sounds super realistic and natural. Now, once you complete this course, you can take this voice agent to the next level by adding some advanced workflows, which is something that we will talk about later in the video. When it comes to the payments, it is optimized following the best practices. To give you an example, let's say you subscribe to this $9 a month paid plan. And now you want to upgrade to the other plan that we have. In this case, you only need to pay the difference, which is $10. So these are the details that your users will thank you for. And you will learn how to implement all of these in this one single tutorial. You will also learn the best practices with Git and GitHub. This includes things like creating a branch, committing our changes, creating a pull request, and merging the changes, which is the workflow that you would like to follow when working with teammates. When it comes to the technologies, we will be using Nex.js with the app router, which includes the latest features like server components, server actions, API routes, and so on and so forth. We will use tailwind CSS and chatsen for styling. Tenstack query for data fetching. Clerk for authentication and payments. Postgress for our database. Recent for sending emails. WBY for the AI voice agents and code rabbit for the code optimizations in our pull requests. So that's the entire project. As usual, we are going to deploy it at the end so that you can put that live link on your resume. The deployment platform we are going to be using is going to be Savala, which is super easy to set up and developer friendly. We will get into the details later in the video. With all that being said, if you're ready, let's get started. The source code of this project will be free in the description. Give it a star and feel free to use it however you wish. One last quick announcement. I have a premium Udemy course with more than 120 hours. We are building all kinds of projects from React to Nex.js to Python and Go. If you're interested, there are over 5,000 students and everyone enjoys it so far. You can find the discount link in the description. Thanks for watching and let's get started. So, to get started with I have created an empty folder on my desktop called Dentwise. This is the name that I came up with. You can call this anything that you wish. Just go ahead and open up this folder in VS Code. Now we would like to initialize a NexJS application. I will open up my terminal. The shortcut is command J or control J. Okay. Now let's say MPX create next app. And normally I would say something like add latest. But in this case, let's go with a specific version so that if you're watching this video in the future, it should still work out as expected. Okay, now this is the version that we'll be using and we would like to initialize this NexJS application under the current folder, right? So we'll put dot at the end. And then this will ask us couple of different questions where we would like to use TypeScript and for the llinter I'll go with biome but you can use ESLint as well. I would say just select what I do so that we can have the exact same codebase. Then let's uh let's say we would like to use tailwind source directory app router turbo pack and we don't really want to customize the default import alias which is add and if you don't know what that is I can explain this later in the video just say no for now and then this will install all the packages and create all the files and folders that we would need. Now, while this is installing, let's see what we're going to be using next, which is going to be shaden. So, this will give us all the components that we are going to need. So, let's say if you want to use a button, we don't really need to create this from scratch. We will just paste this code to our terminal and we're going to get this button. And this is how easy it is to use it. But first, let's say go under the documentation. We would like to set this up in a Nex.js JS application and we would like to first copy this command. Now, if you have never used Nex.js, I think that's completely fine. In this video, I'll try to walk you through the basics. If you just have a basic understanding of React, I think that should be fine. And here I have a warning that says there is another latest version of Nex.js, but that's completely fine. We can skip this. Let's say clear. And I don't know why my terminal looks a little bit weird, but I'll just paste this in. MPX chat at latest and init. Okay. So, let's just use everything as default. I'll press enter. And this will do a couple of different things. So, here it says updating CSS variables under this file. Installing a dependency. And then it created this utils.ts. ts file. So we have this file where we have the popular function CN coming from shad CN. Um you don't really need to think about it too much. Basically shaden will be using it under the hood and then under the app it updated these CSS variables. Okay. Now let's say we would like to use a button. We would basically copy this and paste it. But later in the video, we'll be using most of these components. So, we don't really want to type this out every single time and say, you know, a different components like alerts or, I don't know, like table. So, instead, I would like to install all of them, all of these at once. So, how can we make that work? Well, just open up your terminal and instead of saying add button, just say add. Okay? Don't say anything else. It is going to show you the entire list. And here it says if you want to select all of them just press A. I'll press A and I'll say enter to submit. So it's going to install all these components and once it is done we should have a components folder under the source. Let's see now it is generating it I believe. Okay. So under the source we got the components the UI folder and we have all these files. So if you want to have a card component this is the code this has been written. Now don't get me wrong you don't need to update this code at all. If you just want to use the card component all you have to do let's go into the homepage and let's see how we can use it. So under the app under the page which is our homepage I'll just delete everything and let's say something like maybe just a button right we are going to import it from the components. Let's close this off and let's say click me. So in this file there is a lot of code right? So it has button variants, the button component and the export. But you don't really need to think about this at all. All you have to do just import the component and use it. Now let's run our application. Let's say clear the terminal and mpm run that. Let's visit localhost 3000. Okay. So this is the button that we have and we didn't really need to write any code at all. We just installed it from the chaten. Okay. So we'll be using these other components a lot later in the video. For now let's just try to set up different things. Right. So in our application we are going to have this beautiful purple theme. And I have grabbed this theme from a website called tweak CN. Let's type this out. So this is built for chaten. Basically they have all kinds of uh let's say themes. I'll say start customizing. And from here this is the default. And you can select any of them really. The one that I selected I believe it was the dark matter. And if you want to use this theme you would say give me the code. And here I think I'll just use the hex values. And I'll just say copy. Now we would like to uh we would like to update the index.css file. So they have this root object. Let's scroll. They have dark and they have theme inline. Right? And at the very end they also have body. So we have copied all of them. We will go under the global CSS. Let's shrink this which is the theme in line. Let's shrink root and dark. Okay. So we can delete all of them because we're just going to paste it in, right? You can grab this from the source code as well. Okay, just save. Now we should be able to have this purple theme that works out of the box. So this is how cool it is. And if you want to change the theme, you can do it at the end of this video. Just select any of them. I think this one looks pretty cool. You would just copy the code and do exactly what we have done and you would have this theme. All right. So this is how we can tweak our chat theme. Right now let's go ahead and update the title of this application. And for this we can visit the source folder under the layout. Here we can see we have the entry point right which is our root layout. So this is where we return our actual application right. So we have the children which is basically the which is basically our application. Right? So here let's update the meta data. This is the title of our application of our web app. So here I'll say dance wise. This is the name that we're going to be using. Let's say AI powered dental assistant. And then for the description, let's put something like get instant dental advice through voice calls with our AI assistant. And I'll just say available. How do we type this? Let's say available, I believe. Is this correct? I hope so. Um, here I'll just say 247. Okay. So, we can just update them like customize it. But this is what I'll have. This is our title, the description, and we're just going to leave everything as it is. This file might look pretty uh pretty basic at this point. We are basically getting some fonts. This is what Nex.js does by default. Um, and to be able to use these, you would just say under the body class name, you know, the variable, whatever that is, dot variable. And we're using both of these. Okay. So, that's our layout set up. And let's check it out. Now, we have this title as well as the description. You can see it under the page source. Let's just say description. I'll just say Ctrl F. Okay, here we can see we got this content as our description. So that means it is actually working. Now you might be wondering how do we create different pages. So by default this is the homepage because it is directly under the app folder, right? This is a special folder in NexJS. Let me try to create an about page. So if you want to create an about page, you would first create folder and then you would put this special file name. So it has to be called page.tsx or if you're using JavaScript, that would be JSX. And now I'll just say RFCE. And to be able to get this snippet, I'm using this extension. Probably all of you guys already have it, but if you don't have if you don't have this, go ahead and install it. And then you should be able to say RFC and get the snippet. Now let's say this is the about page. Save and test it out. We'll go to slash about. And here we go. It is working. Now if you want to have about slash I don't know like test. Currently it is 404 but if you would like to create that it would be nested under this folder. So you would say test and then again you would create the page.tsx into this folder. Right? So here you would say page.tsx and you get the point. Like you're going to learn a lot more about these folder structures in the upcoming minutes. But for now let's try to delete this folder because we are not going to need it. Um I think now we can set up the authentication. For this we're going to be using clerk. So, you can find the link in the description. Go ahead and create an account. It is completely free to get started with up to 10,000 monthly users. You don't really need to pay anything, which is pretty generous. And this is going to give us all kinds of components like these that you can see on the screen without you doing too much work. So like we will set up the authentication in seconds so that we can actually focus on our application, right? We don't really want to spend 5 hours setting up the authentication. So just go ahead and sign in. This is what I'll be doing. Let me just sign in with my Google account. You can use GitHub as well. Basically from the dashboard just say create an application and then we can um you can like add all kinds of signin options like Google username, email, GitHub. Let's just have Google and GitHub as well as the email address. But if you want you can add more. It'll just work out of the box. So these are the ones that I'll have. For the application name I'll just say dent. Let's say create application and they have all kinds of SDKs. We'll be using Nex.js. So we have like we have to get the first command and paste it to our terminal. So I'll just kill this. Clear paste this in mpm install cler next.js and then we would like to set up the env file. I'll copy this. go under the I think under the source it should be or actually in the root I'll just create the env paste this in where we have the publishable key and then the secret key and then we would like to create the middleware.ts TS file which is something that clerk uses under the hood and since we are using the source folder it should be within this folder right so let's go ahead and just say middleware where yes and paste this in if you are not using the source folder it should be under the root and this is what they say as well if you just read it and then we can basically make this work it says go under the layout and wrap your application. Well, I don't really know why it is not visible. Let's refresh. Okay, I think this is not really pretty visible. I don't know why this is the case. Normally, it it was pretty clean and crisp, but basically, let's just test it out here. We'll go under the app, under the layout, and we're going to wrap everything with let's say clerk provider component. and let's try to import it. I'll just copy this entire thing. Okay. So, we're using the clerk provider, right? Which is coming from clerk next.js. So, since we wrapped our entire application with this, we can use clerk components within our entire application. So, let's go into the homepage. Let me delete this. I'll just have a div for now. Let's say on page. This could be within an H1. And then let's put a sign up button which is coming from clerk. And this could be like it could be self-closed or an actual full component. Let's say sign up. And let's go and check this out under the homepage. I think we are not running the app. Okay, let's wait for this to be loaded. Okay, so this is the sign up button. That looks ugly, but we are going to make it look nice. If you click to it, it should take you to the sign up page and you can sign up easily. But instead of going into this page, we can make this work like a model. So here I'll say mode should be equal to model. I believe by default it is redirect. But now let's say it is going to be model. Here we go. Let me just zoom out. It is working pretty clean, right? We have this animation. And let's try to sign up with our Google account. Let me select my account. And currently under the users, we don't really have anything, right? Okay, we just signed up. Let's see. It should fetch them. Maybe just refresh this page. Here we go. This user has just signed up now. We signed up, but we still see this component, right? So, we can get rid of this. I will say signed out which is a component that Flor has. So this should be coming from Nex.js. Let's import it. Okay. So this basically says if user is signed out, show them a sign up button. And we can do the same thing. Let's say if user is signed in, show them a sign out button, right? Let's say sign up button and just display it. Let's say log out. Okay, so at this point this feels like an English sentence, right? Let's try to see. Since we are signed in, we see the log out button. If I click to it, now we are logged out. We can see the sign up button. So this is how easy it is to get started with Clerk with their authentication system. Now later in the video we'll be using the subscriptions which is for billing but for now let's just leave it for the upcoming sections. Of course we're going to make the UI look very nice. We're going to have these beautiful buttons that are not going to look like this. But for now we're just trying to set up our codebase. And while we are setting up the env file, let's go ahead and get the connection string for our database which is going to be coming from posgress right. So let's say pg which stands for posgress and our posgress provider is going to be neon. You can find the link in the description. Again they have free plan so you don't really need to worry about by paying or setting up your credit card. It is completely free to get started with and we'll go ahead and set up a Postgress database in seconds. Okay, so I already have an account. I'll just go ahead and log in and then we are going to create a project. So let me just zoom in. We'll say create a project. Uh for the name I'll just say Dwise and I will leave everything as it is. You can update the region uh but I'll just leave it as it is. Let's say create. Then I can zoom out. Now we'll just say connect to your database. Let's say connect which is going to give us a connection string. Now we are going to be using Prisma later in the video. So let's say select the Prisma. Um first get the environment variable which is this one. Now here they have another comment. They say if you're using the version Prisma that is less than 5.10 copy this one but we're going to be using a version above this. So go ahead copy the very first uh database URL variable. Okay. So copy it paste this in and that's all we have to do at the moment. We just got our connection string for our Postgress database. Later in the video we're going to set this up with Prisma. So you will see how that work. And the very last thing that we need to set up is our voice agent which is going to be coming from Bobby. Again you can find the link in the description and it is completely free to get started with. So you don't really need to pay anything to be able to follow along. Okay. So I'll just go ahead and log in. I already have an account. So let me just say open the dashboard. So I am in the dashboard now. And by default, you would have $10 of credit, right? I've used a ton and I still have the half of it. So, uh I think by default, you will have something like 10 credits down here. Now, let's go ahead and create an assistant. This might look a little bit confused because they have bunch of different things, but it is actually pretty uh like beginner friendly. First, we would like to create an assistant. So here just select the assistance and let's say create an assistant. Now they have different templates but we would like to start with a with a blank template right for the assistant name. So we can just say something like dentwise or we can put anything really. I'll just say blank template and create the assistant. So later in the video we are going to put the system prompt like what should be the first message when when the voice agent starts talking right it's going to say hey this is me from Dentwise I can help you with this this this stuff like that right now just go ahead and create it and for now just copy the assistant ID and we're going to paste this into thev file so right after the database URL I'll say next underscore public and I'll say bobby assistant id and just paste this in. All right, so these are the things that we'll be using later in the video which is set we just set that up under the env file. Okay, so we'll get into this dashboard in detail in the upcoming sections. Okay, so I think that's it for this section where we set it up Tailwind, we set it up chaten, Nex.js, we have some components, right? All of them coming from shaden, we have clerk authentication working as expected. UI is not really looking great, but that's fine. We are just getting started with it. So we can go ahead and actually create a git repository, right? And push this to GitHub. So here I am in my GitHub account. I just said create a new repository. For the name I'll just say then twice and then I'll leave description empty. And for now I'll make it private. Once I publish this video it's going to be public but for now uh it'll just be private. Okay. So let's say create the repository and let's commit our changes first. So here I'll just close everything. Here we can see the node modules have been ignored as well as the env file right we can just push everything to GitHub and let's go ahead click to this just say stage all the changes so first we're going to stage them and let's put a message like project setup and then I'll just say commit and which branch is this let's see I'll just command shiftp toggle status this bar. Okay, so by default it is main. That is fine. We just have our very first commit. Let's go ahead copy this which says push an existing repository from the command line. So we have an existing repository, right? We would like to push this to GitHub. Let's kill the terminal. Say clear. And by the way, you can kill this with command C, right? Or control C. I'll paste this in. Run this and let's refresh this page. Okay, so this is our source code that is right here. But now it's it's been pushed to GitHub. Now that was the master branch, right? Um from here on out in every upcoming sections we are going to create a new branch and create pull requests and then merge them which is a real world implementation. Okay, so just keep that in mind. In the upcoming sections, we are going to create new branches just like as if we are in a company, right? We we have different teammates, we would create a new branch and send pull request. If they like it, they would merge that. But if there is something missing, they would add a comment where we need to have another commit. Right? I know that this sounds a little bit weird or complicated, but I think we're going to see it in detail in the upcoming sections. All right, so pause the video, take a look at the codebase. If there is anything that you didn't really understand, you can ask it in the Discord server or under the comments. But I think everything should make sense so far. And with that, I'll see you in the next section. And actually, just one more thing before we end this section. At the end of this course, we are going to deploy our project using Savala. And currently, they give you $50 pre credit if you use the link in the description. Go ahead and get them before they expire. So, with that, hopefully I'll see you in the next section. In this section, we are going to get started with the landing page that we're going to have. So, this is mostly just UI, right? Some designs. So, we're going to have a header component. We will have the hero section, couple of more sections like pricing and at the very end we're going to have a footer component. So let's see how we can build them. These are the images that I will provide you. You can find it from the source code. So let's go ahead and actually get these images. Find the source code. The link will be in the description and go under the public folder. You're going to see a couple of different files. So, let me actually copy them and paste it right here. So, you're going to see the audio.png brain calendar confused robot, right? And then we'll have this call to action email sent. And then we'll have two more which is hero and logo. So, this is the one that we're going to be using for the hero section. And then our dent wise logo. I have generated all of them with AI with chat GPT. So you can use any of them and even you can change it if you really wanted to. Now let's go under the source under the components. I will create one more folder called landing and we're going to put all the components of the landing page under this folder. Right now let's go into the homepage and basically we can delete everything that we have here. Under the return statement, we're going to put our components. So, first I'll just return a div. And let's say this is going to have class name of minimum height screen, let's say. And then the background is going to be this background variable color that we are using. And then we can basically put all of our components. So let's say header, which is something that we are going to create. Let's say we are going to have the hero section. And I'll duplicate this. We'll have how it works. Duplicate it. We will have what to ask. And then we will have a pricing section. Let's say then we're going to have a call to action. Finally, the footer component. So let's try to create every single one of these one by one. Under the landing, you will have header.tsx. uh tsx let's say rfce save and this should be it and just import it make sure it is coming from the components okay so we're not using the button we're not using any of these let's delete them copy the hero let's do the same thing so I'll do the same thing for all of these I can just pause the video create all of them and then come back to the video And as I create them, I'll just import one by one from these specific files. Okay. So, as you can tell, I have created the rest of them and basically just imported in these uh in this file. Now, let's go ahead take a look at it. Under the homepage, um I think we need to run the application. I'll say clear this up. MPM rundev and just refresh. And first we're going to get started with the header component. Okay. Now in our application we will be using the dark mode. Right? So we'll just have dark mode only. As you can tell everything is just black in this application. So to be able to make that work we can visit the layout file under the root. So source app and visit layout.tsx file. And here under the body, we'll just say add the dark class all the time. Now, if you save, hopefully everything should be in the dark mode by default. Okay, now let's get started with the header component. So, I'll go ahead close this, open this up, and get started with it. So, just before we get into coding, let's pretty quickly see the end result. So, we basically have three different sections, right? On the left, we have the logo with the image. We have, let's say, three different links and then two different buttons. So, these are going to be the clerk buttons and we're going to see how to make them look this beautiful. Okay, so let's get started with it. This is going to be a nav component. I'll say nav. And we would like to give some classes. I'll say class name. We want this to be fixed to the top. And let's say right of zero and I'll say zind index of 50. So it is always at the top. So at the top of everything let's say petting x of six from the horizontal direction. Let's say petting y of two. And then we'll say border bottom. And let's say border this is the color that we will give which is border. And let's change the opacity by 50%. And let's say pg dash background. I'd like to update this by changing the opacity with 80. And then I'll just say backdrop blur medium. And you're going to see what this does. And let's say height will be 16, which is like 64 pixels. Okay. Within this, we can have a div. Let's say this is going to have a maximum width. Let's say class name max width of 6x large. And you can see what that equals to which is 15 I mean 1152 pixels. And then we'll say something like MX auto so that it is centered correctly or nicely. We'll say flex justify between and items centered right items center. Then we can have a link component which is going to be coming from next link. Let's say this will take us to the home screen. when we click to it. I think this should be h right not two. Um let's say class name will be flex item centered and we can give some gap like two. Now this length will have the image which is going to be our logo and let's say this is going to be self-closed coming from next image. Um here the source is going to be under the public logo.png PNG and we would like to give some alt let's say dent wise logo and then let's give a width of let's say 32 height of 32 as well and class name I'll just say width of 11 and right after this I'll have a span let's say dent y which is our text say class name we can make it bold bold or in this case I'll go with semib bold and let's say text will be large which is 18 pixels and let's see the output as we build it okay so we're just getting there step by step let's go ahead right after the link we're going to put the div so this will be hidden okay but medium screens and above it is going to be visible okay so this is how we can make responsive design medium screens and above it should be visible but if it is less than medium screens it's going to be hidden and let's say items will be centered let's say gap of eight and we'll just give all this space then we can have a blank in this case I'll use href and this is not going to take us to any place you know this is just for just to make UI look nice um let's give some classes you can always customize this. By the way, I'm just here to build a beautiful UI and that's it. Let's say text muted foreground and then we'll say on hover we can update the text to be just foreground. And this is going to say how it works. Okay. So let's save delete this empty space and we can basically duplicate this twice. So this will be pricing and everything will be the same. And for this one, we'll just say above. Okay. So just go outside of this div. We're going to put one more div that will have our buttons. So let's say class name, legs, items will be centered, gap will be three, let's say. And then we can have a sign in button which will be coming from clerk. And let's try to duplicate this. This time it'll be sign up button. And just make sure that you import them from color conjs. Okay. Now here I'll just say sign in and sign up. And let's see the output. Okay. So this is not really working properly. Let's see why this is the case. We just set fixed top zero, right zero and let's say left zero as well so that it is centered. Okay. Um here there's an overflow that we're going to fix but other than this it is working correctly. Now these buttons should look like this right? It should look like an actual button. So if you want to customize it, you can go here and create your actual buttons. Right? For this, we'll be using button coming from chaten. Um just go ahead import it from the UI button file. And here we can just say login and you can pass the variant. Let's say this is going to be ghost. These are all the options. We'll use ghost for this one. And we can say size will be small. Let's do the same thing for the sign up button. Copy this. Paste this in. And here I'll just say sign up. Um I'll just leave the variant as it is. Like I'll delete that. We'll use the default. And size will be small. Now let's see. Okay. So this is the exact same output that we want to have, right? Okay. So if you click it, that would take you to the signin page. but instead we would like to have the mode to be equal to model. Let's copy this and paste it right here. So now if you click to them you're going to see the model. Now one thing that I don't really like is that if you hover over a button the cursor is not a pointer. This is because of Tailwind CSS version 4. If you want to update it you can go under the global.css CSS and I'll just paste four lines of code. Let me copy it and paste it. You can grab this from the source code under the global.css. Basically, this will add cursor pointer to buttons by default. So, under the button, this is the trick that we do. And this is coming from sha uh tailwind tailwind CSS documentation. just say this is coming from payment documentation. So this is where I copied and pasted. Okay. So now we have this cursor pointer as you can see. But now let's get into the hero section. So as you can tell this is looking absolutely beautiful which means we might need to copy and paste some of the parts like the create background right or some of these decorative elements. So what I would suggest you to do go ahead visit the source code and have this hero file open because we will just copy and paste couple of different things so that we so that we don't really need to waste too much time. Okay, I hope that makes sense. Just have the source code open as we build this component. So, we just built the header. Now, it's time to build the hero section. First, I'll delete everything that we have inside. And this is going to be a section element. Okay. Now, let me just try to disable the AI that I have, otherwise it's going to get a little bit annoying. Okay. And to be able to get this pallet, I'm just pressing commandshiftp or control shiftp then type like toggle status bar visibility and it should toggle the state. Okay. Now let's say the class name for this will be relative. Height will be screen. Let's say items centered but maybe first flex and then I'll say overflow of hidden. Then we can just say padding top of 20. Okay, within this we would like to have the grid background. So this is something that I have generated with AI. So I'll copy and paste it because like if you delete this part and tell me to type it, I cannot really do it and I don't really mind it because AI can build it for me in seconds. So this is how I am using AI every single every single day to get this kind of unnecessary work done for me. Okay, copy and paste this from the source code and I can add a comment like grid background. Um, let's see if we can see anything. Okay, so this is that grid background. It is just fading away as it goes to the bottom. And then let's see what we want to add. Right after this grid background above the section, I will put some gradient. let's say orbs which are going to look like these. So this one and this one again this is something that I have generated with AI. So I'll just paste this in. Basically it is like positioning absolutely giving some width and height and the background color rounded full and making it blurred. Right. So we have two of them that look like these. Okay. So these are the things that we just copied and pasted. Let's go ahead create a div just above the section. Let me scroll down. This will have the class name of relative Z10 with full and the petting x of six. Then we'll have one more div. Let's say this is going to be the maximum width of 7 x large. Let's grab this. center this beautifully with MX auto. And within this, I'll use the div with the grid class. Now, what we're going to be doing is this. We will have basically two grid items. So, this is the first one, the left hand side and the right hand side, right? So, let's go ahead and see how that work. We'll say grid larger screens and above. This should be grid columns of two and gap of 16. And we can center them vertically with items center. And then let's say we will have the left content. If you don't want to type this out, you can copy and paste it from the source code. But if you want to have it, let's just do it step by step. So first I'll say I'll say class name of space y of 10. We're just going to give some spacing. As you can tell from the y direction and within this we'll have one more div and this will have the class name space y of six. Okay. So this will have two different elements. The first one is the badge. So let's say badge and it is this one. This is what we would like to build here. This will be the div with a lot of classes. Let's say this is going to get the inline flex item centered not being checked but let's say items will be centered. Then we can say gap of two petting x of four y of two background gradient to right. So this is how we can use it. And then we'll say from this color just primary 10 to let's say primary five. We are just updating the opacity. And then let's say round it full. It is a circle border say border color is going to be primary. And we can update the opacity. I'll be using 20. And then let's say backdrop blur small. Okay. So, within this div, we are going to have this beautiful circle, right? This a tiny thing. And we're going to make this to be like pulsing. Um, let's see what that means. I'll basically put it right here. So, it's going to have tiny width and height. It's going to get this background. It'll be a circle. And we'll make it to be, you know, animating. And you're going to see this in a second. and just have a span that says AI-P powered dental assistant. And for the class names, let's say text will be small. Um, we'll put the font to be medium. And finally, text color will be primary. Okay. So, this is what we have at the moment. Let's try to zoom in. And as you can tell, this is like animating. And as we put more and more content, this should go up. Now, let's build the rest of the left hand side. So, this is not a CSS class, right? This is not a CSS course. Maybe we can speed up this process a bit. What I'll be doing is just copy and paste step by step and just show you the entire code. Okay. So, this is what we had the badge, right? can shrink this and go right below to it. We will have a main heading. Okay, so this is what I just pasted. Let me show you. It is this entire H1. So we have all these classes just to make it responsive and like bold have the uh letter spacing a bit more tight. Then we have this span that says your dental. Then we give one line break questions and then answer it instantly text. So let's take a look at the end result. It should look like this. So there is literally nothing fancy going on here. Maybe the only thing that you can take a look at is this color where we go from this one to that one which is like a gradient and it is coming from this span. So background gradient to right from this color to that one. And to make it work, you need to add these two classes. Okay. So that's the entire thing. Let's shrink the main heading. Right after this one, we're going to put kind of like subtitle or like description, however you would like to call it. This is going to be a P tag. Let's paste this in. pretty basic CSS classes and that's what we have and right after this text we can put our buttons which are going to have like icons right one on this one and the other one here and for this we are going to be using lucid react say lucid react and this is something that chats already uses so we don't really need to install it so they have millions of different icons that we are going to be using let's see how we can just make that work. Right after this P tag, we had this div, right? Go below to it. So, we'll say CTA buttons. And let's say we're going to have a div. This is the parent. Class name will be flex. Flex of column. But in the small screens and above, they're going to be side by side. So, we can say flex direction will be row. And let's say gap of four so that they have some spacing. And we're going to make them to be sign up button. Let's say sign up button which are going to be coming from clerk. So import this one. Within this we'll have our button coming from chat cn. So just import that. Um here we'll say try voice chat or let's say try voice agent and for the button I think I'll just say size will be large and we would like to use an icon. So here I'll say make icon imported from blueid react and just self close it. Okay, so these are all the imports that we have. For the class name, I'll just say margin right of two, size of five. Okay, let's copy this and paste it right here. I'll just make the mode to be model. Let's do the same thing for this button. And the icon will be let's say calendar icon coming from Lucid React. Text is going to be book appointment. Okay. Now we can save and take a look at the end result. Well, let's make the colors to look like this. Right? We are going to change the variation on this one. I'll say variant could be outline. Okay. Now like they look exactly like this one. And if you click to them that would show you the model where you can basically sign up. Now we can even change the theme of this model so that it is looking like our actual application. Right? It is purple. It has the same styles. And to be able to make it work, we need to visit the layout.tsx file. And under this clerk provider, we will add the appearance, which is going to be an object. And we'll say variables. Again, this is another object. And we can say color primary. And we're going to use our variables. For this one, it'll be hash. let's say E78 A53 and the rest of it I will just grab it from the source code which is four lines of code and put a comma. Okay, now this should be working out. If you click to here as you can tell we get the um the purple theme or sorry the orange theme right not the purple and it even says which one you use the last which is pretty cool. And finally on this left hand side we would like to put some testimonials right we're going to have five different user images and then this rating. So let's see how we can make that work. First we'll go under the hero.tsx. Let's go under this sign up button and under this div which are the CTA buttons. Um we'll just say user testimmonials and then we can have a div. Let's say class name is going to be padding top of eight. So we have some spacing and we'll have one more div within this. Let's say class name flex item centered say items center gap of six. And then now let's say the actual user avatars which are the images right um let me fix the typo avatars. Within this I'll have one div and we'll say class name will be flex and we'll say space x of three but we'll put minus at the beginning so that we have this um we have this effect right every single one of them will go to the left a bit so that like it is on top of each other and it looks pretty pretty fine right um here we will use the image component from next image And for the source, I'll be getting it from the Unsplash. What you can do, install the image, put it under the public. I think that would be maybe a better option, but in this case, I'll just have it just like this. Okay, so this is coming from Unsplash. And you can grab this from the source code if you don't want to type this out, which probably you don't you don't want to. This is insane. Just copy it and paste it from the source code. So, right after this image, I will put another one because we'll have five different images. Let me copy the three the rest the rest of them, right? Okay. So, in total, we have five different images. They are almost the same. The only thing that changes is the source and the alt. Okay. Now, one thing, if you take a look at the browser, it's going to crash because it says you're getting some images from Unsplash, but you didn't configure it under the next config. So, let's go under this file, and we're going to add our configuration into this object. So, let's say for the images, we would like to update this object where the remote patterns is going to be an array of objects. So let's say protocol protocol will be https and let's say host name is going to be images unsplash.com. So basically this says allow all the images are coming from unsplash to be optimized. Okay. Now we can see we got all these let me zoom in all these images with the speedful effect. Okay. Right next to it, we're going to put five stars and a text. Okay. So, let's scroll to the bottom. So, right after this latest image and this div, just go outside of it and I'll say rating and stats. And instead of typing this out, it is like 10 lines of code. I'll paste and walk through it. So we'll import the star icon. Basically we create five different of them which is height and width of four. And instead of doing this you can also say size of four. It is basically the exact same thing. And we're going to fill this in with this color and this is the text color and the rest is the content. And here we go. This is the output. So that was the entire left hand side. I think this is looking pretty cool. Um, now let's try to build the right hand side, which is just our image. And maybe we can put some of these, how do we call them? Like gradient decoratives right here. I'll just go ahead. Let's find the left hand side, which was this, the left content. Go outside of it, and we're going to put the right content. Now say content, which is our hero image. And this is going to get the div with the class name of let's say relative and let's say petting left will be eight only in the large screens and above. So within this div we're going to have one more div I believe. Let's say this is our decorative elements. I'll just copy and paste them. So here under this parent let's paste these. Grab them from the source code. I'll say uh maybe just gradient orbs. Okay, so these are the elements and then right below to it we're going to put our image. This is going to be self closed coming from next image. Source is going to be /hero.png. Um for the width I'll say 600. Let's duplicate it. height will be the same. Let's give it an old tag. Let's say um maybe dentwise AI. And finally class name of full height. Let's say auto. Okay, I think this should be fine. Now let's see how that would look like. Okay, so that's pretty much it for the hero section. It looks exactly the same as what we have here in the end result. Now in the next part, we would like to build the how it works section, right? So this is something that we can build next. So here under the homepage, we will get the how it works component and keep building it. So here for the very first div let's actually convert this to be a section and then for the class names we'll just say something like relative let's say petting y of 32 x of six we'll say overflow should be hidden and then zindex of 10 I think maximum width of 7 xar and mx odo okay within this section we're going to have the header component first. So let's say this will be a div with the class name of text center and margin bottom of 20. Let's see where we are building at the moment. Okay, so it is this header section. Once we build it, we're going to build these three different steps, right? So let's first get this one. Within this, I'll have a div with some class names. Let me copy it and paste it. You can always type this out. I just don't want to waste any time because I have already I have already done this, right? I have written this already. So, you can pause the video, take a look at it. This will give some different colors, make it centered, things like that. And within this, we're going to have a zap icon which will look like this. This is coming from lucid react and this will take the class name let's say size of four and text will be primary right after this I'll put a span let's say simple process and let's give some classes I'll say text will be small font will be medium and then I think text color can be just primary as well okay save it And right after this span and after this div we'll go ahead create an H2 that is going to be this part. So let me provide this to you. It is actually like seven lines of code maybe. So these are all the classes to make it a little bit more responsive. A span and another one. And in the middle we have a line break. Right after this H2, we'll have a P tag. Let's say class name. Um, let's say text will be X large. Text will be muted foreground. Max width 3x large, which is equal to like 768 pixels. Let's say MX auto. And I think for the leading, we can just say relaxed. And if you hover over this, you can see what that does. It'll make the line height to be equal to this value. And for the content, you can put anything but this is what I will have. Okay. So that's the content that I will just provide. Then after this div. So that was our header component, right? Let's shrink this. We'll go ahead and put our steps. Let's see the current output. Okay. So this is what we have just built. Now it is time to build these steps one by one. So once we build one of them we can just copy and paste and change the step the icon as well as the content right the styles are almost the same. And here I don't know if it is visible from the video but there is a connection line right it goes like from here all the way up to until the end. Let's create that first. So here within these steps let's first create a div. Let's say class name will be relative and then I'll just say connection line and I'll paste this in. This is coming from source code. We'll just have this connection line. Okay. Now let's try to build the steps one by one. I'll have a div. This will use the grid layout. So I'll say grid larger screens and above. We can just say grid colums of three and then I'll say gap of 12 and larger larger screens and above gap could be something like eight. Okay. Now let's say step one the very first step. This is going to be div class name of relative and let's say it's going to get the group class. Um within this we will have one more div again with a lot of classes and I would say just go ahead find the step one from the source code and just paste it. Now again this is not really a CSS course so let's not waste 2 hours 5 hours writing all these. So I'll just cut this. Okay, from the source code, find the step one. Copy that div and paste this in and import the image from next image. We are using audio.png. A lot of class names, but let's see how that look. Okay, so this is the very first step. When you hover over this styling changes, I think it looks pretty cool, but we don't really want to waste too much time. This is just styling. I hope you guys don't mind it. So that was the step one. Just uh let's do the same thing for step two as well. Here I'll go ahead say step two. Oops. What have I done? Let's say step two and I will paste it from the source code for the image. This time we are using brain.png. Let's save and let's get the step three as well. I am copying it from the source code and I'll just paste it. Okay, so imagine typing all these. I think it would easily take more than 20 minutes. But here we go. We have the exact same output as we want to have. Now let's put this button. And I think that should be it. So we got the three different steps. Let's get outside of it. So, shrink the steps divot CTA. And let's try to have a div. And this div is going to have the class name of text center and let's say margin top of 16. And I'll just get the sign up button which is going to be our beautiful button coming from chaten. and the icon from blue react. Okay, let's see how that look like. If you clicked it, you can get started by signing up. So that's why it is a sign up button in the first place. Okay, so that's it for the how it works section as well. I know that we have copied and pasted around 100 lines of code which were the steps, but I think that's completely fine. So we can basically get rid of this landing page as quickly as possible, right? Just build this entire page as quickly as possible so that so that we can get into the actual implementation like connecting to the voice agent, setting up our database, you know, sending emails, booking appointments, things like that. All right, so now we need to build the next section which is what to ask. So this is what I called it. This is the what to ask section and we're going to be building it under this component. Now just before we build it, let's take a look at the end result and analyze the UI elements that we have. So this is the component that we are going to be building. And this part is exactly the same what we have just built above, right? So this is the exact same thing with the badge, the title and then the description. So the content is just different and the icon. So we can just copy and paste that part. And the rest is also similar. Basically we have a left hand side and a right hand side just like what we had in the hero section. So instead of typing this out the entire section, I'll just copy and paste. Oops. So we'll just copy and paste this. If you want to, you can take a look at the source code. So, head over to what to ask.tsx file, copy the entire thing and just paste this in, which is like 150 lines of code. Um, if you wanted to, you can pause the video and just try to see the entire classes and try to learn by reading the code. But in this case, I'll just copy and paste and see the end result. Okay, so this is what we have at the moment for this section. So let's not waste any time. Please don't get mad just because we copy and paste. At this point, we are just duplicating the same classes over and over again. Okay, now we can go into the pricing section. And again, I'd like to just copy and paste it from the source code. Uh because this is only UI, it doesn't contain any logic at all. Let's just save. So this is the entire code and see the output. So right after this section we have the pricing section. Just like previously we are using the crit background like in the hero section. This this is like a little bit different uh but the like idea is the same. Then we have three different cards. One of them is highlighted because this is the most popular plan. Um, so yeah, that's the entire thing. Again, please don't get mad just because we copy and paste. I just want to complete this entire homepage as quickly as possible so that we can get into the actual logic. Okay. Now, let's get the call to action section as well, which is this part. Again, we have a left hand side and then a right hand side. So, I will go into the source code. I will find the call to action file which is this one and I'll delete everything. Paste that in. It is less than 100 lines of code. Okay, let's see. This is like this is the exact same thing that we have in the demo. And finally, let's get the footer element. So under the footer.tsx tsx under the source code. Just go ahead copy and paste and it should be working out as expected. All right, so that's it for the entire landing page. I know that it was a little bit annoying to copy and paste at all, but I see a lot of comments where people say just don't spend too much time on styling rather just copy and paste, you know, just handle UI as quickly as possible so that we can see the actual full stack development and that's what we try to do. We have a really nice looking landing page. I think we built this in in 1 hour or something, right? Um in the next sections we can get started with the database connections you know creating a schema explaining more about Prisma handling the payments things like that. Okay. So I hope you enjoyed this entire section. Just before we end it let's add a commit create a new branch and publish our changes. So to create a new branch let's go into the VS Code. close everything and I'll press commandshiftp and I'll just say toggle status bar visibility. Okay, so from here let's just say create a new branch and for the name I'll just say landing page. You can call anything but this is the name that I'll be going with. Now the branch has been created right we would like to stage uh stage all of our changes and let's put a commit message. I'll say something like blending page completed and we'll just say commit this change. Now this has been committed. Let's say publish this branch so that it's going to be pushed to GitHub. Let's say then twice like find the uh source code on the GitHub. Okay. So here we can see just two seconds ago we got a recent pull request. So let's say go ahead create this pull request and here it says you are putting all your changes from landing to the master branch. This is what we would like to do right. So let's pretty quickly see this. Um here basically we had our master branch right we had some commits in the past. Now we just created a new commit but we have created this under the separate branch and we called this as landing page right so that was our branch now we are done with it we committed our change now we would like to take all these changes and put it into the master right because like we are done with it and we want to have this in in the actual code in the actual branch so this is what we are we just send a pull request. So I'll say create the pull request and imagine you have another teammate in your team which is the senior developer. He will come here and take a look at the commit. Um it'll just see all the changes that you have. So like they're going to see you have updated the next config.ts, you have added all these files and they will analyze your code. If they don't like it, they can add a comment like please fix this part. Make this code a little bit more clean and they'll just say start a review. Once you are done with it, you're going to update your code and you will send another commit. Right? And if they accept it, they'll just say merge the pull request. Okay? So you can think of it like this is how that would work in a real world application, right? in a teammate work in teamwork I mean let's go into the source code we have under the pull requests so this is the one that we had now this is open right your teammate would say something like merge the pull request if they if they want to merge this right once you click to this it would take the latest changes and put it into the master branch so your code would be so your master branch would be up to date with the landing page branch. So, I hope that makes sense. At the beginning, it is kind of confusing if you have never used this workflow, but you better get used to it because this is what you're going to be using when you join to a company, right? And here in this case, I have a code rabbit that is going to review my code and tell me if there is anything that I'm doing wrong or any kind of suggestion that we would need. And if you would like to integrate it for free just like what I have done. I'll leave the link in the description. Go ahead and sign up with your GitHub account and you're going to get the first 14 day for completely free. Um just give it a go and test it out. This is what I have done. So you can basically think of it like an AI senior developer in your team. Whenever you have something bad in your code like some something that should be fixed or if you are not following some best practices, this is going to give you those suggestions which are the things that we'll get into in a second. And whenever you send a pull request uh before you merge it, right? Just before you say merge the pull request, you should be able to see all the things that you have in your code that is not okay, right? And uh like if you're doing some wipe coding recently they have this CLI feature basically if you're using something like cloud code or cursor or any kind of similar agentic tools u you can integrate it pretty easily and before you commit your changes in your terminal it'll tell you like what is wrong with your code and what you should fix. Okay. So this is definitely something that I have recently. This is my basically favorite tool and they they were kind enough to sponsor this video. Okay, so here we can see we got the summary by code rabbit in this pull request. Here are the new features that we have added the styling and refactoring that we have done. Then if you want to see the file changes pretty quickly, you can just see uh they have like really I don't know how to say but really small tiny summaries summaries of every single file right and then they even have sequence diagram like what happens if user clicks to the login or sign up button things like that but we are not really interested in this one at the moment. Let's scroll to the bottom and see some potential issues and their fixes. Right here it says anchor targets missing for navbar links. Well, that's correct. So, here under the page.tsx. Um, here it says you should wrap the how it works component with a section and give it an ID so that when you click to this, it should take you to that section. Right? This is something that you can definitely do. I will skip this so that we don't really waste any time. But basically, this will always tell you what you're doing wrong in your code. So again, we have something else. It says wire the CTA buttons to real actions. Currently, it says I think this is not doing anything or both CTA buttons render without any navigation or handler. So they do nothing when clicked. So that's correct. This is the button where we have under the let's see the CTA.tsx. Let's go ahead and find that component. So we have CTA and we have some buttons but when you click to it nothing happens. Right? So this is something that you can fix as well but I'm just going to skip that. Now imagine if you have something security related in your code. Um code rabbit will just find it and it'll tell you hey you have a potential issue go ahead fix this uh fix this part in your code so that your code is safe to interact with. Okay so these are all the things it is mostly related to links where they don't they don't really do anything. So, in this case, I'll just go ahead and merge the pull request. And we're going to be using this a lot later in the video. Okay. Um, I'll just say confirm the merge and then we'll go into our codebase in VS Code. Let's say switch to the master branch. Now, we are going to see that most of the things that we have done are gone, right? So, we don't really have let's go into the page. we don't really have the latest changes. So you can click to this. This will basically get all the latest changes from the master branch. Okay. So these are the things that we have just done. Now our codebase is up to date. These are the latest changes that we have done. And just to let you know, Code Rebbit also has a VS code extension. This is what I'm using as well. Um like feel free to check it out. It should be somewhere here. again uh if you use the link in description I think you're going to get 14 days for free for this extension as well. Okay, so with that hopefully I'll see you in the next section. In this section we are going to get started with our database. So currently we have a project which has a database but within this we don't really have any data or any tables right so we would like to create a schema and to be able to do it we are going to be using Prisma. Now you might be asking what is Prisma in the first place? Well, it is an RM which stands for object relational mapping. I know this sounds complicated if you have never used it. So you can basically think of Prisma as a translator between your code and your database. So Prisma is not your database, right? It is just a tool that allows you to communicate with your database. So instead of writing complex SQL code, you will write some JavaScript code or in this case TypeScript code and that is going to translate it for your database. Okay, so this is not your database. This is Prisma. This is some tool that we're going to be using on the back end that is going to allow us to, you know, communicate with our database to get some data, delete, update, things like that. And here I have an example that I can show you. So what happens if you don't use Prisma and if you use Prisma like what is the differences? Okay. So here without using Prisma let's say you want to create a user. So you would basically write some row SQL code like insert into the users table. Here are the fields and the values. And here we go the values for the email and for the name. But if we take a look at the example where we use Prisma it's really beginner friendly right? You say hey Prisma go ahead under the user table and create some data right and here is the email and here is the name. So this looks more like JavaScript, right? That's why we say object relational mapping and another example. So these are basic examples but I think you get the point. So instead of saying select all from the users where email is equal to the email that we are getting as an argument instead you would say hey Prisma go ahead under the users table find first with this email. Right? So this is a lot more convenient and as the example gets larger and more complex like Prisma is really beginner friendly. Okay. So that's why we're going to be using it and there is something called Prisma schema. This is where you can define your database design and this is what we're going to be doing in a second. So in this file you will have like which tables you want to have right which fields do you want the relations etc. right? all kinds of things that you are going to define in this file. So first let's go ahead and uh install some packages. I'll open up my terminal. First we'll say mpm install prisma and let's use a specific version. So it is going to be let's say 6.16.2. This is the latest version as I am recording this video. But in the future you will have major updates. But if you use this one um like your code is going to work even if you're watching this in the future, right? And now let's say d-savedev. This is going to be a dev dependency. So let's go ahead and install it. Then we're going to run couple of different scripts. Now let's actually install one more package. Let's say mpm install at prisma/client. And again let's use the same version 6.16.2. Um this is going to be an actual dependency right not for the uh development. So just go ahead run this. So this is what we what we are going to be using in the development and this is what will be used in the production. Okay. Now let's run mpx prisma init like this. So this is going to create a folder with a file and let's see. Okay, so a couple of different things happened. We have this Prisma folder with the schema.prisma file, right? So this is where we can define our tables. Um in this example, we are using Postgress and our environment variable is database URL, right? So this is the exact same thing that we have. Um now let's go here. I think I'll delete the output. We don't really need it. Uh I don't really want this to be generated under this location. So I'll just go ahead delete that. Um this should be fine. Now we are going to define our tables. So we're going to have actually three different tables. One for the user, right? One for the doctors and one for the appointments. So these are like all the three models that we are going to create. Now let's get started with the user model. So to create a new model or a table you would say model and whatever the name is. In this case we are going to say user. Okay. So this is the convention that you would be doing. And then every single user is going to have an ID field. So let's say type of this is going to be string and this is going to be a primary key. So we'll just add the ID. And then let's say by default you can give this a value which could be a UU ID or there is a better option CU ID. Okay. So this is what we're going to have. And once you save it should format. I was going to say but looks like it doesn't. I think that's fine. It's already formatted. Um if it doesn't in your case go ahead find this extension. I think let me just shrink those. I have an extension called Prisma I believe. Okay. So you should install this and your code should be highlighted as I have. Okay. Then every single user is going to have an email field, right? So let's say type of this is also going to be string and this has to be unique, right? And we can even format this in a better way just like that. Um, every single user, let's say, is going to have a first name. And let's say this is going to be type of string. And if you want to make this to be optional, you can add a question mark. And let's duplicate this. We're going to get the last name as well. These fields are going to be coming from, let's say, clerk, right? And then let's say every single user could have a phone field. Again, I'll just say this will be string but optional. And then when users are signed up, we would like to have the created at field. Let's say this is going to be type of date time. And by default, it's going to be equal to now, right? Once they sign up, that's going to be that time. So that we can show something like user is member since let's say September 2025, right? This is a create that date. Now in the same way, I'll just duplicate this. Let's say this time this is going to be updated at. So these are the fields that you would like to have. Um so let's say it's going to be daytime and updated at. Okay. So once you like if you're using Prisma for the very first time, this could look a little bit complicated, but I think as you use it more and more, it just feels like a baby tool. Uh one more thing on every single user we would like to have a field called cler ID. Um I'll explain why but for now let's just say type will be string and this is also going to be unique. Now you might be asking why do we need this field in the first place. So here's a quick diagram that I have. So basically when users sign up first they are stored in clerk dashboard right. So at the moment we have this user who is signed up but it is not in the database it is just on the clerk right clerk stores them for the authentication and at some point we would like to take this user and also save it to our database and once we save we would like to know which user this is right so we're going to have the clerk ID which is let's see so you can have like copy user ID if you copy it like this is the clerk ID for this user. Okay, so I hope that's kind of like this will make sense in a couple of minutes. But basically this is the thing that we want to have once we have the user in the database. We would like to have this field so that we know which user this is in the clerk dashboard. Okay. So basically to identify the users. So with this I think that's it for the user model at the moment. Let's save and create another model. So we're going to have the doctor model as well as the appointments. So first let's get started with this one. And just before we build it, let's try to format our code a bit. So basically I'll press tab until they are all in the same line. Okay. Now for the doctors, we are going to have couple of different fields. Let me just copy them and paste them. Basically, they are the exact same thing that we have above, right? Every doctor is going to have an ID which is type of string, primary key and by default Prisma can decide it. Then like each doctor is going to have a name, email which should be unique, own, specialtity, bio and image URL. Bio could be optional, right? And then we could add a gender for every single doctor. Now, instead of saying type of string, we would like to have an enum called gender, right? And let's try to create it. I'll just scroll to the bottom. Let's say enum. Um, here I'll say gender. And enum is basically something that you want to have some specific values, right? So, here I'll say male and female, right? Okay. Now, this has two different values. You can hover over this. I think it doesn't show us but basically the gender is going to be one of them. If you try to put something else your code is going to break right uh it's going to throw some errors and then a doctor could be active or not. So basically admin can deactivate their their accounts. So for that reason we'll just have this is active boolean and by default this could be equal to true. So let's say the default value is going to be true. And finally, just like above, we would like to have the created at and updated at fields. Now that's it for the doctor and user uh models. We would like to create the appointment table as well, right? Let's say model appointment. Okay. Now, this is going to have actually some relationships with these other tables, right? because a user could have multiple different appointments and a doctor also can has some like can have some appointments. So to give you an example, let's say John has booked an appointment for Dr. Jane. Now John is associated with this appointment and same as Dr. Jane, right? They are both part of this appointment. So we need we need to like set up some um relationships and let's see how we can make that work. So first off let's put the fields that the appointment table is going to have. So every single appointment as usual is going to have the ID field the date time and we're going to store it as string duration by default is going to be 30 in minutes right and this is type of integer and then the status of the appointment. Now this is going to be a type of enum. Let's say appointment status and we're going to create it. Let's go ahead actually duplicate this. And the name of this enum is appointment status. And it's going to have two different values. So it will be either confirmed or completed. So when you first book an appointment, it's going to be confirmed. And once you are done with that appointment, it's going to be equal to completed. We're going to see it later in the video, but that's that's it for now. Let's say the type of this by default is going to be confirmed. And you cannot put anything else here. Your code is going to break. So it could be either completed or confirmed in this case. Um then we can have some field. Let me format this. So, we're going to have a field for the note, right? Notes like it's going to be it's going to be optional. You don't really have to put this, but if you wanted to, you can add and the reason for the appointment. So, this is going to be either like teeth cleaning, emergency visit, consultation, things like that. Um, as usual, we're going to put the created at as well as the updated ad fields. Since we have done this multiple times, instead of typing it out, I'm just copying and pasting. Okay. Now, as I said, an appointment is going to be related to a user as well as a doctor, right? So, here I'll just say foreign keys and we'll say user ID, which is going to be type of string and duplicate it. We'll also have a doctor ID that is related to this appointment. And let's put the relationships Okay. So, we'll say a user will be related to an appointment. And here is the type of this user. And then we're going to say at relation. So, if you are seeing this for the first time, it's absolutely normal if it looks confusing. So, we all been there, that's fine. Just follow along. So, the fields that we would like to have the relation with in the appointment table is user ID. We'll say user ID. And then we're going to put a comma. It's going to reference to the ID in the user table. Right? So it's going to be referencing to this field which is the primary key. And then we will say on delete this is optional. We'll say cascade. So what that means basically if you delete a user account it's going to delete all the appointments related to that user. Right? So once you delete a user, let's say a user deletes their account by default like all the appointments will be deleted for that user. So that's what we say. And then I think we can just duplicate this. We'll say we're going to have another relation to a doctor which is type of doctor. Let's format this. The uh like relation fields is going to be doctor ID. is going to reference to the ID field under the doctor table and same on the lead cascade. Now why this is complaining because we have to map this with these models and it is really easy to implement here. Basically, I'll just say a doctor could have some appointments, right? And we'll say it's going to be type of appointment but an array of them because a doctor could have multiple appointments. Right? Now, this is not complaining anymore. Let's do the same thing for the user here. Let's go and say appointments. So, instead of calling this user, maybe we should call this like patients, but I think that's fine. Um, this would also work perfectly fine. And I'll basically copy this entire line. Paste this in. Now, we don't really have anything complaining in our code. Once again, we put the relationships under the appointment. And we had to do relationships under these tables as well. Here, I'll actually say relationships and do it here as well. Okay. So, that's the entire schema. file. Let's just zoom out and try to go over it once again. So we have three different models or three different tables, right? We have a user model with all these fields which are pretty easy to understand. At the end we have a relationship which means a user could have multiple appointments and here is the type and we included the model with the relationships for the user. Right? Then we have done the exact same thing for the doctors. all these fields that a doctor could have which is pretty easy to understand and then the relationship then we include it right here. Okay. Now one more thing just before we push this to our neon database this is optional but I'd like to mention this. So instead of seeing this as a user right I would like to see the table name as users right to be able to make that work we'll just say double at map this with the user's name okay so let's copy it and go do the same thing for doctors and for uh I mean same for the appointments table okay let's say appointments okay So believe it or not that's the entire thing for the schema. Now what we have done is basically a local file in our laptop right nobody knows about this we need to take this and push this to our neon database and I have included this in the notes. Basically, we'll say mpx prisma db push, right? We're going to take all the changes in our laptop and push this to our database or to neon in this case. Let's open up the terminal. Clear this up. And I'll say mpx prisma db push. So, this is going to take a couple of seconds. And then we should be able to see all of our tables right here. Currently we don't have anything right. Okay. So looks like our database is now in sync with our Prisma schema. It is done in 13 seconds. Let's go ahead and refresh. We should be able to have the appointments, doctors, and users. Okay, here we can see we don't really have any data, but all these columns are right here. Okay, so that's perfect. Now it's time to go ahead do something else. which is about syncing the user from clerk dashboard to our database. So I'll leave this section here. In the next one, we're going to be doing that. So I'll see you there. Well, actually just before we end the section, we would like to create a new branch and commit our changes. So I'll open up the toggle bar, I mean status bar, right? I'm I'm going to toggle this with command shiftp. Okay. So we'll go ahead create a new branch. Let's say Prisma- schema. You can call this anything, but this is the name that I'll be going with. And then we would like to go here, stage all of our changes, and then add a commit like schema. Prisma file added. And then let's say commit publish the branch. Let's go into the source code, which was Dentwise. And then we're going to create the pull request. Okay, let's say create this and then I'll wait for the code suggestions from code ravbit and then if you are happy with it, we can just merge the pull request. And once again, if you would like to get this extension for free or this tool, you'll find a link in the description. Go ahead and give access to your repositories. This is what I have done. is completely safe and it should give you a review in a couple of minutes. All right, so we got a quick summary by code rabbit. You can pause the video and read it just to see what we have added. And here is a quick walkthrough of the files and the changes that we have done the sequence diagram like here is how that work. When you create an appointment, what is happening in the background and once you delete a user or a doctor, what is going to happen? It's going to basically cascade delete the related appointments and this is what I have tried to explain. Let's take a look at this once again. I'll go into the schema.prisma file. So whenever you delete a user account from your database, it's going to delete all the appointments that is related to that user because of this field. And same for the doctors. Okay, so this is what they try to explain. And let's scroll to the bottom and see if we have any code suggestions. Well, we just got only one which is about the package version. I think this is a valid version. So, we can ignore this suggestion. This is completely fine. Um, I have tested out like it is working without any issues. And other than this, we don't really have any suggestions or errors in our code, which means that's perfectly good. Right now, we'll just go ahead and merge this pull request. confirm it. Once it is done, we can go back to VS Code. Let's close everything. Switch to the master. Now, looks like we lost all of our changes, but that's fine. We'll just say sync this up and we should get the latest changes in a second. Here we go. Okay, so these are all the things that we have included. So, with that, that's going to be it for this section. Hopefully, I'll see you in the next one. So in this section we are going to solve a problem that we currently have in our codebase or in our application. So we have signed up with an account but this account is not in our database right under the users we cannot find that user. So we basically currently storing the user under the clerk but not in our database. So we need to make it work in a way that once user signed up. We would like to take this user and save it to the database. So you have two different options for this. The first one is using web hooks which is the best way to do it. Um in this case we'll be using the second way which is just having a user sync server action and I'll explain what that means. So I would say this is the best way but it's a little bit more involved. Let's keep it simple and implement this way. Okay. So let's see what we're going to be doing in our codebase. So first we need to create an instance to be able to communicate with our database. Right? And this instance is going to be a Prisma instance. So under the lib I'll say prisma.ts. And to get this file content, we will just say Nex.js Prisma best practices. Let's search for it and click to this comprehensive guide. Okay. So here it says go under the lip under this file, copy this content and paste this in. Now why do we want to follow this best practice? Well, you can't read it. But basically, if you don't do it in this way, you are going to get some errors, right? And this is often occurs due to NexJS hot reloading feature in development where you create multiple instances of the Prisma client. So to get rid of this error, we are going to copy this and this is going to create a global instance. And if you already have one Prisma client, you don't need to create it again. I know that sounds complicated if you are using this for the very first time. But basically this is a file that you don't really need to understand completely just understand the concept that we are creating a Prisma client right a Prisma instance and we are caching it. So that's it. So I'll just add a comment here. Let's say create a Prisma instance and cache it in development. So if you are in production we don't really cach it but in development we would like to cach this for the best performance. Okay. So all in all we are just creating this Prisma instance and we are going to be importing this in other files so that we can communicate with our database. I hope this is not really confusing. Um now let's go ahead under the lib create a folder called actions which are going to be our server actions and let's create one for the users. So let's say users.ts ts and a server action is basically just a function that runs on the server side and to be able to make this a server action file we'll just add this directive at the very top I'll say use server okay now we would like to import the current user from clerk let me just get it in this way okay and then we would like to get the prisma from from this file that we have Right. Okay. Now let's create a function. I'll say export async function called sync user. This is not going to take any arguments. But basically this will have a try and catch block where we would like to take the user from clerk and save it to our postgress database. So let's get the currently authenticated user by calling await current user. Okay. So if we don't have the user, if this is undefined or null, that means user is not authenticated, we'll just return and we're not going to do anything. But if we have the user that is authenticated, we can check if it is existed in our database or not. I'll say existing user say await prisma dot user. Right here we can see it is type safe because we have the user table and then we can say even like doctor or appointment. So here we'll say userfind unique and let's say where the email field or actually the clerk ID field is equal to user do ID because remember this user is coming from clerk right current user it is coming from clerk so we're going to check this field if it is equal to user id if we have an existing user that means we don't need to save this user to the database again. We'll just say return the existing user. Don't do anything. But if we don't have it, we would like to basically say, hey, Prisma, go ahead under the user table and create this new user. And here is the data. Okay. So, we're going to pass the clerk ID, which is user dot ID field. We're going to have the first name, which is going to be equal to user. Oops. Let's say user dot first name. And then we can add the let's say last name. Basically duplicate this last name. And then we can have the email field. That's user. Well, actually email addresses. This is how clerk stores it. This is an array. And we're going to get the first email address, which is the primary one. And then if user has a phone number, we can also get that. We'll say user phone pawn numbers. That's an array. We will go ahead get the first phone number. Okay. So that's that's the data that we would like to save. Let's say const this is our new DB user. And we can just return this from this uh from this method. Right? I'll say return DB user. In the catch, we're going to just get the error. and let's say error in sync user server action. You can handle this in a better way but I'll just keep it simple and leave it as it is. Okay. So now we would like to call this method right once user logs in let's run the application by the way I'll clear this. Let's say mpm run that. So when the user visits our application if they are authenticated they should be able to like they should be saved to our database right currently let's try to log in okay so we are logged in but we are still in this landing page let's go ahead and fix this first I will create a different page let's say something like a dashboard and we'll say page.tsx tsx for now let's say dashboard and leave it simple um here under the homepage right here we could say something like handle the redirection right so I will shrink the left hand side let's say this is going to be an async function um here I'll say const user say await get the current user from clerk right um here I'll say If there is a user, if user is authenticated, let's just redirect them to dashboard, let's say slash dashboard. And this redirect should be coming from next navigation. Okay. So, we can even add a comment like redirect user to dashboard. And like this is a page.tsx file. By default, it is a server component. So you can use async await. But if you put something like use client then you cannot use the async or await right. So this is something that we're going to see later in the video in detail. Okay. Now let's see if we are redirected to the dashboard. That's correct because we are authenticated now. Right. And now let's try to create a component so that we can call our action because we created this but we are never calling it. I will go under the components and I'll create something like user sync.ts or tsx. Let's say rfce. This is not going to return any kind of JSX, right? Instead, we would like to say const get two different things from use user from clerk. And since we are using a hook, we are in next.js, JS this should be a client component so we will add this directive called use client because we are using a hook right and then from here we'll get is signed in and then is loaded so here I'll say I will have a use effect let's initialize it pretty quickly and within this I'll have a function let's say con handle user sync basically within this function we're going to call our server action say async arrow function we're going to build it and eventually we're going to call this method okay so here I'll say if clerk is loaded and if user is signed in in this case we can just go ahead run the let's say sync user server action okay and we and even run this like wrap this with the try and catch let's say fail to sync user okay and here for the dependency array I'll say is loaded and is signed it whenever those updates whenever those changes we would like to run this use effect and we don't really want to return anything. So this component doesn't render anything. Okay, so that's the entire thing. Now we need to call this component at some point. Okay, so this component is calling our server action and we would like to call this component maybe in layout. Let's go under the source app here just above the children. I'll put the sync user component or it was user sync. Okay, I'll just get this one. Okay, once again, this is not the best way of implementing, but it'll definitely work. The best way would be to implement this with web hooks. Okay, let's save. Um, now let's go here. We'll just refresh our page. Hopefully we should be able to have the user in the database. Here we can see whenever you refresh the page the layout file is going to run and it's going to run this component which is going to run our server action which is going to run this logic and if user doesn't exist in our database it's going to run this part where it'll create the user. So since we already have this user, even if you refresh, it's not going to create it again, right? Because it's already in the database. I hope that makes sense. Once again, keep this in mind. The best way would be to implement this with web hooks, which is something that we have done on the channel multiple times at this point. So for this project, I'll just do it in this way, which is perfectly fine for your side projects. Okay. So let's go ahead close everything and create a new branch and commit our changes. So here let's say sync user I'll go ahead add a commit message say user sync added commit publish it and here I'm getting these notifications from two different extensions one is code rabbit where you can start a review or the other one is to create a pull request and this is coming from this extension called github pull pull requests. You can just do the pull request from VS Code, but just to keep it beginner friendly, I'll go ahead do it from the GitHub. So, let's say compare and create the pull request. As usual, I'll just wait for the code suggestions coming from code rabbit and then we are going to end this section. So, here we got the quick summary by code rabbit. Let's zoom in and read it. The new features that we have introduced are the dashboard page. Now signed in users visiting the homepage are now redirected to the dashboard and we have added this background job where user profile syncs automatically after they sign in. Right? And let's scroll to the bottom. Here are the files with the related changes. The sequence diagram. I think that's a perfect way to visualize what is happening in our application. Basically let's zoom in and see um here once you know once the root layout has been mounted we are going to run the server action that we have right it's going to take the user and save it to the database right you can see it like this is exactly what is happening and if user is already existed nothing is going to happen right okay let's scroll to the bottom I'll just zoom out here we have a code suggestion It says you should add the use server under the sync user server action which is something that we have done. So this is something really important. That's why code rabbit tells you to do it even if you have done it before. It just warns you right. This should run on the server side because we are accessing our database. Um so this should be marked with use server and this is exactly what we have done. So we can skip this because we already have done this. And here it says you can just add a like go check. Um I think clerk will always give us some email addresses. So you can of course at this part to make your code a little bit more safe. But what we have is also pretty safe at this point. So with all that being said, we can merge this pull request. Okay, it's done. Let's go back to our codebase. switch to the master branch and you can just sync this up. So this is going to pull all the changes from your GitHub account. Okay. And you can click to this multiple times by the way. Like nothing is going to happen like it's not wrong. It's just going to get you the latest changes. Okay. So with that I think that's going to be it for this section as well. In the next section, we can get started with the admin page where as an admin, we should be able to create some doctors, you know, update their profiles, make them active or inactive, see all the appointments and a lot of different thing, right? So, uh hopefully I'll see you in the next section. In this section, we are going to get started with the admin page. Just before we get into coding, I'd like to mention that you can find all these diagrams in the description for completely free. Just go ahead find the admin page and I'll just walk you through as we build every single one of these components. So, basically, this is the UI that we're going to have. First, we will have a navbar at the top. Then, a welcome section, some uh let's say analytics or statistics, however you'd like to call it. Then the doctors in our database. If you click to this button, a dialogue will open where you can add a new doctor and provide the details like name, email, phone, things like that. And if you click to this edit button, another dialogue will pop up and it's going to get you the details of that doctor. Right? So if you click to this one, I mean this edit button, you will just see this dialogue with the data filled in and you can update it by clicking to this button. Okay? And then if you scroll to the bottom after the doctors, you will see all the recent appointments. And if you click to like click to these buttons, um it's going to update this state. If it is confirmed, it's going to be completed. And if it is completed, it'll be confirmed. Okay? So I hope that made sense. uh just don't worry about this. We'll just go step by step. Okay. So, first uh we need to have this page called /admin. Right? Currently, I think we're going to get 404. Uh just make sure that you're running this application. Okay. Now, let's go ahead and create it. Under the source, under the app, I'll say admin. Let's create a page. And for now, let's say admin page. Okay. Now, how are we going to check if the user is admin or not? So, there will be only one admin. And here I'll go ahead um implement this by adding an environment variable. So, I'll say admin email and this is going to be equal to my email. So, the account that I that I am logged in, right? So, let's open up the cleric dashboard. So, just to show you what I mean, um, I need to show you the dashboard, but basically I signed up with this account. So, that was my email. Go ahead, put your own email that you signed in. So, if you put something like test, like it's not going to work. Okay. So, put the email that you actually signed up to this application. And I don't know why this doesn't load. Okay. Let's wait for this to load and then we can just keep keep going. So under the page.tsx I will go ahead mark this with async and first I'll get the user. So let's say await current user which is coming from clerk and we'll say if there is not a user which means if user is not authenticated just redirect them to the home screen. Okay. And then we'll say get the uh admin email. So this is how we can get that value. Um then I would like to get the currently authenticated user. Right? We need to check if this is admin or not. So I'll say user email and we'll say here's the current user, right? We'll say user email addresses and just go ahead get the very first one. Then we'll say if there is not an admin email or let's say if user email is not equal to the admin email which means user is not authent I mean user is not the admin right user is not the admin and here we just say user is not logged in in this case again we're going to take them to let's say dashboard in this case if they are not logged in they'll be in the landing page but if they are not the admin they will be in the dashboard page right and here we can say you are the admin if they can see this page and let's take a look at it now it says you are the admin because I am logged in with this account let's see it so this is the account that I am currently logged in right it is this email and we just said that under the MV file this is the admin email. So if you put something like test save and save this file now, we shouldn't be able to see that page. If I try to visit the admin page, it's going to redirect me to the dashboard. Okay, I hope that makes sense. Let's bring this back. Go ahead, put your own email that you are currently logged in with clerk. Okay, so we just check uh we we did the server check, right? Now we would like to return a client component so that we can use some hooks, you know, fetch data on the client side. Here I'll say admin dashboard and we can just say client. So this is going to be a client component and we can create this under the admin folder. So we'll say client.tsx tsx that's entire file file name. Now let's say rfce and we are going to mark this with use client. Once again we are calling this with this directive so that we can uh call some hooks and do some client related stuff because by default this is going to be running on the server side. And now let's import it and save both of these files. Okay. Now in this component we are going to fetch some data right so we're going to get the doctors we're going to get the appointments let's say and we're going to you know display them on the UI but how do we fetch the data well we can use the fetch method which is pretty basic you don't really want to do it in a production grade application right instead you would like to use our favorite technology which is the ten stag query and let's search for it I'll say 10stack query Nex.js app router. So go ahead type this out and we are going to get an example and if you zoom out you're going to see the initial setup. Just click to it. Um I think this is the pages router and let's scroll. We should be able to see the app router or is it okay so this is the app router actually. So we'll go ahead and copy everything. You don't really need to understand this completely but basically let's try to copy and paste and I'll try to explain. So here under the source under the components I'll create the providers folder and within this I'll just say tenstack provider.tsx. Now in the documentation they put this file under the under the app under the providers file. It is just an example, right? We can absolutely put it in a different file. So that's not really important. Just go ahead paste this in which is around 50 lines of code. You can't read these comments. I'm just going to leave them. But I think first we need to import the package, right? And let's go ahead open up the terminal. Kill the application. And I'll say mpm install. Let's say tenstack react query. And let's go with a specific version which is going to be 5.89. Oops, not here, but it should be on the terminal. So 5.89.0. Okay, so we're going to run this. It's going to install the package. This is creating a query client, right? And at the end of the day, we are wrapping our entire application with the 10stack provider. Here I'll go ahead call this as 10 stack provider. Now let's save and try to use it under the layout. So here basically we can wrap our entire application with this pen stack provider. Okay, just import it and let's cut this. Wrap our entire application with it. Now that means within our entire app we can use hooks functions coming from tenstack right we can use everything that tenstack provides so this is what what this file basically does okay I hope this is not really confusing even if it is like you don't need to understand everything here 100%. Okay, now let's go under here in this file. Um, now I think now I think before we do anything related to data fetching, we can just build some part of the UI and then come back to actual data fetching. So for now just forget about the ten stag. We just set that up, right? We created it under this file and we wrapped the layout our entire application with tenst tag. And once we use it, I think it's going to be even more clear. Okay, now let's go ahead and try to build the return statement under the dashboard client component. So I'll have a div with some classes like the minimum height should be screen and background is going to be the background variable that we have. And let's try to create a navbar component. So let's say navbar. Now, this is a bit different than what we have in the landing page. So, let's take a look at it. So, this is a lot different than what we have here, right? So, this is the navbar component that is going to be reusable. So, we're going to use it in the dashboard page, voice page, and you know, all the other pages, the admin page, so on and so forth. So let's go ahead copy this name under the source under the components we can just put the navbar.tsx file let's say rfce and import it. Now this is not going to contain any logic. So we can just be a little bit fast and copy and paste as we go. You can open up this open up this file from the source code and just copy and paste just like what I do. So first let's get the user from clerk and since we are using a hook we can wrap this I mean we can mark this with the client directive right okay so import the user I mean import the use user from cleric nextjs and the other thing that we are going to import is the path name so go ahead import this from next navigation and we will be using this to show the active link So let me show you what I mean. So here we are in the voice page, right? I don't know if it is visible from the video, but the color of this is a little bit different than these three links. Okay, so this is a little bit more highlighted and we are going to determine this color depending on the path name. So if it is like let's say voice that link will be highlighted. If it is equal to pro, the pro link will be highlighted. so on and so forth. Okay, now let's have a nav component and it's going to take a couple of different classes which I can basically copy and paste because it doesn't contain any logic at all. So it is fixed to the top um left right zero and zindex of 50 and some colors right some borders height um so on and so forth. Now we'll have one div which is going to be our wrapper div. And let's close this off. So this has like maximum width and the content is centered. Then we can have our logo right. So here I'll just say the logo section. Um let's say we'll have a div and let's give some classes like flex item centered. Let's say items center and gap could be something like eight maybe. Okay. Then I'm going to pass this part where we have a link. Once you click to it, it'll take you to the dashboard page. And we're going to use the image coming from next. Okay. So, import them from next image and next link. And this should be good to go. Then we would like to put the navigation links, right? So right after the logo div we'll have one more div. Let's say class name flex items center and gap is going to be six. Now let me just copy and paste the very first one and you're going to understand the rest of it. Okay. So this is the link and let's get the home icon from Lucid React. So we are giving these classes no matter what. But if the path name is equal to dashboard, we are going to add these classes. But if we are not in the dashboard, we're going to see this classes. Okay. So now let me get the rest of it. If you don't want to type this out, just go ahead grab them from the source code. This is not really important at all. So this is the appointments link. Next, we're going to get the voice and pro links. Okay. import the make icon as well as the crown icon. Okay, now let's save and test it out here. I'll just go into the dashboard page or maybe the admin page. Okay, I think we close the I mean we killed the application. Let's see mpm rundev. And while it is running the application, we can add the right hand inside on the navbar which is this part right we are going to get the user put their name email and this is the component what we call user button. So let's see how we can build it. So after the link and after these two divs I'll have this right section element. If you don't want to type this out, just copy it from the source code. Let's say flex items will be centered and gap is going to be four. And we'll have one more div. It's going to have the same same classes, but this time let's say gap three. And the very first one is going to be the username and then the let's say the email address. So this is what I'm going to do. Oops. So we will say if user has first name display it put an empty space and then the last name. Then do the same thing for the email address. Right? Um then finally right after this span and after this div we're just going to put something like user button component. So we are going to import it from clerk and just call it right here. Now we can take a look at it under the admin page. Hopefully it should be working out as expected. Okay. Um, so this part is not really working. Let's see what is the problem. Probably this right section should be. So I just pause the video and find the solution. So find the logo div, right? Cut this part and scroll to the very bottom and paste it somewhere here. Right, just above the nav. Now let's save. Hopefully, it should be working out. Here we go. We have the logo, the links, and the user information as well as the user button. So, we can manage your account. Um, we can check out the security tab, things like that. So, this is coming from clerk and we can even sign out. Now, since in the navbar we don't really have the admin link, we cannot really see the highlight, but let's go into the dashboard page. I'll search for the dashboard page. In here, let's just put the navbar component. Okay, import it. And here we can just say, you know, dashboard. Let's go into the dashboard page. Now, as you can tell, the link is now highlighted. I hope you can see it from the video, but there is definitely a change on the color, right? Even on the hover state is a bit different. Okay, so that's it for the entire navbar component. No logic at all. We are just getting one hook, right, which is the path name so that we can put these classes dynamically and we are getting the user from clerk so that we can display the email and the full name. Okay, so that was the very first thing that we have under the admin dashboard client file. Now, let's build the next component under the admin page, which is going to be this. Let me show you pretty quickly. So, it's going to be this welcome section. Once again, it doesn't have any logic at all. Just some text, right? We get the username, the first name, display it, and put some beautiful icon. That's it. But here, we actually have some logic because we fetch these numbers from the database. So first let's try to build it and then we're going to get into the next component. So right after the navbar um here we can have a div with some classes which is going to be maximum width of 7x large MX auto some petting from all directions and then let's say the admin welcome section. So this doesn't contain any logic. Once again, let's save a bit time and just paste this in. Please feel free to copy this from the source code. Okay, so it is just a div with 20 lines of code. Um, go ahead import the settings icon and we would like to get the user from clerk. So you already know this how to do it. Let's say give me the user from use user hook. Okay, let's save and take a look at the output. I'll just go right here and under the admin page. Okay, so that's the welcome text that we have. I think that looks pretty cool. And we're going to be using similar design in couple of different places. Now it's time to build the next component where we have some stats, right? And here we need to fetch some data. And now it is time to actually use 10 stack, right? Um let's see how we can make that work. So in tenstack we have two different things. One is a query where you fetch some data, right? This is basically a get request so that you can some fetch data, right? And then we have what we call mutations. And mutation is basically when you want to update something, delete something or create something, right? So we're going to be using mutation whenever we want to create a doctor or edit their profile. But if we want to just fetch data, we're going to be using queries. Right now, let's go ahead and follow a convention. So for the server actions, we are going to be using this folder. Let's say under the lib under the actions, I will create a file called doctors. Let's say doctors. And let's mark this with use server directive. Just for now, follow along with me. It's going to make sense, I promise. So, first I'll have a function. Let's say export async function and for the name we can say something like get doctors and this is not going to take any pars at the moment. So here let's try to have a try and catch block and under the try basically we're going to call our Prisma instance right. So here I'll say we're going to get all the doctors from the database and we'll say Prisma let's import it from the Prisma file and we'll say go under the doctor table and find many right okay so normally that should be it but let's add some objects which is going to be things like uh including the count um for appointments and we can do some ordering right so Well, like you'll see what that means. First, let's say include open up an object. Let's say underscore count. And we would like to count the number of appointments for every single doctor, right? So, we would like to count the number of appointments because here in in this example like once we fetch the doctor profile, we would like to show how many appointments that this doctor has. Same for this one, right? So that's why we go under the appointments and count them. Then we can order by like let's say order these doctors. Just put a comma. Let's say order by with the created at field and let's put it in the descending order. Okay. Now let's say something like return the doctors which is an array of doctors. Right? These are coming from the database. And in the catch I'll um I'll just say console log error fetching doctors and we can just throw an error. Okay. So that's the entire function that we would need to fetch all the doctors in the database right and we are going to even count the number of appointments. But now this this is just an array of doctors. This doesn't give you the count. So what we can do we'll just say okay go ahead return the actual doctor's array but get every single doctor and return an object just keep the doctor fields as it is but on top of it add this appointment count which is equal to doctor dot um count and this should be an error function by the way let's say doctor.count.app appointments. So, I know that this might look a little bit weird for the very first time if you're using it, but basically, let's delete this part. We are trying to find every single doctor in the database. Then, we wanted to count the amount of appointments for every single one of them. So, we had one object with the include field and we wanted to order this with the create that date in the descending order. And you can add ascending as well if you really wanted to. Okay, finally we're just returning this array. Um, that's the server file. This is where we fetch the doctors from the database. Now we'll create a hooks folder. So you're going to see how it'll how it'll look like. So here I'll say something like use dash doctors. Let's say yes. Now since this is a hook we can mark this with the use client directive and then we can create a function let's say use get doctors and this hook is going to call this server action and for this now we are going to be using a query. So let's say use query and this is going to give us some result back right and we would like to return that result. So whatever is coming from the database now this wants you to add an object with the query key here we can say something like get doctors right so this could be anything you can even put hello but it should be something that makes sense and then we can say the query function that we would like to run is our action which is get dockers okay so believe it or not that's the entire thing we are calling a query which is going to call our server action right this is the method that is going to be running and it's going to get us all the doctors okay now how do we call this hook well it is just like it is exact same way calling this hook right so here I'll duplicate this this time we'll say use get doctors this is how we called our hook and this is going to give us some data Right. So we can get the data and also we can get fields like is loading right if there is any errors if it is like fetched is fetching millions of different things. So that's the beauty of a 10stack query, right? You don't really need to build all these states by yourselves, right? You don't need to create them. Um like 10stack query is going to provide that. Okay. So instead of just calling this data, let's make it even better, right? This is optional, but I'll call this as doctors. And by the default like like the default value can be an empty array. Now let's say console log give us all the doctors in the database. I think this should give us an empty array. Let's see on the client. Okay. So because in the database we don't really have any doctors. But that means this is actually working. We are able to fetch everything without any issues. And if you take a look at the terminal there are literally no errors. So once again let me walk you through it what we are doing. So we have created a server action where we can fetch the doctors by using Prisma and we are calling this action under our custom hook. Right? This is the function that is going to be running once you call your hook and we are calling this hook in our component so that we can actually fetch the doctors. So I hope that makes sense. This is the basics of tens query. This is the convention that we are going to follow. Basically we are going to create a server action and from a custom hook we are going to be calling that server action and we are going to call this um call this hook in a component. So in the same way let's duplicate this this entire line and this time it'll be use get appointments. Okay. Now it's your challenge to go ahead and implement this but if you can't just uh follow my solution right so this is going to be appointments now here it says you have two is loading let's try to rename them I'll say doctors loading and for this one we can say something like appointments loading okay now Let's create this custom hook. Under the hooks, this time I'll say use dash appointment. Yes. And mark this with use client. Let's say export function. This is the name. Okay. For now, we can just leave it as it is. Let's try to import it. Okay. Now, we need to go ahead. uh what this says. Okay, so here it says you're not returning anything, which is fine. We're going to fix that. Now, we need to go under the actions and create a file called appointments.ts. Again, this is a server action file. So, we'll say use server and we can create our server action to be able to fetch all the appointments in our database. Now when we want to fetch the appointments, we would like to get couple of different fields. So what do I mean? So this is the table where we will display all the appointments. So when we fetch the appointments, we would like to fetch the patient data like user first name, last name as well as the email. And for the doctor, we can get the full name. And if we wanted to, we can even fetch the image URL. So let's go ahead and see how we can make that work. I'll say export async function and we would like to have this method called get appointments. Okay. So I'll have the try and catch block. Let's import the Prisma. So I'll say await Prisma appointment table. Let's find many. And we can add our let's say options our arguments. So here we'll say go ahead include for the user these fields the first name let's say true so that it can fetch it. Let's duplicate this last name and then we would like to get the email. Let's say true. Why this is complaining? First name does not exist under the user. What do you mean? Let's say user. Wait, do I have a typo? Include user find many. Okay, I think after the user, we should say like select these fields which is the first name, last name, and we have email. Let's take a look at the schema file. Under the user, we have email, first name, and last name. So, these are the things that we would like to include because if you take a look at the appointment in the appointment, we don't really have the user full name or user email, right? They are just some relations. So, that's why we cannot just have these fields under the appointment. We have to go under the user table and grab these fields specifically. And for the doctor, let's put a comma here. I'll say for the doctor, go ahead and select the name. And I think I'll just get the image, the image URL. Let's make it to be equal to true. And if you really wanted to, I think we are missing an object here. Okay. So here we can go ahead say order by create that and I'll just leave it as descending. So that's the entire query that we have. I know it's a little bit large or big but that's fine. Let's say these are going to be all the appointments and we would like to return it at the end of the day and if we have any errors can just throw an error. Okay. So once again we are saying Prisma hey go ahead under the appointment table find all the appointments and for each appointment include some user data and doctor right so that we can nicely display them. So these are the appointment data like status reason date and time but these are the relations under the user table and doctor table. I hope that makes sense. So that's the entire server action. We would like to call this under our hook. So we'll go here and we will just say const result. Once again use query where we can have a query key. We can call this as get appointments. And for the query function we'll just call our server action which was get appointments. And finally we'll just say return this result. Okay. So once again I'd like to walk you through it pretty quickly. We have created a server action where we can fetch the appointments from the database. So this is a server action under the actions. Then we have a client hook so that we can like use the query call this method that is going to give us a result. And we are going to call this custom hook under the component. Now we can say console log give us the doctors and the appointments. So both of them should be empty arrays because we don't have anything in the database. But uh at least we know that we can actually fetch them without getting any errors. So if you see your terminal, you shouldn't have any errors just like what I have. All right. Now let's calculate these stats like the total doctors, active doctors, the total appointments as well as well as the completed appointments. Right? So we are going to go ahead and create an object. So I'm going to delete the console log. Let's say const stats. And this should be pretty easy to implement. So we'll say total doctors which is going to be doctors.length. And let's say active doctors. And in this case, we're going to basically get every single doctor and filter this out. If it is equal to active, we're going to just get the length, right? That's pretty basic JavaScript. And let's do the total appointments. Again, um appointments.length. And instead of pending, we'll actually say completed appointments. And again, we're going to filter out. We'll say if status is equal to completed and this should be completely type safe here. We can say okay we're going to get the length and that's it. So I'll add a comment. Let's say calculate stats from real data. Okay. Now let's add a loading state as well. It'll say if the doctor's loading or if the appointment's loading. For now, we can just return a P tag that says loading. Later in the video, we can make this a better loading state. Okay, so that was the welcome section. Right after this, we would like to show that component. So here I'll go ahead say admin stats and I'll just pass all these stats into it. So here let's say the total doctors. Okay. So active doctors total appointments and completed appointments. We'll just pass them one by one. Let's go and create this component. So under the components we can create a folder called admin and every every component that is related to admin page can go here. Right? So here I'll say admin stats.tsx. Okay. Let's import it. Type script is going to complain. So let's go ahead and add these props as well as an interface. Basically here we'll just say these are all the fields that we are going to get as a prop and let's say admin stats props and we can create this either as an interface or type here I'll say interface admin stats props and they will all be number right now if you delete this part since we give the interface if you press control space you're going to see that this is completely mediately type save and you can get all of them one by one. Okay, so the rest of this component is just going to be UI. No logic at all. This is what we are building. We're just going to put an icon, the number as well as the text. So you can copy this component from the source code. This is what I'll be doing. Again, no logic at all. It's completely, you know, just UI. So here I'll get the return statement. Delete it and paste this in. Now we are using the card component. Let's go ahead and import them as well. At the very top we will import cart from shadian UI and some icons. Okay, let's save and take a look at it. This is what we have, right? The exact same output that we would like to have just like in the demo application. For now, we can see zero for everything because we don't really have anything in the database other than users. But as we build more and more, these values should update. And just in case if you are wondering about this code, please go ahead pause the video and take a look at it. So everything that you see here is just coming directly from Shadzen documentation. So just say chaten card component and the usage that you see here like the code right like everything is exactly coming from the documentation this is what I have done of course I built it previously so that's why I'm just copying and pasting and it doesn't like it doesn't contain any logic at all so we shouldn't be wasting one hour just by typing these components and classes. I hope you don't mind it. Now this section is getting pretty long. Let's go ahead take a five minute break and continue with the next component which is going to be this doctor management uh component. So I'll see you in a couple of minutes. All right. So let's move on. So this is where we left. We would like to build this component so that we can fetch the doctors, display them and then with this button we will be able to see this model where we can add a new doctor right so first let's try to build this UI and then we're going to get into the dialogues which are these components right okay so I'll go into VS code visit this file and right after the admin stats I will call this component called doctors management and we are going to create this. Now this is not going to take any props. So I'll just copy the name. Go under the components under the admin create this file.tsx and let's import it. Okay. Now let's get into this component. And first we would like to call a hook. So we'll say use get doctors. And we can basically dstructure the data. So I'll say give me the data. And we can call this as doctors. You don't have to, but this is just I think makes our code look a lot more cleaner. So doctors and let's say the initial value could be an empty array until we fetch that. Okay. Then we're going to have couple of different states. So let me show you what they are going to be. So if the add dialogue is open or not. Initially it is not open. Once you click to it it becomes true. So we can see it. And then we have another dialogue. So if you click to this um edit button you're going to see another dialogue. Right? So let's create two different states for them. The first one is going to be is add dialogue open. And initially it's going to be false. Let's import the use state and then we'll do the same thing for the edit dialogue. Okay. So this is my hook call my states and I will create one more state. Let's say const selected doctor and let's say set selected doctor. This is going to be equal to null. And once you click to let's say this button the selected doctor is going to be this. Right? If you click to this one, this is the selected doctor. So we are going to update the state so that we can just fill in the model immediately. Right? So once you click to this button, this model will open and we're going to take this data and display it right here. And if user wants to update it, they can, you know, change it on top of it. I hope that makes sense. Now these are all the states. Let's create two different functions. Let's say handle edit doctor. And for now, let's leave it as an empty arrow function. And I will duplicate this. We're going to have another one. Let's say handle close edit dialogue. Okay. So, we're going to get into the details of these functions. But first, let's build the return statement. So, this is going to be an empty fragment because we're going to have couple of different children. Let's first import the card. Um here let's just have a class name like margin bottom of 12. And here we'll have a card header component. So this card header is going to be this part. Let me show you. Okay. So this is the card header, right? And this entire thing is the card itself. Okay. So let's go ahead and try to build it. First, I'll import this from the UI folder. And this will take a couple of different classes. I'll say class name flex flex row um items centered. So here, let's say items center and just by between. I think we don't need to flex row by default. It should be a row already. So let's delete this. And then we'll have a div. So this is the parent component that doesn't really need any classes. But we're going to have a card title inside of it, right? Say card title and we can import this. I'll give some classes like flex items center and gap of two. Now this is going to have the icon which is stethoscope icon coming from lucid react. And let's say class name is going to be size of five and text could be primary. Okay. Then we can say doctor's management. So that's the title. Right after this, we're going to put the description. So I'll say card description. And for the content, I'll just copy it and paste it. You can update it as you wish, but this is what I'm going to have. And then right after this div, we're going to have the button. Let's say this is going to be the button coming from chaten. And we can give some props like the on click. What happens when you click to it? Well, basically we will update the state which is to add the dialogue open to be equal to true. And then let's give some classes. I'll say background gradient to right from primary color to primary as well but oops let's say to primary but we're going to change the opacity a bit like 80 um then I'll say on hover state I think we can say something like from primary 90 to primary 100. Okay, so these are the classes that I'm going to have for this button. Feel free to update it. And uh inside this button, we'll just say add doctor. And just before that, we are going to put a icon. So let's say plus icon class name will be margin right of two and size of four. Okay. So let's save and take a look at take a look at the output. Okay. So this is what we would like to have right once you click to it model should be open and we're going to get there step by step. So that was the cart header itself. Now right after this we can have the card content. So let's import this one as well. Um this is going to have a div let's say last name of space y of four. And basically we would like to take the doctor's array and map through it. Right? For every single doctor, we would like to return something. In this case, we are going to return a def. Let's give the key and make the react happy. So we'll say it should be something unique like doctor ID. And we can give some classes just to make it look nice. Um then we can have one div with the class name of let's say flex then items center and then I think gap of or this should be fine. Then within this div we're going to have an image component. Let's get this from next image. Okay, just import that. And for the source, we will say doctor image URL, right? And then we can have the alt text as let's say doctor.name. Then we can have the width let's say 48. I'll do the same thing for the height. And then we can just add some classes. So I'll say size of 12 rounded full object should be covered and then ring of two let's say and ring color is going to be background and this ring is going to give some box shadow right. Okay. So that's the image itself. We can save this. Right after the image we're going to have one more div. And this div is not going to have any class names but inside we're going to have some content like the doctor name doctor specialtity. If the gender is equal to male we're going to show this one otherwise it's going to be female. Right? So go right after this span and after this div we're going to have one more div. Let's say this time the class name is going to be flex. Items will be center and gap of four. Let's say margin top of one. Within this, we're going to have one div and this is going to include the doctor email. So, let me go ahead and paste this in. So, let's import the mail icon. And then we would like to get the phone number as well. So, I'll basically go right after this div and have the exact same thing, but this time we're going to display the doctor phone. And by the way, this is what we are building at the moment, right? So this is the entire part. And recently we just built the email and the phone number. So that's going to be the gender, you know, the specialtity, doctor, full name, image. Okay. Now we would like to build the right hand side where we are going to display amount of appointments that this doctor has, if their account is active or inactive. And then we're going to put this action. Let's go ahead and try to build it. So after the doctor phone, we have four different divs, right? Not the first one, not the second one, not the third, but after the fourth one, just go here and create one more div. And have the class name flex item center gap of three. And here we'll have one div. Let's say this is going to take the class name of text center. And we're going to put one div. Let's say class name of font semi bold and text color is going to be primary. And within this div, we will basically say doctor.appoint count. Okay. And right after this div, I will have the appointments text. So we just put the number. We would like to put this text as well. So this is going to be inside a div just like this. And then go ahead after this closing div, we're going to check if the account is active or not. If it is active, we're going to show a badge. Let's import it from UI folder. Right? I'll zoom out. So if it is active, we're going to show this batch and it says active. But if it is not active, we'll just show the inactive batch. And this is coming from chaten. Once again, we don't really need to customize this. It has almost every single style that we would need. All right. So right after this check, we can go ahead and add a button, which is going to be this action. So once you click it, it's going to update this state so that we can see the model. So I'll go ahead paste this in. We have a button size of small variant is outline. This is the class name and on click it's going to call this method called handle addedit doctor. Right? This is what we have above for now. It doesn't do anything but that's fine. We're going to build it. And for the content, we will have the edit icon which look like this and the text that says edit. Okay. So just save it and let's see the output. Now we are not going to see anything because we don't have any doctors in the database but we are about to add one by implementing this button where we can display a model. So right after this card just go ahead call a component called add doctor dialogue and this is going to take two different props. Let's say is open and we're going to pass our state which says is add dialogue open and what happens if you want to close this. So we'll say on close you should update our state to be false. Let's say set is at dialog open will be equal to false. Now let's try to create this component. So under the components under the admin. So let me show you once again under the source components admin. We are going to create this.tsx right let's save and try to import it. And this is going to complain because we need to pass the props. And let's go ahead and build this component now. So first let's say we're going to get the is open and on close props. But now TypeScript is not really happy. So let's try to add the interface. We'll say interface at doctor dialogue props. So this is basically the convention. Whatever the component name is, just put it and then at the end put the props. So let's say we're going to get the is open field which is a boolean and then we can have let's say the on close method which is going to be a function where we don't really return anything right let's say void and then we'll say here are the props for this component. Now if you delete them you can see it should be type safe and TypeScript is happy with us at the moment. Okay. Now let's try to build the rest of this component. So first off we would need a state in this component so that we can store the doctor data. Right? We would like to create a doctor where where they have a name specialtity and all these fields that you can see. So let's go ahead and create this state. So this is going to be the new doctor. Let's say use state and this is going to be an object where we have name, email, phone and the rest of it. Now the gender could be male by default. And let's type cast this by saying as the gender, right? And we are going to import it from Prisma. Okay. And here you can see this has two different values, right? And by default it could be active as well. Now we would need a mutation. So when you click this button what is going to happen right in the database we are going to basically do something where we can create the user. So for this we are going to need a server action. Let's go under the lib actions and under the doctors.ts file we are going to create another function. This time instead of get we're going to do create. Right? So here I'll say export async function create doctor and if you want to create a doctor you would like to provide some input right but now the type of this is equal to any let's try to make this type safe I will say this is going to be create doctor input and we can create this as an interface so I'll just copy the name let's say interface And let's say user is going to give us a name which is going to be type of string. Let's say we're going to have the email again a type of string. And I am too lazy to type this out. Basically I will copy and paste. So they're going to they're going to pass a phone speciality gender and is active. And this is going to be coming from the Prisma client. Okay. Now if you take a look at this so if you say something like input dot you can see it is type safe. So here let's just have a try and catch block in the try first I'll say if there is no input.name and we will say or if there is not an email we would like to throw an error. We'll say name and email are required then we can actually create the doctor. So say await prisma go under the doctor table and create something and here's the data that I will pass you right. So say data here are all the inputs right all input fields and on top of it we would like to include the image URL and we are going to generate an image URL by calling a function. Let's say generate avatar and we're going to pass the input name as well as the input dot image. So let's say sorry not image but it should be input gender. And this is the function that we are going to create. Basically depending on the gender it's going to create this kind of an avatar. If it is a female, this would be the type of avatar avatar that the doctor would get. But if if the doctor is male, they're going to get a male avatar image. And these are going to be selected randomly. And for these type of images, we're going to be using this API, which is something that I will provide you in a second. But basically, if you want to get a male profile image, you would put boy in the URL and then you can give a username. So if you give John, this is what you're going to get. If you give something like Bob, it's going to be different, right? And if you want to get some female images, you would say girl. And we are going to decide this with the gender field. Okay, let's go ahead. Now I will create this under the utils. It's going to be a utility function. You can find this from the source code under the utils.ts. So it is going to be like five lines of code where we get the name as well as the gender field and we are going to have this API URL and if the gender is female we will say it's going to be girl and whatever the username is but if it is male it's going to be boy right super simple function nothing complicated there is just a regular expression here which is fine okay now let's go ahead and try to call this method and just import it from the utils folder. And that's the entire thing for this create method. This is going to return us the doctor. And from this method, we can just return it. Now, there is one more thing that we would like to do. Basically, once you add a new doctor, let me show you this. So, once you say add the doctor, you would like to prefetch this data. Right here we can do this on the server side by calling the revalidate path method. We'll say revalidate path which is coming from nextjs right from next cache and the path that we would like to refetch is going to be slashadmin. Okay. So this is going to get rid of the cached data and it's going to give you the latest data. Right? And here on the catch we can handle it properly. As usual I'll go ahead put the console log right but on top of it I will add this check where we say if this is the case that means a doctor with this email already existed right just try to use a different email and here it says code does not exist on this type um let's just say error of you know type of any. But if this is not the case, we're going to throw a general error. So that's the entire method around 20 lines of code. Not that complicated. We have this validation check. Then we create the doctor. Um get rid of the cache. Return the doctor as it is. Okay. So that was the server action. Now we would like to call this mutation under our custom hook. So let's go under the source under the hooks where we have use doctors instead of a get request instead of a query this time we will use a mutation and it is going to be actually pretty similar. So let's say export function and create our custom hook. Let's say use create doctor. You can call this anything that you wish but this is the name that we are going to have. Let's say we are going to return the result of our mutation. So I'll say use mutation and we could do it just like above. So this is going to give us something. Let's say const result and then we could return that result. So it's the exact same thing really. I think this is a little bit more beginner friendly to read. So this is going to get a prop or an argument where the mutation function is going to be our server action. Let's say create doctor and this is going to be coming from the actions. And what happens if you have the success case or if you have some errors? Well, you can handle this by adding the on success. For now, let's just say console log uh you know doctor created and let's duplicate this. We're going to say on error we can get the error itself and let's say error while creating doctor right. Okay. So we're going to make this look a little bit more better, a little bit more clean. But for now just let's let's leave it as it is. Right? We are calling our mutation which is the create doctor and we are returning the result. So let's save and go into this component and try to call our hook. So here I'll say use create doctor and we're going to dstructure a couple of different values. So when we use a query we would say give us the data right here. you can still do it but mostly you would say give me the mutate function and instead of dstructuring um like I will just call this as the mutation and before I do it let me show you the other things so we have like is pending state which means if you are in the loading state while this is creating or not right so we're going to be using these values for now I'll just say instead of dstructuring I'll say create mutation and you can call this anything really but this is the name that I am going to have and then just before we build the return statement let's have couple of different functions so let's say con handle phone change for now let's leave it empty we are going to be using this method so that we can format the phone number right it should be in this beautiful format um then we are going to have one more function so what happens when you click to the save button. So I'll say handle save and for now this could be an arrow function as well an empty function and then let's say const handle close. So what happens if you want to close the model we are going to implement all of them one by one but first let's check the shaden documentation for the dialogue component. Okay, so this is the type of component that we are going to be building and here is the usage. So you would wrap everything with the dialogue and then you have the trigger which is this button and then you have the content right dialog content where you can have header, title, description, so on and so forth. So everything is coming from the documentation. Just keep that in mind. Now let's go here and instead of returning a div, we're going to return the dialogue component. Let's import this from the UI folder. And then this is going to take the open state. We will say is open which is coming from the props. And then what happens on open change basically we're going to call the handle close method. And once you close the model, we are going to call the on close method. And we would like to reset our state. So what I'll be doing is this. Basically, we'll say set new doctor with the initial states. And then right inside this dialogue, I will have dialogue content. Let's import it from UI folder, not from the radics. And this is going to take the class name where I'll say smaller screens and above. The maximum width should be 500 pixels. Then we can add the dialogue header which is going to be looking like this. So this is the header component. I'll just paste this in. Let's import the header. Let's get the title as well as the description. Just make sure that you import all of them from the UI folder, right? And this is the content that we're going to have. So, right after the header, we will have like we're going to have a couple of different divs and then we're going to put every single of these input elements with the label, the input, and we're going to do it couple of different times and then our actions. So, here I'll have a div. Let's say the class name this is going to get will be grid gap of four and petting y of four as well. Then one more div. Let me zoom in. Okay. So we'll have class name of grid. Let's say this time it'll have grid columns of two. And then we'll say gap of four. Then we can go inside where we'll have one more div. as the like that's for the parent where we can have the label as well as the input. So I'm just going to delete it. Paste this in. We have a div. Let's get the label from the UI folder. Okay, from here um and then we're going to get the input. Okay, please feel free to pause the video and type this out. Let's not waste any time. So let's save and see the output. If I click to this one, model should be open. We can see the label as well as the input, right? Okay. So that's what we are doing. And here we have an ID as well as the HTML form. So they are matching. And this is because if you click to the label, it should focus on the input. So that's one thing that I want to mention. Now, we're going to be basically doing the exact same thing couple of different times for the other fields. So, I will get this exact same thing where we have space y of two. This time it is for this field and once you type into it, it's going to update this field, right? It's going to keep everything as it is, but it's going to update the specialtity field. And here in this case, it's going to update the doctor name. Okay, now let's go after these two divs. Okay, so just go after them, paste this in. It's the exact same thing, but this time for email, and it's going to update the email state. Um, here we would like to do for the phone, the exact same thing. So, right after this, paste this in. Okay, what do you mean? It says this method doesn't expect any arguments. So let's go ahead say you're going to get a value sir and type of you will be string. Now this should be happy with us. Okay. So this is super basic react that's why we're just copying and pasting rather than explaining all the details. Right? So after the input after this div we're going to go ahead create one more div. Let's say class name of grit. Let's say grid columns of two and gap of four. And this is going to get the space Y2 class. It should be in this way. And then this is this will take a couple of different fields. And this is where we build the gender select element right here. This is what we call the select element. Let's double check. Okay. So you can add couple of different options right and again you can see the usage from the documentation or just follow along with me and find out right so right after this step I'm going to paste this in and I'll walk you through it first let's import the select get the trigger component the value content as well as the item. So once again everything is coming from the documentation but basically we have a trigger element as well as the content which has the value of male and female and this is the content that user will see. Once you select one of them, this function is going to run and we are going to update the gender field in our state. And here I just type cast this where we say value is going to be as a gender so that we know this is like male or female just to make our code type safe. Okay. So I think that's the entire thing. Um let's see how it look like. I mean how does that how does that look? Okay. So we can select this or this value. Now right next to it we're going to put the status. It's almost the exact same thing but this time it'll be you know active or inactive. So after this select and after this div let's go outside. Paste this in. So the status either active or inactive. Again let's just save this and see the output. Okay. So this is working as expected. Now right after this we're going to put our buttons. So after all these three divs go ahead and create this component called dialog putter and import it. Then we're going to put a button where it just says cancel. The variant is going to be outline. And once you click that it's going to close the model. Right after this we'll have one more button. Let's say this is going to take some props like on click we're going to call the handle save method and then let's give the class name background could be primary and on hover we are going to say bg primary 90% of opacity. Now one more thing we will say this button is going to be disabled in these scenarios right let's say if there is not new doctor name or if there is not new doctor email basically if these fields are empty we want this button to be disabled let's do the same thing for the specialtity I don't know if that's how you pronounce it but I think that should be fine And finally if we are in the loading state and we can check this by saying create doctor mutation is pending right. So this is how we can check for it. We could either dstructure it that I showed you a couple of seconds ago or minutes ago. We can get you know is pending state demutate things like that or we can just say create doctor mutation dot is appending and for the content we'll just say add doctor right but we would like to see it if we are not in the loading state so I'm going to cut this I'll say if create doctor mutation is in the pending state we can say adding dot dot dot what else we'll say add doctor okay now let's try to see the output now this is disabled we cannot click to it once we pass all these fields then we can do but what happens if you click to it nothing happens because we didn't really implement this method okay now let's try to make that work now first off before we built this method I would like to handle the phone change and for this we are going to have a util function let's go under the utils and this is a function that I have generated with AI please feel free to copy and paste it from this file under the source code so this will take the phone number and it's going to beautifully update it right it's going to format it and this is something that you know if you delete and tell me to type out the rest of it I cannot not really do it because I didn't type this out which is completely fine. This is AI generated for in like 2 seconds, right? It's going to take the value and beautifully formatted to us numbers. Okay, let's save and we're going to be calling this here. Basically, I will say call the format phone number method. We're going to pass the value and let's say this is going to give us the formatted value, right? And we can update our state. We'll say set new doctor. Just keep all the fields as it is. But on top of it, just update the phone number to be this formatted value. Let's just say formatted number. Okay. And let's actually say formatted phone number just to make it super beautiful to read. Okay. Now let's try to see the output. If you put like 555 1 2 3 like automatically it updates for you. But if you didn't had this method right if you just had the value as it is if you type it doesn't format it. So that's why we have generated this method. Okay. Now that we have this working as expected we can save this file and get into the handle save method. So once we click to that add doctor button we are going to call this method where we would like to call our mutation. So we'll say create doctor mutation. Go ahead call the mutate method and we would like to pass this object where we will take the new doctor object and pass into it right and then if you wanted to we can add the on success field as well. So in another object you would have on success. You know what happens if it is done successfully? Well, we can close the model right. So we're going to call this method. Let's say call the on close method. And we can also update our state to be reseted. Well actually you know like instead of copying this and pasting in we can sorry like instead of doing this part it is the exact same thing as this method right we can just say call the handle close method and this should be it and we can even shorten this let's go ahead just say handle close Okay, so that's what we're going to have. We can even shorten this. I'm just realizing, but we could just say handle close. And that's it. Let's put this as one line. Okay, so we're going to call the mutate method by passing our state. So this is the new doctor that we would like to create. And on success, just go ahead close the model and reset our state. Well, let's test it out. First, I'll open up my database so that we can check it out in real time. Let's put them side by side. Under the tables, we don't really have any doctors, right? Let's see it pretty quickly. We have a user, but no doctors at all. Let's add something. I'll just refresh. Okay, we have the aloing state. Let's add a doctor. So, I'll say Dr. John Smith. Let's put dot at the end. Let's say general dentistry. And for the email, we can just say John Smithgmail.com. If I can type correctly, I'll say 555 1 2 3 4 5 6 7 mail active. Let's say add the doctor. We have the loading state. It has been added. Hopefully, it should update the UI, but look looks like it doesn't. So, we can fix this. No worries. Let's take a look at the database and just refresh. Okay. So, we got the doctor, right? All the fields as provided and we even got an image URL. So, let's copy it, paste it right here. Okay. So that's the image URL for this user. Now if you go back to the local host, you're going to see this error and we can fix this by going into the next config.ts file just like what we have done here. We can add this domain as well for the images. Okay, once you have add this, if you refresh, it should work as expected. So let's wait for a second loading and then we should be able to see this new doctor that we just added. Okay, now everything is working as expected. But there is one thing that we need to fix. So once we add a new doctor, we would like to see it immediately, right? We don't really want to refresh this so that we can fetch the latest data. And to be able to implement this, we are going to visit our custom hook. Let's close this file. will go under the use doctors. So once we have create the doctor successfully, instead of just having a console log, we are going to update the cache on the client side. So this is how we can do it. Just follow along with me. So we're going to get a query client by using this hook from tenstack and then on success. Let's open up this function. I will say queryclient dot invalidate queries. Basically, we would like to run this query again so that we can get the latest data. And if you want to call this query, you just need to pass the query key here. I'll say here's the query key which is equal to get doctors. Okay. So once again once we successfully create a doctor this method is going to run and this is going to call this query right which is to get the doctors and clean the cache right clear the cache. Let's say invalidate related queries to refresh the data. Okay. Now let's try to test this out once again. I'll just refresh this page and create a new doctor. Let's this time say doctor.jane smmith again. We can say something like general dentistry. That's fine. janegmail.com or maybe Jane Smith. Let's give a different number this time. I'll do backwards because why not? Okay. something like this. Let's say female. And it should be active. Add the doctor. Once it is added here, you can see it immediately fetches the doctors because it's going to call this mutation. So I hope that we understand everything that is going on at the moment. Right? So that's the entire thing for the add doctor dialogue. Next, we would like to create another one so that we can edit the user profile. So we are going to click to this button right it's going to open up another model where we can update the user profile. So this is what we're going to build now. So let's get into the VS code. Before we build the component let's try to build the server action under the doctors.ts. So that was the create doctor method. Now let's say export async function. This time this is going to be update doctor. And if you want to update a doctor profile again we would like to pass some input and type for this is going to be pretty similar actually. So let's say interface update doctor input and this is going to extend let's say extends uh you know the create doctor input and we're going to pass the ID let's say string. So here we're using partial which means we might have some of these fields under this input or we might we might not have right maybe user only wants to update the name of the doctor and we will say you know just get everything and on top of it extend it with the ID so that we know which doctor that we are updating. So I hope that makes sense if not just follow along. I think it's going to be clear. So let's say this is the type of input and here if you just say input dot and you're going to see all these fields are partial right which means they are optional they have question mark but id is always going to be there because we are extending it okay now let's say try and catch block under the try first I would like to validate some required fields right so here I'll say if there is no input name or if there is not email we'll just say these fields are required then after this if check we can find the actual doctor right so I'll say const current doctor and let's say it's going to be equal to a wait prisma doctor right we'll go under this table and we would like to find a unique resource and we'll say where the ID is equal to this input do ID And we would like to select let's say the email field here. I'll just say email should be equal to true. So this is going to have the doctor with the email field because we're going to be using this email in a second. First let's say if there is not the current doctor. We'll just say doctor not found. And one more thing another if check that we're going to have. So if you want to update a profile and if you want to update the email before we update it, we should check if the new email existed or not because email field should be unique. So for that reason, I'll just add a comment. Let's say if email is changing, let's add it as a comment. Check if new email already exists. And here is how we can do it. I'll just say if input email is not equal to the current doctor email which means it is changing and we're going to check if it is already existed in the database or not. If it is existed we are going to throw an error but else we can go ahead and create the I mean update the new doctor. So we'll say await prisma doctor.update update and here are the fields that we would like to update. But first, we need to say which doctor that we would like to update. So we'll say here is the ID that is coming from the input do ID, right? And then we're going to pass another object. Um actually this is going to be the data field, right? Um here are the data that we would like to update. So we'll say name is going to be coming from input.name. So if user wants to update it, we're going to pass it as it is. Then let's duplicate it a couple of times. We're going to say email. Basically, all the fields that a doctor could have. Let's say phone. And after the phone, I have added these three different fields. Now, we don't really want to update the image URL. So, you don't really want to uh have this here, right? So, we're going to ignore this. Um, I think that's the entire method. We can just go ahead and just say return this doctor out of this method. And if we have any errors, we can handle this. I mean just by throwing the error, right? Um so that's the entire server action. We have a couple of different checks. And once everything is fine, we can update it in our database. And just one more thing you might be asking instead of doing all this where we get the input, put the name, email, phone, couldn't we just delete everything and just say get the input fields and spread this? Well, that's what I think as well, but I'll just leave a comment here. If you do this, it's going to trigger the unique constraint violation for email. So, you're going to get an error. That's what I had. So, that's why I'm going to leave it in this way. Okay. So, I will save this file and we're going to go into the hooks and specifically for the use doctor's file. Right. Within this file, we will actually have the exact same thing, but this time in the mutation, we are going to call the update doctor method. Right? So, I'll just paste this in. As you can tell, it is the exact same thing that we have up here. We're going to get the query client, return the result of this mutation. So, you can either assign it first and then return or just immediately return. That's the same thing basically. And here, let's get the server action. And once we update it successfully, we are going to call this query so that we can you know get the latest data instead of using the cache. Okay. So that's the entire hook. Let's save. Now we can go ahead create this component or the dialogue. Right? So I'll say here we will have the edit. Let's say doctor dialogue. And this is going to take a couple of different props just like what we have here. Maybe I can copy it, paste it or the is open. We'll say is edit dialogue open. And here we will do the same thing for the on close method. One more state that we are going to add which is going to be the doctor and this is going to be the selected doctor state. So basically let's say you click to this doctor right it's going to take that doctor's detail and put it into this component. So that's why we are passing it. Now let's copy this name. Go under the source and components under the admin. We're going to paste this in and let's say tsx. Let's try to import it and then get into the component itself. So this component is going to be really similar to this one because at the end of the day this will also be a dialogue. Right now just before we get into this I think we are missing we are missing couple of different things right here which are these methods. So when once we call this we are actually we should be passing the doctor. So I'll say here's the doctor argument and we are going to take this let's say this function gets the doctor you just type of doctor right um go ahead and import it in this case we'll say set selected doctor state with this one and then we're going to make this edit dialogue should be open right we'll just say this is equal to true um here this says some TypeScript errors we have the signature does not match. So to fix this, we are going to go ahead and here explicitly we'll just say this could be either a doctor or a null value. Now this should be happy with us. And once we close this dialogue, we'll say set is edit dialogue open should be equal to false because we just closed that. And the selected doctor is going to be equal to null since the dialogue is closed. Right? And we would like to call this as well once we click to the like once we call the on close method. And here I think this is wrong. Let's fix it. We will say handle closeedit dialogue. Okay. So once again what we have updated is this part as well as these two methods. Now let's go here under this component we are going to create an interface say interface and we can put this as this name. So we'll say ops at the end. This is taking the is open field which is a boolean value on close method which is basically a function that doesn't return anything. Let's say this is a function and it doesn't return anything. And then this is getting the doctor state. Let's scroll to the bottom. We are getting a doctor that could be either doctor type or it could be null. Okay. Now let's try to dstructure these fields there. Type safe. That's perfect. Okay. Now we're going to have one local state. I'll say const and let me give a little bit space. I'll say editing doctor and it said editing doctor let's say use state initially this could be null or we could just pass the doctor as it is and here let's say this could be type of doctor or it could be even null. Now, even though we set this up, like if you console log this, let's see what you're going to get. Let's say editing doctor. I think we are going to get undefined even though we passed it. This is how React works. Kind of annoying, but once you refresh, we should be able to see null, right? Even if you clicked it, it's still null. And you might be saying like, we have this, right? Why is it null? Well, let's not talk about the why, but we can talk about the solution here. We can add a key, which is going to be let's say selected doctor ID. Okay. Now, if you refresh this page, so it is null initially, but if you click to it here, we can see we are getting the actual doctor value, which is a Jane in this case. If you click to this one, it should be John. All right. So that's the solution. This is going to force this component to be mounted every single time the doctor field changes. I hope that makes sense. And to be honest, this is a bit advanced React. So if you have seen this for the very first time, that's really good. Uh because this is literally some advanced React. You wouldn't see this in other places. Seriously, like you you probably you have never seen this. Uh but anyways, let's go into this component. Let's import our hook which is use update doctor. We are going to get our mutation and then we can have the exact same method that we have here which is to handle the phone number change. So I'll go ahead paste this in import the method and this time we'll say set editing doctor. Um this value is going to be editing doctor just keep it as it is but we'll like to update the phone uh field but what this says I think we have some kind of a warning um basically it says this could be undefined so you have to add some check um I'll say if editing doctor if this is available right only then go ahead run this if this is null don't don't do it So this is how we make Typescript happy. And then on handle save we are going to be doing a similar thing what we have done right here. We are going to call our mutation and on success we can call the handle close method. So I will go into this component just paste this in on handle save on success we are going to call handle close which is basically is going to close the model and set this state to be equal to null. Okay, so really really easy three functions. I hope this is not really confusing. And then we're going to have the return statement. Let's give a little bit space which is going to be a dialogue component. So believe it or not, that's the exact same thing what we have here. We will just change a couple of different places like the content. Instead of saying add new doctor, we will say things like edit this current doctor. First, let me copy this dialogue. Paste this in and close this off. Okay. So, we are going to have this component. Let's import it. Then, um I think I'll just get the dialogue content as well right after this. Paste this in. Close this off. And import the component from the UI folder. Right. And let's get the content. So I'll copy this part, paste this in and import all of them one by one. Okay. So we're going to update the text. Let's say edit doctor. Um here we are going to update the description as well. Say information and status. Okay. then we'll go right after the header and we'll have a check. So here I'll say if there is editing doctor right in this case we would like to render render this right hand side which is going to be this div that is um that is getting the class name of grid and basically we are trying to build this model at the moment right so we have the header now it is time to build the actual inputs so since we have done this previously I think you can just copy and paste it from the source code it's not really that important at this point. So this is what I just pasted. Let's import the label as well as the input. Okay. So we're having the name specialty and we are updating only those fields once we type into the input. Then we are going to shrink this grid columns too. Go right after this div. Paste this in where we have the email field. And then right after this one, we're going to get one more div. So after this one, paste this in where we have the phone field. And then right after this one, we are going to paste the select items just like what we have done previously. So let's get all these components. Get the gender type from Prisma client. Select value content as well as the item. where we have the status that could be one of these values and the same for the sorry the gender. Okay, sorry the gender could be one of these fields under the select item and the status which could be one of these either active or inactive. And then we'll go right below to this conditional check, right? And we're going to add the dialogue footer component where we can let's see where we can call the handle close method as well as the handle save when we click one of these buttons. Okay, so I know that we have copied and pasted almost the entire thing, but that's basically the same content, right? You could probably make a reusable component instead of copying and pasting. But I think that's completely acceptable. It's really easy to understand in this way. Um I think we have everything as expected, right? We should just test this out. I'll go in admin dashboard. Let's try to update the name of this uh of this doctor. We'll say Jane Smith 123. update. Okay, here we can see it's been updated. Let's refresh. It should still have that one, two, three at the end, which means this is actually coming from the database. Let's refresh. Um, let's see. Okay, so this is the actual update. Let's put it back. And we can update multiple fields at at once, right? We can say inactive. Dentistry 1 2 3. Save changes as you can tell. Now let's bring this back. I'll make this to be active. And this should be good to go. Now here we can see as we update the dashboard is also changing. I just realized that if this is inactive the active doctors is going to be one. So everything is in sync actually. Um okay so that's the entire thing for the admin dashboard. I would like to update this loading state which doesn't really look nice. So we are going to be going in this component and here this is your challenge to build a beautiful return statement but just to make you happy I think I will have one of them. So let's say loading UI and we can create this at the bottom of this component. So since it is 10 lines of code, I have just copied it and paste it from the source code. Feel free to pause the video and type this out or you could even change this UI completely. Let's refresh and see. Okay, so this is the component that we just added. It has the navbar and then the loading spinner as you can tell. Now with this I think we have completed the entire admin dashboard the admin page. So what we have learned in this section is this convention where we basically have some hooks that are calling our server actions right. So in this case we are getting the appointments and here we have everything related to doctors that could be either a mutation or some queries right and we have learned about how to update the cache as well right using tenstack query so with that let's try to open up the status bar and commit our changes first I'll create a new branch let's say admin page And then stage all of our changes for the message we could say admin page added and let's say commit I think then publish the branch go into source code and create the pull request. So we have so many changes. It's going to take a bit, you know, it's going to take a while for code rabbit to review the entire code and give us some suggestions. So let's wait for it and then come back to the video. So these are the new features that we have introduced in this section. Here are all the files with the related changes that we have done. You can pause the video and read it. Let's not waste any time by reading them, right? You can always do it. Then we have the sequence diagram where basically this says under the page.tsx tsx under the admin page there is an authentication check right if you are not the admin or if you don't have this environment variable is going to redirect you to the dashboard page but if you're admin you can see the admin dashboard client component which is going to handle the getting doctors getting appointments and then within this component we have the create doctor functionality right and then updating uh functionality as well. And here is how everything works. You can pause the video and take a look at it, but we have already explained this in detail. Then let's see some code suggestions from code rabbit. Um here it says instead of just getting the loading state, you could also get the error, right? This is something that you can get by the way from tenstack. You can also get the refetge method. So this is a really really good suggestion that you would like to have in a real world application. But here let's not waste any time but let's take a look at the code itself. So if there is any kind of an error you would like to show the error UI just like the loading UI and in the error UI you are going to call the on on retry method which is basically going to refetch the data again. Okay. So this is something that you can look into if you're interested. Then here some more optimization. It says only call this method if it is you know if it is already open. Let's scroll to the bottom. Okay. So this is one of the ways. Let's go into the component. So I think it should be under the edit doctor dialogue. So you remember we have this field but it was equal to null. So we went into the admin dashboard client and we have let's see where that was. So here we have included the key. So this is the other way of doing it. This is what we have selected. But if you want to, you could add this use effect that they show you. It is just a different way of doing it. And then like literally you can pause the video, take a look at all these suggestions. I think these are all the things that I would normally implement if this was my let's say actual project that I was building. But since this is just a tutorial, I would like to skip them and merge my pull request so that this tutorial doesn't get 10 hours. Okay, let's say merge this. Confirm. And then we'll go ahead get the latest changes. Let's close it all, get into the master branch, and sync this up. Okay, now our codebase is up to date. Do we have any errors? Oh, we don't. Okay, so that was the entire section. Hopefully, I'll see you in the next one. So, in this section, let's get started with the pro page. So, first like this is the UI that we're going to get into in a second. But first, let's create this page under the app folder. I'll say pro and then page.tsx. TSX let's say pro page and leave this as it is. Now we are going to have three different plans, right? The first one is free where you don't really pay anything and you're going to get access to these features. Let me change the color. Okay, so these are the features that you will unlock in the free plan. And then in the basic plan, you're going to pay $9 a month and you're going to get access to everything in free plus these features. And in the pro plan, everything in basic plus the rest of rest of these, right? And we are going to be implementing this payments, the subscriptions with clerk. So head over to your clerk dashboard. You can find the link in the description. Select your project, right? And then under the subscriptions, we are going to say get started. Okay. So, we're going to have three different plans, but for now, let's just say create the very first one. And this could be the free plan, right? Let's click to it. And we are going to include all these features. So, unlimited appointment booking, the basic text chat support, and appointment reminders. So these are the things that we can type out one by one. The only thing that you need to fill in is the name. So I'll just paste this in which is unlimited appointment booking. Right? You're going to get a key by default and you can leave the description like this is optional. Okay. I'll just say publicly available. Let's create this feature. And then I will create these two as well. Okay. So basic text chat support and appointment reminders. Here I'll say add another one. So here is the name and let's say create the feature and then one more which is going to be appointment reminders. Okay. So say create feature and we can save this. So that was the free plan. Let's go back and we are going to create one more plan and this is going to be AI. Let's say basic. We can leave the description empty. Okay. So, this is um optional as you can tell. For the base fee, we can say it's going to be $9 a month. You can add annual discount even have a free trial. So, these are the option that you can toggle. And for the free trial, you can say like it could be 7 days or 14 days, right? But in my case, I'm not going to have free trial. Um let's add our features. So we are going to say everything in free plus these I mean everything in free plus these features. Right? Okay. So let's say like instead of including them I'll just type it out. Let's say everything in free. So that's the first feature that we're going to see. And then let's add the rest of it, right? Let's start with this one. 10 AI voice calls per month. Then the next one is going to be AI dental guidance. So here I just put the name and let's say create the feature. And then the other one is going to be priority support. Let's add one more. And then I'll just say create this one as well. Okay. So here I'll just say save this feature. I mean save this plan as well. Now you might be asking in our application we don't really have appointment reminders or we don't have something like um priority support, right? So that's true but we are just adding this just to have this beautiful table. You can always add these features once you complete the project. Right now let's go ahead create this plan where we will have everything in basic and these three features. So here I'll go back create one more plan for the name we can say AIO and this is going to be the key and if you're wondering what that is once we use it I think you will see it um here for the fee we can say $19 a month you can add annual discount free trial but I'll just skip them just to keep it simple in here we can say everything in basic add this feature let's say basic and on top of this let me add all these three features one by one so this is the first one and let's add the second one so I just added these two as well okay so let's say save and that should be it for all of our plants now it is time to actually use them in our code. So we will go ahead find the VS code right. Um here let's say this is going to be an async function because we're going to have one check by awaiting the current user. So we'll say con something like user and we'll say if there is no user maybe you can just redirect the user to the homepage right but else we can see the return statement. So let me zoom in under this return. Let's say we're going to have a fragment. The first thing that we would like to see is the navbar component. And right after this, we're going to have the actual content. So I'll say div. Let's give the class name maximum width of 7x large auto. Let's give some padding from like the x direction. is going to get six. Vertically, it's going to get eight. And then petting top of 24. Then within this div, we can have the welcome section. So let's see it what I mean. So we have the navbar at this point. On top of it, we're going to have the welcome section. So this is something that we have built in the past as well. But let's go ahead and see how we can build it. Once again, I'll have the margin bottom of 12 class and let's say overflow dash hidden. Within this, I will have one div. Let's say class name is going to be flex item centered. This is kind of annoying. In the past, when you just say IC, this would get get you the item center automatically. But now it gets this one just kind of annoying. uh let's say justify between we'll have background gradient to bottom right from this color which is primary and let's change the opacity. So from primary 10 to let's say background. Okay. So this should be let me zoom out to dash background. And then we can say rounded 3x large. We'll say petting of eight. Let's give the border and border primary of let's say 20. Within this, I'll have one div with the class name being space y of four. And then I'll have one more div. I'll say class name inline dash flex. If you don't want to type this out, just uh grab the code from the source code. I'll say items centered. Then within this, we'll say gap of two, padding x of three, y of one. I'll say background primary. Let's change the opacity. I'll say rounded full border and border primary. Oops. Let's just say border primary of 20. So AI kind of annoying at this point, but these are all the classes that I have. Within this div, we are going to have one div. Okay. So, it's going to be this this circle and it's going to be animated with the tailwind class. I'll go right here within this div paste this in. This is that circle. And right after this, we will have a span that says upgrade to pro. Okay. So after this div, let's go and get one more div that is going to have the H1 that says unlock premium AI dental care and this is the description which is this part the heading as well as the description and on the right hand side we would like to put this icon and beautifully design it. So here right after the P tag after these two divs I'll go ahead paste this in where we have the crown icon and we can save this see the output. Let's go under the pro page. Um here it says let's refresh. Let's refresh again. Am I running the application? Let me check. Yes, we are running. Uh maybe I just need to log in once again. Looks like we logged out. Okay, so we are redirected. It took a bit of time, but that's fine. Now let's go under the pro page. Oh wait, why am I running the demo application? Wait, I'll open up the terminal. This is running on localhost 3001. Okay, on my second screen, you cannot see it, but I am running the demo application. So let me kill the terminal and kill the other application as well. Now let's run this. Let's say mpm rundev. Okay, sorry about this. But now once you refresh, you're only going to see this part, not the pricing table. And we are going to implement this in a second. Okay, so this is the actual application that we have, right? The other one was the demo application that I built previously. Okay, so far so good. We have this welcome section as well as the navbar. Now let's go ahead and build the pricing table. So after the first, second, third, and fourth div. Okay, so just go right below to all of them, we will say the pricing section, which is going to be maybe five lines of code. Let's say space y of 8. Then we're going to have one div that is going to have the class name of text center and I'll just have the space Y of four. Then H2 which is going to say choose your plan and this is going to take some classes like text 3x large. Then let's say font is going to be bold. Right after this H2, we will have a P tag. Let's say class name is going to be text muted foreground. And here by the way, I don't know if you have realized, but I am doing this all the time. So I don't need to type this properly to get the auto suggestion. Right? So if it is text me to the foreground, I can say like te Mfr, right? It's going to understand it. Let's do it. I'll say TE empty you know FR and press command space I can see the suggestion so that's really cool and keep this in mind let's say maximum width of 2x large um MX of auto let's say then this is going to get a text which I am pretty lazy to type out let me copy and paste okay so pause the video type this out and we should be good to go so that was the text. Let's save. All right. But where the hell is our table, right? How can we get this pricing table? Well, we don't really need to write any code for this because we already have our plans. We created the billing, right? So, we'll just say, hey clerk, could you please give us a pricing table? So, under this p under this div, go ahead and call the pricing table which is coming from clerk next.js. JS. Okay, now let's save and hopefully we should be good to go. Um, it says this component cannot be rendered when billing is disabled, but I think we have enabled it, right? Let's see once again. Oh, come on. You might be kidding. Didn't we created all the plans under the billing? Not this but subscriptions. Okay, I'll just say enable the billing. I think we created the plans but looks like we didn't enable it. And for the payment gateway, we'll leave it as the recommended. Okay, that's fine. Let's say create bill and guide, which is something that you can do for now. Let's say maybe refresh this page. It should be working out. We just enabled it. Okay, here we go. This is the plan that we are currently using. By default, it is the free plan. And if you say subscribe to the basic plan, which is like $9 a month, you're going to get this beautiful popup and you can either pay with your actual card, but we are in testing mode. So, I'll just say pay with the test card. Okay, payment was successful. It is done and you should get an email. Let's actually check this out. So, here we can see 0 minutes ago. Just now we got the receipt from the Dentwise application. So, that's pretty cool. Um, let's leave this and we'll just say continue. Now, this is the active plan that we have and it takes us to the dashboard. But let's visit the pro page. This is the active plan that we are currently in. Now, how do we check if this plan is active or not in our code? Right? How do we know if this user is in the pro plan or if it is in the free plan? So, I'll show you in a second in the code. But one more thing I'd like to mention. So, from here, if you want to upgrade to AI Pro, you don't need to pay $19 because you already pay $9, right? If you say switch to this plan, you're gonna see that you have $9 discount. All you have to pay is like $10. Okay? And let's go here under the manage account. We can see the billing. Even if you say, you know, like cancel my plan, it's still going to be active for for like 30 days because we just paid it, right? Um so yeah, even if you cancel now, it's not going to be cancelled immediately. um you will still be able to use it for the next one month just like any other production grade application like Netflix, Spotify, things like that right okay now let me show you how we can check if this user is on the pro plan or not so I'll go here and first import the oath let's say await oath which is going to be coming from clerk and we can dstructure one thing called has okay Then let's say if user has and put an object here we are going to put our key let's say plan and the key that we have for this was AI_basic let um like let's remember that under the subscriptions and here you can see your monthly recurring revenue entire revenue right let's go here this is the user he's under the AI basic plan. Wait, how do we like how do we find our plans? Let's go under the subscriptions. Subscription plans. Okay, so under the subscription plans here you can see we have the plan keys, right? This is for the free plan, AI basic and AI pro. Now we're going to check if user has any of these, right? Which is AI_basic and AI plan. So here I'll say uh plan AI_basic or let's say let me copy this as well AI pro and we can assign this into a variable like has pro plan and user actually has one of them which is this one at the moment. Let's say console log has pro plan and see it under the terminal. Currently it is equal to true. Right? Because user is on this plan. This is going to be equal to true. And we can see it in the terminal. So this is how we can easily check if user has paid. if user is under the paid plan or under the free plan just in one line and this is coming directly from the clerk's documentation. So that's the entire thing for this entire section. We are going to be using this in the upcoming sections but in this page we don't really need to check for it because in our component we can already see if user is in the active plan or not. Right? Okay. Now let's go back into VS Code, open up our status bar, close everything, and let's create a new branch. Here we can say pro-page stage all of our changes, which is only one page with 60 lines of code. That's fine. Let's say pro page and pricing table added. Commit. publish this branch and let's go into the source code which was a dent wise and we are going to create the pull request as as usual. Okay, now let's wait for the code suggestions from code rabbit. I don't really think if we have any anything bad so far right at this point but let's just wait what we're going to get from our AI assistant. So after 5 minutes, here is our quick summary. Here are all the features that we have introduced, the file that we have updated with the summary itself, the sequence diagram that you can pause the video and take a look at it. And then let's scroll to the bottom here. This says instead of getting chrome icon from Lucid React, you should get this one. and a AI things like chrome icon doesn't existed because um you know the lucid react team have added this chrome icon after the crone itself so this is completely fine if we skip this because this like this icon is actually working right and we can see it here on the output then let's scroll to the bottom here I think we have a typo it says it should be bg G primary instead of like we are missing the Y at the end. Let's see where that is. BG primary. Okay. So here we should have a Y. We can add this. If I add it now, I have to add a new commit. So I'm not going to add it at this point. But in the next section, we can fix that. Um let's go back. And we don't really have any other suggestions. So that's cool. Let's say merge this pull request. Confirm it. And once this is done, we'll go back to VS Code. Switch to the master and get the latest changes. So currently we don't have the pro page, right? But once we sync this up with the master branch, we can actually see the latest changes. So these are all the things that we have added in this section. Hopefully I'll see you in the next one. In this section, we are going to get started with the voice page. This is the end result that we are going to have. So basically we have some text, you know, some cards just to make the UI look nice. And then right after this, we are going to have this widget where we can start the call with our AI voice agent. So, we're going to get into it in a couple of minutes. But first, let's try to build the UI elements. So, under the source, under the app, let's create the voice folder. And we are going to create the page.tsx file. Let's generate this and let's say something like voice page and pretty quickly test it out if it is existed or not. Okay. So, that's the voice page. Here we'll go ahead and check if user has access to the paid plan or not. Let me say this will be an async function and we can import the oath from clerk call this await and we'll say const and dstructure the has method. So I will say con has a pro plan. I'll say has and call the method pass an object which is going to be plan. And if you remember our plan was either AI basic or it was AI pro right I will copy this paste this in let's say it should be AI pro if user has one of them that means they are in the pro plan but if it is equal to false we'll say if has no pro plan then we can return something like pro plan required UI right so this is a component that we can build. Let's go under the source components and we are going to create this folder called voice. So we are going to create this folder called voice and every component that we would need in this voice page will go here. Right? So I'll copy this name. This is our very first component. Let's say tsx. This is just UI. It's not going to contain any logic at all. And you can copy this from the source code. Just find this file, copy the content, and paste this in. It's around 80 lines of code. I know that's a little bit annoying, but let's save and just see the output here. I will import this. And let's say if true so that we can actually see it. Okay. So, if you are not on the paid plan, this is what you're going to see. Once you click to it, it's going to take you to the pro page. Right. So far, no logic at all. Just a welcome section or kind of like banner. I don't know how you would like to call this. And then a cart. Okay. So again, no logic at all. I hope you guys don't mind it. Just some CSS classes. If we type it out, it would take 20 minutes, which is unnecessary. Okay. Let's do command C. In our case, we are going to see this part because we are in the pro plan, right? We can see the voice page that is going to look like this. Once again, we can copy and paste this part as well as this part. But we can actually build this in uh without copying because this contains some logic but these two doesn't. Okay. So I'll go under the VS code. Let's say in the return statement we'll have this part. Let me give a little bit space. Okay. So I'll have a div and let's put the class name maximum width of 7x large as usual. Um actually actually we're going to put it in a different place for now. Let's say minimum height should be screen and the background can be the background color and then right after this we're going to put the navbar component as usual and then we can put the main content. So I'll have the div let's say class name maximum width 7x large MX auto and here are the rest of the classes just some pettings and within this div we are going to have two different components one is the welcome section and then the feature cards. So once again this is the welcome section and then these are the feature cards. Let's try to copy and paste them from the source code. So go under the components under the voice create both of these files. Okay, let's try to import this and copy this name. Create it as well and just import it. Now from the source code, you should be able to copy them and paste them in. So, the welcome section is like 30 lines of code. And this one is going to be a little bit larger, but that's fine. Let's delete the content. Pastes in almost 80 lines of code where we just have some UI elements. No logic at all. Okay, let's save every single file and see the output. Okay, so this is what we would like to have a welcome section and some featured cards, you know, that says how to use it, some features just to make UI look nice. Okay, on top of them, we would like to add this widget where we can actually talk to our AI assistant. And to be able to implement this, we are going to be using VPY, which will allow us to build voice AI agents. So, we already have an account. Let's go ahead and log in. I'll just open up the dashboard. And I think I am already logged in. So I'll walk you through it all the steps. But first, let's visit the documentation. Okay. So this is going to allow us to build voice AI agents that can make and receive phone calls. Now on top of the phone calls, we can have web calls. Right? In our example, we'll be using the web calls, but you can definitely integrate the phone calls as well. If you would like to maybe at the end of this tutorial, you can go through this section where they show you all these steps. So, you would like to first create an assistant, which is something that we have already done. Add the first message, the system prompt, and then you would like to set up a phone number. They give you like up to 10 10 different phone numbers, but they are only for US. If you would like to have an international phone number, you would like to just import it into their dashboard. And you can grab them from, I believe, a platform like Tilio and then just scroll to the bottom. You're going to see that you need to attach your assistant to that phone number, make your first call. So you can go through these examples but in our case we would like to uh we would like to have some web calls right now they have two different ways of integrating it. The first one is the client side the other one is the server side. So this is best for userfacing applications voice widgets and mobile applications. So this is what we'll be using because we're going to build a voice widget right this is what we call that widget right. So for that reason we will go through the client side implementation. But if you would like to build some backend automation like bulk operations system integrations then you would like to see the server side call management section. So I'll walk you through the client side. You would like to install this package create a VP instance and then start the call by passing the assistant ID. Then Bobby is going to give you a bunch of different events like call started. Here is something that you can do once call ends like what's going to happen. You can redirect the user to the dashboard page you know do anything that you want and then you can also get the messages as you talk. What do I mean? Let's see here in this example as you can tell AI is talking right and as as he talk as he talks we are going to see all the messages um that VP is going to send us okay so there are there are a lot of different events and I think we're going to go over all of them or maybe some of them and then here is the widget implementation this is just an example but in our case we are going to build it from scratch so let's scroll to the top and go through these steps one by one. We would like to install VB AI /w. Right, I'll copy this. Go into my terminal. Let's open this up. I'll kill my terminal and I'll say something like, you know, paste this in. But I'd like to install a specific version so that if you're watching this in the future, it should still work out as expected. We'll go ahead uh we'll go ahead and say 2.3.9 which is currently the latest version but it could change in the future. So while this is getting installed we can go under the lib and we can create a file called vapi.ts and this is what we're going to be doing. Basically, we will um instantiate our instance, right? I will paste this in and let's say export this this VBY instance. So, we're going to be using this in the upcoming minutes. And we would like to put a public API key. So, this is going to be coming from environment variables. So, we'll say process.v NV dot let's say next_public bobby api key but now let's try to create this under the nv file because we don't have it at the moment and the value is going to be equal to something that we're going to get from the dashboard okay so from here let's find the API keys and we would like to create a public key in my case I already have but if you don't have it you can say add key um name could be something like dentwise because why not say dentwise API key I think we can leave everything as it is selected assistant so this is the one that we have created right at the beginning of this tutorial make sure this is selected select the assistant itself and just say create this public token. Okay, let's copy the value and paste this in. And just to format this file, I'll take this put it right after the assistant ID. Okay, let's save. Go through here. Now, TypeScript says I don't know if this is undefined or not. So, we can say don't worry, we will always have this environment variable. You can either add exclamation point or you can say as string. So it's the same thing. Okay. Now we'll go under the page. Outside of these two divs, we can create the bobby fidget component. So let's go and create this file. Under the components under the voice, I'll say lobby widget.tsx. And we can generate the content for this file. You know, just keep it simple for now. and then import this component. Okay. Now, just before we build the widget itself, first we need to set up our assistant. So, let's go under the assistance. We already have something, but this is just a blank template with minimal defaults, right? So, we'd like to have a first message for our assistant and let the assistant know about our platform, right? what we have, what are our services like dental consultation, you know, teeth cleaning, emergency visit, what are the prices, things like that. So here we will add a first message and we are going to put a system prompt just to you know make our AI assistant know about our platform which is a dentwise right and now in this dashboard you can customize anything and everything in this case for the provider you can go from open AI all the way up to inflection AI I'll just leave everything as it is you can even change the model so in this case we are using one of the cheapest models If you would like to go even cheaper, you can select a mini model, right? But I'll just leave everything as it is. Then you can select the mode. In our case, assistant should speak first. Then the first message like what the AI assistant is going to say. So here, let me copy and paste it. And I'm going to provide this to you. Actually, let me tell you where you can copy it. Find the source code under the source under the lib. You're going to see this file called VPrompt. Yes. Okay. So, this is the first message. Go ahead, uncomment this, copy it, and then you can comment it again. So, just copy this and then paste it here for the first message. And what it says is basically, hey there, this is Riley, your dental assistant from Dentwise. I am here to help you with all your dental needs. I can provide information about our services. give you immediate tips for dental pain or concerns, help you understand different treatment options, and share oral health prevention advice. So, what can I help you with today? That's the very first message. And then for the system prompt, again, go ahead, copy everything that we have here, which is around 100 lines of code. Uncommented, copy, and then comment it again. And then you can delete this file from your source code. You don't really need it, but this will basically give an identity and purpose to our AI assistant. So here we say you are Riley. This is your name, an AI dental assistant for Dentwise, right? And you offer 24/7 support for dental concerns and questions. Please feel free to pause the video and read the rest of it. We're going to give a voice and persona, right? What should AI sound like, right? uh the conversation flow and if if user wants to have some appointments if they want to book some appointments this should say you know I cannot book you something because like it is including some payments the AI assistant will be here just to help them out with the information okay so you can read the rest of it this is the entire um the prompt just to let AI know what is all about. Okay, so I have copied it. I'm going to delete this part. Paste this in. That's a bit huge, but that's fine. The larger this is, the better it is. So that AI knows everything. You can include some files where you know where you have some, let's say, where you have some system prompt, but we just paste it instead. And feel free to take a look at all these configurations. So you can update the voice. In this case, I think we can use the Elliot. You have other voices as well, like Kylie, ages 23, female, and she has the American accent. We're going to go with uh Elliot. You can also change the voice provider. In this case, I'll be going with Fabi, but you can use any of them really. Okay, so there are a lot of different configurations that we can do, but let's try to keep it simple and leave everything as it is. We could include some tools, which are the things that I'll get into in a second. So that's everything that we would need for this AI assistant. We give the first message as well as the system prompt. Now, let's say publish. Okay, if you want to test it out, you can click to this button and talk to the assistant. Let's just see how it's going to look like. Let's give the access to the microphone. Hi there, this is Riley, your dental assistant from Dentwise. I'm here to help you with all your dental needs. I can provide information about our service prices, give you immediate tips for dental pain or concerns, help you understand different treatment options, and share oral health prevention advice. Okay. >> Okay. So, as you can tell, it is reading the first message. As soon as we start the call, we can end the call and we can get the messages. So, this is that event that we just talked about. Let's scroll to the top. So this event is getting sent by Voppy and in our application we are going to get the message display it right here just like what they do here. Okay. So for now everything is working as expected. We already have the assistant ID. All we have to do just go back into VS Code, find this VBY widget file and build the content. So first let me zoom in. This is going to be a client component. Let's uh let's go to the top and say use client. This is our directive. And then we're going to need couple of different states. Let me import the use state. So we are going to have a state if the call is active or not. If we are in the connecting state, if the AI is speaking, you know the messages initially is going to be an empty array and then if call has ended or not. Initially they are all equal to false. Then we would like to get the user from clerk and if you know we are going to check if clerk is loaded or not. So let's import this as well. Then we would like to have a message container ref. Let's import this hook because you know we are going to be using this whenever there is a new message. So we will make this to scroll automatically. That's why we are creating this reference. And to be able to implement this, we can just have a use effect with two lines of code. I'll just add a comment. Let's say auto scroll or messages. So this will basically make the scroll top position to be at the very end. Right? It is scrolling whenever messages changes. And then let's have one more use effect. Let's initialize this. Here I'll just add a comment. Let's say setup event listeners or voppy and now we'll be using all these events right. So first let's say give us the voppy instance which is coming from this file. So we are getting this so that we can interact with our VP assistant. So let's say voppy.on. We have all kinds of events. First we'll say once call starts please go ahead run this method which is handle call start. And we are going to create this method in a second. Let's add one more on. So at the end you can say dot on and let's actually format this. Okay. What happens once the call ends? We would like to call handle call end. So we will do this couple of different times. This time it's going to be on speech start. Let's duplicate this. This time it'll be speech end. Let's say handle speech start. And I'll do the exact same thing. Handle speech end. Let's duplicate this. When we got a message, let's say handle message. And finally, we'll have on. Once we got an error, we would like to handle it as well, right? Let's say handle error. Now, let's try to create all of these methods one by one. And we are going to create them under the use effect. So, first let's get started with this one. Handle call start. Once call starts, we can put a console log and update our states. Right? So set connecting is uh is going to be equal to false. Call active true and call ended is equal to be false. Then let's get the call end method. So you can copy them from the source code if you don't want to type it out. We'll say call ended and these are going to be the latest states. Then what happens once the speech starts? So we'll say AI started speaking. And by the way, this is going to be sent once AI starts talking, right? Not you. This is for AI. And same for AI as well. So we'll say AI stop talking. Um then we can update the state to be equal to false. And then finally, we're going to put these two methods. Let me save. Get the formatting. Okay. So for the handle message, we are going to check if the mode is final. we will update our state. We're going to keep all the previous messages and just add the new one on top of it. Okay. Then for the handle error, we'll just say vopy error and basically set these states to be false. All right. So these are all the methods. I know that we just copied and paste it which could be annoying but we are not really doing anything special other than updating the state. Right? You can pause the video, type it out or get it from the source code. And for the performance reasons, we could add a cleanup function where basically instead of listening for them, we will unlisten for them whenever the component unmounts. Right? This is for performance reasons. Um that's the entire use effect. Now we can add one check. I'll say if clerk is not loaded, we can just return nothing until it loads. We'll we're not going to see anything. Once clerk loaded, then we can see the widget and we will have one function. So, this is going to be outside of the use effect. We are done with it. We will have a method. Let's say con toggle call and I'll say async. So we will say if call is already active we'll just say wy just go ahead and stop this call please right but in the else case let's say else we will do some different things here the first thing I'll have is a try and catch block under the try I will update couple of different states so I will say set connecting to be equal to true no messages and call ended is equal to false and once Once we update these states, we are going to start the call. So I'll say await start and this is going to expect you to pass the assistant ID. So we can say process dom next public and we're going to pass the Bobby assistant ID. Okay. So just make sure that this is matching with whatever you have under the file. So maybe just copy it from here and paste this in so that you don't have any typos. So that's it for the entire try block. Under the catch, we can just get the error and maybe put a console log. Then set the connecting state to be equal to false because we failed to start the call. Right? That means we are not connecting anymore. All right. So I think we are completely done with the entire logic which is around 100 lines of code. The rest is going to be UI. Um, we're going to get into the return statement in a second, but first I'd like to open up the global CSS file. We're going to add like 10 lines of code, which is going to be the animation when AI starts talking. So here you can see we have five different elements, right? They're going to be like uh moving like a wave, right? So you're going to see how that will look like. First, let's create a key frame under the global.css. So, let's say key frames. Then, we can give it a name. In this case, I'll just say sound dashwave. We're going to have the initial state, which is 0%. Let's duplicate this. We're going to have 50% and then once the animation completed, which is 100%. So, here the height should be equal to 10%. At the beginning it should be equal to height of 10% as well. But in the middle of the animation it should be 100%. Okay. And to be able to use this we are going to create a class. Let's say animate soundwave. And we'll just say animation should be sound wave. So make sure that this name is matching with this one. Duration could be 1.2 seconds. Ease in and out. And I'll just say it should be infinite. Okay. So, we just added 10 lines of code for this animation. And you're going to see how to make it work. Let's just close everything. Go under the Bobby widget and get started with the return statement. So, the first thing that we would need is a div with some class names, right? which are going to be maximum width of 5x large center this with MX auto some petting flex overflow hidden and padding bottom of 20 then we can do something like this let me just give a little bit space we are going to have the title first let's say title and it's going to be this section so this is what I call the title this component let's try to get this from the source code. It is like eight lines of code. Talk to your AI dental assistant. So, we have one H1 and then a description. Let's try to see that's how it look like, right? Then we are going to put these two cards. The first one is for AI and the other one is for the user. Let's try to do it. Just shrink the title and we're going to have the video call area. Now if you don't want to type this out, you can get it from the source code. But I think I'll just try to write some part of it. So I'll say div um class name is going to be grid. Let's say grid columns of one medium screens and above. They could be side by side. So I'll say grid um like grid columns of three. I think I've just did a mistake. This should be one and this should be two. Then we're going to give gap of let's say six and margin bottom of eight. Then the first card that we would like to build is the AI assistant. So this is the AI assistant card. Once we build it, we're going to build the user cart. Oops. Let's go ahead find this part and get started with our very first card component. Let's import it from the UI folder. Okay, so this will take some classes. Let me just paste them. And then we're going to have a div within this. And let's close this off. Again, just some classes just to make UI look nice. Then I'd like to put the AI voice animation, which is going to be this one. So here we can see background updates. And then we have these waves. Let's paste this in and I'll walk you through it. So this is the AI voice animation which is a div. If we are in the speaking state opacity would be 30 but otherwise it is like invisible. And then for the wave animation we are going to create five different divs right which are these. So 1 2 3 4 5. And if we are speaking the height is going to be updated all the time. I mean if the AI is speaking right. So here we say if is speaking is equal to true go ahead add this animation otherwise don't do it and height is normally going to be 5%. But if we are in the speaking state it should always be updated and let's actually select all of these variables which is is speaking and I'll make them to be true so that we can see the output. So this is the animation that we can have, right? It's like infinitely going on. Let's bring this back. Now we shouldn't be able to see anything because opacity is equal to zero. If we are not in the speaking uh speaking state all right then we are going to shrink the AI voice animation. So that was the div. just go outside of it and get the other div which is this one. Let's import the image from next image and you're going to see how that look. Let's say this is equal to true. Okay. So as you can tell again there is a pulse animation going on right before right after the like the background. Let me zoom in. Okay. This is the background of the logo that we can see. And I'll just bring this back. So that's the entire thing for the AI assistant cart. Now we're going to build the user cart which is almost the exact same thing but without any animations, right? We have the user image, a text, and then the user full name. And then I think this like kind of like a batch that says ready. Okay. So, we're going to build this, but I think we forgot to add this text. This one, and then the speaker indicator, right? So, here, let's try to build it. Um, right after this div, let's say AI logo. So, right after this, I think we are going to pass the H2 and P tag. Let's see the output. Zoom out. Okay. Finally, let's put the speaking indicator right after the P tag. I'll just say speaking indicator. So once again, it doesn't have any logic other than some variables. We'll just say if it is speaking, we would like to have this class. Same for this div. And then if it is speaking, text should be speaking. But else if the call is active, it's going to be listening. call ended or we are in the waiting state. Let's see. Okay, so now we are in the waiting state. Once we start the call, the text should update. Okay, so that's the entire cart. Let's shrink it. I know that it was kind of annoying to copy and paste, but there is literally no logic in this component in the cart. Right now, let's get the user cart. I would say just copy it from the source code. There's maybe 20 lines of code. After this comment, okay, let's save. We have the user image URL, the user full name. If this is equal to undefined, we are going to see guest as their name. And then the badge is going to say ready. Okay, so let's see what it says. I'll refresh. Are we going to get the same error? Okay, so we need to add the image.clerk.com. clerk.com under the next config just like what we have done in the past. I'll duplicate this host name should be image.clerk.com. Now if you refresh it should be working out as expected. We should have two different cards. For some reason they are side by like they are not side by side. Let's go here and try to fix it. So, looks like I am missing a dash right here. MD grid columns of two. And now they are side by side. And the very last component that we need to add is going to be a button at the very bottom of this UI, right? It's going to say um it'll say start the call. So, let's go ahead scroll to the bottom right after the user card. and maybe outside of this div. I'll just say whole controls. Just make sure you are outside of the card and outside of this div. It's going to be 10 lines of code. Let's import the button. Okay, you can pause the video, type this out or grab it from the source code. Again, the button would be disabled if you are in this state. Once you clicked it, we are going to start the call or end it. And then if we are in the connecting state, we're going to see this kind of animation with the animate pink class which is coming from tailwind. And then the button text itself here. So let's say I mean let's save and see if you click it. As you can tell it is connecting then um AI should start speaking. Let me just allow the access. Hi there, this is Riley, your dental assistant from Dentwise. I'm here to help you with all your dental needs. I can provide information about our >> Okay, so that's awesome. I can end the call and as you can tell it says call ended. Now, one thing that we are missing is the message container, right? So, we are going to have the Dentwise AI. If the AI is speaking or if we are speaking, we should have our own name. So this is going to be a component that we are going to put right above the call controls. Here I'll say something like message container. And again please feel free to copy it from the source code. I know we have copied a lot of stuff but we are almost 5 hours in into this tutorial. Let's not waste any um any time by these stupid UI elements. Okay. So we'll say if message length is greater than zero we would like to render this right hand side which is the message container bunch of classes just to make it look nice and scroll behavior will be smooth. We're going to map 3D messages, put the message content. If the role is assistant, we will say Dentwise AI. If it is us, we're going to say you. And once the call ended, we will say by the system call ended. Thank you for using Dentwise AI. Okay, so that's the entire thing. Let's save and give it a go. Okay, so that was the previous messages. Right here it says call ended. Thank you for using Dentwise AI. Then you can navigate to different pages. But let's try to run this again. And actually I'll try to talk with AI. If I say I'd like to book an appointment, it should say no, I cannot do it. You should be doing it from the appointments page. Okay. So let's give it a go. Hi there. This is Riley, your dental assistant from Dentwise. I'm here to help you with all your dental needs. I can provide information about our service prices, give you immediate tips for dental pain or concerns, help you understand different treatment options, and share oral health prevention advice. What can I help you with today? >> Can you please provide information about your service prices? I'm really curious. >> Of course. Here's a clear breakdown of our service prices and what each includes. One, regular dental checkup, $120. What's included? A thorough oral examination, basic x-rays if needed, and an oral health assessment. Duration 3045 minutes. Frequency recommendation every 6 months to catch any potential problems early. Two, teeth cleaning $90. What's included? Okay. Removal >> plus polishing for a clean, healthy smile, >> dude. >> Duration. >> Listen. Okay. Um, I was wondering if I can >> Sorry about that. >> Yeah. Yeah, you're good. I'm wondering if I can book an appointment with your help. Is that possible? >> I'm glad you're interested in booking an appointment. However, I can't directly book appointments for you since that process involves secure payment details, which I don't handle. You can easily book through the Dentwise platform though. There you'll be able sad. All right, I'm just call and deleting this application. >> No, I'm just kidding. But here we can see AI doesn't allow us to book an appointment with like from the voice page. If you want to book something, we should be visiting the appointments page. So this is something that we're going to do in the next section. But for now, I think we are completely done with the voice page. So the entire functionality is working smoothly without any issues or problems. But I would say this workflow is pretty simple at this point. You can just start the call and talk with AI which is fine. But I think it is simple, right? It's a little bit simple. And if you want to take it to the next level, here is a challenge that I have. But for now, don't worry about it. Please think about this once you complete the entire tutorial. Okay? So this will basically allow you to book an appointment just by talking to this AI assistant. So let's say you would say something like I want to book an appointment and here is the date and time the doctor that I would like to have an appointment with and then the service like teeth cleaning. your AI assistant which is VPY in the background would do a web hook call to your API and in your endpoint you would save that appointment to the database which is Postgress and then maybe send an email to the user. So this is the workflow that we're going to do manually under the appointments page in the next section but you could actually implement it with the AI assistant as well. And here is how to do it like in simple terms. Under the dashboard you have some tools. So this is the documentation. You can take a look at it. But here under the actual dashboard there are some tools and the one that you would like to use. So the tool that you would like to create would be API request where you would pass your request URL. This would be the uh let's say your endpoint, right? And then you would select the HTTP method. I think it would be post in this case, but you can do any other actions and then just implement it in your Nex.js API routes. So it would be under the app under the API folder. So this is a challenge that you can keep in mind once you complete the entire tutorial. Not for now. Just for now, don't worry about it. We are going to implement this in the next section manually under the appointments page. But the possibilities are endless by adding different tools like you can transfer the call, you can end it when users has something, you can do some queries, send text, integrate with third party services like Slack, Google sheet, Google calendar, things like that. But for now, we would like to keep it simple and just have this functionality where we can chat with AI with our AI assistant to get information about this Dentwise service. And if you want to build other kinds of tools, you can just go under the documentation um under the examples, you're going to see they have like bunch of different really really cool examples and you can go over it with step-by-step explanation. So definitely check it out if you're interested. But for now, I'll go ahead and commit my changes. Let's open up the terminal, kill this app, toggle the status bar, um create a new branch, something like voice dash page. Let's stage all of our changes. Let's say voice page addit. publish this branch and let's create the pull request. Okay, so probably we're going to get a bunch of different suggestions because currently our lobby widget, this is not really the best code. I would admit it. So it's like 300 lines of code and it's probably not the best code. I'll just add a comment, but maybe I would do it in the next section. So, this is not the best code. You can always optimize it. And I don't really want to get any reviews. I don't want code rabbit to embarrass me in front of you. So, for that reason, I'll just say merge the pull request. And then once this is done, we can go back to the master branch and get the latest changes. So, let's say sync this up. And with this that's the entire uh entire section. Hopefully in the next one we are going to build the appointments page. So I'll see you there. In this section we are going to get started with the dashboard page. So we are going to have three different components. The first one is the welcome section which is some UI element. Then we have two different components. Now this is what we call the main actions. And then finally, we have the activity overview. So, we can see the upcoming uh upcoming appointments as well as some stats. Let's go ahead and get started from top to bottom. First, I'll visit the dashboard page under the app under the dashboard. So, this doesn't have any content at the moment. We're going to have a couple of different components for the dashboard page. So let's create this folder and let's get into this file itself. We can make this to be an empty fragment. And then we have the navbar at the top. Then right after this, let me first kill the AI. Just copil it. I'll just disable it so that it is not really annoying. Okay. So let me give a bit space. This is the div that we are going to have with these class names. So maximum width of 7x large. center this and give some paddings. Um, we have used this couple of times I know but the best practice would be to create a component for this right something like a you know container wrapper or you know some kind of um some kind of component that is going to wrap your entire application. So instead of every time doing this you would say something like my wrapper right and wrap the content with it. Um but just to keep it simple this is what I'm using. Uh just keep that in mind. You can always optimize your code. Okay. So that's the div. We are going to have the welcome section component. It is not going to be coming from the voice uh voice component. Right. We are going to have another one for the dashboard page. So under the components under the dashboard I'll say welcome section.tsx. Let's try to import this component. Make sure it is coming from the dashboard folder. And you can get this content from uh from source code which is like 30 lines of code just some UI you know just only some classes. Let me kill the status bar. So we are getting the current user so that we can put their name like user first name and depending on the time we're going to either say good morning, afternoon or evening. So let's save and actually try to import it. Um here what it says. So we are not saying export default. Let's do it. Okay, now we don't have any errors and actually if you just copy it and paste it, you're not going to get any errors. Um, let's go ahead see that's the output and I can see good morning at the moment. Now, right after this component, we are going to have another one called main actions. So, I'll duplicate this. Let's say main actions and copy the name just create it under this folder under the components. Let's try to import it. So this is going to be these two cards. This will take you to the voice page if you click to the link. And this will take you to the appointments page. So again, they are just UI, no logic at all other than a link. If you wanted to, you can um type this out, but in this case, I would like to copy and paste because this is not a CSS course and we are like 5 hours in into this tutorial. So I will delete this entire thing, copy it and paste it from the source code. Okay, so we just have a huge div with some classes. Um, here let's just say export default option and see the output. Let's refresh. Okay, so it is working as expected. If you click to it, that would take you to the voice page. And if you click to this one, it would take you to the appointments page, which is something that we don't have yet. Now, the other component that we would like to build is this overview. So, I'll go ahead duplicate this. Let's say activity overview. Copy the name and create it under the components. and import it in this file. Now let's save and get into this component. Now within this component we are going to have two different components. So the first one is the dental health overview and the other one is the next appointment. Okay. So let's say cut this div and paste this in where we have a dental health overview. Let's create this one under the components and just import it and do the same thing for this one. Okay. So, first we are going to get started with this overview component which is going to show us the completed visits, right? the completed appointments, total appointments that this user has when they sign up, the members and state and then some kind of like CTA buttons. Okay, so let's uh go into this component and try to build the logic. So here we would like to fetch some data and for this we can create a server action. Let's go under the let me find it under the source lab actions. This is related to appointments. So we can go into this file and let's shrink everything. I think that's the only method that we have. Let's create another one. So I'll say export async function get user let's say user appointment stats. And this is not going to take any arguments. Let's go into it. I'll say try and catch block first. Let's get the user ID from clerk. We'll say await oath call this method. This will give the authenticated users ID. And we can say if this is equal to null, right? If this is undefined like if this is a falsy value, we'll just say you must be authenticated. Then we can find the user uh from our database. We'll say const user. We're going to pass the clerk ID to be able to find it. We'll say await prisma dot let's say user find unique and we're going to pass our filter. I'll say where um the clerk ID this should be under an object. So clerk ID is equal to this user ID that we have right and if user is not in our database again we can say row new error maybe something like user not found. What else we can basically get the total count right the total count as well as the completed count for the appointments. So here this is what I'll be doing. I'm going to use promise.all await this. This is the first call where we get the total count. Right under the appointment we say count where this user is the owner of the appointment. And then we're going to count the like the completed count. Right? We say if the status is equal to completed go ahead give us those appointments. And this should be type safe I believe. Okay, completed or confirmed. In this case, we're going to get the completed appointments. So, this is going to make it faster because we are using promise.all. Let's say these calls will run in parallel. Let's say instead of waiting each other. Okay. And then finally we can just say return an object where the total appointments is equal to the total count and the completed appointments are equal to the uh completed count. And in the catch we can say something like they're both equal to zero or you can throw some error. And for the um you know just for the debugging purposes I'll put a console log. So that's our entire method. Now we can go ahead and call this under this component. Let's make this to be an async function. We can say await call this method and this is going to give us a value which we can call as appointment stats and then I I will also get the user object. Let's say await give us the current user from XJS and then let's build the return statement. Now since we already have the logic the return statement is just a card with with a beautiful styling. So if you would like to get it, you can do so from the source code. This is what I'll be doing. Let's copy it and paste it. Then import all these elements. So we'll get the cart cart header the title brain icon cart description as well as the content. I think we need to get some more icons like message square icon and we're going to get the format method from date fns. So you can import it. Let's get the link from next link button from the UI folder and this entire thing. Let's save and see the output. Um let's refresh maybe. Okay. So this is the component that we just have. This user is uh signed up in September. Total appointments is equal to zero and completed visits is also equal to zero. That's correct because we didn't have like we didn't created any appointments yet. In the next section actually we will create them and if it is completed it should update the UI. So this is working as expected. When you click to these when you click to these links they should also work out right. So as you can tell they are taking us to the related pages. So that's great. Now next thing that we need to build is this component. Let's go into that file and build it as well. Now in this component we will also be fetching some data. So let's mark this with the async keyword. And here is the data that we're going to fetch the appointments. But for this we need to build the server action. So we are going to call this but first we need to build it. Let's go under the actions appointments and create this method. So I will put it right uh right before to this one. say export async function put the name and this is not going to take any arguments so let's build it step by step it's going to be pretty easy I mean easier than what you think so first off we are going to get the user ID from clerk if user is not authenticated we would throw an error and then check this user in our database again if there is no user we'll say user not found uh you know just have some error on the UI Okay, but else we can actually get the appointments where this user is the owner of the appointment. So here we'll say go under the appointment table find many where the user is this current user right and for the user information and doctor we would like to include some fields for the user we're going to select the first name last name and email and for the doctor we're going to get the name as well as the image URL because if you remember under the appointment let's see it under the schema under the appointment you don't have any information about the user. We just have the relation. So we need to go under those tables to be able to fetch the details. So that's what we are doing with the include and select fields. Then we're going to order these appointments. And then at the end we can just say return the appointments as it is. And then in the catch let's handle it. I'll put my classic two lines a console log and then an error. So that's the entire method where we have two different checks you know just to check if user is authenticated and if we have the user in the database after that we are going to fetch all the appointments and return them. Now instead of returning the appointments as they are maybe we can transform it a bit so that our code is a little bit more easy to read in the upcoming sections. So here instead of just saying appointments I will actually map over this and I will get each uh appointment and we are going to call a method with it. So let's say transform appointment. So this is the method that we can create at the top of this file. So this function is going to get every single appointment and it's going to return the re uh it's going to return the appointment as it is but on top of it it's going to add these fields like the patient name, the patient email, doctor name, image URL as well as the date. Okay. So you can copy this from the source code. Again, we are just doing this so that our code is a little bit more easy to work with in the upcoming sections. So, when you want to use the PA patient name, as you can tell, this is already been combined, you know, with the first name and last name. Okay, now that's the entire thing. Just make sure you update this line. Let's go ahead import this method. Make sure it is the appointments, not the stats. Now that we have the appointments, we would like to get only the upcoming appointments, right? So, we would like to do some filtering because here we would like to see the next upcoming uh appointments. Okay. So, I will paste this in where we have this array that we are filtering out. We're going to import the pars ISO from data finess if it is the same day with today or if it is after today which is in the future right and we'll say if status is equal to confirmed so this will include all the upcoming appointments and we can get the earliest one by doing this so we're going to get the very first result then we can check if there is no next appointment we can just say return null don't return anything. But in the else case, we can calculate some variables. Let's import the format field or the method from data finns. And we're going to be using the these in a second. Before we build the return statement here, let's say instead of returning null, we can return a component like no appointments. and we can create it under the components. So under the components under the dashboard I'll just say no next appointments.tsx and let's say no next appointments. And this is just a UI so we can copy and paste the content from the source code. This is what I'll be doing. I'll just get it from the source code. We're going to import the card card header, card title, this icon link, um the button as well as the card content. Okay, so just 30 lines of code. Um save and let's import it. It should be no next appointments and let's say export default this uh component. Let's see the output by refresh. Okay, so there are no upcoming appointments for this account, right? So for this account, we don't really have any appointments. If you click it, it should take you to the dashboard appointments page. But if we have some appointments, we should see this UI which is going to be under the return statement. So again, it's not going to contain any logic. Let's just get it from the source code. I'll just copy it and paste it right here. And once again, I completely understand if you're getting annoyed by just copying and pasting, but I would say you shouldn't be because we are like 5 hours in and at this point you understand everything, right? So, we learned how to create server actions, how to use them in our server components. The rest is just UI. It's basically HTML and CSS. So, that's why I just find it pretty unnecessary to type all these out. Like what is the difference just copying and pasting and doing this like flex item centered in gap of three? It just doesn't make any sense, right? Um this is a full stack course. So that's why I just try to copy and paste these UI elements as much as possible and I hope you don't mind it. Let's import everything step by step. We'll get our icons and it should be good to go. Okay. So, currently we cannot really see this in the UI because we don't have any appointments. Uh once we have once we have some appointments, the UI should be updated and we should see something like this which is something that we can check in the upcoming sections. So, I believe that's the entire section for this one. Right. Let's go ahead open up the status bar and we are going to commit our changes under a new branch. So I'll say dashboard dash page. That's the branch name. We can stage all of our changes. Add a commit something like um I don't know, let's say dashboard page addit commit and publish the branch. Let's go into the source code. and we are going to create the pull request. Now we could wait for the suggestions from code rabbit. This is what I would do if this was my actual project, right? If this was my software as a service, I would wait for the comments so that I can optimize my optimize my codebase. But since this tutorial is just getting so long, I would like to skip the reviews and just merge the pull request. But you can definitely wait for it if you would like to if you want to optimize your codebase. So here I'll just say merge this pull request. Once it is done, we can go here, switch to the master and fetch the latest changes. Okay, it is done successfully. That means this section is also completed. So I'll see you in the next one. In this section, we are going to build the appointments page. So here are the steps how that would look like. So first we are able to see the doctors within this div and then at the bottom we can see our upcoming appointments with the doctor itself. you know the time, date and the service. So first we are going to get started with this section. Okay. So here this is what we call the progress steps and depending on which step you are it is going to be highlighted. So first you would click to a doctor. In this example I have chosen this one and then you would say continue to time selection. Then you are in the second step where you can select an appointment type, date as well as the available time. Now if it is already booked by someone else, you can see this is disabled. You cannot select it. But if you select something that is available like this one, you can see this button where we can review our booking. Once you click to it, you're going to see this uh UI where you can confirm the booking. Or if you want to modify it, you can click to this button and go back. And you can go back with this button as well. Once you confirm it, you will have a loading state and then a model popup. It'll say appointment confirmed. Um, here are the details. We just sent an email to your inbox. Okay, so I hope that makes sense. You can get these diagrams in the description for completely free. Now, let's jump into coding and build it. Now one thing that I want to fix currently if you click to this button it takes you /dashboard/appoints but I just want to have / appointments right not dashboard let me fix it this is going to be under the navbar component maybe in your case it is already fixed but here I'll just say you know dashboard slash appointments okay so um I will update this href Let's say this should be just slash appointments. And let's do the same thing for this part. Let's say slash appointments. Oops. Okay. Now I think we have the exact same thing in three different components which are the dental health overview. Let me search for it. Maybe in your case it is already fixed so you can ignore this. and then the main actions and then finally I think the no next appointments component. Okay, right here. So that was the link fix uh link fixes. Let's go under the app create this appointments folder and then I'll say page.tsx. This is going to be let me select both of them. Um let's say appointments page and then this is going to be a client component. So let's say use client directive. So before we build the return statement here, let's go ahead and get some states that we are going to need in this page. I will grab them from the source code. Let me paste and I will walk you through it. So here are all the states that we are going to be using. Let's import the state from React. So this is the state management for the booking process. This could be done with something like sustand for larger applications. Okay, just keep this in mind. This is some kind of um optimization that you could have done using something like sustain. But here in this example, I'll just keep it simple and just have bunch of different states. So we are going to have the selected dentist ID and it's going to be selected whenever you click to one of these, right? and then the selected date, time, the selected appointment type, current step that we are in if we should show the confirmation model or not and then the booked appointment. So we are going to have this state so that we can show the details right here in this model. Okay. So let's see how we are going to build the return statement. But just just before that let's have some functions. So I'll say con handle select dentist. Let's say we are going to get a doctor ID or dentist ID. That's the same thing. Let's say this will be type of string. And in this function we are going to do something for now. Let's just leave it as an empty method. And then we'll have closed handle book appointment. This could be an async error function and we can leave the logic empty for now. Okay. Now let's get into the return statement. So as always we're going to have the navbar component. Let's say this is going to be self closed and then we're going to have a div right below to it. So I will say class name and here are the classes. Right after this we're going to have the title which is kind of like the header component. So let's save and see the output what we're going to get under the slash appointments page. Okay. So this is the exact same thing that we have right here. Then the next thing that we can build is the progress steps. So I will go into VS code. Right after this header component, let's just say header instead of title. I will have the progress steps component. And we're going to pass the current step as a prop which is going to be the current step state. Okay. So it's this one. And initially it does one which is select dentist. Second step is select time. And the last one is to confirm. So let's say current step and we are going to create this under the components. Let's create one for the appointments. Paste this in. and let's say import this here in this file. Let's get into the component itself. So let's say this is taking some props such as the current step and this is a type of number right and at the top I will have an import which is an icon coming from lucid react and then the progress steps. So this is going to be let's say a constant and you know this is a hard-coded data that's why we are following this convention where it is completely uppercased right um here let's go ahead under the return statement let's say we're going to have a div and before we add the content let me show you the end result so here as you can tell we have a number right and then the text content which is coming from the array that we just have right select dentist choose time and confirm. This is the exact same thing. Now we are seeing an arrow at the end right we have one two but for the very last item we don't really see it and we can check this by using the index. So let's go ahead I will just grab this from the source code which is like 20 lines of code and here is how that work. So this is the entire div which is flex item centered gap four and let's say something like maybe margin bottom of four as well. So we have a progress tabs.m map for each step name we're going to get this step number and is active field right if it is active what's going to happen is that it's going to get these classes but else is going to get background muted. So here let's see in this example like this is the active one that's why it is a different color and these are muted and this is how we can check for it. So please just pause the video go over this code. Um at the very end we also say do not show the uh error for the last step. So super basic JavaScript at this point. That's the entire component. And I will just make this margin bottom to be eight. Let's save and see the output. Okay. So at the beginning, this is the first step that is selected. Um in the upcoming minutes, we're going to see those to be selected as well. Okay. So that was the very first component that we just built. Now it's time to build the dentist's component. Let's visit VS code under the page right after the progress steps. I will say if the current step is equal to one, we would like to render this right hand side which is going to be a component. Let's say doctor selection step. We are going to create this component and this is going to get some props like the selected dentist ID and then we're going to pass the on continue method. Let me type this out correctly. Okay. So if they say continue what's going to happen is that they are going to go into the next step. So once they click to it, we'll say current step should be equal to two. And then on select dentist, we're going to call our method which is handle select dentist. And let's build this method actually. So they are going to send us a doctor ID, right? A dentist ID. And we can just say set the selected dentist ID with this, you know, doctor ID. And actually let's just say dentist id and then uh we can reset the state let's say set selected date to be empty and time as well as the type. Now why this is important because we are updating the dentist right I'll just add a comment let's say recent I mean sorry reset the state when dentist changes all right then we can go into this component let's see if we created it I don't think so let's just go under the components under the appointments let's say tsx and let's try to import it here in this file. Okay. Now we need to get the props and first I will create an interface for this at the very top. Oops. Let's say interface doctor selection step props. And here we'll say we are getting an object which is type of this. And then we can dstructure them. So we'll get on continue on select dentist and then the selected dentist ID which can be string or null. This is a method that doesn't return anything and take uh take the dentist ID as an argument and this is a method that doesn't return anything and just above the return statement we would like to fetch some data which are going to be the available doctors. So the doctors that has the is active field equal to true right to be able to build it we need to have a server action. So I will go under the let's say use doctor's hook first. Let's create our hook and then we are going to create the server action. So let me paste this in and I'll just zoom in. So this is literally a query. We give a query key which is get available doctors and we're going to call this method right the server action which is something that we can create. I will copy the name and we'll go under the doctor's file under the actions. Let's shrink everything in this file so that it is not really confusing. We'll say export async function get available doctors. And at this point it should be pretty easy to build this function. Here is my code that I will walk you through it step by step. So we have a try and catch block. Under the catch we handle the error and then in the try we say go under the doctor table find many where the active is active equal to true. Right? And then we can include the amount of appointments because we are going to display them right here. Right? This doctor has six appointments. This one has nothing. And then under the uh let's go under the VS code like under the return statement we will say get all the fields and add this appointment count on top of the object. So that's the entire thing and we are ordering by the name in ascending order. Okay, super simple nothing crazy just a query with the where uh I mean with the wear clause. Okay, let's import this. And by the way having a result is the exact same thing like doing this is the exact same thing doing with this right okay so this is just a little bit more beginner friendly to understand that's why I will leave it in this way now we are going to call our hook which is called use available doctors right I'll copy the name paste this in and call this hook which is going to give us some data And we can get the is loading state as well. Now data could be renamed to something else something like doctors or dentists right and let's say by default this is going to get the empty array as the initial value. Okay. Now we can say if we are in the loading state for now let's just return a H1 a class name of text or a start let's say we're going to fix this but for now this is a placeholder let's say loading. All right now let's get into the actual return statement. So this is going to be a div with the class name. Oops. Let's say class name space y of six and then we can delete the content. This is going to have an h2 that says choose your dentist and we can give some classes say class name text of 2x large and then let's say font semi bold. Right after this, we will have a div which is going to be this grid element, right? The grid parent where we can display cards right next to each other. So here honestly I'll just get the class name just like this grid medium screens and above this is going to get two grids side by side. Larger screens and above is going to get three and gap is going to be six. Then we can go ahead let me save. Within this we'll say doctors do map and for each of them we are going to return something. So here let's say this is what we are going to have um what am I doing? Okay it should be in this way. Now let's go ahead say for each doctor we are going to return a card component. Import the card and get into the props. So we're going to pass the key. Let me kill the AI. It is super annoying at this point. Let's just ignore this for like 40 minutes. Okay, so the key should be something unique like doctor ID. And then we can give some classes and this is going to be dynamic. And I'll show you why. Because if a doctor is selected, we're going to see a border around it. So in this case if you select this doctor as you can tell there is an outline around it and this is how we can check for it and on click let's say once you click to this we're going to call our method which is on select dentist and we're going to pass the doctor id into it right um that should be it you know what instead of calling this doctors I'll just say dentists This is what what should have what I have should have done at the beginning. Say dentists get a dentist and replace them. So this makes our code a little bit more readable I believe. Right after the cart we're going to have the cart header component which you can grab it from the source code. Let me paste and walk you through it. So, we're going to import the cart header with a div and then the image itself which is going to display the dentist image URL. Then we can have the card title which is going to have the dentist name, the description that will have the specialtity, the star icon, you know, we don't really have any reviews, but just to have a nice UI, I'll put five for every single doctor. And then the amount of appointments. So let's save and see it. Do we have any doctors? Yes, we have because we have created them under the admin page, you know, in the previous sections, right? So these are the doctors that we have. Let's say Jane is going to be inactive. And if we go into the appointments page, now this is inactive. We should not be able to see it, right? Okay. Now let's bring this back to be active. And then we're going to build the card content. So right after the cart header, let's shrink this. We're going to have card content. Let's import it. We'll have the map pen icon where it says our location is in the dent. You can put an actual location. I'll ignore it. Let's say phone icon and then the badge. So here basically we display the dentist bio their phone. Um that's it. And then at the end we have a batch. Let's save and see the output. So this is exactly what we have for these doctors in the database. And if I try to select one of them as you can tell UI updates but we would like to have a button at the bottom as well. So I'll go here. I think right below to this div. Okay. So uh just above the very last one I'll say if there is a selected dentist then go ahead to display this button which should look like this and once we click to it we're going to call a method which is the on continue that will take us to the next step. So I will click to it. Now we are in the next step and let's try to build this component now. And you know what? Just before we build this step, let's go under the appointments page. I'll just refresh. So here we have a really ugly loading state. Let's try to fix it pretty quickly. I will go under the components under the appointments. I will say something like doctor cards loading. TSX and you can grab this from the source code. Basically, this is going to be using the shaden skeleton component. So again, this is something that we are not going to build by ourselves, but we're just going to use the component with some class names. Okay, so that's why I'm just going to grab it from the source code. Here we have doctor cards loading where basically we would like to show six different skeleton cards. So this is how we can do it. Six times we are going to call this component which is up here. Okay. So UI only no logic at all. For that reason we copied it and paste it. Let's go here and fix the loading state. So we are going to return a div that has a title that says choose your dentist. And then we're going to show the actual skeleton. Let's save. And I'll just make this to be true so that we can see it clearly. Okay, if you're in the loading state, that's what we're going to see. And if you want to update the count, you would just go here. Let's say show me only three, which is fine, but I'll just leave it as six. Okay. And then let's put the actual loading state and test it out. I'll refresh. We can see it for a split second. Once data is fetched, um UI is is going to get updated. Oh, now it is time to build the second step which is to select the time. Here is the end result. Basically on the left we have a header component where we can go back and then we have the appointment type selection. Right? So once you select one of them the available date will be visible and then you're going to get the next five days. Okay. So whatever the date today is you're going to get the next 1 2 3 4 5 days. Okay, so this is the next five days and we'll see how to do it and then we're going to get the available times which is going to be an array of like times right and then if they are booked we are going to make them to be disabled. So let's try to see it how we can make this step by step. First I'll give you two different utility functions under the utils file. So under the source code, find this file. Okay. And shrink this. We're going to have two more functions. One is to get the next five days. So here I'm just pasting it. And again I have proudly generated this with AI. So I don't really need to memorize this logic. I just said give me a function that is going to give me the next 5 days starting from today. Right? Okay. Then the other method which is basically returning an array of dates, right? That's it. Okay. So, go ahead and grab both of these functions. We're going to be using them. And then we will go under the appointments and we're going to create a server action. Let's shrink everything and go at the very bottom. And here is the server action. I'll just walk you through it. It is tell of code. We have a try and catch block. Under the catch, if there is an error, we will just return an empty array. But in the try, we will just try to successfully fetch the appointments where here is the doctor that we get as an argument, the date and status like it is either confirmed or completed, right? And then we're going to just select the time in the return statement. We are going to return the time itself. So this is going to give us the booked time slots because we passed the date and the specific doctor and we don't really care about the status if it is confirmed or completed. We'll just consider this as you know uh booked time slot. Okay. Now we would like to call this action under our hook which is going to be under the use appointments. So at this point it's going to be the exact same thing as this one. We'll just try to return a use query. Here is our function. Here is the query key. Let's import it. We're going to pass the doctor ID as well as the date because this is what we expect under the server action. And then here there is a field that we're just learning for the very first time. Uh basically a query would run automatically. But here we say only run this query only if you know the both doctor ID and date are provided. So if let's say this is undefined if this is not provided this function is not going to run. Okay just keep this in mind. Um that's one thing that time to time we use it with a tst query. Okay, let's save this and we should be able to consume this hook in our component and that component is going to be under the page.tsx. Let's scroll to the bottom after the current step of one, we will say if step is equal to two, right? And if there is a selected dentist, then we can run this component which is going to be equal to time selection step. And as you can tell, this is going to also get some props. And let me provide all of them one by one. So we will have the selected dentist ID within this component. Selected date, time, and the appointment type. On the back method, we're going to go one step back, right? The current step will be equal to one because we are in the second step. On continue, we'll go to the next step. And on date change, time change, and type change, we are going to pass the setter methods, which are all of these that we have above. Okay, so I think that's going to make sense. Just don't worry about it for now. Copy the method name or component name and then paste it under the appointments. We'll say tsx. Let's generate it, import it, and get into the component. So, first off, we would like to make our code type safe. So, we'll just go at the very top as always, paste our interface, right? So, these are all the props that we are getting as you can tell. And here are the specific types. Now, let's say we would like to use this interface, which is time, selection, steps, props, and then import all of them one by one. Okay, it's kind of long but that's fine. Again, a better option would be use something like Zastand, right? This is something that I have mentioned. Uh just please keep that in mind here. Our goal is to actually learn how to build it rather than trying to write the perfect code that is possible. All right, so let's go into this method. We are going to call our utility functions which is to get the next five days as well as get the available time slots. So these are only the you know only the string values. These are not coming from database to be able to get the booked time slots. We're going to be using our hook which is this one. And we're going to pass the selected dentist ID and selected date. This is going to give us the data back. And before we get into the return statement, we can have a function called handle date select. So what happens when you select a date? Here I'd like to pretty quickly show you something. So whenever you update the date, right, the time slots should be reseted. Now what I mean, let's say you selected this date, right, and this time, but if you click to this date now, this should not be selected, right? it should be reseted. So for that reason here we will first let's get the date we'll say it is type of string we will call on date change pass the date and then we would like to do the reset we'll say reset time when the date changes we'll say on time change call this and put the time to be nothing the empty state all right now we can go under the return statement And here let's say we will have a div with the class name space y of six. And then first we're going to have the header with the back button. Let's import the button and then this icon. Let's save and see the output. Okay. If you say back, you're in the first step. And then let's try to build the rest of the content. Right after this div we will have another one. This will be the grid component. Let's say grid larger screens and above. This time we can put you know grid columns of two and a gap of eight. Okay. Now let's say appointment type selection. So that's going to be that component here. Let me copy and paste. It is 10 lines of code. So we will go through the appointment types which is an array that I will provide. And for each appointment type you will show a card. So this is basically what we are trying to build. This is the first card, second, third and fourth. We're going to put the uh appointment type, whatever the type is, the duration as well as the price. So only UI related, no logic at all. But once you collect them, we're going to call our method which is on type change and we're going to pass the type ID. And these appointment types can be coming from the utils. You can find it from the source code. Once again, this is just a constant where we have checkup, cleaning, consultation, and emergency. Here is the ID, name, and duration as well as the price for all of these. Okay, save, import it, get the card, card content, and we should be good to go. Let's select this doctor. And if it is selected, as you can tell, we are getting this beautiful outline as well. Now we can build the next component which is the available dates and then the available times. So head over to VS Code, shrink the appointment type selection, and then we'll say this time date and time selection. Here I'll have a div so that we can give some spacing. Let's say class name space y of four. Um here first I'll have an h1 with the class name text large. Then I'll have the font of medium. Within this h3 we can say available dates and then here is the date selection. So I am adding these comments so that if you would like to copy and paste from the source code, you can just see the comment and copy the actual content right below to it. Okay, so we're going to get the available dates map through it on click you know call our handler method at the class name variant is going to be updated depending on the selected date if it is you know selected um and then we're going to show the date itself. Let's see and you're going to see I mean let's save and you're going to see what I mean here. We can see looks like we don't really have the gap working this is the case. This should be gap-8. Okay. If you select this one you know UI updates depending on whatever you select. And then once we select a date we should be able to see the times. So I will go right here under my code um shrink this after the date selection. I will just go a little bit below and I'll say time selection and only show this when the date is selected. Let's import this icon. Please feel free to pause the video and read the code. We basically say if there is a selected date go ahead display this div where we have a heading and then we are going to map through the available time slots and we're going to check if it is booked or not and this is how we can do it. So on click we say if it is not booked go ahead call this method and the rest is pretty easy to understand. If it is booked, we're going to put this text. Um, it should be disabled. Classes should change. Things like that. Let's save and see the output. I'll select this doctor. Let's say teeth cleaning tomorrow and this date. Okay. Once we select everything, we should be able to see a button. So, here um let's go at the very end, I think. Yeah, just above this div I will paste it here. It says if there is selected type, selected date and time only then show this button which is going to take us to the next step. Okay, here we can see I'll say remove the booking. Now we are in the confirm step. That should look like this one, right? So it's going to be this component. Let's try to build it now. So when we say confirm booking basically we would like to save something to the database and to be able to make that work let's visit the appointments server action the file itself we are going to have uh that method where we can actually book an appointment so I'll say let me zoom in first export async function and I'll say book appointment we will get an input Let's say type of this will be book um appointment input and we're going to create this then let's say we will have a try and catch block and let's put this type first I'll make this to be an interface and we don't need to export it where we'll get a doctor ID date and time and maybe optionally a reason if you want to extend this application once you complete the entire project. So under the try first I would like to get the authenticated user from clerk. Let's import the oath. I think we already have it. And if user is not there the user ID unavailable we'll say you must be logged in. And then we can validate the required fields. So we will say if any of them is equal to undefined we'll say all these fields are required. Right? And then we can find the user in our database by passing the clerk ID right and then we can actually say if this user is not in the database then you should set set up your account properly. But if we pass all these if checks that means everything is successfully done. All we have to do is creating the appointment in the database. So here I'll say here is the new appointment that we create by using the create method. Here is the data by default status should be equal to confirmed. Here are all the details like the date, time, user ID, doctor ID, etc. And then we're going to include these fields under the user and then these fields under the doctor. Right? We're going to select them in this query. Okay. Finally, at the end, we can just say return the appointment and we can transform it by using our method. Let's say transform appointment and pass the appointment into it. Um, here under the catch, let's handle this with a console log and maybe throwing an error. So, at this point, this should be pretty easy to understand. We are just doing the same thing again and again, right? Get the user ID. Check if it is available. Do some validations. Get the user from database. Check if it is available. If everything is done successfully, we can call our method. We can call our method by passing the data that we would like to save in the database and some select fields for user and doctor so that we can have it under the appointment itself. Okay. So that's the entire thing for the server action. Now we would like to call this within a query, right? um or a mutation I should say because this is like a post request where we would like to create something we will go under the use appointments book this file and we are going to create one more function which is like around the 10 lines of code where we say use book appointments this is our custom hook we're going to get the query client and import the mutation here is our action that we're going to be calling on success case we would like to fetch the user appointments again right we would like to invalidate that query on error you can handle this by showing a toast or something I'll leave it as uh like as it is with a console error just to keep it simple okay so that's the entire setup that we need to do creating a server action as well as a custom hook now we would like to call it within our component so here under the page tsx go under the states and paste this in where we call our hook and get the mutation. We're going to be calling this in a second. And then we have one method called handle book appointment. So we are going to build it as well. But first let's scroll to the bottom. So that was the first step. Second step right after this we're going to have the third step. Let me paste this in. It is almost the exact same thing. So we are going to create this component and it's going to take all these states. Here we have the pending state and then three different methods. One to go back, the other one to uh modify is basically again going back and then the confirmation. Let's create this component. tsx import and then let's pass our interface. So here I'll go ahead paste this in and then let me get all the props by making it type safe. Okay. So if you delete one of them as you can tell it is right here completely type safe. Then we can build the return statement. Just before that let's find the appointment type here. I'll say import the appointment types find the selected type and set this to be you know set it to be this variable because we're going to be using it. Now let's say under the return statement we are going to have a div with the class name being space y of six and then we're going to have the header with the back button. So I will provide this to you. Import the button and then the back icon. And then after the header, let's shrink this. We're going to have a card component. Let's import it. Last name will be maximum width of 2x large. And then we're going to have the card header with the title that says appointment summary. And this is what we're building by the way. Okay, appointment summary. Now we're going to put this component called doctor info. We're going to fetch the doctor's you know profile pick name and their specialty. And then we're going to get the rest of the details. And currently this is what we have. Let's see it pretty quickly. Okay. So right after the cart header I'll say card content import this which will take a class name. Let's say um I believe space y4. Yes. And then we're going to have the doctor info in a second. And then now we'll have the appointment details. Okay. Let's paste this in. As you can tell, we have the appointment type, duration, you know, the date we're going to format it. Um selected time and price. Okay, let's see. This is exactly what we have. that looks the same thing just like in the demo right it is this section but now we would like to have this one and I'm going to call this as a separate component so here I'll just say doctor info and I'm going to pass the doctor ID into it under the components appointments let's say create this filet tsx and import it right here. And this component is nothing special actually. We're just going to get uh oops, we have an error. Um let's save this maybe. Okay. So, we're just going to get the image of the doctor name as well as their specialty and then just display it on the UI nicely. So, it is like 20 lines of code where we get the available doctors, find the selected one which is we are getting from the prop. Um if it is null we'll just return null but otherwise we're going to show the image you know the name as well as the specialty. So super quick component you can either type it out or copy and paste. I know it is kind of annoying but like we are almost about about to end the tutorial. Okay. So let's go ahead see it one last time. I'll select this doctor regular checkup tomorrow at 9:00. remove the booking. Okay, as you can tell, we can see the entire details. And at the bottom, let's put the action buttons. I'll go ahead scroll to the very bottom right after the card. I'll say we're going to have two different buttons. And here are the on click. Like what's going to happen? And if we are in the loading state, this button should be disabled. And the content should update as well. Now, if you save, you can see the buttons. But if you say confirm booking, nothing is going to happen. It's because we don't have the functionality yet. Right? On conf on confirm, we are calling our handlebook appointment which does nothing. So let's go ahead and build it. Just to make TypeScript happy, first I'll have some let's say validation. I'll say if selected dentist ID is undefined or any of them is equal to undefined, we can throw a toast, right? And how can we use this to? Well, you can import it from Soner um just like this. But to be able to make this work, you're going to go under the layout. Let's find it source um app and layout. You would like to put this component at some point. Let's say toaster which is coming from ser as well. Okay, it is this import. And once you put that you should be able to see a toast uh in this case right here we have error but you have like success and I believe like info you can see all of them from the chaten documentation let me pretty quickly display it instead of skeleton we'll just say toast itself okay why we cannot see it Um, okay. So, this is the type of toast that we would like to get and you can customize it if you wish. Just keep that in mind. Everything is coming from the documentation. Um, and you don't really need to memorize any of them. So, we're going to import the appointment types and find the selected type, then assign it to this variable because we are going to be using it. Then we'll say book appointment mutation call the mutate method with the object where we can pass our variables. So we'll say here is the doctor ID. Let's say here is the date. So date, time and reason. So these are all the variables. Then we can add some on success and on error case. Here I'll open up an object. Let's say on success we would like to do something on the front end here which is going to be an async function. So here I accidentally paused the video but I was saying we are going to get the appointment as the argument and we are going to first store it in a uh you know in a in a state if I can't talk here is that state that we'll be using and I can just paste this in we'll say store the appointment details to show it in the model which is going to be this one right here are all the details and we will also send an email which is something that we will do in The next section for now let's say set booked appointment and the value is going to be this one that we are getting as the result here let's add a to-do let's say send email using resend which is going to be in the next section then um here we can say show the success model and for this we'll say set show confirmation model to be equal to true then we can reset the form and I am too lazy to type this out here. Basically, we're going to get the rest of the state and set them with the initial states, right? And here I will have another object I think not another object but right after the on success I'll just say on error we would like to do something else. In this case I'll just show a toast. So here is how that would look like. Now, let's actually save and test it out. First, I'll open up my database. Let me log in pretty quickly. I believe we don't really have any appointments at all. Let's select the project under the tables under the appointments. We should have zero documents, right? We have some doctors, users, but zero appointments. So, let's go ahead get one appointment from Dr. Jane. Regular checkup. Let's say tomorrow it should be at 9. Review this and confirm. So we are in the loading state. Button is disabled. And once it is done, we are in the home screen. That's fine under the appointments because um you know the state has been resetted. Now the important part is if we have the appointment or not. Here we go. Once you refresh the time, duration status is confirmed. And from the admin dashboard you can make this to be completed regular checkup as you can tell everything is right here even with the relations. So the doctor is a Jane here are all the details and the user is I think me. Okay that's my profile. Now, let's go under the admin page and see if we can have the appointment at the very bottom. Um, looks like we cannot get it. Uh, didn't we build it? I thought we build it. No. Say admin page dashboard client. So, we have the doctor's management. Okay, that's fine. But we should be also we should be able to see the appointments as well. So find the admin page. Okay. So I think that's one thing that we are missing. Let's pretty quickly try to implement this as well and then complete this section. So I just paused the video and realized we are missing one more thing. So that's the first one. We will definitely include this in our project. But first we should include something else under the appointment page, right? So we are missing this component the upcoming appointments. Currently we can see the doctors even though we have an appointment we cannot fetch that. So let's try to build it under the um under the appointments page. So right after the steps we're going to display it. But I think we are missing a hook. So I will go under the use appointment file and we are going to get one custom hook that we will create that is going to give us the appointments for a specific user. So here I'll just paste this in. We will get user specific appointments by calling our server action. This is something that we have already created. Here we can see this is going to get get us all the appointments for this specific user. and we're going to return it to the client. Okay, so that's our result. Let's try to call this hook under the page.tsx. I'll just scroll to the very top right after my mutation. I'll just call the hook and this is going to give us some data and I am going to call this as user appointments. Okay, so we can leave it as data as well. But just to make our code a little bit more readable, I'll update the variable name. Now let's scroll to the bottom. Right after all these steps, we have one div. Just go outside of it. And we will say um maybe something like show existing appointments for the current user. And you can copy this from source code. It is like 20 lines of code. I promise it is nothing complex. We will basically say if the user appointments length is greater than zero, go ahead render this part where we get the array map through it for every single one of them display an image and the you know doctor name and call the format method from data fs. Okay, let's see. We have only one appointment that should be fetched in a second as you can tell and you can even include a skeleton for it as well. I'll leave it as a challenge for you. So with this I think that's going to be it for this entire section. In the next one we are going to implement the emails as well as this table under the admin page. So I'm going to leave it for the next section. Let's go ahead under the VSS code. Close everything. toggle the status bar and create a new branch called appointments page and we will stage all of our changes. Add a commit message like uh I don't know let's say appointments page addit pretty classic. Publish this and visit the source code as always. We are going to create the pull request. Now we have a lot of changes. I think it is almost like 800 lines. We could wait for the code rabbit to give us some suggestions. That's what I would do if I were in your shoes. But since I'm teaching, let's not waste any time. I'll go ahead merge the poll request. Let's say confirm. And then I'll uh I'll switch to the master. Let's get the latest changes. So if you want to see what can be improve on our code, definitely wait for the suggestion. Um it should give you everything that you would need just to make this code a little bit more clean. If you have any, you know, critical issues uh with code rabbit you can fix all of them. All right, so with that that's going to be it for this entire section. Hopefully I'll see you in the next one. In this section we are going to implement sending emails. So this is the confirmation model that we're going to see once we have an appointment and in the background we are going to send this template to the user. Okay. So we're going to put the doctor name, appointment type, date, uh you know the time, duration, cost, and then the location. All right. So let's try to build it. To be able to send the emails, we are going to be using resend.com. So they have bunch of different SDKs. You can implement it in seconds. In this uh project we are going to be using Nex.js and here is how that work in simple terms. We will get into the details. Let me zoom in. Okay. So in the API routes we are going to create a post method and then we are going to call this send method. We're going to pass the from email to to the user, right? The subject and then we are going to create an email template that is going to look like this. Okay. Now, if you would like to get some pre-made templates, you can get them from their website. So, they have this user welcome email template. Here is the code. Here is the output. Right here we can see this is basically some um you know HTML and CSS but they have wrapped it with React components. So as you can tell they are importing all of these from React email components. So we will get into the details but first you should log in. So I just logged in. We are going to get an API key. So this is the one that I have generated in the past but let's try to create another one from scratch. I'll say Dwise and then let's say full access. Now, one thing that you can do is adding your own domain if you have one. But if you don't have it, you can use the recent uh free uh domain, right? But you should not use it in the production only for testing purposes. Okay? So, we'll just use it for the testing purposes. So, I'll say add this API key without selecting my domain and go ahead copy this. This is your API key and we are going to store it right here. So I'll say recent API key. Then I think for now that's it. We can install our package. I'll open up the terminal. Let's kill this with Ctrl C. And then I'll say mpm install recent. Let's go with a specific version like 6.1.0. This is the latest version at the time that I'm recording this video. So, we are going to install this. Once this is done, let's import one more package which is going to be let's say mpm install at react- email/mponents. Okay. And for the version, this should be 0.5. I forgot to type it out, but let's see. under the packages. So, let's find React email. Okay, 5.5. Go ahead, put it at the end if you're watching this video in the future, right? 0.5 and just install it. Now, let's say mpm rundev. We're going to start our application. And to be able to implement this, first I'll go under the lib. I'll create recent.ts Yes, just like in the past for any kind of third party services, we're going to set them up under the web folder. And this is all we have to do is basically importing recent, creating an instance by passing the API key and exporting the recent instance. And just make sure that this is matching with your environment variable name. So if you have a typo like this, it's not going to work. Okay, just make sure they're matching. Um that was the very first step. The other one is to create an endpoint. So here is how that will work. Under the appointments page here we have a method right let me show you this. So in this method when we call the mutation we would like to call an API endpoint. So here we'll just say something like fetch a send a post method to this endpoint right and this is going to be created under the app folder under the API folder which is a special folder and then our API route. So this is going to be a folder let's say send appointment- email and then the special file called route.s s or js. So this folder name is special. This one is not. You can say anything here. But this file name is just like page.tsx. It is special file for API routes. And here let me show you how to get started with this file. First I'm going to delete this patch so that we don't have any errors. And let me turn this down. Okay. So here we'll just say export async function. And this is going to be a post request right a post method. So we need to call this as post. And then this will take the request as the first argument. Let's say request and type of this is equal to request. Um here we can have a try and catch block. Under the try first we would like to get the body. So this is the you know user data that you're going to get on the back end. Let's say await request.json JSON call the method then from here we can dstructure couple of different values. So from let's say from body we will get couple of different values such as the user email let's say doctor name and I'm too lazy to type this out let me provide them to you. So from the front end we are going to send all these values right doctor name appointment date time and type duration and price because we are going to include these in the email right let's go here under the email as you can tell we will have all these details. So we will fetch them like we're going to get it in the back end and let's first validate the required fields. Here I'll say if there is no user email or doctor name or any of them we will say next response let's import it from here we'll just say missing required fields and status code is 400 but else we can just send the email to able to make this work we'll just say await resend and import it from the lab we'll say emails dot send which is getting an object right this is the payload so just like in the documentation we will say from and you can put anything I'll just say then wise and then the domain so let's say no dash reply at recent dev and here I'll just warn you with a comment I'll say do not use this in production only for testing purposes like in production you should use your own domain. Okay, here maybe I can add it here. So that's the from field to let's say we're going to send it to the user email. Um and then we can put a subject. I think I'll just say appointment confirmation just like this. And I'll put the brand name which is Dentwise. And then you can add like in the documentation I believe they were using HTML. Let's pretty quickly see it. So they are using HTML and putting something super basic, but we can use React components just like what they have right here, right? So we're going to have the email as a React component. So they have this field called React. And we don't have the component. We will create it. Just type this out with me. We'll say appointment confirmation email. And this will take some props here. We can say doctor name. And again I'll just provide all of them. Let's say appointment, date, time, type, duration, and then the price. Now we need to create this which is something that we're going to do in a second. Let's say this will give us the data as well as the error, right? Okay. If we have any errors, we can handle it. So here I'll just paste this in. If we have any errors, send 500 failed to send email. But else 200 email send successfully. And here is the email ID. And let's handle the catch block as well. Okay, so that's it. But now we need to create this component. So I will go under the source components under the emails, copy this, paste it and I'll say tsx. So this is the file name. Now this is a function, right? This is a react component. So you can call this just by having a function call, right? So this is something that I see beginners can't really understand. But dude, take a look at this. This is literally a function. You can basically call it in this way. So it doesn't have to be something like appointment confirmation email. Right? So this is one of the ways to call it when you're rendering but you can also call it in this way. This is still rendering that um component because at the end of the day it is just a function. So TypeScript is not happy with us. It says you should provide some interface and make it type safe. So let's do this exactly. I will say here are the props that we're getting and here is our interface. Now if you take a look at the documentation of recent you're going to see they're using tailwind which is we can do as well but I have already built it with you know pure CSS which is something that we would be doing in like normally. So I will provide all the CSS classes. So this is something that I have generated completely with AI. I'm not going to lie, right? So I'll just be 100% honest here. I have generated all these styles with AI, which I don't really mind. There is no point for me to type these um styling just to get this output, right? I'm happy with it. So I'll just copy and paste from AI. If you want to update it, you can do so. This is just some CSS like super basic CSS classes. Now we can get into the email itself. Here is how that work. You can check this out from recent documentation if you're interested. But we would like to import the HTML element first. We will wrap everything with it. And we're going to put the head component as well. Let's say import this. Leave it as a self-closed component. Then we can have a preview element. I'll say your dental appointment has been confirmed. And we are going to get all these components from a package that we have already installed which is react email um components. Okay. So as you can tell now whenever you want to use a link image heading like text section you'll be using these components from that package. Okay. So now let's get into the body element. I'll say here is the body with the styles and we will get into it with a container component. For the styles I'll say style. This is going to get the container class which are these style styles right the margin padding and maximum width. Then within this we can get a section. Now if you don't want to type this out just copy the entire component from uh you know from the source code then we will get the heading which is this part. So we are starting from here and going all the way until the end. Now just to speed up the process I think I will copy the rest of it from source code and paste this in because there is literally no logic other than some texts classes and UI elements. Um there is one thing which is this link. If you click to it this will take you to the appointments page. Here we can see it says whatever the application URL is. whatever the application URL is just put /appointments at the end and let's create this environment variable so I will paste this in this is the key in localhost right in development this should be localhost I'll say localhost 300 but in deployment we're going to put whatever the URL is so maybe it would be something like tentwise.com or anything right so We will update this once we deploy the app. But in our local MB, this is going to be the value. And I think that's the entire component. We can save. This is happy with us. We built the entire API route. All we have to do is calling this API route which is send appointment email under the page.tsx. So we're going to delete this to-do and call that endpoint. So here I'll delete the comment and I would like to wrap everything with try and catch block. The reason is if sending email fails we don't really want our entire application fail because it is just a background job right. So here I'll have my try and catch block which is like 20 lines of code and please let me walk you through it. So we are going to call the fetch method by passing our endpoint which is slash API slash send appointment email. Right? Make sure this is correct. Then we're going to pass the object where we'll say the method is post. This is what we're expecting the headers which is JSON and then the body. So we're going to send the user email and the rest of it here it is. um if response is not okay, we can put a console log for debugging purposes and same for the catch. Now let's go ahead and test this out. One thing that I want to mention um like this is the account that you signed up, right? You can only send emails to this one since you are not using your own domain, right? So here we don't have any domains. That means we can only send emails to this account. In your case, this should be your email. So go ahead with this email. Log to our application. This is what I have done. This is the clerk account. The recent account, they are the same email. And I will have a new appointment. So I'll say John, let's select it. next week. This time I'll do a consultation review booking and confirm it. Okay, so looks like it is done. Let's check the database I was going to say, but we can see it already. Um, we didn't see the confirmation model. We will build it in a second. But let's take a look at my inbox. So, I just tested out and realized the sending email didn't actually work. And here is the solution that we're going to do. Basically, we need to install this package. So, this is in my case what I have. I'll go ahead say clear. Maybe in your case, you don't really have any errors, but just in case if you have it, say mpm install and install this package react email render. Okay, let's run this and test this out once again. So here let's select this doctor this date this time and let's go with this service. Hopefully this time we should be able to get an email. If not we can see how we can fix that. Okay. So here we can see 0 minutes ago we got the email just now. And if you cannot see it here maybe check out your spam um spam folder. It should be there. Right. The email is actually working in the terminal. We can see it is actually done successfully. Okay. If you want to change the logo by the way, you have to go ahead install it to somewhere and you know put the URL right here. This is the URL that I have because I have hosted this image um in this URL so that it can work properly here in the email. Okay. So that's how we can make the emails work successfully. And now we are missing this model which is called appointment confirmation model. Right. It is like a dialogue that we're going to get the details inside of that. So let's close everything other than our page. Scroll to the bottom where we have the step of three. Right? Go outside of this div and have this code block. So we will say if there is a booked appointment just show the model copy the name under the components we are under the appointments page so we'll go under this folder paste this in oops so here we will paste this in and we'll just say tsx and the entire um content is going to be coming from our source code as always. So I'll just go ahead copy and paste because there is no logic at all here. We can see we don't have anything else other than return statement. So it is a dialogue. We got some props and we are using them within this component. So that's the entire thing. Let's save and import it and just try to see in action. So I would like to have another appointment. Let's say this time this doctor let's have just tomorrow. Here we can see this is booked. So we should select another date or time and let's say confirm this booking. Once it is done we are going to get the model. And here we can see we have all the details. This doesn't have any logic at all other than a link which would take you to the dashboard appointments page. Let me fix it. This is wrong. Um, the link is wrong, I mean. And when you copy and paste it from the source code, you're going to get the correct version. Okay, so that's the entire thing. I hope you didn't mind it that we copied and pasted this component. But that's the entire gist of it. So that's it for this entire section. Let's go ahead close everything. Open up the status bar. Um, I'll have a new branch. Let's say sending emails and let's commit our changes. Here is my commit message. Publish this branch and let's get into the source code. Okay, so I'll wait for the suggestions that are coming from code rabbit and then we should be able to merge the pull request. So here are all the features that we have introduced in this section. The files that we have updated with the related you know quick summaries, the sequence diagram, what is happening in the background step by step. You can take a look at this. Then let's scroll and see some code suggestions. Um here it says you should probably and definitely uh validate the email format because uh currently this user email is not validated which would lead to injection issues or sending to in invalid addresses. So this is something definitely that you should be doing right you can copy it paste it into the source code. Um so have this validate email for uh like val email validation I would say and then here it says you should make the errors a little bit more specific. So this is the way to do it. If user email is missing you would say you're missing the user email. If the appointment date is missing you would say hey this field is missing right in the error case. Um and then let's scroll to the bottom here. It says you should make this to be an environment variable. I absolutely agree, but we are just trying to keep it simple. Um, in a production grade codebase, this is how you would have it coming as an um environment variable. Let's scroll to the bottom. Here it says you should check, you know, if the email sent successfully or not. Depending on that, you would either show the model or show some kind of error, right? The using the toast method. So that's one thing that you can do as well. Then if you're going to be doing it, you should add this state to the model. Um here it says for the image URL you are using external image which could uh you know it's not the best practice. It could lead some problems things like let's say what happens if this service is down you are not going to get the image or if it is slow down right if the service is not working you are not going to see the images under the emails right because this is hosted in that service so this is something that you should definitely keep in mind but here we're just trying to make it work in development that's why I put the URL and And here it says in case this is undefined, you should put a fallback. I don't think this is if like 100% necessary because we'll just always make sure that we have this environment variable. Okay, so these are all the things that you could have included. And here it says you can also add the if check. So these are just some code optimizations that you could have done. In my case, I'd like to not waste any time. So I'll say merge the pull request. go into VS Code. This is done successfully. Let's go here. Switch to the master and we're going to get the latest changes. Okay. So now our code codebase is up to date. So that's it for this entire section. I'll see you in the next one. So in this section we are going to add this table under the admin dashboard. So currently we are missing that component. Let's go ahead and create that. First I'll visit the admin dashboard client. Let's scroll to the bottom. Right after the doctor's management, we will have one more component called recent appointments. So basically admin should be able to see all the appointments, right? So I'll copy this. Go under the components under the admin and create this and say tsx. So this component is going to be using a table which is going to be coming from shad cn ui. So it is this entire component. So you can just search for it by typing the table. This is a similar example that we have right. And to be able to use it you need to wrap everything with the table component. Then you can add a caption table header row head. As you can tell it is just some you know uh elements some components. So no logic at all. For that reason we can copy and paste from the source code. But first we need to have a mutation. So basically when you click to this badge it's going to toggle the state. So if you click to this it would be confirmed and if you click to this one it would be sorry if you click to this one it it would be completed right. So either it's going to be completed or it would be confirmed by clicking to them we can toggle the state. So for this let's first create a server action. I will go under the appointments actions file. Let's shrink everything and go at the very bottom create one function which is going to be called as let's say export async function and call this as update appointment status and we will get an input let's put the type we're going to get the ID so that we know which appointment that we are updating let's say string and then we can get the status so let's say This is going to be either confirmed and or completed. And we can use appointment status enum for this right. We have this under the schema.prisma. So it's going to be either one of these. Okay. Now let's get into the function. It's going to be super easy. I'll say await prisma do appointment where we would like to update one. Um here is the filter. We'll say where the ID is equal to input do ID and the data that we would like to update which is the status itself. So we'll say input dot status right that's entire thing let's say you're going to give us the new appointment the updated appointment and we can just return this back to the client and then let's handle the catch blog. This is what I'll have. I think that's the entire method that we would need. Now, we would like to call this under our hooks. So, under the use appointments, we will go ahead and create a custom hook, which is going to be like maybe five lines of code that I can copy and paste. So, we're getting the query client. Um, this is going to be a mutation, not a query because we are updating something, right? We are not fetching data. So we're going to call our mutation which was this one and then once it is done successfully once we update it we will say call this query again so that we can see the latest changes right we update the cache okay so I hope that makes sense now let's go into the recent appointment and try to get started with it first I would like to call my hooks so here I will get all the appointments and then I will at my mutation because we're going to be using those. Then what happens if you click to this badge, right? We would like to call our mutation by sending the new state. So just imagine if we click to this one, we would like this to be completed, right? So we are going to add the new status. Let me create a method. Let's say give a space handle toggle appointment appointment status we are going to get the appointment ID which is going to be type of string and we can go inside first I'll say let's find the appointment this should be an arrow function by the way we'll say appointments dotfind point. Let's get each appointment. I'll say appointment ID is equal to the appointment ID. So that's going to give us which appointment that we click. Right? And let's create the new status. We will say if appointment status is equal to confirmed then new status is going to be completed. Otherwise it would be confirmed. So this is how we can toggle the state and we'll say call our mutation dot mutate pass the object here is the ID which is the appointment ID and then the status which is the new status. So super easy nothing complicated. And then we can add a batch for the status. Right? If it is confirmed, this is the color and the text color. And if it is completed, we're going to get this green badge. Here is the function. So here, let's import the badge from UI. If the case is confirmed, this is what we're going to see. It says confirmed. If it is completed, this is what we're going to return. And in the default case, we can just have something. I think if you don't add this, TypeScript might not be happy. Well, looks like it's happy. But the best practice is always include a default case when you're using a switch statement. Okay, so with that, I think we can just get into the return statement. I'll give Ela space. If you don't want to type this out, just go ahead into the source code, copy the entire return statement, which is around, let's say, 70 lines of code. So, first we're going to get get a card element. And then we will have the cart header with some text. Let me get the calendar icon from Lucid React and cart description. Let's save and see the output. Okay, so that's the title. Now it's time to build the cart content. So still within the cart, we'll say cart content. Let's import it. And we are going to have a div that will make this to be rounded. Let's turn this off. And then we're going to have the table itself. So this table is going to be coming from chaten. And then we will have the table header. So as you can tell, we need to import all these components one by one and just see the output. Okay. Okay. So, these are the kind of like the headers that we have. Now, it's time to put the data. We're just going to be going under the table body. So, instead of typing this out, I'll go right after the table header and paste this in and walk you through it. So, we have table body. Let's import it. Then, we map through the appointments. For each of them, we are going to return a row which is going to have a table cell within this. Right? Um here we are getting the patient email doctor name. We have some TypeScript errors. We're going to fix it. Uh but let's get the button as well. Okay. So that's entire thing. Once you click to that button, it's going to call the method with the appointment ID. Now let's see why this is not type save. We will go under the appointments. So this is what we are returning right the appointments. But I think we should call the transform appointment with this method. let's say transform appointment and pass the appointments into it because here we are passing some fields like patient email uh patient name which is like which are the things that we are using for now I will save this file and looks like we have some more errors let's try to fix them um I think I just did a mistake let's go back this is not how we call it instead we say appointments map for every single appointment to call the transform appointment method. Right? That's how we were using it. So that's the correct way. Go ahead fix this line and you shouldn't have any errors. Now let's test it out. So these are all the appointments that we that we currently have in the database. Let's try to update some of them. So I'll say this should be completed. I just click to add. You can add a loading state. I'll just ignore it in this case and it should be completed as well. If you click to it again, it should be confirmed in a second. Here we go. So, this is working as expected. There is one more thing that I want to fix. So, currently if you say this doctor should be inactive. Save the change and go back to the appointments page. So, this is inactive, right? Let's go here and let's wait for this to be loaded. Okay, now she's inactive, we cannot see it. That's good. But if you go back to admin and make this to be active and go back to the appointments here, you can see the cash has not been updated. She should be visible because we make her account to be active. But it is not working. So let's try to fix it. We will go under the hook called use doctors. And when we update the doctors just instead of invalidating this query let's do another one I will duplicate and I will say we would like to fetch the get available doctors query as well okay so which is this query just put this line and test it out once again it will go back to the admin page let's say this is inactive Um, okay. So, I don't know how to show this, but let's say this should be active and then go to the appointments page. After 1 second, it should be fetched again, right? So, this is how we can make that query run. I'll say inactive. Go back. She should not be here as you can tell. Okay, I'll just leave it as active. So that's the fix that you can add. And just one more thing before we end the section, let's go into the layout file under the app. So we are calling the user sync component which is completely fine. But I think we can call this method in a better place because what happens if this doesn't run entirely and we have some navigations. So under the homepage okay so under the app under the homepage just before we take the user to the dashboard page we can save them to the database if if they are not already. So I will say await call the sync user action and once this is done once user in the database then navigate them to the dashboard page. Okay I hope that makes sense. This is really important. I will leave this as a comment. So I will say this is so here is the comment. This is done in the homepage component, right? Instead of calling the component itself, we're just going to call the server action. Um once again, the best way of doing this, the best way of syncing the user is basically calling a web hook. Okay. So I'll just say webs which is something that you can take as a challenge and implement once you complete the entire course. So with this in mind I think we can save this file and commit our changes in the next section. We can deploy the entire project because we are pretty much done with it. Instead of having a new branch here I'll just commit them to the master. So if you wanted to have some code suggestions of course you can do a pull request and wait the suggestions from code ravit but in this case since these are some changes some fixes I will go ahead and commit them directly to the master. Okay so I stage all of my changes and I'll say something like small fixes um commit this and sync up the changes. Now if you take a look at the git repo we should be able to see something like now here we can see we just got the small fixes. All right so that's the entire section. Hopefully in the next one we are going to deploy this entire project. So I'll see you there. All right. So now that our entire application is working successfully it is time to deploy it. And for the hosting platform, we are going to be using Savala, which has one of the best developer experience in the market. And they are kind enough to give you $50 free credits if you use the link in the description down below. They have services like application hosting, object storage, database hosting, and static site hosting. And in this case, we're going to be using application hosting to be able to deploy our app. There are some really good reviews about Savala and honestly I have been using them for a while now in the past projects as well and I really like this service. So that's why I wanted to share it uh in the first place. Um here I'm already convinced by taking all these features uh into account, right? So they would give you unlimited users for your team which is something that the other products would give you in a paid plan, right? the more users you would get, the more you would pay. But in this case, this is not really this is not really the case. So, thinking all this, I'll go ahead and log in. And again, you can use the link in the description to get $50 free credits. Okay. So, I'll just open up that login link. Let me go ahead and visit my dashboard. All right. So, we are going to deploy an application from our GitHub repository, which is this one. Now there is one thing that we would like to update in our code where we'll go under the package JSON we have some scripts right when we build our script before we uh run next build we're going to run this which is Prisma generate. So this is going to give us all the types that are coming from Prisma client so that our code doesn't crash in production. Okay. So with this let's go ahead add a commit. I'll say get add all get commit-m. You can also like you could have done this from here as well, but this time let's do it from the terminal. I'll say build script updated and let's say get push. We don't really need to start a review at this point. It's going to take 2 seconds. Let's refresh. We should be able to get the latest change. Okay. Now, we're going to go into the dashboard. And in the dashboard, we will just say deploy an application. So from here go ahead select your repository. In this case this is what I have. So I have selected it and the default branch is going to be master. In your case it could be main. Just don't touch it. Leave it as it is. Do not select any other branches. And I'll say enable automatic deployment on commit. Okay. So we can scroll leave everything as it is. For the resources I will use the $5 a month plan. But you don't need to worry about this because we have $50 free credits, right? I've been using this for like two months maybe and I still have $43. In your case, this could be 50. I just select this and say create. Do not say create and deploy because I'll show you something. So this is going to create the project for us and we would like to add some environment variables. Once we add it, then we are going to deploy the app. So here let's visit the NV file, copy everything and maybe just delete these codes under the database URL because we don't really need them. So we have clerk uh secrets, right? Clerk environment variables, database URL, VPY, admin email, recent and our front end application. So I will copy everything and paste it right here. Now actually before we paste it I'd like to show you something. So from here under the overview let's say visit the application. So we got a URL right under the environment variables once we paste everything instead of local host we are going to put this URL because we are in deployment there is nothing like local host. Instead this is our app URL. But just make sure to delete the very last slash. Okay. So, we don't need that slash. This is how it should be. And I'll say save. Now, hopefully we should be able to deploy this project. So, under the deployments, I'll say deploy now, which would take around maybe 2 minutes. And then we should be good to go. So, in my case, it took around 3 minutes. Let's say visit the application. And here we go. Now, let's wait for the images to be loaded. Looks like we cannot see it. Let's refresh once again. Maybe that's the first time thing. But now our our actual application is live on the internet. Okay, I think images are not loading. And I know the solution. Don't worry, we're going to fix it in a second. But that's the entire landing page. Let's try to login with our account. I'll go with my Google account which was this user. So once we log in, we should be navigated to the home screen. All right. So here we can see everything is working as expected. We can schedule a new appointment. We can get the data from the database. Uh but now the images are not really loading, right? Some of them loads, some of them doesn't. Now this is because we are using the image component. Let me show you this. We are using the image component and we are not deploying this application to Nex.js. So there are some stuff happening in the background and it cannot find it properly like images are not loading and the fix is pretty easy. We'll go under the next config.ts file. Let's close everything here under the images. So right after the remote patterns I'll say unoptimized to be equal to true. Now let's add a commit. I'll say clear everything. Get add all get commit-m let's say oops I don't know why this is scrolling down but let's say get commit-m next config yes update it. Okay I screw it up. Let me actually just add the commit message from here. commit this and sync the changes. Now what is going to happen is that Savala is going to see this latest commit. Right? So we just had one more commit where we updated the next config.ts. Savala is going to see it immediately and it's going to start a new deployment. As you can tell once this is done, our application should be live with the latest changes. So let's wait for this to be completed and we are going to test it out in production. And while this is deploying let's talk about why we are not deploying our application to somewhere like Versel. So this is an example just one of them. There are endless example like these where you have like $300 a month every single month right? This is your bill and just because you update something on your code, you end up with extra $3,000. So if you're not really an expert, if you don't know what you're doing exactly, I wouldn't deploy an application to pursel on the paid plan where on Savala everything is a lot more transparent and absolutely cheaper. And I think in their homepage they had a testimonial where the guy says I have gone from 3K to $650 per month which is like 78% off. Um so yeah that's one thing that I wanted to mention. Now let's see if our application is live. Looks like yes it is. I'll say visit the app. Now we should be able to see all the images without any let's say any errors right we had some errors let's go under the appointments let's see if we can see those images here we can see they are live here and let's try to sign out in the landing page we can also get those images now okay so as you can tell everything is working as expected when it comes to the images so let's try to log in with Google once again and we are going to call our AI agent and test out if it is going to work or not. Okay. So, I'll go to the voice page. Let's scroll to the bottom. We are on the paid plan by the way. That's why we can access it, right? We are in the $9 a month plan. I'll start the call. Let's give access to the microphone. >> Hi there, this is Riley, your dental assistant from Dentwise. I'm here to help you with all your dental needs. I can provide information about our service prices, give you immediate tips for dental pain or concerns, help you understand different treatment options, and share oral health prevention advice. What can I help you with today? >> Oh, yeah. Can you please provide me the services and fees that you have? >> Of course. I'd be happy to explain our service pricing. Here's what we offer. Regular dental checkup, $120. This includes a comprehensive oral examination, basic x-rays, and oral health assessment. >> All right, so I'll just end the call. As you can tell, this is working. Once we end the call, u as you can tell now as we speak and he doesn't really care what's what we say, right? Uh we can go under the appointments. We can have another appointment. Let's say with Dr. Jane. I'll have the teeth cleaning tomorrow. Let's say at this time. Confirm this booking. Once it is done, we should have this model and we're going to get an email in our inbox. And then we can I think visit the admin page. This is account that we are admin. So we are access so we so we can have the access to this page. Um here these are all the doctors that we have at the moment. I don't know why the images are not loading but they should be loaded eventually. I'll just refresh. We can add more doctors. We can edit them. Um here the images are a bit weird. Why it is not loading? Is this URL correct? Let's copy it and paste it here. Okay, so their service might be down. That's why it is not loading or my internet should be a bit right? It's really slow. But here we can see they're actually loading. Um, other than this, we can say confirmed or completed. Once you click to it, it updates the state. Let's make it to be confirmed. All right. So we can say they could be inactive, active. Everything is working I believe. So with this I think we have completed the entire project. I hope you enjoyed it from start till the end. We have built a really really good landing page. Right? Let's see it pretty quickly. Like this is one of the best landing pages that I think I have coded in a tutorial. It's really cool. looks like a modern startup and it doesn't really look like if you copied it or paste it. Right now I know in this tutorial we have some parts that just copy paste completely but they don't contain any logic at all right such as our landing page. So here we have all these components where we copy and pasted some of them but they don't have any logic and I hope you don't really mind it. So, with that, let me know what you think in the comments, and hopefully I'll see you in the next project.